use crate::stack::build::StackBuilder;
use crate::stack::{StackNode, SymbolPush};
use crate::syntax::{SyntaxGraph, SyntaxKind, SyntaxNode};
use crate::{CodeGraph, Language, NodeId};
impl StackBuilder {
pub(super) fn add_lookups(&mut self, syntax: &SyntaxGraph, language: Language) {
let lookups = syntax
.nodes_by_type
.get(&SyntaxKind::SymbolLookup)
.map_or(&[][..], |ids| ids.as_slice());
for lookup in lookups {
self.add_lookup(syntax, language, *lookup);
}
}
fn add_lookup(&mut self, syntax: &SyntaxGraph, language: Language, lookup: NodeId) {
let Some(SyntaxNode::SymbolLookup {
symbol,
symbol_scope: Some(scope),
..
}) = syntax.nodes.get(&lookup)
else {
return;
};
if is_bound_by(syntax, language, lookup) {
return;
}
self.graph.add_node(
lookup,
StackNode::PushSymbol(SymbolPush {
symbol: symbol.clone(),
precedence: 0,
is_reference: true,
}),
);
if matches!(self.graph.nodes.get(scope), Some(StackNode::Scope(_))) {
self.graph.add_edge(lookup, *scope, 0);
}
}
}
fn is_bound_by(syntax: &SyntaxGraph, language: Language, lookup: NodeId) -> bool {
let mut child = lookup;
while let Some(parent) = syntax.ast_parents(child).first().copied() {
if let Some(binds) = binds(syntax.nodes.get(&parent), child, language) {
return binds;
}
child = parent;
}
false
}
fn binds(parent: Option<&SyntaxNode>, child: NodeId, language: Language) -> Option<bool> {
let python = language == Language::Python;
match parent {
Some(
SyntaxNode::ArrayInitializer { .. }
| SyntaxNode::Object { .. }
| SyntaxNode::Pair { .. }
| SyntaxNode::RestPattern { .. },
) => None,
Some(SyntaxNode::ParameterList) => Some(true),
Some(SyntaxNode::VariableDeclaration { variable_id, .. }) => {
Some(*variable_id == Some(child))
}
Some(SyntaxNode::CatchClause {
catch_declaration, ..
}) => Some(*catch_declaration == Some(child)),
Some(SyntaxNode::ForEachStatement { variable_id, .. }) => Some(*variable_id == child),
Some(SyntaxNode::ForStatement { initializer_id, .. }) => {
Some(python && *initializer_id == Some(child))
}
Some(SyntaxNode::Assignment {
variable_id,
operator: None,
..
}) => Some(python && *variable_id == child),
_ => Some(false),
}
}
#[cfg(test)]
mod tests {
use crate::stack::{build_stack_graph, StackGraph, StackNode, SymbolPush};
use crate::syntax::{SyntaxGraph, SyntaxNode};
use crate::{Language, NodeId};
use alloc::string::String;
const FILE: NodeId = NodeId(1);
const HOLDER: NodeId = NodeId(2);
const LOOKUP: NodeId = NodeId(3);
fn lookup_in(scope: NodeId) -> StackGraph {
let mut syntax = SyntaxGraph::new();
syntax.add_node(FILE, SyntaxNode::File);
syntax.add_node(HOLDER, SyntaxNode::ArgumentList);
syntax.add_ast_edge(FILE, HOLDER);
let symbol = String::from("x");
let symbol_scope = Some(scope);
syntax.add_node(
LOOKUP,
SyntaxNode::SymbolLookup {
symbol,
symbol_scope,
value: None,
},
);
syntax.add_ast_edge(HOLDER, LOOKUP);
build_stack_graph(&syntax, Language::Python, "mod.py")
}
#[test]
fn a_lookup_pushes_its_symbol_without_attaching_scopes() {
let push = SymbolPush {
symbol: String::from("x"),
precedence: 0,
is_reference: true,
};
assert_eq!(
lookup_in(FILE).nodes.get(&LOOKUP),
Some(&StackNode::PushSymbol(push))
);
}
#[test]
fn a_lookup_searches_from_its_scope() {
assert_eq!(lookup_in(FILE).successors(LOOKUP), [(FILE, 0)]);
}
#[test]
fn a_lookup_whose_scope_is_not_yet_a_stack_scope_stays_unlinked() {
assert_eq!(lookup_in(HOLDER).successors(LOOKUP), []);
}
}