use tree_sitter::Node;
use super::c_bind;
use crate::paths::Lang;
use crate::scope_model::walk::{Backend, Spec, dispatch};
use crate::scope_model::{IntroKind, Model, ScopeKind, Write};
const PREPROC: &[&str] = &[
"preproc_include",
"preproc_def",
"preproc_function_def",
"preproc_call",
];
const C_READS: &[&str] = &["identifier", "type_identifier"];
const C_SPEC: Spec = Spec {
skip_kinds: PREPROC,
block_scoped: &["compound_statement", "lambda_expression"],
function_kinds: &["function_definition", "method_definition"],
read_kinds: C_READS,
exclude_fields: &[],
};
const CPP_SPEC: Spec = Spec {
skip_kinds: PREPROC,
block_scoped: &["compound_statement", "lambda_expression"],
function_kinds: &["method_definition"],
read_kinds: C_READS,
exclude_fields: &[],
};
const OBJC_SPEC: Spec = Spec {
skip_kinds: &[
"preproc_include",
"preproc_def",
"preproc_function_def",
"preproc_call",
"class_interface",
"protocol_declaration",
"property_declaration",
"method_declaration",
],
block_scoped: &["compound_statement", "lambda_expression"],
function_kinds: &["function_definition", "method_definition"],
read_kinds: C_READS,
exclude_fields: &[],
};
pub(super) fn collect(root: Node, src: &[u8], lang: Lang) -> Vec<crate::scope_model::Scope> {
let mut c = Collector {
src,
model: Model::rooted(),
lang,
};
dispatch(&mut c, root, 0);
c.model.scopes
}
struct Collector<'a> {
src: &'a [u8],
model: Model,
lang: Lang,
}
impl Backend for Collector<'_> {
fn spec(&self) -> &'static Spec {
match self.lang {
Lang::Cpp => &CPP_SPEC,
Lang::ObjC => &OBJC_SPEC,
_ => &C_SPEC,
}
}
fn model(&mut self) -> &mut Model {
&mut self.model
}
fn text_of(&self, n: Node) -> &str {
n.utf8_text(self.src).unwrap_or("")
}
fn custom(&mut self, n: Node, scope: usize) {
match n.kind() {
"function_definition" => self.walk_function(n, scope),
"declaration" | "field_declaration" => self.bind_declaration(n, scope),
"assignment_expression" | "augmented_assignment_expression" => {
self.bind_assignment(n, scope);
}
"update_expression" => self.bind_update(n, scope),
"for_statement" => self.walk_for_statement(n, scope),
"for_range_statement" => self.walk_children_excluding_field(n, scope, "declarator"),
_ => self.walk_children(n, scope),
}
}
}
fn export_macro_class(n: Node) -> bool {
matches!(
n.child_by_field_name("type").map(|t| t.kind()),
Some("class_specifier" | "struct_specifier" | "union_specifier")
)
}
impl Collector<'_> {
fn walk_function(&mut self, n: Node, scope: usize) {
if export_macro_class(n) {
return;
}
let s = self.model().open_scope(ScopeKind::Function, scope);
self.walk_children(n, s);
}
fn bind_update(&mut self, n: Node, scope: usize) {
let arg = n
.child_by_field_name("argument")
.or_else(|| n.named_child(0));
if let Some(operand) = arg.filter(|o| o.kind() == "identifier") {
self.rebind_local(operand, scope, false, None);
} else if let Some(arg) = arg {
dispatch(self, arg, scope);
}
}
fn bind_declaration(&mut self, n: Node, scope: usize) {
if c_bind::skip_declaration_bind(n, self.src) {
self.walk_children(n, scope);
return;
}
let decl_value = n.child_by_field_name("value");
let in_block = n
.parent()
.is_some_and(|p| p.kind() == "compound_statement");
let mut cursor = n.walk();
for d in n.children(&mut cursor) {
self.bind_decl_child(d, scope, decl_value, in_block);
}
self.dispatch_opt(decl_value, scope);
}
fn bind_decl_child(&mut self, d: Node, scope: usize, decl_value: Option<Node>, in_block: bool) {
match d.kind() {
"init_declarator" | "identifier" => self.bind_declarator(d, scope),
"function_declarator" if in_block => self.bind_ctor_declarator(d, scope),
k if k.ends_with("_declarator") && k != "function_declarator" => {
self.try_bind_local(d, scope, decl_value);
}
_ => {}
}
}
fn bind_ctor_declarator(&mut self, fd: Node, scope: usize) {
let rhs = fd.child_by_field_name("parameters");
self.try_bind_local(fd, scope, rhs);
if let Some(params) = rhs {
self.walk_ctor_args(params, scope);
}
}
fn walk_ctor_args(&mut self, params: Node, scope: usize) {
let mut cursor = params.walk();
for p in params
.children(&mut cursor)
.filter(|ch| ch.kind() == "parameter_declaration")
{
if let Some(ty) = p.child_by_field_name("type") {
if p.child_by_field_name("declarator").is_none() {
dispatch(self, ty, scope);
continue;
}
}
self.walk_children(p, scope);
}
}
fn bind_declarator(&mut self, d: Node, scope: usize) {
let rhs = init_rhs(d);
self.try_bind_local(d, scope, rhs);
self.dispatch_opt(rhs, scope);
}
fn try_bind_local(&mut self, d: Node, scope: usize, rhs: Option<Node>) {
let Some(name) = declarator_ident(d) else {
return;
};
if !c_bind::should_bind(d, self.text_of(name), rhs, self.src) {
return;
}
self.bind_var(
name,
scope,
Write::assign(name.start_byte(), name.id(), rhs.map(|v| v.id())),
IntroKind::Assign,
);
if condition_introduces(d) {
let n = self.text_of(name).to_string();
self.model().record_read(scope, &n, name.start_byte());
}
}
fn dispatch_opt(&mut self, n: Option<Node>, scope: usize) {
if let Some(n) = n {
dispatch(self, n, scope);
}
}
fn walk_for_statement(&mut self, n: Node, scope: usize) {
if let Some(init) = n.child_by_field_name("initializer") {
self.walk_for_init_reads(init, scope);
}
self.walk_children_excluding_field(n, scope, "initializer");
}
fn walk_for_init_reads(&mut self, n: Node, scope: usize) {
match n.kind() {
"init_declarator" => self.dispatch_opt(init_rhs(n), scope),
"assignment_expression" | "augmented_assignment_expression" => {
if let Some(right) = n.child_by_field_name("right") {
dispatch(self, right, scope);
}
}
k if self.spec().read_kinds.contains(&k) => {
dispatch(self, n, scope);
}
_ => {
let mut c = n.walk();
for ch in n.children(&mut c) {
self.walk_for_init_reads(ch, scope);
}
}
}
}
fn bind_assignment(&mut self, n: Node, scope: usize) {
let left = n.child_by_field_name("left");
let right = n.child_by_field_name("right");
let plain = n
.child_by_field_name("operator")
.map_or(false, |o| self.text_of(o) == "=");
if let Some(left) = left {
if left.kind() == "identifier" {
self.rebind_local(left, scope, plain, right.map(|r| r.id()));
} else {
self.walk_children(left, scope);
}
}
if let Some(right) = right {
dispatch(self, right, scope);
}
}
}
fn declarator_ident(d: Node) -> Option<Node> {
if d.kind() == "identifier" {
Some(d)
} else {
d.child_by_field_name("declarator")
.and_then(resolve_declarator_name)
}
}
fn init_rhs(d: Node) -> Option<Node> {
(d.kind() == "init_declarator")
.then(|| d.child_by_field_name("value"))
.flatten()
}
fn resolve_declarator_name<'tree>(mut decl: Node<'tree>) -> Option<Node<'tree>> {
while decl.kind().ends_with("_declarator") && decl.kind() != "init_declarator" {
decl = decl
.child_by_field_name("declarator")
.or_else(|| decl.named_child(0))?;
}
(decl.kind() == "identifier").then_some(decl)
}
fn condition_introduces(d: Node) -> bool {
let Some(decl) = d.parent().filter(|p| p.kind() == "declaration") else {
return false;
};
match decl.parent().map(|p| p.kind()) {
Some("condition_clause") => true,
Some("init_statement") => decl
.parent()
.and_then(|p| p.parent())
.is_some_and(|g| g.kind() == "condition_clause"),
_ => false,
}
}