feat(natu): detect single-use let bindings
This commit was merged in pull request #116.
This commit is contained in:
@@ -14,7 +14,7 @@ impl ScopeGraph {
|
||||
edges: Vec::new(),
|
||||
lets: Vec::new(),
|
||||
};
|
||||
let root = graph.add_scope(None);
|
||||
let root = graph.add_scope(None, ScopeKind::Root);
|
||||
graph.build_expr(&file.root, root);
|
||||
graph
|
||||
}
|
||||
@@ -36,7 +36,7 @@ impl ScopeGraph {
|
||||
.flat_map(|binding| self.build_expr(&binding.value, scope))
|
||||
.collect(),
|
||||
ast::Expr::Lambda { parameters, body } => {
|
||||
let scope = self.add_scope(Some(scope));
|
||||
let scope = self.add_scope(Some(scope), ScopeKind::Lambda);
|
||||
for parameter in parameters {
|
||||
self.add_declaration(¶meter.name, scope);
|
||||
}
|
||||
@@ -62,13 +62,14 @@ impl ScopeGraph {
|
||||
body: &ast::Expr,
|
||||
parent: NodeId,
|
||||
) -> Vec<NodeId> {
|
||||
let scope = self.add_scope(Some(parent));
|
||||
let scope = self.add_scope(Some(parent), ScopeKind::Let);
|
||||
let mut graph_bindings = bindings
|
||||
.iter()
|
||||
.map(|binding| Binding {
|
||||
declaration: self.add_declaration(&binding.key.name, scope),
|
||||
name: binding.key.name.clone(),
|
||||
references: Vec::new(),
|
||||
is_lambda: matches!(binding.value, ast::Expr::Lambda { .. }),
|
||||
range: binding.range,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@@ -92,6 +93,33 @@ impl ScopeGraph {
|
||||
references
|
||||
}
|
||||
|
||||
pub(crate) fn crosses_lambda_scope(&self, reference: NodeId, declaration: NodeId) -> bool {
|
||||
let Some(mut scope) = self.edge_target(reference, Node::is_scope) else {
|
||||
return false;
|
||||
};
|
||||
let Some(declaration_scope) = self
|
||||
.edges
|
||||
.iter()
|
||||
.find(|edge| edge.to == declaration && self.nodes[edge.from.0].is_scope())
|
||||
.map(|edge| edge.from)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
loop {
|
||||
if scope == declaration_scope {
|
||||
return false;
|
||||
}
|
||||
if matches!(self.nodes[scope.0], Node::Scope(ScopeKind::Lambda)) {
|
||||
return true;
|
||||
}
|
||||
let Some(parent) = self.edge_target(scope, Node::is_scope) else {
|
||||
return false;
|
||||
};
|
||||
scope = parent;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn resolve(&self, reference: NodeId) -> Vec<NodeId> {
|
||||
let Node::Reference(name) = &self.nodes[reference.0] else {
|
||||
return Vec::new();
|
||||
@@ -115,8 +143,8 @@ impl ScopeGraph {
|
||||
}
|
||||
}
|
||||
|
||||
fn add_scope(&mut self, parent: Option<NodeId>) -> NodeId {
|
||||
let scope = self.add_node(Node::Scope);
|
||||
fn add_scope(&mut self, parent: Option<NodeId>, kind: ScopeKind) -> NodeId {
|
||||
let scope = self.add_node(Node::Scope(kind));
|
||||
if let Some(parent) = parent {
|
||||
self.add_edge(scope, parent);
|
||||
}
|
||||
@@ -165,17 +193,24 @@ impl ScopeGraph {
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Node {
|
||||
Scope,
|
||||
Scope(ScopeKind),
|
||||
Declaration(Declaration),
|
||||
Reference(String),
|
||||
}
|
||||
|
||||
impl Node {
|
||||
fn is_scope(&self) -> bool {
|
||||
matches!(self, Self::Scope)
|
||||
matches!(self, Self::Scope(_))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ScopeKind {
|
||||
Root,
|
||||
Let,
|
||||
Lambda,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Declaration {
|
||||
name: String,
|
||||
@@ -202,5 +237,6 @@ pub(crate) struct Binding {
|
||||
pub(crate) declaration: NodeId,
|
||||
pub(crate) name: String,
|
||||
pub(crate) references: Vec<NodeId>,
|
||||
pub(crate) is_lambda: bool,
|
||||
pub(crate) range: ast::SourceRange,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user