use petgraph::{graph::NodeIndex, visit::EdgeRef, Direction, Graph}; use serde::{Deserialize, Serialize}; use tree_sitter_graph::{ ast::File, functions::Functions, ExecutionConfig, Identifier, NoCancellation, Variables, }; use crate::{ error::{ConfigError, StagError}, stdlib, text_range::{self, TextRange}, }; #[derive(Default)] pub struct StagBuilder<'a> { language: Option, stag_file: Option<&'a str>, filename: Option<&'a str>, source: Option<&'a str>, } impl<'a> StagBuilder<'a> { pub fn with_language(&mut self, language: tree_sitter::Language) -> &mut Self { self.language = Some(language); self } pub fn with_stag_file(&mut self, stag_file: &'a str) -> &mut Self { self.stag_file = Some(stag_file); self } pub fn with_source(&mut self, source: &'a str) -> &mut Self { self.source = Some(source); self } pub fn with_file_name(&mut self, filename: &'a str) -> &mut Self { self.filename = Some(filename); self } pub fn execute(&self) -> Result { let mut parser = tree_sitter::Parser::new(); let language = self.language.clone().ok_or(ConfigError::MissingLanguage)?; parser.set_language(&language)?; let file = File::from_str( language, self.stag_file.ok_or(ConfigError::MissingStagSource)?, ) .map_err(ConfigError::from)?; let mut functions = Functions::stdlib(); functions.add(Identifier::from("scope"), stdlib::ScopeShorthand); functions.add(Identifier::from("def"), stdlib::DefShorthand); functions.add(Identifier::from("ref"), stdlib::RefShortHand); functions.add(Identifier::from("cover"), stdlib::CoverRanges); functions.add(Identifier::from("range"), stdlib::NodeRange); let globals = Variables::new(); let config = ExecutionConfig::new(&functions, &globals); let src = self.source.ok_or(ConfigError::MissingSource)?; let tree = parser.parse(src.as_bytes(), None).unwrap(); let graph = file .execute(&tree, &src, &config, &NoCancellation) .map_err(ConfigError::from)?; let mut sg = ScopeGraph::new(tree.root_node().range().into()); sg = build_scope_graph(graph, sg); Ok(sg) } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct LocalDef { pub range: TextRange, pub text: String, pub symbol: Option, } impl LocalDef { /// Initialize a new definition pub fn new(range: TextRange, text: String, symbol: Option) -> Self { Self { range, text, symbol, } } pub fn name<'a>(&self, buffer: &'a [u8]) -> &'a [u8] { &buffer[self.range.start.byte..self.range.end.byte] } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct LocalImport { pub range: TextRange, pub text: String, } impl LocalImport { /// Initialize a new import pub fn new(range: TextRange, text: String) -> Self { Self { range, text } } pub fn name<'a>(&self, buffer: &'a [u8]) -> &'a [u8] { &buffer[self.range.start.byte..self.range.end.byte] } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Reference { pub range: TextRange, pub text: String, pub symbol: Option, } impl Reference { /// Initialize a new reference pub fn new(range: TextRange, text: String, symbol: Option) -> Self { Self { range, text, symbol, } } } #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct LocalScope { pub range: TextRange, } impl LocalScope { pub fn new(range: TextRange) -> Self { Self { range } } } pub struct ScopeStack<'a> { pub scope_graph: &'a ScopeGraph, pub start: Option>, } impl<'a> Iterator for ScopeStack<'a> { type Item = NodeIndex; fn next(&mut self) -> Option { if let Some(start) = self.start { let parent = self .scope_graph .graph .edges_directed(start, Direction::Outgoing) .find(|edge| *edge.weight() == EdgeKind::ScopeToScope) .map(|edge| edge.target()); let original = start; self.start = parent; Some(original) } else { None } } } /// A node in the ScopeGraph #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Node { kind: NodeKind, metadata: Metadata, } impl Node { pub fn new(kind: NodeKind) -> Self { Self { kind, metadata: Default::default(), } } pub fn scope(s: LocalScope) -> Self { Self::new(NodeKind::Scope(s)) } pub fn local_def(d: LocalDef) -> Self { Self::new(NodeKind::Def(d)) } pub fn reference(r: Reference) -> Self { Self::new(NodeKind::Ref(r)) } pub fn import(i: LocalImport) -> Self { Self::new(NodeKind::Import(i)) } /// Produce the range spanned by this node pub fn range(&self) -> TextRange { match &self.kind { NodeKind::Scope(l) => l.range, NodeKind::Def(d) => d.range, NodeKind::Ref(r) => r.range, NodeKind::Import(i) => i.range, } } } #[derive(Serialize, Deserialize, Default, Debug, Clone)] struct Metadata {} /// The kind of a lexical node in the ScopeGraph #[derive(Serialize, Deserialize, Debug, Clone)] pub enum NodeKind { /// A scope node Scope(LocalScope), /// A definition node Def(LocalDef), /// An import node Import(LocalImport), /// A reference node Ref(Reference), } /// Describes the relation between two nodes in the ScopeGraph #[derive(Serialize, Deserialize, PartialEq, Eq, Copy, Clone, Debug)] pub enum EdgeKind { /// The edge weight from a nested scope to its parent scope ScopeToScope, /// The edge weight from a definition to its definition scope DefToScope, /// The edge weight from an import to its definition scope ImportToScope, /// The edge weight from a reference to its definition RefToDef, /// The edge weight from a reference to its import RefToImport, } /// A graph representation of scopes and names in a single syntax tree #[derive(Debug, Serialize, Deserialize, Clone)] pub struct ScopeGraph { /// The raw graph pub graph: Graph, // Graphs do not have the concept of a `root`, but lexical scopes follow the syntax // tree, and as a result, have a "root" node. The root_idx points to a scope node that // encompasses the entire file: the file-global scope. root_idx: NodeIndex, } impl ScopeGraph { pub fn new(range: TextRange) -> Self { let mut graph = Graph::new(); let root_idx = graph.add_node(Node::scope(LocalScope::new(range))); Self { graph, root_idx } } pub fn get_node(&self, node_idx: NodeIndex) -> Option<&Node> { self.graph.node_weight(node_idx) } /// Insert a local scope into the scope-graph fn insert_local_scope(&mut self, new: LocalScope) -> Option { if let Some(parent_scope) = self.scope_by_range(new.range, self.root_idx) { let new_scope = Node::scope(new); let new_idx = self.graph.add_node(new_scope); self.graph .add_edge(new_idx, parent_scope, EdgeKind::ScopeToScope); Some(new_idx) } else { None } } /// Insert a def into the scope-graph fn insert_local_def(&mut self, new: LocalDef) { if let Some(defining_scope) = self.scope_by_range(new.range, self.root_idx) { let new_def = Node::local_def(new); let new_idx = self.graph.add_node(new_def); self.graph .add_edge(new_idx, defining_scope, EdgeKind::DefToScope); } } /// Insert a def into the scope-graph, at the root scope #[allow(unused)] fn insert_global_def(&mut self, new: LocalDef) { let new_def = Node::local_def(new); let new_idx = self.graph.add_node(new_def); self.graph .add_edge(new_idx, self.root_idx, EdgeKind::DefToScope); } /// Insert an import into the scope-graph fn insert_local_import(&mut self, new: LocalImport) { if let Some(defining_scope) = self.scope_by_range(new.range, self.root_idx) { let new_imp = Node::import(new); let new_idx = self.graph.add_node(new_imp); self.graph .add_edge(new_idx, defining_scope, EdgeKind::ImportToScope); } } /// Insert a ref into the scope-graph fn insert_ref(&mut self, new: Reference) { let mut possible_defs = vec![]; let mut possible_imports = vec![]; if let Some(local_scope_idx) = self.scope_by_range(new.range, self.root_idx) { // traverse the scopes from the current-scope to the root-scope for scope in self.scope_stack(local_scope_idx) { // find candidate definitions in each scope for local_def in self .graph .edges_directed(scope, Direction::Incoming) .filter(|edge| *edge.weight() == EdgeKind::DefToScope) .map(|edge| edge.source()) { if let NodeKind::Def(def) = &self.graph[local_def].kind { if new.text == def.text { match (def.symbol.as_ref(), new.symbol.as_ref()) { // both contain symbols, but they don't belong to the same namepspace (Some(d), Some(r)) if d != r => {} // in all other cases, form an edge from the ref to def. // an empty symbol belongs to all namespaces: // * (None, None) // * (None, Some(_)) // * (Some(_), None) // * (Some(_), Some(_)) if def.namespace == ref.namespace _ => { possible_defs.push(local_def); } }; } } } // find candidate imports in each scope for local_import in self .graph .edges_directed(scope, Direction::Incoming) .filter(|edge| *edge.weight() == EdgeKind::ImportToScope) .map(|edge| edge.source()) { if let NodeKind::Import(import) = &self.graph[local_import].kind { if new.text == import.text { possible_imports.push(local_import); } } } } } if !possible_defs.is_empty() || !possible_imports.is_empty() { let new_ref = Node::reference(new); let ref_idx = self.graph.add_node(new_ref); for def_idx in possible_defs { self.graph.add_edge(ref_idx, def_idx, EdgeKind::RefToDef); } for imp_idx in possible_imports { self.graph.add_edge(ref_idx, imp_idx, EdgeKind::RefToImport); } } } fn scope_stack(&self, start: NodeIndex) -> ScopeStack<'_> { ScopeStack { scope_graph: self, start: Some(start), } } // The smallest scope that encompasses `range`. Start at `start` and narrow down if possible. fn scope_by_range(&self, range: TextRange, start: NodeIndex) -> Option { let target_range = self.graph[start].range(); if target_range.contains(&range) { let mut child_scopes = self .graph .edges_directed(start, Direction::Incoming) .filter(|edge| *edge.weight() == EdgeKind::ScopeToScope) .map(|edge| edge.source()) .collect::>(); child_scopes.sort_by_key(|s| self.graph[*s].range()); let target_child_scope = child_scopes.binary_search_by(|x| { if self.graph[*x].range().contains(&range) { std::cmp::Ordering::Equal } else { self.graph[*x].range().cmp(&range) } }); if let Some(t) = target_child_scope .ok() .and_then(|idx| child_scopes.get(idx)) .and_then(|s| self.scope_by_range(range, *s)) { return Some(t); } else { return Some(start); } } None } /// Produce a list of interesting ranges: ranges of defs and refs pub fn hoverable_ranges(&self) -> Box + '_> { let iterator = self.graph .node_indices() .filter_map(|node_idx| match &self.graph[node_idx].kind { NodeKind::Scope(_) => None, NodeKind::Def(d) => Some(d.range), NodeKind::Ref(r) => Some(r.range), NodeKind::Import(i) => Some(i.range), }); Box::new(iterator) } /// Produce possible definitions for a reference pub fn definitions( &self, reference_node: NodeIndex, ) -> Box + '_> { let iterator = self .graph .edges_directed(reference_node, Direction::Outgoing) .filter(|edge| *edge.weight() == EdgeKind::RefToDef) .map(|edge| edge.target()); Box::new(iterator) } /// Produce possible imports for a reference pub fn imports(&self, reference_node: NodeIndex) -> Box + '_> { let iterator = self .graph .edges_directed(reference_node, Direction::Outgoing) .filter(|edge| *edge.weight() == EdgeKind::RefToImport) .map(|edge| edge.target()); Box::new(iterator) } /// Produce possible references for a definition/import node pub fn references( &self, definition_node: NodeIndex, ) -> Box + '_> { let iterator = self .graph .edges_directed(definition_node, Direction::Incoming) .filter(|edge| { *edge.weight() == EdgeKind::RefToDef || *edge.weight() == EdgeKind::RefToImport }) .map(|edge| edge.source()); Box::new(iterator) } pub fn node_by_range(&self, start_byte: usize, end_byte: usize) -> Option { self.graph .node_indices() .filter(|&idx| self.is_definition(idx) || self.is_reference(idx) || self.is_import(idx)) .find(|&idx| { let node = self.graph[idx].range(); start_byte >= node.start.byte && end_byte <= node.end.byte }) } pub fn node_by_position(&self, line: usize, column: usize) -> Option { self.graph .node_indices() .filter(|&idx| self.is_definition(idx) || self.is_reference(idx)) .find(|&idx| { let node = self.graph[idx].range(); node.start.line == line && node.end.line == line && node.start.column <= column && node.end.column >= column }) } #[cfg(test)] pub fn find_node_by_name(&self, src: &[u8], name: &[u8]) -> Option { self.graph.node_indices().find(|idx| { matches!( &self.graph[*idx].kind, NodeKind::Def(d) if d.name(src) == name) }) } #[cfg(test)] pub fn debug(&self, src: &[u8]) -> crate::debug::ScopeDebug { let graph = &self.graph; let start = self.root_idx; crate::debug::ScopeDebug::new(graph, start, src) } pub fn is_definition(&self, node_idx: NodeIndex) -> bool { matches!(self.graph[node_idx].kind, NodeKind::Def(_)) } pub fn is_reference(&self, node_idx: NodeIndex) -> bool { matches!(self.graph[node_idx].kind, NodeKind::Ref(_)) } pub fn is_scope(&self, node_idx: NodeIndex) -> bool { matches!(self.graph[node_idx].kind, NodeKind::Scope(_)) } pub fn is_import(&self, node_idx: NodeIndex) -> bool { matches!(self.graph[node_idx].kind, NodeKind::Import(_)) } } fn build_scope_graph( tsg: tree_sitter_graph::graph::Graph, mut scope_graph: ScopeGraph, ) -> ScopeGraph { let nodes = tsg.iter_nodes().collect::>(); for node in nodes .iter() .map(|node_ref| &tsg[*node_ref]) .filter(|node| is_scope(node)) { let range = text_range::value_to_range(node.attributes.get(&Identifier::from("range")).unwrap()); let scope = LocalScope::new(range.into()); scope_graph.insert_local_scope(scope); } for node in nodes .iter() .map(|node_ref| &tsg[*node_ref]) .filter(|node| is_import(node)) { let range = text_range::value_to_range(node.attributes.get(&Identifier::from("range")).unwrap()); let text = node .attributes .get(&Identifier::from("text")) .and_then(|id| id.clone().into_string().ok()) .expect("import without text"); let import = LocalImport::new(range.into(), text); scope_graph.insert_local_import(import); } for node in nodes .iter() .map(|node_ref| &tsg[*node_ref]) .filter(|node| is_def(node)) { let range = text_range::value_to_range(node.attributes.get(&Identifier::from("range")).unwrap()); let symbol = node .attributes .get(&Identifier::from("symbol")) .and_then(|id| id.clone().into_string().ok()); let text = node .attributes .get(&Identifier::from("text")) .and_then(|id| id.clone().into_string().ok()) .expect("def without text"); let local_def = LocalDef::new(range.into(), text, symbol); // TODO: fix scoping here scope_graph.insert_local_def(local_def); } for node in nodes .iter() .map(|node_ref| &tsg[*node_ref]) .filter(|node| is_ref(node)) { let range = text_range::value_to_range(node.attributes.get(&Identifier::from("range")).unwrap()); let symbol = node .attributes .get(&Identifier::from("symbol")) .and_then(|id| id.clone().into_string().ok()); let text = node .attributes .get(&Identifier::from("text")) .and_then(|id| id.clone().into_string().ok()) .expect("ref without text"); let ref_ = Reference::new(range.into(), text, symbol); scope_graph.insert_ref(ref_); } scope_graph } fn is_string_attr(node: &tree_sitter_graph::graph::GraphNode, key: &str, value: &str) -> bool { matches!(node.attributes.get(&Identifier::from(key)).and_then(|v| v.as_str().ok()), Some(v) if v == value) } fn is_scope(node: &tree_sitter_graph::graph::GraphNode) -> bool { is_string_attr(node, "kind", "scope") } fn is_import(node: &tree_sitter_graph::graph::GraphNode) -> bool { is_string_attr(node, "kind", "import") } fn is_def(node: &tree_sitter_graph::graph::GraphNode) -> bool { is_string_attr(node, "kind", "def") } fn is_ref(node: &tree_sitter_graph::graph::GraphNode) -> bool { is_string_attr(node, "kind", "ref") } #[cfg(test)] mod tests { use super::*; use expect_test::{expect, Expect}; fn counts(src: &str) -> (usize, usize, usize, usize) { let sg = build_graph(src); let nodes = sg.graph.node_weights(); nodes.fold((0, 0, 0, 0), |(s, d, r, i), node| match node.kind { NodeKind::Scope(_) => (s + 1, d, r, i), NodeKind::Def(_) => (s, d + 1, r, i), NodeKind::Ref(_) => (s, d, r + 1, i), NodeKind::Import(_) => (s, d, r, i + 1), }) } pub fn test_scopes(src: &str, expected: Expect) { let graph = build_graph(src); let observed = graph.debug(src.as_bytes()); expected.assert_debug_eq(&observed) } pub fn build_graph(src: &str) -> ScopeGraph { StagBuilder::default() .with_source(src) .with_stag_file(crate::RUST) .with_language(tree_sitter_rust::LANGUAGE.into()) .execute() .unwrap() } #[test] fn declare_const_and_static() { let src = r#" const a: () = (); static b: () = (); "#; let (_, def_count, _, _) = counts(src); // a, b assert_eq!(def_count, 2); } #[test] fn declare_let_statement() { let src = r#" fn main() { let a = (); let (b, c) = (); let S { d, e } = (); let S { field: f, g } = (); let S { h, .. } = (); let S { i, field: _ } = (); } "#; let (_, def_count, _, _) = counts(src); // main, a, b, c, d, e, f, g, h, i assert_eq!(def_count, 10); } #[test] fn declare_function_params() { let src = r#" fn f1(a: T) {} fn f2(b: T, c: T) {} fn f3((d, e): (T, U)) {} fn f4(S {f, g}: S) {} fn f5(S {h, ..}: S) {} fn f6(S { field: i }: S) {} "#; let (_, def_count, _, _) = counts(src); // f1, f2, f3, f4, f5, f6, a, b, c, d, e, f, g, h, i assert_eq!(def_count, 15); } #[test] fn declare_closure_params() { let src = r#" fn main() { let _ = |x| {}; let _ = |x, y| {}; let _ = |x: ()| {}; let _ = |(x, y): ()| {}; } "#; let (_, def_count, _, _) = counts(src); // main, // x, // x, y, // x, // x, y assert_eq!(def_count, 7); } #[test] fn declare_labels() { let src = r#" fn main() { 'loop: loop {}; 'loop: for _ in () {} 'loop: while true {} } "#; let (_, def_count, _, _) = counts(src); // main, 'loop x3 assert_eq!(def_count, 4); } #[test] fn declare_types() { let src = r#" struct One { two: T, three: T, } enum Four { Five, Six(T), Seven { eight: T } } union Nine {} type Ten = (); "#; let (_, def_count, _, _) = counts(src); assert_eq!(def_count, 10); } #[test] fn declare_namespaces() { let src = r#" mod one {} pub mod two {} mod three { mod four {} } "#; let (_, def_count, _, _) = counts(src); assert_eq!(def_count, 4); } #[test] fn declare_let_expr() { let src = r#" if let a = () {} if let Some(a) = () {} while let a = () {} while let Some(a) = () {} "#; let (_, def_count, _, _) = counts(src); assert_eq!(def_count, 4); } #[test] fn refer_unary_expr() { let src = r#" fn main() { let a = 2; !a; -a; *a; } "#; let (_, _, ref_count, _) = counts(src); assert_eq!(ref_count, 3); } #[test] fn refer_binary_expr() { let src = r#" fn main() { let a = 2; let b = 3; a + b; a >> b; } "#; let (_, _, ref_count, _) = counts(src); assert_eq!(ref_count, 4); } #[test] fn refer_control_flow() { let src = r#" fn main() { let a = 2; // 1 if a {} // 2 if _ {} else if a {} // 3 while a { // 4 break a; } // 5 a?; // 6 return a; // 7 a.await; // 8 yield a; } "#; let (_, _, ref_count, _) = counts(src); assert_eq!(ref_count, 8); } #[test] fn refer_assignment() { let src = r#" fn main() { let mut a = 2; a += 2; a = 2; a *= 2; } "#; let (_, _, ref_count, _) = counts(src); assert_eq!(ref_count, 3); } #[test] fn refer_struct_expr() { let src = r#" fn main() { let a = 2; let b = 2; S { a, b }; S { ..a }; S { field: a, b }; } "#; let (_, _, ref_count, _) = counts(src); assert_eq!(ref_count, 5); } #[test] fn refer_dot() { let src = r#" fn main() { let a = S {}; a.b; a.foo(); } "#; let (_, _, ref_count, _) = counts(src); assert_eq!(ref_count, 2); } #[test] fn refer_arguments() { let src = r#" fn main() { let a = 2; let b = 3; foo(a, b); } "#; let (_, _, ref_count, _) = counts(src); assert_eq!(ref_count, 2); } #[test] fn function_params() { test_scopes( r#" fn foo(t: T, u: U) -> R {} "#, expect![[r#" scope { definitions: [ foo { context: "fn §foo§(t: T, u: U) -> R {}", }, ], child scopes: [ scope { definitions: [ t { context: "fn foo(§t§: T, u: U) -> R {}", }, u { context: "fn foo(t: T, §u§: U) -> R {}", }, ], child scopes: [], }, scope { definitions: [], child scopes: [], }, ], } "#]], ); } #[test] fn use_statements() { test_scopes( r#" mod intelligence; use bleep; use super::test_utils; use intelligence::language as lang; use crate::text_range::{TextRange, Point}; "#, expect![[r#" scope { definitions: [ intelligence { context: "mod §intelligence§;", referenced in (1): [ `use §intelligence§::language as lang;`, ], }, bleep { context: "use §bleep§;", }, test_utils { context: "use super::§test_utils§;", }, lang { context: "use intelligence::language as §lang§;", }, TextRange { context: "use crate::text_range::{§TextRange§, Point};", }, Point { context: "use crate::text_range::{TextRange, §Point§};", }, ], child scopes: [], } "#]], ) } #[test] fn lifetimes() { test_scopes( r#" impl<'a, T> Trait for Struct<'a, T> { fn foo<'b>(&'a self) -> &'b T { } } "#, expect![[r#" scope { definitions: [], child scopes: [ scope { definitions: [ 'a { context: "impl<§'a§, T> Trait for Struct<'a, T> {", referenced in (2): [ `impl<'a, T> Trait for Struct<§'a§, T> {`, `fn foo<'b>(&§'a§ self) -> &'b T { }`, ], }, T { context: "impl<'a, §T§> Trait for Struct<'a, T> {", referenced in (2): [ `impl<'a, T> Trait for Struct<'a, §T§> {`, `fn foo<'b>(&'a self) -> &'b §T§ { }`, ], }, ], child scopes: [ scope { definitions: [ foo { context: "fn §foo§<'b>(&'a self) -> &'b T { }", }, ], child scopes: [ scope { definitions: [ 'b { context: "fn foo<§'b§>(&'a self) -> &'b T { }", referenced in (1): [ `fn foo<'b>(&'a self) -> &§'b§ T { }`, ], }, self { context: "fn foo<'b>(&'a §self§) -> &'b T { }", }, ], child scopes: [], }, scope { definitions: [], child scopes: [], }, ], }, ], }, ], } "#]], ) } #[test] fn generics_and_traits() { test_scopes( r#" trait Foo {} fn foo<'a, 'b, T, U: Foo + 'a>(t: T, u: U) where T: Foo + 'b, { } "#, expect![[r#" scope { definitions: [ Foo { context: "trait §Foo§ {}", referenced in (2): [ `fn foo<'a, 'b, T, U: §Foo§ + 'a>(t: T, u: U)`, `where T: §Foo§ + 'b,`, ], }, foo { context: "fn §foo§<'a, 'b, T, U: Foo + 'a>(t: T, u: U)", }, ], child scopes: [ scope { definitions: [], child scopes: [ scope { definitions: [], child scopes: [ scope { definitions: [], child scopes: [], }, ], }, ], }, scope { definitions: [ 'a { context: "fn foo<§'a§, 'b, T, U: Foo + 'a>(t: T, u: U)", referenced in (1): [ `fn foo<'a, 'b, T, U: Foo + §'a§>(t: T, u: U)`, ], }, 'b { context: "fn foo<'a, §'b§, T, U: Foo + 'a>(t: T, u: U)", referenced in (1): [ `where T: Foo + §'b§,`, ], }, T { context: "fn foo<'a, 'b, §T§, U: Foo + 'a>(t: T, u: U)", referenced in (3): [ `fn foo<'a, 'b, T, U: Foo<§T§> + 'a>(t: T, u: U)`, `fn foo<'a, 'b, T, U: Foo + 'a>(t: §T§, u: U)`, `where §T§: Foo + 'b,`, ], }, U { context: "fn foo<'a, 'b, T, §U§: Foo + 'a>(t: T, u: U)", referenced in (1): [ `fn foo<'a, 'b, T, U: Foo + 'a>(t: T, u: §U§)`, ], }, t { context: "fn foo<'a, 'b, T, U: Foo + 'a>(§t§: T, u: U)", }, u { context: "fn foo<'a, 'b, T, U: Foo + 'a>(t: T, §u§: U)", }, ], child scopes: [], }, scope { definitions: [], child scopes: [], }, ], } "#]], ) } #[test] fn type_constructors() { test_scopes( r#" struct Highlight {} enum Direction { Incoming, Outgoing } fn foo() -> Highlight { Highlight { } } fn bar() -> Direction { Direction::Incoming } "#, expect![[r#" scope { definitions: [ Highlight { context: "struct §Highlight§ {}", referenced in (2): [ `fn foo() -> §Highlight§ {`, `§Highlight§ { }`, ], }, Direction { context: "enum §Direction§ { Incoming, Outgoing }", referenced in (2): [ `fn bar() -> §Direction§ {`, `§Direction§::Incoming`, ], }, foo { context: "fn §foo§() -> Highlight {", }, bar { context: "fn §bar§() -> Direction {", }, ], child scopes: [ scope { definitions: [], child scopes: [ scope { definitions: [], child scopes: [], }, ], }, scope { definitions: [], child scopes: [ scope { definitions: [ Incoming { context: "enum Direction { §Incoming§, Outgoing }", }, Outgoing { context: "enum Direction { Incoming, §Outgoing§ }", }, ], child scopes: [], }, ], }, scope { definitions: [], child scopes: [], }, scope { definitions: [], child scopes: [], }, scope { definitions: [], child scopes: [], }, scope { definitions: [], child scopes: [], }, ], } "#]], ) } #[test] fn macros() { test_scopes( r#" fn main() { let (a, b, c) = (); // top-level tokens assert_eq!(a, b + c); // nested tokens println!("{}", if a { b } then { c }); } "#, expect![[r#" scope { definitions: [ main { context: "fn §main§() {", }, ], child scopes: [ scope { definitions: [], child scopes: [], }, scope { definitions: [ a { context: "let (§a§, b, c) = ();", referenced in (2): [ `assert_eq!(§a§, b + c);`, `println!("{}", if §a§ { b } then { c });`, ], }, b { context: "let (a, §b§, c) = ();", referenced in (2): [ `assert_eq!(a, §b§ + c);`, `println!("{}", if a { §b§ } then { c });`, ], }, c { context: "let (a, b, §c§) = ();", referenced in (2): [ `assert_eq!(a, b + §c§);`, `println!("{}", if a { b } then { §c§ });`, ], }, ], child scopes: [], }, ], } "#]], ) } // Self::method and self.method can be raised as references #[test] fn handle_self_type_and_var() { test_scopes( r#" struct MyStruct {} impl MyStruct { fn foo() { Self::foo() } fn bar(&self) { self.bar() } } "#, expect![[r#" scope { definitions: [ MyStruct { context: "struct §MyStruct§ {}", referenced in (1): [ `impl §MyStruct§ {`, ], }, ], child scopes: [ scope { definitions: [], child scopes: [ scope { definitions: [], child scopes: [], }, ], }, scope { definitions: [], child scopes: [ scope { definitions: [ foo { context: "fn §foo§() {", referenced in (1): [ `Self::§foo§()`, ], }, bar { context: "fn §bar§(&self) {", referenced in (1): [ `self.§bar§()`, ], }, ], child scopes: [ scope { definitions: [], child scopes: [], }, scope { definitions: [], child scopes: [], }, scope { definitions: [ self { context: "fn bar(&§self§) {", }, ], child scopes: [], }, scope { definitions: [], child scopes: [], }, ], }, ], }, ], } "#]], ) } #[test] fn let_else_1_65_support() { test_scopes( r#" fn main() { let a = 3; if let b = a && let c = b && let d = c { d } else { return; } } "#, expect![[r#" scope { definitions: [ main { context: "fn §main§() {", }, ], child scopes: [ scope { definitions: [], child scopes: [], }, scope { definitions: [ a { context: "let §a§ = 3;", referenced in (1): [ `if let b = §a§`, ], }, ], child scopes: [ scope { definitions: [ b { context: "if let §b§ = a", referenced in (1): [ `&& let c = §b§`, ], }, c { context: "&& let §c§ = b", referenced in (1): [ `&& let d = §c§ {`, ], }, d { context: "&& let §d§ = c {", }, ], child scopes: [], }, scope { definitions: [], child scopes: [], }, scope { definitions: [], child scopes: [], }, ], }, ], } "#]], ) } }