From 01612eaaff75c30db0064e28b4b2d7bcc6420c31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Thu, 5 Sep 2024 19:04:56 -0400 Subject: [PATCH] Refactor: extract binding resolution code into a separate module --- crates/metaslang/bindings/src/lib.rs | 169 +------------------ crates/metaslang/bindings/src/resolver.rs | 187 ++++++++++++++++++++++ 2 files changed, 192 insertions(+), 164 deletions(-) create mode 100644 crates/metaslang/bindings/src/resolver.rs diff --git a/crates/metaslang/bindings/src/lib.rs b/crates/metaslang/bindings/src/lib.rs index a2535da8d..e2b080803 100644 --- a/crates/metaslang/bindings/src/lib.rs +++ b/crates/metaslang/bindings/src/lib.rs @@ -1,8 +1,8 @@ pub mod builder; +pub mod resolver; use std::collections::HashMap; use std::fmt::{self, Display}; -use std::iter::once; use std::sync::Arc; use builder::{BuildResult, Selector}; @@ -10,12 +10,9 @@ use metaslang_cst::cursor::Cursor; use metaslang_cst::KindTypes; use metaslang_graph_builder::ast::File; use metaslang_graph_builder::functions::Functions; +use resolver::Resolver; use semver::Version; -use stack_graphs::graph::{Node, StackGraph}; -use stack_graphs::partial::{PartialPath, PartialPaths}; -use stack_graphs::stitching::{ - Appendable, ForwardPartialPathStitcher, GraphEdgeCandidates, StitcherConfig, -}; +use stack_graphs::graph::StackGraph; type Builder<'a, KT> = builder::Builder<'a, KT>; type GraphHandle = stack_graphs::arena::Handle; @@ -240,169 +237,13 @@ impl<'a, KT: KindTypes + 'static> Reference<'a, KT> { .map(|file| self.owner.stack_graph[file].name()) } - /// Attempt to resolve the reference and find its definition. If the - /// reference cannot be resolved in the current state of the bindings (eg. - /// you may still need to add an imported file), this function will return - /// `None`. Otherwise, it will always return the definition with the - /// longest, not shadowed, path in the underlying stack graph. This is to - /// ensure that we always get the actual definition of some identifier - /// reference, and not an intermediate result such as an import alias. - /// - /// There are multiple reasons why a reference may resolve to more than one - /// definition in well formed and valid code. For example: - /// - /// 1. Variable shadowing: this should be resolved in the rules file by - /// setting the precedence attribute to the appropriate edges. - /// - /// 2. Destructuring imports (with or without aliases): these are - /// represented in the graph as intermediate definition nodes along the - /// path to the actual definition; hence why this function will return - /// the longest path available. - /// - /// 3. Overriden virtual methods: a reference will find valid paths to all - /// available definitions in the class hierarchy. - /// - // TODO: review the comments above pub fn jump_to_definition(&self) -> Option> { - let alternatives = self.resolve(); - if alternatives.len() <= 1 { - alternatives - .first() - .and_then(|path| self.owner.to_definition(path.end_node())) - } else { - // attempt to disambiguate from all found alternatives - - // remove aliases - let alternatives = alternatives - .into_iter() - .filter(|path| { - self.owner - .selectors - .get(&path.end_node()) - .map_or(true, |s| !matches!(s, Selector::Alias)) - }) - .collect::>(); - - match alternatives.len() { - 0 => { - // TODO: this is an error because the actual definition - // is not found, but maybe we can revert back to - // returning the longest path? - panic!("More than one definition found but they are all aliases") - } - 1 => alternatives.first().map(|path| Definition { - owner: self.owner, - handle: path.end_node(), - }), - _ => { - for (index, result) in alternatives.iter().enumerate() { - let definition = self.owner.to_definition(result.end_node()).unwrap(); - let selector = self.owner.selectors.get(&result.end_node()); - println!( - " {index}. {definition} (length {length}) (selector = {selector})", - index = index + 1, - length = result.edges.len(), - selector = - selector.map_or(String::from(""), |s| format!("{s:?}")), - ); - let Some(selector) = selector else { - continue; - }; - - if let Selector::ParentDefinitions(related) = selector { - let enclosing_node = related.first().expect("at least one parent"); - if self.owner.stack_graph[*enclosing_node].is_definition() { - let enclosing_def = - self.owner.to_definition(*enclosing_node).unwrap(); - println!(" Selector points to {enclosing_def}"); - let related_sel = self.owner.selectors.get(enclosing_node); - if let Some(Selector::ParentReferences(parents)) = related_sel { - println!(" with parents:"); - for parent in parents { - let parent_reference = - self.owner.to_reference(*parent).unwrap(); - let parent_definition = - parent_reference.jump_to_definition().unwrap(); - println!( - " {parent_reference} -> {parent_definition}" - ); - } - } - } else { - println!(" Virtual selector doesn't point to a definition"); - } - } - } - - panic!(concat!( - "More than one non-alias definitions found and ", - "disambiguation not implemented yet" - )); - } - } - } + Resolver::build_for(self).first() } pub fn definitions(&self) -> Vec> { - self.resolve() - .into_iter() - .map(|path| Definition { - owner: self.owner, - handle: path.end_node(), - }) - .collect() - } - - fn resolve(&self) -> Vec { - let mut partials = PartialPaths::new(); - let mut reference_paths = Vec::new(); - ForwardPartialPathStitcher::find_all_complete_partial_paths( - &mut GraphEdgeCandidates::new(&self.owner.stack_graph, &mut partials, None), - once(self.handle), - StitcherConfig::default(), - &stack_graphs::NoCancellation, - |_graph, _paths, path| { - reference_paths.push(path.clone()); - }, - ) - .expect("should never be cancelled"); - - let mut results = Vec::new(); - for reference_path in &reference_paths { - if reference_paths - .iter() - .all(|other| !other.shadows(&mut partials, reference_path)) - { - results.push(reference_path.clone()); - - if path_pushes_super(reference_path, &mut partials, &self.owner.stack_graph) { - println!( - "Found push `super` in path ending at {}", - self.owner.to_definition(reference_path.end_node).unwrap() - ); - } - } - } - results - } -} - -fn path_pushes_super( - path: &PartialPath, - partials: &mut PartialPaths, - stack_graph: &StackGraph, -) -> bool { - for edge in path.edges.iter(partials) { - let source_node_handle = stack_graph.node_for_id(edge.source_node_id).unwrap(); - let source_node = &stack_graph[source_node_handle]; - if matches!(source_node, Node::PushScopedSymbol(_) | Node::PushSymbol(_)) { - let symbol = &stack_graph[source_node.symbol().unwrap()]; - if "super" == symbol { - return true; - } - } + Resolver::build_for(self).all() } - false } impl Display for Reference<'_, KT> { diff --git a/crates/metaslang/bindings/src/resolver.rs b/crates/metaslang/bindings/src/resolver.rs new file mode 100644 index 000000000..ee00bd741 --- /dev/null +++ b/crates/metaslang/bindings/src/resolver.rs @@ -0,0 +1,187 @@ +use std::iter::once; + +use metaslang_cst::KindTypes; +use stack_graphs::{ + graph::Node, + partial::{PartialPath, PartialPaths}, + stitching::{ForwardPartialPathStitcher, GraphEdgeCandidates, StitcherConfig}, +}; + +use crate::{builder::Selector, Bindings, Definition, GraphHandle, Reference}; + +pub struct Resolver<'a, KT: KindTypes + 'static> { + owner: &'a Bindings, + reference_handle: GraphHandle, + partials: PartialPaths, + pub results: Vec, +} + +impl<'a, KT: KindTypes + 'static> Resolver<'a, KT> { + pub fn build_for(reference: &Reference<'a, KT>) -> Self { + let mut resolver = Self { + owner: reference.owner, + reference_handle: reference.handle, + partials: PartialPaths::new(), + results: Vec::new(), + }; + resolver.resolve(); + resolver + } + + fn resolve(&mut self) { + let mut reference_paths = Vec::new(); + ForwardPartialPathStitcher::find_all_complete_partial_paths( + &mut GraphEdgeCandidates::new(&self.owner.stack_graph, &mut self.partials, None), + once(self.reference_handle), + StitcherConfig::default(), + &stack_graphs::NoCancellation, + |_graph, _paths, path| { + reference_paths.push(path.clone()); + }, + ) + .expect("should never be cancelled"); + + for reference_path in &reference_paths { + if reference_paths + .iter() + .all(|other| !other.shadows(&mut self.partials, reference_path)) + { + self.results.push(reference_path.clone()); + + if self.path_pushes_super(reference_path) { + println!( + "Found push `super` in path ending at {}", + self.owner.to_definition(reference_path.end_node).unwrap() + ); + } + } + } + } + + fn path_pushes_super(&mut self, path: &PartialPath) -> bool { + for edge in path.edges.iter(&mut self.partials) { + let source_node_handle = self + .owner + .stack_graph + .node_for_id(edge.source_node_id) + .unwrap(); + let source_node = &self.owner.stack_graph[source_node_handle]; + if matches!(source_node, Node::PushScopedSymbol(_) | Node::PushSymbol(_)) { + let symbol = &self.owner.stack_graph[source_node.symbol().unwrap()]; + if "super" == symbol { + return true; + } + } + } + false + } + + pub fn all(self) -> Vec> { + self.results + .into_iter() + .filter_map(|path| self.owner.to_definition(path.end_node)) + .collect() + } + + /// Attempt to resolve the reference and find its definition. If the + /// reference cannot be resolved in the current state of the bindings (eg. + /// you may still need to add an imported file), this function will return + /// `None`. Otherwise, it will always return the definition with the + /// longest, not shadowed, path in the underlying stack graph. This is to + /// ensure that we always get the actual definition of some identifier + /// reference, and not an intermediate result such as an import alias. + /// + /// There are multiple reasons why a reference may resolve to more than one + /// definition in well formed and valid code. For example: + /// + /// 1. Variable shadowing: this should be resolved in the rules file by + /// setting the precedence attribute to the appropriate edges. + /// + /// 2. Destructuring imports (with or without aliases): these are + /// represented in the graph as intermediate definition nodes along the + /// path to the actual definition; hence why this function will return + /// the longest path available. + /// + /// 3. Overriden virtual methods: a reference will find valid paths to all + /// available definitions in the class hierarchy. + /// + pub fn first(&self) -> Option> { + if self.results.len() <= 1 { + self.results + .first() + .and_then(|path| self.owner.to_definition(path.end_node)) + } else { + // attempt to disambiguate from all found alternatives + + // remove aliases + let results = self + .results + .iter() + .filter(|path| { + self.owner + .selectors + .get(&path.end_node) + .map_or(true, |s| !matches!(s, Selector::Alias)) + }) + .collect::>(); + + match results.len() { + 0 => { + // TODO: this is an error because the actual definition + // is not found, but maybe we can revert back to + // returning the longest path? + panic!("More than one definition found but they are all aliases") + } + 1 => results.first().map(|path| Definition { + owner: self.owner, + handle: path.end_node, + }), + _ => { + for (index, result) in results.iter().enumerate() { + let definition = self.owner.to_definition(result.end_node).unwrap(); + let selector = self.owner.selectors.get(&result.end_node); + println!( + " {index}. {definition} (length {length}) (selector = {selector})", + index = index + 1, + length = result.edges.len(), + selector = + selector.map_or(String::from(""), |s| format!("{s:?}")), + ); + let Some(selector) = selector else { + continue; + }; + + if let Selector::ParentDefinitions(related) = selector { + let enclosing_node = related.first().expect("at least one parent"); + if self.owner.stack_graph[*enclosing_node].is_definition() { + let enclosing_def = + self.owner.to_definition(*enclosing_node).unwrap(); + println!(" Selector points to {enclosing_def}"); + let related_sel = self.owner.selectors.get(enclosing_node); + if let Some(Selector::ParentReferences(parents)) = related_sel { + println!(" with parents:"); + for parent in parents { + let parent_reference = + self.owner.to_reference(*parent).unwrap(); + let parent_definition = + parent_reference.jump_to_definition().unwrap(); + println!( + " {parent_reference} -> {parent_definition}" + ); + } + } + } else { + println!(" Virtual selector doesn't point to a definition"); + } + } + } + + panic!(concat!( + "More than one non-alias definitions found and ", + "disambiguation not implemented yet" + )); + } + } + } + } +}