use rowan::TextRange;
use smol_str::SmolStr;
use crate::syntax::{SyntaxKind, SyntaxNode, SyntaxToken};
use super::binding::{Binding, BindingId, BindingKind};
use super::import::{
ExportEntry, ImportItem, LoadKind, ModuleLoad, ModulePath, QualifiedRead, Visibility,
};
use super::scope::{Scope, ScopeId, ScopeKind};
use super::{Access, IdentRef, SemanticModel};
pub(crate) fn build(root: &SyntaxNode) -> SemanticModel {
let mut builder = Builder {
model: SemanticModel::default(),
global_decls: Vec::new(),
};
let file = builder.push_scope(ScopeKind::File, None, root.text_range());
builder.declare_in(root, file);
builder.walk_children(root, file);
builder
.model
.idents
.sort_by_key(|ident| (ident.range.start(), ident.range.end()));
builder.model
}
fn creates_scope(kind: SyntaxKind) -> bool {
matches!(
kind,
SyntaxKind::FUNCTION_DEF
| SyntaxKind::MACRO_DEF
| SyntaxKind::ARROW_EXPR
| SyntaxKind::DO_EXPR
| SyntaxKind::LET_EXPR
| SyntaxKind::FOR_EXPR
| SyntaxKind::WHILE_EXPR
| SyntaxKind::TRY_EXPR
| SyntaxKind::COMPREHENSION
| SyntaxKind::BRACES_COMPREHENSION
| SyntaxKind::TYPED_COMPREHENSION
| SyntaxKind::GENERATOR
| SyntaxKind::STRUCT_DEF
| SyntaxKind::ABSTRACT_DEF
| SyntaxKind::PRIMITIVE_DEF
| SyntaxKind::MODULE_DEF
| SyntaxKind::QUOTE_EXPR
| SyntaxKind::QUOTE_SYM
)
}
fn is_augmented_assign(kind: SyntaxKind) -> bool {
matches!(
kind,
SyntaxKind::PLUS_EQ
| SyntaxKind::MINUS_EQ
| SyntaxKind::STAR_EQ
| SyntaxKind::SLASH_EQ
| SyntaxKind::BACKSLASH_EQ
| SyntaxKind::SLASH_SLASH_EQ
| SyntaxKind::CARET_EQ
| SyntaxKind::PERCENT_EQ
| SyntaxKind::PIPE_EQ
| SyntaxKind::AMP_EQ
| SyntaxKind::SHL_EQ
| SyntaxKind::SHR_EQ
| SyntaxKind::USHR_EQ
| SyntaxKind::DIV_EQ
| SyntaxKind::XOR_EQ
)
}
fn is_broadcast_assign(kind: SyntaxKind) -> bool {
matches!(
kind,
SyntaxKind::DOT_EQ
| SyntaxKind::DOT_PLUS_EQ
| SyntaxKind::DOT_MINUS_EQ
| SyntaxKind::DOT_STAR_EQ
| SyntaxKind::DOT_SLASH_EQ
| SyntaxKind::DOT_BACKSLASH_EQ
| SyntaxKind::DOT_SLASH_SLASH_EQ
| SyntaxKind::DOT_CARET_EQ
| SyntaxKind::DOT_PERCENT_EQ
| SyntaxKind::DOT_SHL_EQ
| SyntaxKind::DOT_SHR_EQ
| SyntaxKind::DOT_USHR_EQ
| SyntaxKind::DOT_DIV_EQ
| SyntaxKind::DOT_XOR_EQ
)
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum AssignOp {
Plain,
Augmented,
Broadcast,
}
fn assign_op(node: &SyntaxNode) -> AssignOp {
for element in node.children_with_tokens() {
if let Some(token) = element.into_token() {
let kind = token.kind();
if is_augmented_assign(kind) {
return AssignOp::Augmented;
}
if is_broadcast_assign(kind) {
return AssignOp::Broadcast;
}
if matches!(kind, SyntaxKind::EQ | SyntaxKind::UNICODE_ASSIGN_OP) {
return AssignOp::Plain;
}
}
}
AssignOp::Plain
}
fn is_field_access(node: &SyntaxNode) -> bool {
node.kind() == SyntaxKind::BINARY_EXPR
&& node
.children_with_tokens()
.filter_map(|e| e.into_token())
.any(|t| t.kind() == SyntaxKind::DOT)
}
fn name_ident(node: &SyntaxNode) -> Option<SyntaxToken> {
node.children_with_tokens()
.filter_map(|e| e.into_token())
.find(|t| t.kind() == SyntaxKind::IDENT)
}
struct ImportClause {
leading_dots: u32,
components: Vec<(SmolStr, TextRange)>,
alias: Option<(SmolStr, TextRange)>,
range: TextRange,
unbindable: bool,
}
impl ImportClause {
fn binding_name(&self) -> Option<(&SmolStr, TextRange)> {
if let Some((name, range)) = &self.alias {
return Some((name, *range));
}
if self.unbindable {
return None;
}
self.components.last().map(|(name, range)| (name, *range))
}
fn path(&self) -> ModulePath {
ModulePath {
leading_dots: self.leading_dots,
components: self
.components
.iter()
.map(|(name, _)| name.clone())
.collect(),
}
}
fn as_item(&self) -> Option<ImportItem> {
if self.unbindable || self.components.is_empty() {
return None;
}
Some(ImportItem {
name: self.components.last().unwrap().0.clone(),
alias: self.alias.as_ref().map(|(name, _)| name.clone()),
range: self.range,
})
}
}
fn import_path_parts(path: &SyntaxNode, clause: &mut ImportClause) {
let mut seen_name = false;
let component = |clause: &mut ImportClause, text: &str, range: TextRange| {
clause.components.push((SmolStr::new(text), range));
clause.unbindable = false;
};
for element in path.children_with_tokens() {
match element {
rowan::NodeOrToken::Token(t) => match t.kind() {
SyntaxKind::DOT if !seen_name => clause.leading_dots += 1,
SyntaxKind::DOT_DOT if !seen_name => clause.leading_dots += 2,
SyntaxKind::DOT_DOT_DOT if !seen_name => clause.leading_dots += 3,
SyntaxKind::IDENT => {
component(clause, t.text(), t.text_range());
seen_name = true;
}
SyntaxKind::DOT_DOT_DOT => component(clause, "..", t.text_range()),
SyntaxKind::DOT | SyntaxKind::DOT_DOT | SyntaxKind::COLON => {}
k if k.is_operator() => {
if !seen_name && t.text().starts_with('.') {
clause.leading_dots += 1;
}
component(clause, t.text().trim_start_matches('.'), t.text_range());
seen_name = true;
}
_ => {}
},
rowan::NodeOrToken::Node(n) => match n.kind() {
SyntaxKind::NAME => {
if let Some(t) = name_ident(&n) {
component(clause, t.text(), t.text_range());
}
seen_name = true;
}
SyntaxKind::QUOTE_SYM | SyntaxKind::PAREN_EXPR => {
if let Some(t) = quoted_symbol_token(&n) {
component(clause, t.text(), t.text_range());
}
seen_name = true;
}
SyntaxKind::MACRO_NAME => {
if let Some(t) = macro_name_ident(&n) {
component(clause, &format!("@{}", t.text()), n.text_range());
}
seen_name = true;
}
SyntaxKind::INTERPOLATION => {
clause.unbindable = true;
seen_name = true;
}
_ => {}
},
}
}
}
fn quoted_symbol_token(node: &SyntaxNode) -> Option<SyntaxToken> {
node.descendants_with_tokens()
.filter_map(|e| e.into_token())
.find(|t| {
t.kind() == SyntaxKind::IDENT
|| (t.kind().is_operator() && t.kind() != SyntaxKind::COLON)
})
}
fn macro_name_ident(node: &SyntaxNode) -> Option<SyntaxToken> {
node.children_with_tokens()
.filter_map(|e| e.into_token())
.filter(|t| t.kind() == SyntaxKind::IDENT)
.last()
}
fn collect_import_clauses(stmt: &SyntaxNode) -> (Vec<ImportClause>, Option<Vec<ImportClause>>) {
let mut before: Vec<ImportClause> = Vec::new();
let mut after: Option<Vec<ImportClause>> = None;
for element in stmt.children_with_tokens() {
match element {
rowan::NodeOrToken::Token(t) => match t.kind() {
SyntaxKind::COLON => after = Some(Vec::new()),
SyntaxKind::DOLLAR => {
if let Some(clause) = after.as_mut().unwrap_or(&mut before).last_mut() {
clause.unbindable = true;
}
}
_ => {}
},
rowan::NodeOrToken::Node(n)
if matches!(n.kind(), SyntaxKind::IMPORT_PATH | SyntaxKind::IMPORT_ALIAS) =>
{
let mut clause = ImportClause {
leading_dots: 0,
components: Vec::new(),
alias: None,
range: n.text_range(),
unbindable: false,
};
let path = if n.kind() == SyntaxKind::IMPORT_ALIAS {
clause.alias = n
.children_with_tokens()
.filter_map(|e| e.into_token())
.filter(|t| t.kind() == SyntaxKind::IDENT && t.text() != "as")
.last()
.map(|t| (SmolStr::new(t.text()), t.text_range()));
n.children().find(|c| c.kind() == SyntaxKind::IMPORT_PATH)
} else {
Some(n.clone())
};
if let Some(path) = path {
import_path_parts(&path, &mut clause);
}
after.as_mut().unwrap_or(&mut before).push(clause);
}
_ => {}
}
}
(before, after)
}
fn qualified_name_chain(node: &SyntaxNode) -> Option<(Vec<SmolStr>, SyntaxNode)> {
let mut reversed: Vec<SmolStr> = Vec::new();
let mut cursor = node.clone();
loop {
let mut children = cursor.children();
let lhs = children.next()?;
let rhs = children.next()?;
let field = name_ident(&rhs).filter(|_| rhs.kind() == SyntaxKind::NAME)?;
reversed.push(SmolStr::new(field.text()));
match lhs.kind() {
SyntaxKind::NAME => {
reversed.push(SmolStr::new(name_ident(&lhs)?.text()));
reversed.reverse();
return Some((reversed, lhs));
}
SyntaxKind::BINARY_EXPR if is_field_access(&lhs) => cursor = lhs,
_ => return None,
}
}
}
fn annotation_parts(node: &SyntaxNode) -> (Option<SyntaxNode>, Vec<SyntaxNode>) {
let mut pattern = None;
let mut types = Vec::new();
let mut seen_colon = false;
for element in node.children_with_tokens() {
match element {
rowan::NodeOrToken::Token(token) if token.kind() == SyntaxKind::COLON_COLON => {
seen_colon = true;
}
rowan::NodeOrToken::Node(child) => {
if seen_colon {
types.push(child);
} else {
pattern = Some(child);
}
}
_ => {}
}
}
(pattern, types)
}
fn peel_signature(start: SyntaxNode) -> (Option<SyntaxNode>, Vec<SyntaxNode>, Option<SyntaxNode>) {
let mut wheres = Vec::new();
let mut return_ty = None;
let mut cursor = Some(start);
while let Some(node) = cursor {
match node.kind() {
SyntaxKind::WHERE_EXPR => {
let mut children = node.children();
cursor = children.next();
wheres.extend(children);
}
SyntaxKind::TYPE_ANNOTATION => {
let (pattern, types) = annotation_parts(&node);
if pattern.as_ref().is_some_and(has_call_core) {
return_ty = types.into_iter().next();
cursor = pattern;
} else {
return (Some(node), wheres, return_ty);
}
}
_ => return (Some(node), wheres, return_ty),
}
}
(None, wheres, return_ty)
}
fn has_call_core(node: &SyntaxNode) -> bool {
let mut cursor = Some(node.clone());
while let Some(n) = cursor {
match n.kind() {
SyntaxKind::CALL_EXPR => return true,
SyntaxKind::WHERE_EXPR => cursor = n.children().next(),
SyntaxKind::TYPE_ANNOTATION => cursor = annotation_parts(&n).0,
_ => return false,
}
}
false
}
enum AssignSlot {
Existing(BindingId),
NewIn(ScopeId, BindingKind),
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum DeclKind {
Local,
Global,
Const,
}
fn type_name_of(start: &SyntaxNode) -> Option<SyntaxNode> {
match start.kind() {
SyntaxKind::NAME => Some(start.clone()),
SyntaxKind::CURLY_EXPR | SyntaxKind::BINARY_EXPR | SyntaxKind::COMPARISON_EXPR => {
start.children().next().and_then(|c| type_name_of(&c))
}
_ => None,
}
}
struct Builder {
model: SemanticModel,
global_decls: Vec<Vec<SmolStr>>,
}
impl Builder {
fn push_scope(
&mut self,
kind: ScopeKind,
parent: Option<ScopeId>,
range: TextRange,
) -> ScopeId {
let id = ScopeId(self.model.scopes.len() as u32);
self.model.scopes.push(Scope {
kind,
parent,
range,
bindings: Vec::new(),
});
self.global_decls.push(Vec::new());
id
}
fn push_binding(
&mut self,
name: &str,
kind: BindingKind,
scope: ScopeId,
def_range: TextRange,
) -> BindingId {
let id = BindingId(self.model.bindings.len() as u32);
self.model.bindings.push(Binding {
name: SmolStr::new(name),
kind,
scope,
def_range,
read: false,
});
self.model.scopes[scope.0 as usize].bindings.push(id);
id
}
fn push_ident(
&mut self,
name: &str,
range: TextRange,
scope: ScopeId,
access: Access,
is_macro: bool,
binding: Option<BindingId>,
) {
self.model.idents.push(IdentRef {
name: SmolStr::new(name),
range,
scope,
access,
is_macro,
binding,
});
}
fn scope(&self, id: ScopeId) -> &Scope {
&self.model.scopes[id.0 as usize]
}
fn find_in_scope(&self, scope: ScopeId, name: &str) -> Option<BindingId> {
self.scope(scope)
.bindings
.iter()
.rev()
.copied()
.find(|&b| self.model.bindings[b.0 as usize].name == name)
}
fn innermost_global(&self, scope: ScopeId) -> ScopeId {
let mut cursor = scope;
loop {
let s = self.scope(cursor);
if s.kind.is_global() {
return cursor;
}
cursor = s.parent.expect("local scope chains end at a global scope");
}
}
fn resolve_assign(&self, name: &str, scope: ScopeId) -> AssignSlot {
if self.scope(scope).kind.is_global() {
return match self.find_in_scope(scope, name) {
Some(b) => AssignSlot::Existing(b),
None => AssignSlot::NewIn(scope, BindingKind::Global),
};
}
let mut cursor = scope;
loop {
let s = self.scope(cursor);
if s.kind.is_global() {
break;
}
if self.global_decls[cursor.0 as usize]
.iter()
.any(|n| n == name)
{
let global = self.innermost_global(cursor);
return match self.find_in_scope(global, name) {
Some(b) => AssignSlot::Existing(b),
None => AssignSlot::NewIn(global, BindingKind::Global),
};
}
if let Some(b) = self.find_in_scope(cursor, name) {
return AssignSlot::Existing(b);
}
match s.parent {
Some(parent) => cursor = parent,
None => break,
}
}
AssignSlot::NewIn(scope, BindingKind::Local)
}
fn resolve_read(&self, name: &str, scope: ScopeId) -> Option<BindingId> {
let mut cursor = Some(scope);
while let Some(id) = cursor {
if let Some(b) = self.find_in_scope(id, name) {
return Some(b);
}
let s = self.scope(id);
cursor = if s.kind.is_global() { None } else { s.parent };
}
None
}
fn resolve_macro_read(&self, name: &str, scope: ScopeId) -> Option<BindingId> {
let mut cursor = Some(scope);
while let Some(id) = cursor {
let hit = self.scope(id).bindings.iter().rev().copied().find(|&b| {
let binding = &self.model.bindings[b.0 as usize];
match binding.kind {
BindingKind::Macro => binding.name == name,
BindingKind::Import => binding.name.strip_prefix('@') == Some(name),
_ => false,
}
});
if hit.is_some() {
return hit;
}
let s = self.scope(id);
cursor = if s.kind.is_global() { None } else { s.parent };
}
None
}
fn declare_in(&mut self, node: &SyntaxNode, scope: ScopeId) {
for child in node.children() {
self.declare_node(&child, scope);
}
}
fn declare_node(&mut self, node: &SyntaxNode, scope: ScopeId) {
match node.kind() {
SyntaxKind::FUNCTION_DEF => {
self.declare_function_name(node, scope, BindingKind::Function);
}
SyntaxKind::MACRO_DEF => {
self.declare_function_name(node, scope, BindingKind::Macro);
}
SyntaxKind::STRUCT_DEF | SyntaxKind::ABSTRACT_DEF | SyntaxKind::PRIMITIVE_DEF => {
self.declare_type_name(node, scope);
}
SyntaxKind::MODULE_DEF => self.declare_module_name(node, scope),
SyntaxKind::LOCAL_STMT => self.declare_declaration(node, scope, DeclKind::Local),
SyntaxKind::GLOBAL_STMT => self.declare_declaration(node, scope, DeclKind::Global),
SyntaxKind::CONST_STMT => self.declare_declaration(node, scope, DeclKind::Const),
SyntaxKind::IMPORT_STMT | SyntaxKind::USING_STMT => {
self.declare_import(node, scope);
}
kind if creates_scope(kind) => {}
SyntaxKind::ASSIGNMENT_EXPR => {
let mut children = node.children();
let lhs = children.next();
if let Some(lhs) = &lhs
&& assign_op(node) == AssignOp::Plain
&& has_call_core(lhs)
{
self.declare_signature_name(lhs.clone(), scope, BindingKind::Function);
return;
}
if let Some(lhs) = lhs
&& assign_op(node) != AssignOp::Broadcast
{
self.declare_target(&lhs, scope);
}
if let Some(lhs) = node.children().next()
&& !is_binding_target(lhs.kind())
{
self.declare_node(&lhs, scope);
}
for rest in children {
self.declare_node(&rest, scope);
}
}
_ => self.declare_in(node, scope),
}
}
fn declare_import(&mut self, node: &SyntaxNode, scope: ScopeId) {
let (before, after) = collect_import_clauses(node);
let clauses = match &after {
Some(items) if !before.is_empty() => items,
Some(_) => return,
None => &before,
};
for clause in clauses {
if let Some((name, range)) = clause.binding_name()
&& self.find_in_scope(scope, name).is_none()
{
self.push_binding(name, BindingKind::Import, scope, range);
}
}
}
fn declare_function_name(&mut self, node: &SyntaxNode, scope: ScopeId, kind: BindingKind) {
let Some(sig) = node.children().find(|c| c.kind() == SyntaxKind::SIGNATURE) else {
return;
};
if let Some(start) = sig.children().next() {
self.declare_signature_name(start, scope, kind);
}
}
fn declare_signature_name(&mut self, start: SyntaxNode, scope: ScopeId, kind: BindingKind) {
let (core, _, _) = peel_signature(start);
let name = match core {
Some(core) if core.kind() == SyntaxKind::CALL_EXPR => core
.children()
.next()
.filter(|c| c.kind() == SyntaxKind::NAME),
Some(core) if core.kind() == SyntaxKind::NAME => Some(core),
_ => None,
};
if let Some(name) = name
&& let Some(token) = name_ident(&name)
{
self.declare_name(&token, scope, Some(kind));
}
}
fn declare_type_name(&mut self, node: &SyntaxNode, scope: ScopeId) {
if let Some(name) = node
.children()
.find(|c| c.kind() == SyntaxKind::SIGNATURE)
.and_then(|sig| sig.children().next())
.and_then(|start| type_name_of(&start))
&& let Some(token) = name_ident(&name)
{
self.declare_name(&token, scope, Some(BindingKind::Type));
}
}
fn declare_module_name(&mut self, node: &SyntaxNode, scope: ScopeId) {
if let Some(name) = node
.children()
.find(|c| c.kind() == SyntaxKind::SIGNATURE)
.and_then(|sig| sig.children().find(|c| c.kind() == SyntaxKind::NAME))
&& let Some(token) = name_ident(&name)
{
self.declare_name(&token, scope, Some(BindingKind::Module));
}
}
fn declare_declaration(&mut self, node: &SyntaxNode, scope: ScopeId, decl: DeclKind) {
for child in node.children() {
self.declare_decl_pattern(&child, scope, decl);
}
}
fn declare_decl_pattern(&mut self, node: &SyntaxNode, scope: ScopeId, decl: DeclKind) {
match node.kind() {
SyntaxKind::NAME => {
if let Some(token) = name_ident(node) {
self.declare_declared_name(&token, scope, decl);
}
}
SyntaxKind::TUPLE_EXPR
| SyntaxKind::BARE_TUPLE_EXPR
| SyntaxKind::ARG
| SyntaxKind::SPLAT_EXPR
| SyntaxKind::PAREN_EXPR => {
for child in node.children() {
self.declare_decl_pattern(&child, scope, decl);
}
}
SyntaxKind::TYPE_ANNOTATION => {
if let Some(pattern) = annotation_parts(node).0 {
self.declare_decl_pattern(&pattern, scope, decl);
}
}
SyntaxKind::ASSIGNMENT_EXPR => {
let mut children = node.children();
if let Some(target) = children.next() {
self.declare_decl_pattern(&target, scope, decl);
}
for rest in children {
self.declare_node(&rest, scope);
}
}
_ => self.declare_node(node, scope),
}
}
fn declare_declared_name(&mut self, token: &SyntaxToken, scope: ScopeId, decl: DeclKind) {
if token.text() == "_" {
return;
}
match decl {
DeclKind::Local | DeclKind::Const => {
let kind = if decl == DeclKind::Const {
BindingKind::Const
} else {
BindingKind::Local
};
if self.find_in_scope(scope, token.text()).is_none() {
self.push_binding(token.text(), kind, scope, token.text_range());
}
}
DeclKind::Global => {
self.global_decls[scope.0 as usize].push(SmolStr::new(token.text()));
let global = self.innermost_global(scope);
if self.find_in_scope(global, token.text()).is_none() {
self.push_binding(
token.text(),
BindingKind::Global,
global,
token.text_range(),
);
}
}
}
}
fn declare_target(&mut self, node: &SyntaxNode, scope: ScopeId) {
match node.kind() {
SyntaxKind::NAME => {
if let Some(token) = name_ident(node) {
self.declare_name(&token, scope, None);
}
}
SyntaxKind::TUPLE_EXPR
| SyntaxKind::BARE_TUPLE_EXPR
| SyntaxKind::ARG
| SyntaxKind::SPLAT_EXPR
| SyntaxKind::PAREN_EXPR => {
for child in node.children() {
self.declare_target(&child, scope);
}
}
SyntaxKind::TYPE_ANNOTATION => {
if let Some(first) = node.children().next() {
self.declare_target(&first, scope);
}
}
_ => {}
}
}
fn declare_name(&mut self, token: &SyntaxToken, scope: ScopeId, kind: Option<BindingKind>) {
if token.text() == "_" {
return;
}
if let AssignSlot::NewIn(target, default_kind) = self.resolve_assign(token.text(), scope) {
self.push_binding(
token.text(),
kind.unwrap_or(default_kind),
target,
token.text_range(),
);
}
}
fn walk_children(&mut self, node: &SyntaxNode, scope: ScopeId) {
for child in node.children() {
self.walk_node(&child, scope);
}
}
fn walk_node(&mut self, node: &SyntaxNode, scope: ScopeId) {
match node.kind() {
SyntaxKind::NAME => self.record_name_read(node, scope),
SyntaxKind::ASSIGNMENT_EXPR => self.handle_assignment(node, scope),
SyntaxKind::FUNCTION_DEF => self.handle_function_def(node, scope),
SyntaxKind::MACRO_DEF => self.handle_function_def(node, scope),
SyntaxKind::ARROW_EXPR => self.handle_arrow(node, scope),
SyntaxKind::DO_EXPR => self.handle_do(node, scope),
SyntaxKind::LET_EXPR => self.handle_let(node, scope),
SyntaxKind::FOR_EXPR => self.handle_for(node, scope),
SyntaxKind::WHILE_EXPR => self.handle_while(node, scope),
SyntaxKind::TRY_EXPR => self.handle_try(node, scope),
SyntaxKind::COMPREHENSION
| SyntaxKind::BRACES_COMPREHENSION
| SyntaxKind::TYPED_COMPREHENSION
| SyntaxKind::GENERATOR => self.handle_comprehension(node, scope),
SyntaxKind::STRUCT_DEF | SyntaxKind::ABSTRACT_DEF | SyntaxKind::PRIMITIVE_DEF => {
self.handle_type_def(node, scope);
}
SyntaxKind::MODULE_DEF => self.handle_module(node, scope),
SyntaxKind::IMPORT_STMT | SyntaxKind::USING_STMT => self.handle_import(node, scope),
SyntaxKind::EXPORT_STMT | SyntaxKind::PUBLIC_STMT => {
self.handle_name_list(node, scope);
}
SyntaxKind::LOCAL_STMT | SyntaxKind::GLOBAL_STMT | SyntaxKind::CONST_STMT => {
self.handle_declaration(node, scope);
}
SyntaxKind::BINARY_EXPR if is_field_access(node) => {
if let Some((path, root)) = qualified_name_chain(node) {
self.model.qualified_reads.push(QualifiedRead {
path,
range: node.text_range(),
scope,
is_macro: false,
});
self.record_name_read(&root, scope);
} else if let Some(base) = node.children().next() {
self.walk_node(&base, scope);
}
}
SyntaxKind::KEYWORD_ARG => {
for child in node.children().skip(1) {
self.walk_node(&child, scope);
}
}
SyntaxKind::INTERPOLATION => self.walk_interpolation(node, scope),
SyntaxKind::MACRO_CALL => self.walk_macro_call(node, scope),
SyntaxKind::QUOTE_EXPR | SyntaxKind::QUOTE_SYM => self.walk_quoted(node, scope),
_ => self.walk_children(node, scope),
}
}
fn handle_assignment(&mut self, node: &SyntaxNode, scope: ScopeId) {
let op = assign_op(node);
let mut children = node.children();
let Some(lhs) = children.next() else { return };
if op == AssignOp::Plain && has_call_core(&lhs) {
return self.handle_short_form(node, scope, lhs);
}
if op == AssignOp::Broadcast {
self.walk_node(&lhs, scope);
} else {
let access = if op == AssignOp::Augmented {
Access::ReadWrite
} else {
Access::Write
};
self.walk_target(&lhs, scope, access);
}
for rest in children {
self.walk_node(&rest, scope);
}
}
fn walk_target(&mut self, node: &SyntaxNode, scope: ScopeId, access: Access) {
match node.kind() {
SyntaxKind::NAME => {
if let Some(token) = name_ident(node) {
self.write_name(&token, scope, access);
}
}
SyntaxKind::TUPLE_EXPR
| SyntaxKind::BARE_TUPLE_EXPR
| SyntaxKind::ARG
| SyntaxKind::SPLAT_EXPR
| SyntaxKind::PAREN_EXPR => {
for child in node.children() {
self.walk_target(&child, scope, access);
}
}
SyntaxKind::TYPE_ANNOTATION => {
let mut children = node.children();
if let Some(first) = children.next() {
self.walk_target(&first, scope, access);
}
for annotation in children {
self.walk_node(&annotation, scope);
}
}
_ => self.walk_node(node, scope),
}
}
fn write_name(&mut self, token: &SyntaxToken, scope: ScopeId, access: Access) {
if token.text() == "_" {
return;
}
let binding = match self.resolve_assign(token.text(), scope) {
AssignSlot::Existing(b) => b,
AssignSlot::NewIn(target, kind) => {
self.push_binding(token.text(), kind, target, token.text_range())
}
};
if access == Access::ReadWrite {
self.model.bindings[binding.0 as usize].read = true;
}
if self.model.bindings[binding.0 as usize].def_range == token.text_range() {
return; }
self.push_ident(
token.text(),
token.text_range(),
scope,
access,
false,
Some(binding),
);
}
fn record_name_read(&mut self, node: &SyntaxNode, scope: ScopeId) {
if let Some(token) = name_ident(node) {
self.record_token_read(&token, scope);
}
}
fn record_token_read(&mut self, token: &SyntaxToken, scope: ScopeId) {
if token.text() == "_" {
return;
}
let binding = self.resolve_read(token.text(), scope);
if let Some(b) = binding {
self.model.bindings[b.0 as usize].read = true;
}
self.push_ident(
token.text(),
token.text_range(),
scope,
Access::Read,
false,
binding,
);
}
fn handle_import(&mut self, node: &SyntaxNode, scope: ScopeId) {
let kind = if node.kind() == SyntaxKind::USING_STMT {
LoadKind::Using
} else {
LoadKind::Import
};
let (before, after) = collect_import_clauses(node);
if let Some(items) = after {
if let Some(base) = before.into_iter().next() {
self.model.module_loads.push(ModuleLoad {
kind,
path: base.path(),
alias: base.alias.map(|(name, _)| name),
items: Some(items.iter().filter_map(ImportClause::as_item).collect()),
range: node.text_range(),
scope,
});
}
} else {
for clause in before {
if clause.components.is_empty() {
continue;
}
self.model.module_loads.push(ModuleLoad {
kind,
path: clause.path(),
alias: clause.alias.map(|(name, _)| name),
items: None,
range: clause.range,
scope,
});
}
}
for child in node.descendants().skip(1) {
if child.kind() == SyntaxKind::INTERPOLATION {
self.walk_node(&child, scope);
}
}
let mut after_dollar = false;
for element in node.children_with_tokens() {
if let Some(token) = element.into_token() {
match token.kind() {
SyntaxKind::DOLLAR => after_dollar = true,
SyntaxKind::IDENT if after_dollar => {
self.record_token_read(&token, scope);
after_dollar = false;
}
SyntaxKind::WHITESPACE => {}
_ => after_dollar = false,
}
}
}
}
fn handle_name_list(&mut self, node: &SyntaxNode, scope: ScopeId) {
let visibility = if node.kind() == SyntaxKind::EXPORT_STMT {
Visibility::Exported
} else {
Visibility::Public
};
let mut skip_keyword = node.kind() == SyntaxKind::PUBLIC_STMT;
for element in node.children_with_tokens() {
match element {
rowan::NodeOrToken::Token(t) => match t.kind() {
SyntaxKind::IDENT if skip_keyword => skip_keyword = false,
SyntaxKind::IDENT => {
self.push_export(t.text(), t.text_range(), scope, visibility);
}
k if k.is_operator() => {
self.push_export(t.text(), t.text_range(), scope, visibility);
}
_ => {}
},
rowan::NodeOrToken::Node(n) => match n.kind() {
SyntaxKind::MACRO_NAME => {
if let Some(t) = macro_name_ident(&n) {
let name = format!("@{}", t.text());
self.push_export(&name, n.text_range(), scope, visibility);
}
}
SyntaxKind::PAREN_EXPR => {
let mut inner = n.children();
match (inner.next(), inner.next()) {
(Some(name), None) if name.kind() == SyntaxKind::NAME => {
if let Some(t) = name_ident(&name) {
self.push_export(t.text(), t.text_range(), scope, visibility);
}
}
_ => self.walk_children(&n, scope),
}
}
SyntaxKind::INTERPOLATION => self.walk_node(&n, scope),
_ => {}
},
}
}
}
fn push_export(
&mut self,
name: &str,
range: TextRange,
scope: ScopeId,
visibility: Visibility,
) {
let binding = if let Some(bare) = name.strip_prefix('@') {
self.resolve_macro_read(bare, scope)
} else {
self.find_in_scope(self.innermost_global(scope), name)
};
if let Some(b) = binding {
self.model.bindings[b.0 as usize].read = true;
}
self.model.exports.push(ExportEntry {
name: SmolStr::new(name),
visibility,
range,
scope,
binding,
});
}
fn handle_declaration(&mut self, node: &SyntaxNode, scope: ScopeId) {
for child in node.children() {
match child.kind() {
SyntaxKind::ASSIGNMENT_EXPR => self.handle_assignment(&child, scope),
_ => self.walk_target(&child, scope, Access::Write),
}
}
}
fn handle_type_def(&mut self, node: &SyntaxNode, scope: ScopeId) {
let struct_scope = self.push_scope(ScopeKind::Struct, Some(scope), node.text_range());
if let Some(start) = node
.children()
.find(|c| c.kind() == SyntaxKind::SIGNATURE)
.and_then(|sig| sig.children().next())
{
self.walk_type_signature(&start, scope, struct_scope);
}
if let Some(body) = node.children().find(|c| c.kind() == SyntaxKind::BLOCK) {
for stmt in body.children() {
match stmt.kind() {
SyntaxKind::NAME | SyntaxKind::TYPE_ANNOTATION => {}
_ => self.declare_node(&stmt, struct_scope),
}
}
for stmt in body.children() {
match stmt.kind() {
SyntaxKind::NAME => self.bind_field(&stmt, struct_scope),
SyntaxKind::TYPE_ANNOTATION => {
let (pattern, types) = annotation_parts(&stmt);
if let Some(pattern) = pattern {
self.bind_field(&pattern, struct_scope);
}
for ty in types {
self.walk_node(&ty, struct_scope);
}
}
_ => self.walk_node(&stmt, struct_scope),
}
}
}
}
fn bind_field(&mut self, node: &SyntaxNode, scope: ScopeId) {
if node.kind() == SyntaxKind::NAME
&& let Some(token) = name_ident(node)
&& token.text() != "_"
{
self.push_binding(token.text(), BindingKind::Field, scope, token.text_range());
}
}
fn walk_type_signature(
&mut self,
start: &SyntaxNode,
enclosing: ScopeId,
struct_scope: ScopeId,
) {
match start.kind() {
SyntaxKind::NAME => {
if let Some(token) = name_ident(start) {
self.write_name(&token, enclosing, Access::Write);
}
}
SyntaxKind::CURLY_EXPR => {
let mut children = start.children();
if let Some(name) = children.next()
&& name.kind() == SyntaxKind::NAME
&& let Some(token) = name_ident(&name)
{
self.write_name(&token, enclosing, Access::Write);
}
for args in start
.children()
.filter(|c| c.kind() == SyntaxKind::ARG_LIST)
{
for arg in args.children() {
self.bind_type_param_spec(&arg, struct_scope);
}
}
}
SyntaxKind::BINARY_EXPR | SyntaxKind::COMPARISON_EXPR => {
let mut children = start.children();
if let Some(name_part) = children.next() {
self.walk_type_signature(&name_part, enclosing, struct_scope);
}
for supertype in children {
self.walk_node(&supertype, struct_scope);
}
}
_ => self.walk_node(start, struct_scope),
}
}
fn handle_module(&mut self, node: &SyntaxNode, scope: ScopeId) {
if let Some(name) = node
.children()
.find(|c| c.kind() == SyntaxKind::SIGNATURE)
.and_then(|sig| sig.children().find(|c| c.kind() == SyntaxKind::NAME))
&& let Some(token) = name_ident(&name)
{
self.write_name(&token, scope, Access::Write);
}
let body = node.children().find(|c| c.kind() == SyntaxKind::BLOCK);
let range = body
.as_ref()
.map_or_else(|| node.text_range(), |b| b.text_range());
let module_scope = self.push_scope(ScopeKind::Module, Some(scope), range);
if let Some(body) = body {
self.declare_in(&body, module_scope);
self.walk_children(&body, module_scope);
}
}
fn handle_let(&mut self, node: &SyntaxNode, scope: ScopeId) {
let end = node.text_range().end();
let mut current = scope;
if let Some(bindings) = node
.children()
.find(|c| c.kind() == SyntaxKind::LET_BINDINGS)
{
for item in bindings.children() {
let range = TextRange::new(item.text_range().start(), end);
if item.kind() == SyntaxKind::ASSIGNMENT_EXPR {
let mut parts = item.children();
let target = parts.next();
for rhs in parts {
self.walk_node(&rhs, current);
}
current = self.push_scope(ScopeKind::Let, Some(current), range);
if let Some(target) = target {
self.bind_param_pattern(&target, current, BindingKind::LetVar);
}
} else {
current = self.push_scope(ScopeKind::Let, Some(current), range);
self.bind_param_pattern(&item, current, BindingKind::LetVar);
}
}
}
if let Some(body) = node.children().find(|c| c.kind() == SyntaxKind::BLOCK) {
let body_scope = self.push_scope(ScopeKind::Let, Some(current), body.text_range());
self.declare_in(&body, body_scope);
self.walk_children(&body, body_scope);
}
}
fn handle_for_binding(
&mut self,
node: &SyntaxNode,
outer: ScopeId,
end: rowan::TextSize,
kind: ScopeKind,
) -> ScopeId {
let mut current = outer;
let clauses: Vec<SyntaxNode> = node.children().collect();
let mut i = 0;
while i < clauses.len() {
let target = &clauses[i];
if let Some(iterable) = clauses.get(i + 1) {
self.walk_node(iterable, current);
}
let range = TextRange::new(target.text_range().start(), end);
current = self.push_scope(kind, Some(current), range);
self.bind_param_pattern(target, current, BindingKind::ForVar);
i += 2;
}
current
}
fn handle_for(&mut self, node: &SyntaxNode, scope: ScopeId) {
let end = node.text_range().end();
let mut current = scope;
for child in node.children() {
if child.kind() == SyntaxKind::FOR_BINDING {
current = self.handle_for_binding(&child, current, end, ScopeKind::For);
}
}
if current == scope {
current = self.push_scope(ScopeKind::For, Some(scope), node.text_range());
}
if let Some(body) = node.children().find(|c| c.kind() == SyntaxKind::BLOCK) {
self.declare_in(&body, current);
self.walk_children(&body, current);
}
}
fn handle_while(&mut self, node: &SyntaxNode, scope: ScopeId) {
let while_scope = self.push_scope(ScopeKind::While, Some(scope), node.text_range());
self.declare_in(node, while_scope);
self.walk_children(node, while_scope);
}
fn handle_try(&mut self, node: &SyntaxNode, scope: ScopeId) {
for child in node.children() {
match child.kind() {
SyntaxKind::BLOCK => {
let s = self.push_scope(ScopeKind::Try, Some(scope), child.text_range());
self.declare_in(&child, s);
self.walk_children(&child, s);
}
SyntaxKind::CATCH_CLAUSE => {
let s = self.push_scope(ScopeKind::Catch, Some(scope), child.text_range());
for part in child.children() {
match part.kind() {
SyntaxKind::NAME => {
self.bind_param_pattern(&part, s, BindingKind::CatchParam);
}
SyntaxKind::BLOCK => {
self.declare_in(&part, s);
self.walk_children(&part, s);
}
_ => self.walk_node(&part, s),
}
}
}
SyntaxKind::ELSE_CLAUSE | SyntaxKind::FINALLY_CLAUSE => {
let kind = if child.kind() == SyntaxKind::FINALLY_CLAUSE {
ScopeKind::Finally
} else {
ScopeKind::Try
};
let s = self.push_scope(kind, Some(scope), child.text_range());
self.declare_in(&child, s);
self.walk_children(&child, s);
}
_ => self.walk_node(&child, scope),
}
}
}
fn handle_comprehension(&mut self, node: &SyntaxNode, scope: ScopeId) {
let end = node.text_range().end();
let mut children = node.children().peekable();
if node.kind() == SyntaxKind::TYPED_COMPREHENSION
&& let Some(ty) = children.next()
{
self.walk_node(&ty, scope);
}
let rest: Vec<SyntaxNode> = children.collect();
let mut current = scope;
for child in &rest {
if child.kind() == SyntaxKind::FOR_BINDING {
current = self.handle_for_binding(child, current, end, ScopeKind::Comprehension);
}
}
if current == scope {
current = self.push_scope(ScopeKind::Comprehension, Some(scope), node.text_range());
}
for child in &rest {
if child.kind() != SyntaxKind::FOR_BINDING {
self.declare_node(child, current);
self.walk_node(child, current);
}
}
}
fn handle_function_def(&mut self, node: &SyntaxNode, scope: ScopeId) {
let signature = node
.children()
.find(|c| c.kind() == SyntaxKind::SIGNATURE)
.and_then(|sig| sig.children().next());
let body = node.children().find(|c| c.kind() == SyntaxKind::BLOCK);
let fn_scope = self.push_scope(ScopeKind::Function, Some(scope), node.text_range());
if let Some(start) = signature {
self.walk_signature(start, scope, fn_scope);
}
if let Some(body) = body {
self.declare_in(&body, fn_scope);
self.walk_children(&body, fn_scope);
}
}
fn handle_short_form(&mut self, node: &SyntaxNode, scope: ScopeId, lhs: SyntaxNode) {
let fn_scope = self.push_scope(ScopeKind::Function, Some(scope), node.text_range());
self.walk_signature(lhs, scope, fn_scope);
for rhs in node.children().skip(1) {
self.declare_node(&rhs, fn_scope);
self.walk_node(&rhs, fn_scope);
}
}
fn walk_signature(&mut self, start: SyntaxNode, enclosing: ScopeId, fn_scope: ScopeId) {
let (core, wheres, return_ty) = peel_signature(start);
for spec in &wheres {
self.bind_type_param_spec(spec, fn_scope);
}
match core {
Some(core) if core.kind() == SyntaxKind::CALL_EXPR => {
let mut children = core.children();
if let Some(callee) = children.next() {
match callee.kind() {
SyntaxKind::NAME => {
if let Some(token) = name_ident(&callee) {
self.write_name(&token, enclosing, Access::Write);
}
}
SyntaxKind::PAREN_EXPR | SyntaxKind::TUPLE_EXPR => {
self.bind_params(&callee, fn_scope, false);
}
_ => self.walk_node(&callee, enclosing),
}
}
for args in children {
if args.kind() == SyntaxKind::ARG_LIST {
self.bind_params(&args, fn_scope, false);
} else {
self.walk_node(&args, fn_scope);
}
}
}
Some(core) if core.kind() == SyntaxKind::TUPLE_EXPR => {
self.bind_params(&core, fn_scope, false);
}
Some(core) if core.kind() == SyntaxKind::NAME => {
if let Some(token) = name_ident(&core) {
self.write_name(&token, enclosing, Access::Write);
}
}
Some(core) => self.walk_node(&core, fn_scope),
None => {}
}
if let Some(ty) = return_ty {
self.walk_node(&ty, fn_scope);
}
}
fn handle_arrow(&mut self, node: &SyntaxNode, scope: ScopeId) {
let fn_scope = self.push_scope(ScopeKind::Function, Some(scope), node.text_range());
let mut children = node.children();
if let Some(params) = children.next() {
if params.kind() == SyntaxKind::TUPLE_EXPR {
self.bind_params(¶ms, fn_scope, false);
} else {
self.bind_param_pattern(¶ms, fn_scope, BindingKind::Param);
}
}
for body in children {
self.declare_node(&body, fn_scope);
self.walk_node(&body, fn_scope);
}
}
fn handle_do(&mut self, node: &SyntaxNode, scope: ScopeId) {
let params = node.children().find(|c| c.kind() == SyntaxKind::DO_PARAMS);
let body = node.children().find(|c| c.kind() == SyntaxKind::BLOCK);
for child in node.children() {
if !matches!(child.kind(), SyntaxKind::DO_PARAMS | SyntaxKind::BLOCK) {
self.walk_node(&child, scope);
}
}
let start = params
.as_ref()
.or(body.as_ref())
.map_or_else(|| node.text_range().start(), |n| n.text_range().start());
let range = TextRange::new(start, node.text_range().end());
let fn_scope = self.push_scope(ScopeKind::Function, Some(scope), range);
if let Some(params) = params {
self.bind_params(¶ms, fn_scope, false);
}
if let Some(body) = body {
self.declare_in(&body, fn_scope);
self.walk_children(&body, fn_scope);
}
}
fn bind_params(&mut self, list: &SyntaxNode, scope: ScopeId, keyword: bool) {
let kind = if keyword {
BindingKind::KeywordParam
} else {
BindingKind::Param
};
for child in list.children() {
match child.kind() {
SyntaxKind::ARG => {
for pattern in child.children() {
self.bind_param_pattern(&pattern, scope, kind);
}
}
SyntaxKind::KEYWORD_ARG => {
let mut parts = child.children();
if let Some(pattern) = parts.next() {
self.bind_param_pattern(&pattern, scope, kind);
}
for default in parts {
self.walk_node(&default, scope);
}
}
SyntaxKind::PARAMETERS => self.bind_params(&child, scope, true),
_ => self.bind_param_pattern(&child, scope, kind),
}
}
}
fn bind_param_pattern(&mut self, node: &SyntaxNode, scope: ScopeId, kind: BindingKind) {
match node.kind() {
SyntaxKind::NAME => {
if let Some(token) = name_ident(node)
&& token.text() != "_"
{
self.push_binding(token.text(), kind, scope, token.text_range());
}
}
SyntaxKind::TYPE_ANNOTATION => {
let (pattern, types) = annotation_parts(node);
if let Some(pattern) = pattern {
self.bind_param_pattern(&pattern, scope, kind);
}
for ty in types {
self.walk_node(&ty, scope);
}
}
SyntaxKind::SPLAT_EXPR
| SyntaxKind::TUPLE_EXPR
| SyntaxKind::ARG
| SyntaxKind::PAREN_EXPR => {
for child in node.children() {
self.bind_param_pattern(&child, scope, kind);
}
}
_ => self.walk_node(node, scope),
}
}
fn bind_type_param_spec(&mut self, spec: &SyntaxNode, scope: ScopeId) {
match spec.kind() {
SyntaxKind::NAME => {
if let Some(token) = name_ident(spec) {
self.push_binding(
token.text(),
BindingKind::TypeParam,
scope,
token.text_range(),
);
}
}
SyntaxKind::BRACES | SyntaxKind::ARG => {
for child in spec.children() {
self.bind_type_param_spec(&child, scope);
}
}
SyntaxKind::BINARY_EXPR | SyntaxKind::COMPARISON_EXPR => {
let mut children = spec.children();
if let Some(param) = children.next() {
self.bind_type_param_spec(¶m, scope);
}
for bound in children {
self.walk_node(&bound, scope);
}
}
_ => self.walk_node(spec, scope),
}
}
fn walk_interpolation(&mut self, node: &SyntaxNode, scope: ScopeId) {
for element in node.children_with_tokens() {
match element {
rowan::NodeOrToken::Token(token) if token.kind() == SyntaxKind::IDENT => {
self.record_token_read(&token, scope);
}
rowan::NodeOrToken::Node(child) => self.walk_node(&child, scope),
_ => {}
}
}
}
fn walk_macro_call(&mut self, node: &SyntaxNode, scope: ScopeId) {
for child in node.children() {
if child.kind() == SyntaxKind::MACRO_NAME {
self.walk_macro_name(&child, scope);
} else {
self.walk_node(&child, scope);
}
}
}
fn walk_macro_name(&mut self, name: &SyntaxNode, scope: ScopeId) {
let mut parts: Vec<(SmolStr, Option<SyntaxToken>)> = Vec::new();
for element in name.children_with_tokens() {
match element {
rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::IDENT => {
parts.push((SmolStr::new(t.text()), Some(t)));
}
rowan::NodeOrToken::Node(n) if n.kind() == SyntaxKind::NAME => {
if let Some(t) = name_ident(&n) {
parts.push((SmolStr::new(t.text()), Some(t)));
}
}
_ => {}
}
}
let Some((macro_name, macro_token)) = parts.pop() else {
return;
};
if parts.is_empty() {
let binding = self.resolve_macro_read(¯o_name, scope);
if let Some(b) = binding {
self.model.bindings[b.0 as usize].read = true;
}
if let Some(token) = macro_token {
self.push_ident(
token.text(),
token.text_range(),
scope,
Access::Read,
true,
binding,
);
}
return;
}
if let Some(token) = &parts[0].1 {
self.record_token_read(token, scope);
}
let mut path: Vec<SmolStr> = parts.into_iter().map(|(text, _)| text).collect();
path.push(SmolStr::new(format!("@{macro_name}")));
self.model.qualified_reads.push(QualifiedRead {
path,
range: name.text_range(),
scope,
is_macro: true,
});
}
fn walk_quoted(&mut self, node: &SyntaxNode, scope: ScopeId) {
for child in node.children() {
if child.kind() == SyntaxKind::INTERPOLATION {
self.walk_node(&child, scope);
} else {
self.walk_quoted(&child, scope);
}
}
}
}
fn is_binding_target(kind: SyntaxKind) -> bool {
matches!(
kind,
SyntaxKind::NAME
| SyntaxKind::TUPLE_EXPR
| SyntaxKind::BARE_TUPLE_EXPR
| SyntaxKind::ARG
| SyntaxKind::SPLAT_EXPR
| SyntaxKind::PAREN_EXPR
| SyntaxKind::TYPE_ANNOTATION
)
}