use alloc::collections::BTreeSet;
use alloc::vec::Vec;
use crate::syntax::SyntaxGraph;
use crate::NodeId;
fn is_self_ref(graph: &SyntaxGraph, def_nid: NodeId, lookup_nid: NodeId) -> bool {
graph.nodes.get(&def_nid).is_some_and(|node| {
node.value_id() == Some(lookup_nid) || node.variable_id() == Some(lookup_nid)
})
}
fn is_valid_def(graph: &SyntaxGraph, def_nid: NodeId, lookup_nid: NodeId) -> bool {
!is_self_ref(graph, def_nid, lookup_nid) && def_nid < lookup_nid
}
fn dedup_by_subpath(graph: &SyntaxGraph, definitions: Vec<NodeId>) -> Vec<NodeId> {
let mut seen_subpaths: BTreeSet<Option<NodeId>> = BTreeSet::new();
definitions
.into_iter()
.filter(|def_nid| {
let subpath = graph.subpath_index.get(def_nid).copied().flatten();
seen_subpaths.insert(subpath)
})
.collect()
}
fn valid_defs_in_scope(
graph: &SyntaxGraph,
def_nids: &[NodeId],
lookup_nid: NodeId,
) -> Option<Vec<NodeId>> {
let valid_defs: Vec<NodeId> = def_nids
.iter()
.copied()
.filter(|&def_nid| is_valid_def(graph, def_nid, lookup_nid))
.collect();
(!valid_defs.is_empty()).then(|| dedup_by_subpath(graph, valid_defs))
}
fn find_defs_in_scope(
graph: &SyntaxGraph,
symbol: &str,
scope: NodeId,
lookup_nid: NodeId,
) -> Option<Vec<NodeId>> {
let direct = graph
.symbol_index
.get(symbol)
.and_then(|scope_defs| scope_defs.get(&scope))
.and_then(|def_nids| valid_defs_in_scope(graph, def_nids, lookup_nid));
if direct.is_some() {
return direct;
}
graph
.symbol_index
.iter()
.filter(|(compound_key, _)| {
compound_key.contains(',') && compound_key.split(',').any(|part| part == symbol)
})
.find_map(|(_, scope_defs)| {
scope_defs
.get(&scope)
.and_then(|def_nids| valid_defs_in_scope(graph, def_nids, lookup_nid))
})
}
#[must_use]
pub fn get_all_scope_definitions(graph: &SyntaxGraph, lookup_nid: NodeId) -> Vec<NodeId> {
let Some(node) = graph.nodes.get(&lookup_nid) else {
return Vec::new();
};
let (Some(symbol), Some(scope)) = (
node.symbol().filter(|symbol| !symbol.is_empty()),
node.symbol_scope(),
) else {
return Vec::new();
};
let mut current_scope = scope;
loop {
if let Some(definitions) = find_defs_in_scope(graph, symbol, current_scope, lookup_nid) {
return definitions;
}
match graph.scope_parent.get(¤t_scope) {
Some(parent_scope) => current_scope = *parent_scope,
None => return Vec::new(),
}
}
}
#[cfg(test)]
mod tests {
use alloc::borrow::ToOwned;
use alloc::vec;
use alloc::vec::Vec;
use super::get_all_scope_definitions;
use crate::syntax::{SyntaxGraph, SyntaxNode};
use crate::NodeId;
const SCOPE: NodeId = NodeId(1);
const PARENT_SCOPE: NodeId = NodeId(0);
const LOOKUP: NodeId = NodeId(50);
fn declaration(value_id: Option<NodeId>, variable_id: Option<NodeId>) -> SyntaxNode {
SyntaxNode::VariableDeclaration {
variable: "x".to_owned(),
variable_type: None,
value_id,
variable_id,
access_modifier: None,
}
}
fn graph_with_lookup(symbol: &str, symbol_scope: Option<NodeId>) -> SyntaxGraph {
let mut graph = SyntaxGraph::new();
graph.add_node(
LOOKUP,
SyntaxNode::SymbolLookup {
symbol: symbol.to_owned(),
symbol_scope,
value: None,
},
);
graph
}
fn index(graph: &mut SyntaxGraph, symbol: &str, scope: NodeId, defs: Vec<NodeId>) {
for def_nid in &defs {
if !graph.nodes.contains_key(def_nid) {
graph.add_node(*def_nid, declaration(None, None));
}
}
graph
.symbol_index
.entry(symbol.to_owned())
.or_default()
.insert(scope, defs);
}
#[test]
fn resolves_a_definition_in_the_lookup_scope() {
let mut graph = graph_with_lookup("x", Some(SCOPE));
index(&mut graph, "x", SCOPE, vec![NodeId(10)]);
assert_eq!(get_all_scope_definitions(&graph, LOOKUP), vec![NodeId(10)]);
}
#[test]
fn climbs_the_scope_chain_to_a_parent_definition() {
let mut graph = graph_with_lookup("x", Some(SCOPE));
graph.scope_parent.insert(SCOPE, PARENT_SCOPE);
index(&mut graph, "x", PARENT_SCOPE, vec![NodeId(10)]);
assert_eq!(get_all_scope_definitions(&graph, LOOKUP), vec![NodeId(10)]);
}
#[test]
fn the_lookup_scope_shadows_the_parent_definition() {
let mut graph = graph_with_lookup("x", Some(SCOPE));
graph.scope_parent.insert(SCOPE, PARENT_SCOPE);
index(&mut graph, "x", PARENT_SCOPE, vec![NodeId(10)]);
index(&mut graph, "x", SCOPE, vec![NodeId(20)]);
assert_eq!(get_all_scope_definitions(&graph, LOOKUP), vec![NodeId(20)]);
}
#[test]
fn ignores_definitions_declared_after_the_lookup() {
let mut graph = graph_with_lookup("x", Some(SCOPE));
index(&mut graph, "x", SCOPE, vec![NodeId(60)]);
assert_eq!(get_all_scope_definitions(&graph, LOOKUP), Vec::new());
}
#[test]
fn skips_definitions_referencing_the_lookup_itself() {
let mut graph = graph_with_lookup("x", Some(SCOPE));
graph.add_node(NodeId(10), declaration(Some(LOOKUP), None));
graph.add_node(NodeId(20), declaration(None, Some(LOOKUP)));
index(
&mut graph,
"x",
SCOPE,
vec![NodeId(10), NodeId(20), NodeId(30)],
);
assert_eq!(get_all_scope_definitions(&graph, LOOKUP), vec![NodeId(30)]);
}
#[test]
fn keeps_the_first_definition_per_subpath() {
let mut graph = graph_with_lookup("x", Some(SCOPE));
graph.subpath_index.insert(NodeId(10), Some(NodeId(9)));
graph.subpath_index.insert(NodeId(20), Some(NodeId(9)));
index(&mut graph, "x", SCOPE, vec![NodeId(10), NodeId(20)]);
assert_eq!(get_all_scope_definitions(&graph, LOOKUP), vec![NodeId(10)]);
}
#[test]
fn resolves_a_symbol_inside_a_compound_key() {
let mut graph = graph_with_lookup("value", Some(SCOPE));
index(&mut graph, "done,value", SCOPE, vec![NodeId(10)]);
assert_eq!(get_all_scope_definitions(&graph, LOOKUP), vec![NodeId(10)]);
}
#[test]
fn a_compound_fragment_match_does_not_count() {
let mut graph = graph_with_lookup("val", Some(SCOPE));
index(&mut graph, "done,value", SCOPE, vec![NodeId(10)]);
assert_eq!(get_all_scope_definitions(&graph, LOOKUP), Vec::new());
}
#[test]
fn returns_empty_without_a_symbol_scope() {
let mut graph = graph_with_lookup("x", None);
index(&mut graph, "x", SCOPE, vec![NodeId(10)]);
assert_eq!(get_all_scope_definitions(&graph, LOOKUP), Vec::new());
}
#[test]
fn returns_empty_for_an_empty_symbol() {
let mut graph = graph_with_lookup("", Some(SCOPE));
index(&mut graph, "", SCOPE, vec![NodeId(10)]);
assert_eq!(get_all_scope_definitions(&graph, LOOKUP), Vec::new());
}
#[test]
fn returns_empty_for_a_missing_lookup_node() {
let graph = SyntaxGraph::new();
assert_eq!(get_all_scope_definitions(&graph, LOOKUP), Vec::new());
}
}