//! C / C++ extraction — a faithful Rust port of `TreeSitterExtractor`'s c/cpp //! paths (src/extraction/tree-sitter.ts) plus languages/c-cpp.ts, one dual- //! language module flagged like tsjs/ (checklist: //! docs/design/ccpp-kernel-port-checklist.md — read it before editing). //! //! The seven preParse blanking passes are NOT here: the TS route point //! (src/extraction/kernel/index.ts) applies `extractor.preParse` before the //! kernel call, so this walker receives the SAME blanked bytes the wasm //! extractor parses (all blanks are equal-length-space replacements — every //! offset survives). `.metal`/`.cu`/`.cuh` arrive as language 'cpp' with their //! dialect blanks already applied. //! //! Quirks mirrored bug-for-bug (each pinned by the parity gates): //! - cpp namespace prefix stack (#1291): named `namespace a::b {` pushes the //! name AS WRITTEN onto the qualifiedName prefix; anonymous falls through. //! No namespace NODE is minted (#1093 crowd-out). //! - cpp brace scopes (brace_scopes.rs): when the tree has errors, each node //! is walked in the namespaces, and at declaration level the classes, the //! source's braces put it in, not the tree's (error recovery closes scopes //! at the wrong `}`). Only the error-extract hatch reaches it here. //! - out-of-line `Cls::method` defs: name = LAST `::` segment of the //! declarator's qualified_identifier (BFS that skips parameter_list + //! trailing_return_type), receiver = the template-stripped qualifier, //! qualifiedName composed against the namespace prefix with the re-spelled- //! prefix anchor rule; owner `contains` edge to the FIRST earlier //! struct/class/enum/trait of the receiver's bare name. //! - macro-name salvage: recoverCppMacroDefinedName (ALL-CAPS macro def whose //! real name is the lone first argument) at resolveName, and //! recoverMangledCppName (glued "Ret name" → last token) as the universal //! post-hoc net for BOTH c and cpp. //! - `class MACRO Name` misparse residue: isMisparsedFunction drops the //! phantom function (name starts `namespace`, C++ keywords, or the bodyless //! class/struct `type` + non-function_declarator shape, #946/#1061) but //! still walks the body. //! - C file-scope variables: init/pointer/array declarators only — a BARE //! identifier declarator is the macro-prototype misparse and is skipped //! (loses uninit scalars by design); cpp declarations instead take the TS //! GENERIC fallback (direct identifier children only → `int x;` extracts, //! `int x = 5;` does not — bug-for-bug). //! - a class/struct/union/enum defined in a declaration's `type` //! (`struct Foo { … } foo;`) is walked before the variables — in C, and in //! C++ unless the tree has errors and no brace scopes (walkDeclaredTypes); //! an unnamed one takes the name of the first declarator identifier //! (`static struct { … } SPT;` → `SPT`, in function bodies too), and the //! comment above the declaration is its docstring (c/cpp //! getDeclarationWrapper). //! - static-member/value-read pass (cpp only): `field_expression` is in //! MEMBER_ACCESS_TYPES (listed for Scala, same node kind in cpp), so //! `Capitalized.member` / `Capitalized->member` VALUE reads emit //! `references` refs; qualified_identifier is checked too but its scope //! child is namespace_identifier/template_type/…, never a plain //! identifier, so it can't emit. //! - explicit operator calls (#1247) ride an ERROR child — but has_error() //! defers the whole file to wasm, so the ported branch is a faithful no-op //! here; kept so an error-free shape (if a grammar bump ever produces one) //! stays parity-true. //! - local fn-pointer fan-out (#932-adjacent): `auto k = &fn<…>;` records //! per-caller targets (insertion-ordered, branch reassignments accumulate); //! a later bare `k(args)` emits one `calls` ref PER target and suppresses //! the local name. Template args stripped like base-class refs (#1043). //! - pure-virtual methods (#1727): cpp in-class `virtual T f(...) = 0;` is a //! `field_declaration` (not `function_definition`); mint a method node so //! abstract-base calls and cpp-override synthesis have a target. Mirrors //! TS `methodTypes` + `classifyMethodNode` / `isAbstract`. //! - stack construction (#1035): cpp `declaration` with class-like named //! `type` and an init_declarator whose value is argument_list / //! initializer_list → `instantiates` (most-vexing-parse excluded). //! - value-reference edges: C only (VALUE_REF_LANGS has 'c', not 'cpp') — //! shadow prune via init_declarator counts, MAX_VALUE_REF_NODES cap, //! CODEGRAPH_VALUE_REFS=0 kill switch. //! - fn-ref capture (#756): cFamilySpec for both; cpp adds addressOfOnly //! (bare identifiers only qualify in file-scope value/list positions). //! //! Files with parse errors defer to wasm (`defer:`) — error recovery is //! encoding-dependent and the wasm recovery is canonical. use crate::buffers::{ build_meta, edge_kind_index, node_kind_index, Arena, BoolFlags, EdgeRow, EmitOut, NodeRow, RefRow, StrRef, Tables, FLAG_IS_ABSTRACT, FLAG_IS_EXPORTED, FUNCTION_REF_CODE, NONE, NONE_STR, }; use crate::docstring::preceding_docstring; use crate::ids; use crate::textutil as util; use regex::Regex; use std::collections::{HashMap, HashSet, VecDeque}; use std::sync::OnceLock; use tree_sitter::{Node, Parser}; mod brace_scopes; use brace_scopes::{BraceScopes, NestedIntervals}; const MAX_VALUE_REF_NODES: usize = 20_000; // --- compiled regexes (JS \w/\s spelled as ASCII classes for parity) --------- /// recoverCppMacroDefinedName: macro-shaped parsed name. fn macro_shaped_re() -> &'static Regex { static RE: OnceLock = OnceLock::new(); RE.get_or_init(|| Regex::new(r"^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+$").unwrap()) } fn has_lower_re() -> &'static Regex { static RE: OnceLock = OnceLock::new(); RE.get_or_init(|| Regex::new(r"[a-z]").unwrap()) } fn single_arg_macro_replacement_re() -> &'static Regex { static RE: OnceLock = OnceLock::new(); RE.get_or_init(|| Regex::new( r"^(?:[A-Za-z_][A-Za-z0-9_:]*[ \t\r\n]+)+[*& \t\r\n]*([A-Za-z_][A-Za-z0-9_]*)[ \t\r\n]*\([^(){};#]*\)[ \t\r\n]*$" ).unwrap()) } /// normalizeCppReturnType: smart-pointer/optional unwrap. fn ret_wrapper_re() -> &'static Regex { static RE: OnceLock = OnceLock::new(); RE.get_or_init(|| { Regex::new(r"\b(?:std\s*::\s*)?(?:unique_ptr|shared_ptr|weak_ptr|optional)\s*<\s*([^,>]+?)\s*>") .unwrap() }) } fn ret_keyword_re() -> &'static Regex { static RE: OnceLock = OnceLock::new(); RE.get_or_init(|| Regex::new(r"\b(?:const|volatile|typename|struct|class|enum)\b").unwrap()) } fn angle_group_re() -> &'static Regex { static RE: OnceLock = OnceLock::new(); RE.get_or_init(|| Regex::new(r"<[^>]*>").unwrap()) } fn ptr_ref_re() -> &'static Regex { static RE: OnceLock = OnceLock::new(); RE.get_or_init(|| Regex::new(r"[*&]+").unwrap()) } fn ws_run_re() -> &'static Regex { static RE: OnceLock = OnceLock::new(); RE.get_or_init(|| Regex::new(r"\s+").unwrap()) } fn simple_ident_re() -> &'static Regex { static RE: OnceLock = OnceLock::new(); RE.get_or_init(|| Regex::new(r"^[A-Za-z_][A-Za-z0-9_]*$").unwrap()) } /// recoverMangledCppName's `Ret (name)` idiom guard. fn ret_paren_name_re() -> &'static Regex { static RE: OnceLock = OnceLock::new(); RE.get_or_init(|| Regex::new(r"^\S+\s+\([A-Za-z_][A-Za-z0-9_]*\)").unwrap()) } /// Operator-call receiver: simple identifier / dotted member chain. fn operator_receiver_re() -> &'static Regex { static RE: OnceLock = OnceLock::new(); RE.get_or_init(|| Regex::new(r"^[A-Za-z_][A-Za-z0-9_.]*$").unwrap()) } /// Symbolic operator tail (`/^[^\w\s]/` in JS). fn symbolic_op_re() -> &'static Regex { static RE: OnceLock = OnceLock::new(); RE.get_or_init(|| Regex::new(r"^[^A-Za-z0-9_\s]").unwrap()) } /// extractStaticMemberRef's capitalized-receiver test. fn capitalized_re() -> &'static Regex { static RE: OnceLock = OnceLock::new(); RE.get_or_init(|| Regex::new(r"^[A-Z][A-Za-z0-9_]*$").unwrap()) } /// normalizeValue's qualified `&Cls::m` member-pointer test (`/^[A-Za-z_][\w:]*$/`). fn qualified_ref_re() -> &'static Regex { static RE: OnceLock = OnceLock::new(); RE.get_or_init(|| Regex::new(r"^[A-Za-z_][A-Za-z0-9_:]*$").unwrap()) } /// CPP_NON_CLASS_RETURN (languages/c-cpp.ts). fn is_non_class_return(name: &str) -> bool { matches!( name, "void" | "bool" | "char" | "short" | "int" | "long" | "float" | "double" | "unsigned" | "signed" | "size_t" | "ssize_t" | "auto" | "wchar_t" | "char8_t" | "char16_t" | "char32_t" | "int8_t" | "int16_t" | "int32_t" | "int64_t" | "uint8_t" | "uint16_t" | "uint32_t" | "uint64_t" | "intptr_t" | "uintptr_t" | "nullptr_t" ) } /// CPP_PRIMITIVE_NAMES (languages/c-cpp.ts) — recoverMangledCppName's guard. fn is_cpp_primitive_name(name: &str) -> bool { matches!( name, "bool" | "void" | "int" | "char" | "short" | "long" | "float" | "double" | "unsigned" | "signed" | "wchar_t" | "char8_t" | "char16_t" | "char32_t" | "char_t" | "size_t" | "auto" | "const" | "struct" | "class" | "enum" | "union" | "typename" ) } /// NAME_STOPLIST (function-ref.ts). fn is_stoplisted(name: &str) -> bool { matches!( name, "this" | "self" | "super" | "null" | "nil" | "true" | "false" | "undefined" | "new" | "NULL" | "nullptr" | "None" ) } /// LITERAL_RECEIVER_TYPES (tree-sitter.ts) — full set; membership is what the /// TS code tests even though only a few kinds occur in the c/cpp grammars. fn is_literal_receiver(kind: &str) -> bool { matches!( kind, "string" | "string_literal" | "interpreted_string_literal" | "raw_string_literal" | "template_string" | "concatenated_string" | "formatted_string" | "f_string" | "line_string_literal" | "string_content" | "heredoc_body" | "number" | "number_literal" | "integer" | "integer_literal" | "float" | "float_literal" | "int_literal" | "decimal_integer_literal" | "real_literal" | "char_literal" | "character_literal" | "rune_literal" | "regex" | "regex_literal" | "true" | "false" | "boolean_literal" | "bool_literal" | "none" | "null" | "nil" | "null_literal" | "undefined" | "list" | "list_literal" | "array" | "array_literal" | "array_creation_expression" | "dictionary" | "dict_literal" | "object" | "tuple" | "set" ) } /// stripCppTemplateArgs (languages/c-cpp.ts): depth-counted removal of every /// balanced `<…>` group; `<` and `>` never reach the output. fn strip_cpp_template_args(name: &str) -> String { if !name.contains('<') { return name.to_string(); } let mut out = String::with_capacity(name.len()); let mut depth = 0u32; for ch in name.chars() { if ch == '<' { depth += 1; } else if ch == '>' { depth = depth.saturating_sub(1); } else if depth == 0 { out.push(ch); } } out.trim().to_string() } /// recoverMangledCppName (languages/c-cpp.ts) — universal post-hoc salvage for /// a name still mangled by an unblanked macro ("Ret name" → "name"). fn recover_mangled_cpp_name(name: String) -> String { if !name.chars().any(|c| c.is_whitespace()) || name.starts_with("operator") || name.starts_with('~') { return name; } if ret_paren_name_re().is_match(&name) { return name; // `Ret (name)` idiom — leave alone } let before_params = match name.find('(') { Some(i) => &name[..i], None => &name[..], }; // (JS: `beforeParams.trim().split(/\s+/)` — split_whitespace already // ignores leading/trailing whitespace, so no explicit trim.) let candidate = before_params.split_whitespace().last().unwrap_or(""); if candidate.is_empty() || !simple_ident_re().is_match(candidate) || is_cpp_primitive_name(candidate) { return name; } candidate.to_string() } /// normalizeCppReturnType (languages/c-cpp.ts). fn normalize_cpp_return_type(raw: &str) -> Option { let mut t = raw.trim().to_string(); if t.is_empty() { return None; } if let Some(c) = ret_wrapper_re().captures(&t) { if let Some(inner) = c.get(1) { t = inner.as_str().to_string(); } } let t = ret_keyword_re().replace_all(&t, " "); let t = angle_group_re().replace_all(&t, " "); let t = ptr_ref_re().replace_all(&t, " "); let t = ws_run_re().replace_all(&t, " "); let t = t.trim(); if t.is_empty() { return None; } let parts: Vec<&str> = t.split("::").filter(|p| !p.is_empty()).collect(); let last = *parts.last()?; if is_non_class_return(last) || !simple_ident_re().is_match(last) { return None; } Some(last.to_string()) } /// JS `String.replace(/->/g,'.').replace(/\s+/g,'')` used on receivers. fn arrow_dot_no_ws(s: &str) -> String { s.replace("->", ".").chars().filter(|c| !c.is_whitespace()).collect() } #[derive(Clone, Copy, PartialEq, Eq)] pub enum Variant { C, Cpp, } #[derive(Clone)] struct Scope { row: u32, kind: &'static str, name: String, /// The qualified name its members are qualified under in place of the /// scopes around it: a class written in a scope (objectPathOwners in /// tree-sitter.ts, written_scope_qualified_name). qualified: Option, } #[derive(Default)] struct Extra { docstring: Option, signature: Option, visibility: Option, is_exported: Option, is_abstract: Option, return_type: Option, qualified_name: Option, /// (endLine, endColumn) in place of the node's own (brace scopes). end: Option<(u32, u32)>, } struct ValueScope<'t> { row: u32, node: Node<'t>, name: String, } /// Capture mode for a fn-ref candidate (gate policy keys on it). #[derive(Clone, Copy, PartialEq, Eq)] enum Mode { Args, Rhs, Value, List, Varinit, } struct Cand { from: u32, name: String, mode: Mode, explicit_ref: bool, line: u32, column_byte: usize, row: usize, } /// Per-node metadata for the receiver-method owner lookup (mirrors the TS /// side's scan over `this.nodes` — FIRST match wins, earlier-in-file only). struct NodeMeta { kind: &'static str, name: String, } pub struct Walker<'t> { src: &'t str, file_path: &'t str, variant: Variant, line_starts: Vec, arena: Arena, node_id_allocator: ids::NodeIdAllocator, tables: Tables, stack: Vec, nodes_meta: Vec, node_ids: Vec, /// C/C++ enclosing `namespace ns { … }` names (cpp only ever non-empty). namespace_prefix: Vec, /// cppLocalFnPtrs: caller row → local name → insertion-ordered targets. local_fn_ptrs: HashMap>>, defined_fn_names: HashSet, imported_names: HashSet, fn_ref_cands: Vec, fs_values: HashMap, fs_value_counts: HashMap, value_scopes: Vec>, /// A cpp file whose tree has errors is walked in the scopes its braces /// open (visit_in_brace_scope; only under the error-extract hatch): the /// scan, and the class-like nodes extracted so far by their bodies' braces. brace_scopes: Option, class_scopes: NestedIntervals, class_scope_rows: HashSet, /// walkDeclaredTypes (tree-sitter.ts): a class-like type defined in a /// declaration is walked in C, and in C++ unless the tree has errors and /// its braces don't balance (no brace scopes; only under the hatch). walk_declared_types: bool, } pub fn extract(file_path: &str, source: &str, language: &str) -> Result { let variant = match language { "c" => Variant::C, "cpp" => Variant::Cpp, other => return Err(format!("ccpp walker got language '{other}'")), }; let grammar = crate::langs::grammar_for(language).ok_or("no c/cpp grammar")?; let t0 = std::time::Instant::now(); let mut parser = Parser::new(); parser .set_language(&grammar) .map_err(|e| format!("set_language({language}) failed: {e}"))?; let tree = parser .parse(source, None) .ok_or_else(|| "parser returned null tree".to_string())?; // Measurement hatch (parity sweeps only — never set in production): skip // the defer so the sweep can QUANTIFY how often UTF-8 vs UTF-16 error // recovery actually diverges on this language's erroring files. let no_defer = std::env::var("CODEGRAPH_KERNEL_CCPP_ERROR_EXTRACT").as_deref() == Ok("1"); if tree.root_node().has_error() && !no_defer { return Err("defer: parse tree contains errors — wasm recovery is canonical".to_string()); } let brace_scopes = if variant == Variant::Cpp && tree.root_node().has_error() { brace_scopes::scan(source) } else { None }; let walk_declared_types = variant == Variant::C || !tree.root_node().has_error() || brace_scopes.is_some(); let mut w = Walker { src: source, file_path, variant, line_starts: util::line_starts(source), arena: Arena::default(), node_id_allocator: ids::NodeIdAllocator::default(), tables: Tables::default(), stack: Vec::new(), nodes_meta: Vec::new(), node_ids: Vec::new(), namespace_prefix: Vec::new(), local_fn_ptrs: HashMap::new(), defined_fn_names: HashSet::new(), imported_names: HashSet::new(), fn_ref_cands: Vec::new(), fs_values: HashMap::new(), fs_value_counts: HashMap::new(), value_scopes: Vec::new(), brace_scopes, class_scopes: NestedIntervals::new(), class_scope_rows: HashSet::new(), walk_declared_types, }; let line_count = source.bytes().filter(|b| *b == b'\n').count() as u32 + 1; let base_name = file_path.rsplit(['/', '\\']).next().unwrap_or(file_path); let mut flags = BoolFlags::default(); flags.set(FLAG_IS_EXPORTED, false); let file_id = w.arena.put(&ids::file_node_id(file_path)); let name_ref = w.arena.put(base_name); let qn_ref = w.arena.put(file_path); w.tables.push_node(&NodeRow { kind: node_kind_index("file").unwrap(), visibility: 0, flags, start_line: 1, end_line: line_count, start_column: 0, end_column: 0, name: name_ref, qualified_name: qn_ref, id: file_id, docstring: NONE_STR, signature: NONE_STR, decorators: NONE_STR, type_parameters: NONE_STR, return_type: NONE_STR, extra_json: NONE_STR, }); w.nodes_meta.push(NodeMeta { kind: "file", name: base_name.to_string() }); w.node_ids.push(ids::file_node_id(file_path)); w.stack.push(Scope { row: 0, kind: "file", name: base_name.to_string(), qualified: None }); w.visit_node(tree.root_node()); w.flush_fn_ref_candidates(); w.flush_value_refs(tree.root_node()); w.stack.pop(); let duration_ms = t0.elapsed().as_secs_f64() * 1000.0; let meta = build_meta(&w.tables, w.arena.len(), NONE_STR, duration_ms); Ok(EmitOut { meta, nodes: w.tables.nodes, edges: w.tables.edges, refs: w.tables.refs, arena: w.arena.into_vec(), }) } impl<'t> Walker<'t> { fn text(&self, node: Node) -> &'t str { &self.src[node.byte_range()] } fn line_of(&self, node: Node) -> u32 { node.start_position().row as u32 + 1 } fn col_of(&self, node: Node) -> u32 { util::col16(self.src, &self.line_starts, node.start_position().row, node.start_byte()) } fn end_col_of(&self, node: Node) -> u32 { util::col16(self.src, &self.line_starts, node.end_position().row, node.end_byte()) } fn top_row(&self) -> u32 { self.stack.last().map(|s| s.row).unwrap_or(0) } fn inside_class_like(&self) -> bool { self.stack .last() .map(|s| matches!(s.kind, "class" | "struct" | "union" | "interface" | "trait" | "enum" | "module")) .unwrap_or(false) } fn push_ref_at(&mut self, from_row: u32, name: &str, kind_code: u8, node: Node) { let name_ref = self.arena.put(name); self.tables.push_ref(&RefRow { from_idx: from_row, kind: kind_code, line: self.line_of(node), column: self.col_of(node), reference_name: name_ref, candidates: NONE_STR, from_id_str: NONE_STR, }); if kind_code == edge_kind_index("imports").unwrap() { if util::simple_name().is_match(name) { self.imported_names.insert(name.to_string()); } else if let Some(c) = util::qualified_import().captures(name) { self.imported_names.insert(c[1].to_string()); } } } fn create_node(&mut self, kind: &'static str, name: &str, node: Node<'t>, extra: Extra) -> Option { if name.is_empty() { return None; } let start_line = self.line_of(node); let column = self.col_of(node); let id = self.node_id_allocator.generate(self.file_path, kind, name, start_line, column); // (c/cpp define no resolveBody hook, so createNode's endLine extension // for sibling-body grammars never fires — endLine is the node's own, // or the `}` of its body in brace scopes.) let (end_line, end_column) = extra.end.unwrap_or_else(|| (node.end_position().row as u32 + 1, self.end_col_of(node))); let qualified = extra.qualified_name.unwrap_or_else(|| { let mut parts: Vec<&str> = self.namespace_prefix.iter().map(|s| s.as_str()).collect(); for s in &self.stack { if let Some(q) = &s.qualified { parts.clear(); parts.push(q); } else if s.kind != "file" { parts.push(&s.name); } } let mut qn = parts.join("::"); if !qn.is_empty() { qn.push_str("::"); } qn.push_str(name); qn }); let mut flags = BoolFlags::default(); if let Some(v) = extra.is_exported { flags.set(FLAG_IS_EXPORTED, v); } if let Some(v) = extra.is_abstract { flags.set(FLAG_IS_ABSTRACT, v); } let name_ref = self.arena.put(name); let qn_ref = self.arena.put(&qualified); let id_ref = self.arena.put(&id); let doc_ref = opt_str(&mut self.arena, extra.docstring.as_deref()); let sig_ref = opt_str(&mut self.arena, extra.signature.as_deref()); let ret_ref = opt_str(&mut self.arena, extra.return_type.as_deref()); let row = self.tables.push_node(&NodeRow { kind: node_kind_index(kind).unwrap(), visibility: extra.visibility.unwrap_or(0), flags, start_line, end_line, start_column: self.col_of(node), end_column, name: name_ref, qualified_name: qn_ref, id: id_ref, docstring: doc_ref, signature: sig_ref, decorators: NONE_STR, type_parameters: NONE_STR, return_type: ret_ref, extra_json: NONE_STR, }); self.nodes_meta.push(NodeMeta { kind, name: name.to_string() }); self.node_ids.push(id); let parent_row = self.top_row(); self.tables.push_edge(&EdgeRow { source_idx: parent_row, target_idx: row, kind: edge_kind_index("contains").unwrap(), provenance: 0, line: NONE, column: NONE, metadata_json: NONE_STR, source_id_str: NONE_STR, target_id_str: NONE_STR, }); if kind == "function" || kind == "method" { self.defined_fn_names.insert(name.to_string()); } // captureValueRefScope (capture is variant-agnostic like the TS side; // flushValueRefs gates on the language — C only). let target_kind_ok = kind == "constant" || kind == "variable"; if target_kind_ok && util::utf16_len(name) >= 3 && util::has_upper_or_underscore().is_match(name) { let parent_ok = self .stack .last() .map(|s| matches!(s.kind, "file" | "class" | "module" | "struct" | "union" | "enum")) .unwrap_or(false); if parent_ok { self.fs_values.insert(name.to_string(), row); *self.fs_value_counts.entry(name.to_string()).or_insert(0) += 1; } } if matches!(kind, "function" | "method" | "constant" | "variable") { self.value_scopes.push(ValueScope { row, node, name: name.to_string() }); } Some(row) } // --- name extraction ----------------------------------------------------- /// extractName: extractNameRaw + the universal recoverMangledName net /// (wired for BOTH c and cpp in languages/c-cpp.ts). fn extract_name(&self, node: Node) -> String { recover_mangled_cpp_name(self.extract_name_raw(node)) } /// extractNameRaw for the c/cpp extractor configs (nameField 'declarator'; /// cpp resolveName = extractCppQualifiedMethodName). fn extract_name_raw(&self, node: Node) -> String { if let Some(name) = self.recover_single_arg_macro_defined_name(node) { return name; } if self.variant == Variant::Cpp { if let Some(hook) = self.extract_cpp_qualified_method_name(node) { return hook; } } if let Some(name) = self.unnamed_type_variable_name(node) { return name; } if self.variant == Variant::Cpp { if let Some(mut parts) = self.cpp_written_class_parts(node) { return parts.pop().unwrap_or_default(); } } if let Some(name_node) = node.child_by_field_name("declarator") { let mut resolved = name_node; // Unwrap pointer/reference declarators (`int* f()`, `T& f()`). while matches!(resolved.kind(), "pointer_declarator" | "reference_declarator") { let inner = resolved .child_by_field_name("declarator") .or_else(|| resolved.named_child(0)); match inner { Some(i) => resolved = i, None => break, } } // C++ conversion operator: `operator `. if resolved.kind() == "operator_cast" { return match resolved.named_child(0) { Some(t) => format!("operator {}", self.text(t).trim()), None => self.text(resolved).to_string(), }; } if resolved.kind() == "function_declarator" || resolved.kind() == "declarator" { let inner = resolved .child_by_field_name("declarator") .or_else(|| resolved.named_child(0)); return match inner { Some(i) => self.text(i).to_string(), None => self.text(resolved).to_string(), }; } return self.text(resolved).to_string(); } for i in 0..node.named_child_count() { if let Some(c) = node.named_child(i) { if matches!(c.kind(), "identifier" | "type_identifier" | "simple_identifier" | "constant") { return self.text(c).to_string(); } } } "".to_string() } /// cppWrittenClassParts (languages/c-cpp.ts): the `::` segments of a class, /// struct or union name written with a scope, the scope's template /// arguments dropped (`["SkipList", "Node"]` for `struct SkipList::Node`). None for a plain name and for a specialization, /// whose innermost name has template arguments. fn cpp_written_class_parts(&self, node: Node) -> Option> { if !matches!(node.kind(), "class_specifier" | "struct_specifier" | "union_specifier") { return None; } let written = node.child_by_field_name("name")?; if written.kind() != "qualified_identifier" { return None; } let mut innermost = Some(written); while let Some(n) = innermost.filter(|n| n.kind() == "qualified_identifier") { innermost = n.child_by_field_name("name"); } if innermost.map(|n| n.kind()) != Some("type_identifier") { return None; } let parts: Vec = strip_cpp_template_args(self.text(written)) .split("::") .map(|p| p.trim().to_string()) .filter(|p| !p.is_empty()) .collect(); let identifier = |p: &String| { let mut chars = p.chars(); chars.next().is_some_and(|c| c.is_ascii_alphabetic() || c == '_') && chars.all(|c| c.is_ascii_alphanumeric() || c == '_') }; if parts.is_empty() || !parts.iter().all(identifier) { return None; } Some(parts) } /// writtenScopeQualifiedName (tree-sitter.ts): a class whose name is /// written in a scope is qualified like an out-of-line method under it. fn written_scope_qualified_name(&self, node: Node, name: &str) -> Option { if self.variant != Variant::Cpp { return None; } let parts = self.cpp_written_class_parts(node)?; if parts.len() < 2 { return None; } Some(self.compose_receiver_qualified_name(&parts[..parts.len() - 1].join("::"), name)) } /// unnamedTypeVariableName (languages/c-cpp.ts): an unnamed class, struct, /// union or enum defined in a declaration takes the name of the first /// variable the declaration declares (`static struct { … } SPT;` → `SPT`). fn unnamed_type_variable_name(&self, node: Node) -> Option { if node.child_by_field_name("name").is_some() { return None; } let declaration = defining_declaration(node)?; let mut cursor = declaration.walk(); let identifier = declaration .children_by_field_name("declarator", &mut cursor) .find_map(c_declarator_identifier)?; Some(self.text(identifier).to_string()) } /// extractCppQualifiedMethodName (languages/c-cpp.ts:75). fn extract_cpp_qualified_method_name(&self, node: Node) -> Option { if let Some(n) = self.recover_cpp_macro_defined_name(node) { return Some(n); } let declarator = node.child_by_field_name("declarator")?; let qid = find_declarator_qualified_id(declarator)?; let text = self.text(qid).trim(); let parts: Vec<&str> = text.split("::").filter(|p| !p.is_empty()).collect(); parts.last().map(|s| s.to_string()) } /// recoverSingleArgMacroDefinedName: only a local macro definition proves /// that its argument is the function name, in either parser shape (#1373). fn recover_single_arg_macro_defined_name(&self, node: Node) -> Option { if node.kind() != "function_definition" { return None; } let declarator = node.child_by_field_name("declarator")?; let (macro_node, argument) = if declarator.kind() == "parenthesized_declarator" && declarator.named_child_count() == 1 { let m = node.child_by_field_name("type")?; let a = declarator.named_child(0)?; if m.kind() != "type_identifier" || a.kind() != "identifier" { return None; } (m, a) } else if declarator.kind() == "function_declarator" && node.child_by_field_name("type").is_none() { let m = declarator.child_by_field_name("declarator")?; let params = declarator.child_by_field_name("parameters")?; let param = params.named_child(0)?; if m.kind() != "identifier" || params.named_child_count() != 1 || param.kind() != "parameter_declaration" || param.named_child_count() != 1 { return None; } let a = param.named_child(0)?; if a.kind() != "type_identifier" { return None; } (m, a) } else { return None; }; let macro_name = self.text(macro_node); let mut scope = Some(node); while let Some(current) = scope { if current.kind() == "preproc_else" || current.kind().starts_with("preproc_elif") { return None; } let mut previous = current.prev_named_sibling(); while let Some(prev) = previous { previous = prev.prev_named_sibling(); if prev.kind().starts_with("preproc_if") { return None; } if prev.kind() == "preproc_call" && prev.child_by_field_name("directive").map(|n| self.text(n)) == Some("#undef") && prev.child_by_field_name("argument").map(|n| self.text(n).trim()) == Some(macro_name) { return None; } if !matches!(prev.kind(), "preproc_function_def" | "preproc_def") || prev.child_by_field_name("name").map(|n| self.text(n)) != Some(macro_name) { continue; } let params = prev.child_by_field_name("parameters")?; let param = params.named_child(0)?; let value = prev.child_by_field_name("value")?; if params.named_child_count() != 1 || param.kind() != "identifier" { return None; } let replacement = self.text(value).replace("\\\r\n", " ").replace("\\\n", " "); let captures = single_arg_macro_replacement_re().captures(replacement.trim())?; if replacement.split(|c: char| !c.is_ascii_alphanumeric() && c != '_').any(|s| s == "typedef") || captures.get(1)?.as_str() != self.text(param) { return None; } return Some(self.text(argument).to_string()); } scope = current.parent(); } None } /// recoverCppMacroDefinedName (languages/c-cpp.ts:49). fn recover_cpp_macro_defined_name(&self, node: Node) -> Option { if node.kind() != "function_definition" { return None; } let declarator = node.child_by_field_name("declarator")?; if declarator.kind() != "function_declarator" { return None; } let inner = declarator.child_by_field_name("declarator")?; if inner.kind() != "identifier" { return None; } let macro_name = self.text(inner); if !macro_shaped_re().is_match(macro_name) { return None; } let params = declarator.child_by_field_name("parameters")?; if params.named_child_count() < 2 { return None; } let lone_ident_text = |p: Node| -> Option<&'t str> { if p.kind() == "parameter_declaration" && p.named_child_count() == 1 && p.named_child(0).map(|c| c.kind() == "type_identifier").unwrap_or(false) { Some(self.text(p.named_child(0).unwrap())) } else { None } }; let name = params.named_child(0).and_then(lone_ident_text)?; if !has_lower_re().is_match(name) { return None; } for i in 1..params.named_child_count() { if let Some(p) = params.named_child(i) { if lone_ident_text(p).is_some() { return None; } } } Some(name.to_string()) } /// extractCppReceiverType (languages/c-cpp.ts:86). fn receiver_type_of(&self, node: Node) -> Option { let declarator = node.child_by_field_name("declarator")?; let qid = find_declarator_qualified_id(declarator)?; let text = self.text(qid).trim(); let parts: Vec<&str> = text.split("::").filter(|p| !p.is_empty()).collect(); if parts.len() <= 1 { return None; } let receiver = strip_cpp_template_args(&parts[..parts.len() - 1].join("::")); if receiver.is_empty() { None } else { Some(receiver) } } /// extractCppReturnType: the `type` field, normalized. fn return_type_of(&self, node: Node) -> Option { let type_node = node.child_by_field_name("type")?; normalize_cpp_return_type(self.text(type_node)) } /// cppExtractor.getVisibility: the FIRST access_specifier among the /// parent's children decides (document order, not nearest-preceding — /// bug-for-bug with the TS loop). fn visibility_of(&self, node: Node) -> Option { let parent = node.parent()?; for i in 0..parent.child_count() { let Some(child) = parent.child(i) else { continue }; if child.kind() == "access_specifier" { let text = self.text(child); if text.contains("public") { return Some(1); } if text.contains("private") { return Some(2); } if text.contains("protected") { return Some(3); } } } None } /// cExtractor.isConst: any named `type_qualifier` child reading "const". fn is_const_declaration(&self, node: Node) -> bool { (0..node.named_child_count()) .filter_map(|i| node.named_child(i)) .any(|c| c.kind() == "type_qualifier" && self.text(c) == "const") } /// `#1727`: C++ pure-virtual method declaration (`virtual int read(int key) = 0;`). /// tree-sitter-cpp shapes these as `field_declaration` whose declarator unwraps /// to a `function_declarator`, with the pure-virtual `= 0` as a DIRECT /// `number_literal` "0" child (default-arg `= 0` lives inside /// `parameter_declaration` and must not match). fn is_cpp_pure_virtual_method_decl(&self, node: Node<'_>) -> bool { if node.kind() != "field_declaration" { return false; } let Some(mut declarator) = node.child_by_field_name("declarator") else { return false; }; while matches!(declarator.kind(), "pointer_declarator" | "reference_declarator") { let inner = declarator .child_by_field_name("declarator") .or_else(|| declarator.named_child(0)); let Some(inner) = inner else { return false; }; declarator = inner; } if declarator.kind() != "function_declarator" { return false; } (0..node.named_child_count()) .filter_map(|i| node.named_child(i)) .any(|c| c.kind() == "number_literal" && self.text(c) == "0") } /// cppExtractor.isMisparsedFunction (languages/c-cpp.ts:811). cpp only. fn is_misparsed_function(&self, name: &str, node: Node) -> bool { if self.variant != Variant::Cpp { return false; } if name.starts_with("namespace") { return true; } if matches!(name, "switch" | "if" | "for" | "while" | "do" | "case" | "return") { return true; } is_macro_misparsed_type_decl(node) } /// composeReceiverQualifiedName (tree-sitter.ts:1424). fn compose_receiver_qualified_name(&self, receiver_type: &str, name: &str) -> String { let base = format!("{receiver_type}::{name}"); if self.namespace_prefix.is_empty() { return base; } let receiver_head = receiver_type.split("::").next().unwrap_or(""); let anchor = self.namespace_prefix.iter().position(|p| p == receiver_head); let prefix: &[String] = match anchor { Some(i) => &self.namespace_prefix[..i], None => &self.namespace_prefix[..], }; if prefix.is_empty() { base } else { format!("{}::{}", prefix.join("::"), base) } } // --- visitNode ----------------------------------------------------------- fn visit_node(&mut self, node: Node<'t>) { if self.brace_scopes.is_some() { self.visit_in_brace_scope(node); } else { self.dispatch_node(node); } } /// visitInCppBraceScope: walk `node` in the namespaces and classes its /// source braces put it in (see brace_scopes.rs). The namespaces apply /// everywhere; the enclosing classes only at declaration level, where the /// stack above the file node holds nothing but class scopes. fn visit_in_brace_scope(&mut self, node: Node<'t>) { let at = node.start_byte(); let namespaces = self.brace_scopes.as_ref().map(|s| s.namespaces_at(at)).unwrap_or_default(); let saved = std::mem::replace(&mut self.namespace_prefix, namespaces); let mut base = self.stack.len(); while base > 1 && self.class_scope_rows.contains(&self.stack[base - 1].row) { base -= 1; } let mut walked: Option> = None; if base == 1 { let classes = self.class_scopes.at(at); let same = classes.len() == self.stack.len() - base && classes.iter().zip(&self.stack[base..]).all(|(c, s)| c.row == s.row); if !same { walked = Some(self.stack.split_off(base)); self.stack.extend(classes); } } self.dispatch_node(node); if let Some(walked) = walked { self.stack.truncate(base); self.stack.extend(walked); } self.namespace_prefix = saved; } /// cppBodyEnd: where a class-like node ends in brace scopes — the `}` /// that closes its body — or None to keep the tree's. fn brace_body_end(&self, body: Node<'t>) -> Option<(u32, u32)> { let close = self.brace_scopes.as_ref()?.close_of(body.start_byte())?; Some(BraceScopes::position_of(self.src, &self.line_starts, close + 1)) } /// openCppClassScope: a class-like node's body as a scope for visit_in_brace_scope. fn open_class_scope(&mut self, scope: &Scope, body: Node<'t>) { let Some(close) = self.brace_scopes.as_ref().and_then(|s| s.close_of(body.start_byte())) else { return }; if self.class_scopes.add(body.start_byte(), close, scope.clone()) { self.class_scope_rows.insert(scope.row); } } fn dispatch_node(&mut self, node: Node<'t>) { stack_guard!(); let kind = node.kind(); let mut skip_children = false; // C/C++ function-like macros become `constant` nodes carrying the // directive as their signature — a value, never a callee (#1838). // Mirrors tree-sitter.ts visitNode. if kind == "preproc_function_def" { if let Some(name_node) = node.child_by_field_name("name") { let name = self.text(name_node).to_string(); let signature = Some(self.text(node).trim().to_string()); self.create_node("constant", &name, node, Extra { signature, ..Extra::default() }); } return; } // C++ namespace blocks: prefix-only, no node (#1291/#1093). Anonymous // namespaces fall through to the generic walk. if self.variant == Variant::Cpp && kind == "namespace_definition" { let ns_name = node .child_by_field_name("name") .map(|n| self.text(n).to_string()) .unwrap_or_default(); if !ns_name.is_empty() { self.namespace_prefix.push(ns_name); for i in 0..node.named_child_count() { if let Some(c) = node.named_child(i) { self.visit_node(c); } } self.namespace_prefix.pop(); return; } } self.maybe_capture_fn_refs(node); if self.is_cpp_constructor_declaration(node) { self.extract_method(node); skip_children = true; } else if kind == "function_definition" { // functionTypes for both; cpp's methodTypes also lists it, so // inside a class-like scope it extracts as a method. if self.inside_class_like() && self.variant == Variant::Cpp { self.extract_method(node); } else { self.extract_function(node); } skip_children = true; } else if self.variant == Variant::Cpp && kind == "class_specifier" { self.extract_class(node); skip_children = true; } else if kind == "struct_specifier" { self.extract_aggregate(node, "struct"); skip_children = true; } else if kind == "union_specifier" { self.extract_aggregate(node, "union"); skip_children = true; } else if kind == "enum_specifier" { self.extract_enum(node); skip_children = true; } else if kind == "type_definition" || (self.variant == Variant::Cpp && kind == "alias_declaration") { skip_children = self.extract_type_alias(node); } else if kind == "declaration" && !self.inside_class_like() { // A class, struct, union or enum defined in the declaration's type // (`struct Foo { … } foo;`) is a definition like one written on its // own, and the variables keep their nodes beside it — so is, in // brace scopes, a class the tree reads as a declaration's type // (glued to the tokens after it by error recovery). The fn-ref // scan skips the walked type, which captured its own (the TS // walked set). let defined_type = node .child_by_field_name("type") .filter(|t| self.walk_declared_types && is_class_like_definition(*t)); if let Some(t) = defined_type { self.visit_node(t); } self.extract_variable(node); match defined_type { Some(t) => { self.maybe_capture_fn_refs(node); for i in 0..node.named_child_count() { if let Some(c) = node.named_child(i) { if c.id() != t.id() { self.scan_fn_ref_subtree(c, 1); } } } } None => self.scan_fn_ref_subtree(node, 0), } skip_children = true; } else if self.variant == Variant::Cpp && kind == "field_declaration" && self.inside_class_like() && self.is_cpp_pure_virtual_method_decl(node) { // Pure-virtual methods have no `function_definition` body — mint the // method node so calls through the abstract base and cpp-override // synthesis have a target (#1727). Non-pure field_declarations fall // through to the children walk (data members / prototypes). self.extract_method(node); skip_children = true; } else if kind == "preproc_include" { self.extract_import(node); } else if kind == "call_expression" { self.extract_call(node); } else if kind == "new_expression" { // INSTANTIATION_KINDS: cpp `new Foo(...)`. (No anonymous-class // body exists under new_expression in this grammar; children are // still walked for nested calls.) self.extract_instantiation(node); } if !skip_children { for i in 0..node.named_child_count() { if let Some(c) = node.named_child(i) { self.visit_node(c); } } } } // --- extractors ---------------------------------------------------------- fn extract_function(&mut self, node: Node<'t>) { stack_guard!(); // Receiver present (out-of-line `Cls::method` def) → method instead. if self.variant == Variant::Cpp && self.receiver_type_of(node).is_some() { self.extract_method(node); return; } let name = self.extract_name(node); if name == "" { if let Some(body) = node.child_by_field_name("body") { self.visit_function_body(body); } return; } // Misparse artifacts: drop the node, still walk the body (#946/#1061). if self.is_misparsed_function(&name, node) { if let Some(body) = node.child_by_field_name("body") { self.visit_function_body(body); } return; } let extra = Extra { docstring: preceding_docstring(node, self.src), signature: self.constructor_signature(node), visibility: if self.variant == Variant::Cpp { self.visibility_of(node) } else { None }, return_type: self.return_type_of(node), ..Extra::default() }; let Some(row) = self.create_node("function", &name, node, extra) else { return }; // (extractTypeAnnotations + extractDecoratorsFor are structural no-ops // for c/cpp: not in TYPE_ANNOTATION_LANGUAGES, and the decorator node // kinds never appear as direct children/preceding siblings in these // grammars — `attribute` only occurs under attribute_declaration.) self.stack.push(Scope { row, kind: "function", name, qualified: None }); if let Some(body) = node.child_by_field_name("body") { self.visit_function_body(body); } self.stack.pop(); } fn extract_method(&mut self, node: Node<'t>) { stack_guard!(); let receiver_type = if self.variant == Variant::Cpp { self.receiver_type_of(node) } else { None }; if !self.inside_class_like() && receiver_type.is_none() { // (object-literal parents don't occur in c/cpp) — treat as function. self.extract_function(node); return; } let name = self.extract_name(node); if self.is_misparsed_function(&name, node) { if let Some(body) = node.child_by_field_name("body") { self.visit_function_body(body); } return; } let extra = Extra { docstring: preceding_docstring(node, self.src), signature: self.constructor_signature(node), visibility: if self.variant == Variant::Cpp { self.visibility_of(node) } else { None }, is_abstract: if self.variant == Variant::Cpp && self.is_cpp_pure_virtual_method_decl(node) { Some(true) } else { None }, return_type: self.return_type_of(node), qualified_name: receiver_type .as_ref() .map(|r| self.compose_receiver_qualified_name(r, &name)), ..Extra::default() // extractMethod passes no isExported }; let Some(row) = self.create_node("method", &name, node, extra) else { return }; // Out-of-line def: contains edge from the FIRST earlier-in-file // struct/class/enum/trait node of the receiver's name. if let Some(receiver_type) = &receiver_type { if !self.inside_class_like() { let owner_row = self .nodes_meta .iter() .position(|m| { m.name == *receiver_type && matches!(m.kind, "struct" | "union" | "class" | "enum" | "trait") }) .map(|i| i as u32); if let Some(owner_row) = owner_row { self.tables.push_edge(&EdgeRow { source_idx: owner_row, target_idx: row, kind: edge_kind_index("contains").unwrap(), provenance: 0, line: NONE, column: NONE, metadata_json: NONE_STR, source_id_str: NONE_STR, target_id_str: NONE_STR, }); } } } self.stack.push(Scope { row, kind: "method", name, qualified: None }); if let Some(body) = node.child_by_field_name("body") { self.visit_function_body(body); } self.stack.pop(); } /// docstringFor a class-like node: the comment above the declaration it is /// defined in (the c/cpp getDeclarationWrapper), else above the node. fn class_like_docstring(&self, node: Node<'t>) -> Option { preceding_docstring(defining_declaration(node).unwrap_or(node), self.src) } /// extractClass for cpp class_specifier (skipBodilessClass, #1093). fn extract_class(&mut self, node: Node<'t>) { stack_guard!(); let Some(body) = node.child_by_field_name("body") else { return }; let name = self.extract_name(node); let scoped = self.written_scope_qualified_name(node, &name); let extra = Extra { docstring: self.class_like_docstring(node), visibility: self.visibility_of(node), end: self.brace_body_end(body), qualified_name: scoped.clone(), ..Extra::default() }; let Some(row) = self.create_node("class", &name, node, extra) else { return }; let scope = Scope { row, kind: "class", name, qualified: scoped }; self.open_class_scope(&scope, body); self.extract_inheritance(node, row); self.stack.push(scope); for i in 0..body.named_child_count() { if let Some(c) = body.named_child(i) { self.visit_node(c); } } self.stack.pop(); } /// Extract a struct-like declaration while preserving its semantic kind. fn extract_aggregate(&mut self, node: Node<'t>, kind: &'static str) { stack_guard!(); let Some(body) = node.child_by_field_name("body") else { return }; let name = self.extract_name(node); let scoped = self.written_scope_qualified_name(node, &name); let extra = Extra { docstring: self.class_like_docstring(node), visibility: if self.variant == Variant::Cpp { self.visibility_of(node) } else { None }, end: self.brace_body_end(body), qualified_name: scoped.clone(), ..Extra::default() }; let Some(row) = self.create_node(kind, &name, node, extra) else { return }; let scope = Scope { row, kind, name, qualified: scoped }; self.open_class_scope(&scope, body); self.extract_inheritance(node, row); self.stack.push(scope); for i in 0..body.named_child_count() { if let Some(c) = body.named_child(i) { self.visit_node(c); } } self.stack.pop(); } fn extract_enum(&mut self, node: Node<'t>) { stack_guard!(); let Some(body) = node.child_by_field_name("body") else { return }; let name = self.extract_name(node); let extra = Extra { docstring: self.class_like_docstring(node), visibility: if self.variant == Variant::Cpp { self.visibility_of(node) } else { None }, ..Extra::default() }; let Some(row) = self.create_node("enum", &name, node, extra) else { return }; self.extract_inheritance(node, row); self.stack.push(Scope { row, kind: "enum", name, qualified: None }); for i in 0..body.named_child_count() { let Some(child) = body.named_child(i) else { continue }; if child.kind() == "enumerator" { self.extract_enum_members(child); } else { self.visit_node(child); } } self.stack.pop(); } /// extractEnumMembers: enumerator's `name` field (C/C++ always has one; /// the TS fallbacks for other grammars are unreachable here). fn extract_enum_members(&mut self, node: Node<'t>) { if let Some(name_node) = node.child_by_field_name("name") { let name = self.text(name_node).to_string(); self.create_node("enum_member", &name, node, Extra::default()); } } /// extractTypeAlias for type_definition / alias_declaration. Returns true /// when children were consumed (typedef struct/enum bodies). fn extract_type_alias(&mut self, node: Node<'t>) -> bool { stack_guard!(); let name = self.extract_name(node); if name == "" { return false; } let docstring = preceding_docstring(node, self.src); // resolveTypeAliasKind: first child that is an enum/struct specifier // WITH a body decides the node kind (anon inner specifier takes the // typedef's name). let mut resolved: Option<&'static str> = None; for i in 0..node.named_child_count() { let Some(child) = node.named_child(i) else { continue }; if child.kind() == "enum_specifier" && child.child_by_field_name("body").is_some() { resolved = Some("enum"); break; } if child.kind() == "struct_specifier" && child.child_by_field_name("body").is_some() { resolved = Some("struct"); break; } if child.kind() == "union_specifier" && child.child_by_field_name("body").is_some() { resolved = Some("union"); break; } } if matches!(resolved, Some("struct") | Some("union")) { let kind = resolved.unwrap(); let Some(row) = self.create_node( kind, &name, node, Extra { docstring, ..Extra::default() }, ) else { return true; }; self.stack.push(Scope { row, kind, name, qualified: None }); let type_child = node .child_by_field_name("type") .or_else(|| self.find_child_by_kind(node, "struct_specifier")) .or_else(|| self.find_child_by_kind(node, "union_specifier")); if let Some(tc) = type_child { self.extract_inheritance(tc, row); let body = tc.child_by_field_name("body").unwrap_or(tc); for i in 0..body.named_child_count() { if let Some(c) = body.named_child(i) { self.visit_node(c); } } } self.stack.pop(); return true; } if resolved == Some("enum") { let Some(row) = self.create_node( "enum", &name, node, Extra { docstring, ..Extra::default() }, ) else { return true; }; self.stack.push(Scope { row, kind: "enum", name, qualified: None }); if let Some(inner) = self.find_child_by_kind(node, "enum_specifier") { self.extract_inheritance(inner, row); if let Some(body) = inner.child_by_field_name("body") { for i in 0..body.named_child_count() { let Some(child) = body.named_child(i) else { continue }; if child.kind() == "enumerator" { self.extract_enum_members(child); } else { self.visit_node(child); } } } } self.stack.pop(); return true; } self.create_node("type_alias", &name, node, Extra { docstring, ..Extra::default() }); false } fn find_child_by_kind(&self, node: Node<'t>, kind: &str) -> Option> { (0..node.named_child_count()) .filter_map(|i| node.named_child(i)) .find(|c| c.kind() == kind) } /// extractVariable: C takes the dedicated branch (file-scope declarators, /// tree-sitter.ts:2795); cpp takes the TS GENERIC fallback (direct /// identifier children). fn extract_variable(&mut self, node: Node<'t>) { let is_const = self.variant == Variant::C && self.is_const_declaration(node); let kind: &'static str = if is_const { "constant" } else { "variable" }; let docstring = preceding_docstring(node, self.src); // isExported?.() ?? false — EXPLICIT false (tri-state flag set). let is_exported = Some(false); if self.variant == Variant::C { if has_function_ancestor(node) { return; } for i in 0..node.named_child_count() { let Some(child) = node.named_child(i) else { continue }; if !matches!( child.kind(), "init_declarator" | "pointer_declarator" | "array_declarator" ) { continue; } let Some(name_node) = c_declarator_identifier(child) else { continue }; let name = self.text(name_node); if name.is_empty() { continue; } let value_node = if child.kind() == "init_declarator" { child.child_by_field_name("value") } else { None }; let signature = value_node.map(|v| util::init_signature(self.text(v))); self.create_node( kind, name, child, Extra { docstring: docstring.clone(), signature, is_exported, ..Extra::default() }, ); } } else { // Generic fallback: direct identifier children only (`int x;` // extracts; `int x = 5;` nests in an init_declarator and does not). for i in 0..node.named_child_count() { let Some(child) = node.named_child(i) else { continue }; if child.kind() != "identifier" { continue; } let name = self.text(child).to_string(); if !name.is_empty() && name != "" { self.create_node( kind, &name, child, Extra { docstring: docstring.clone(), is_exported, ..Extra::default() }, ); } } } } /// extractImport via the c/cpp extractImport hook: `#include ` / /// `#include "local.h"`. A hook miss (`#include MACRO`) extracts nothing. fn extract_import(&mut self, node: Node<'t>) { let import_text = self.text(node).trim().to_string(); let module_name: Option = if let Some(sys) = self.find_child_by_kind(node, "system_lib_string") { let t = self.text(sys); let t = t.strip_prefix('<').unwrap_or(t); let t = t.strip_suffix('>').unwrap_or(t); Some(t.to_string()) } else if let Some(lit) = self.find_child_by_kind(node, "string_literal") { self.find_child_by_kind(lit, "string_content").map(|sc| self.text(sc).to_string()) } else { None }; let Some(module_name) = module_name else { return }; self.create_node( "import", &module_name, node, Extra { signature: Some(import_text), ..Extra::default() }, ); if !module_name.is_empty() { let parent = self.top_row(); self.push_ref_at(parent, &module_name, edge_kind_index("imports").unwrap(), node); } } // --- calls / instantiation ---------------------------------------------- fn extract_call(&mut self, node: Node<'t>) { if self.stack.is_empty() { return; } let caller_row = self.top_row(); let func = node .child_by_field_name("function") .or_else(|| node.named_child(0)); let calls_kind = edge_kind_index("calls").unwrap(); // C++ explicit operator call `a.operator+(b)` (#1247): the // operator_name hides in an ERROR child. (has_error() defers such // files to wasm, so this scan is a faithful no-op today.) if self.variant == Variant::Cpp { if let Some(func) = func { let mut operator_name = String::new(); 'err: for i in 0..node.named_child_count() { let Some(child) = node.named_child(i) else { continue }; if child.kind() != "ERROR" { continue; } for j in 0..child.named_child_count() { if let Some(op) = child.named_child(j) { if op.kind() == "operator_name" { operator_name = self.text(op).to_string(); break 'err; } } } } if !operator_name.is_empty() { let sym = operator_name["operator".len()..].trim().to_string(); if symbolic_op_re().is_match(&sym) { let compact: String = sym.chars().filter(|c| !c.is_whitespace()).collect(); operator_name = format!("operator{compact}"); } let receiver = arrow_dot_no_ws(self.text(func)); if receiver != "this" && !operator_receiver_re().is_match(&receiver) { return; } let callee = if receiver == "this" { operator_name } else { format!("{receiver}.{operator_name}") }; self.push_ref_at(caller_row, &callee, calls_kind, node); return; } } } let mut callee_name = String::new(); if let Some(func) = func { if func.kind() == "field_expression" { // `obj.method()` / `ptr->method()` — the `field` field. let property = func .child_by_field_name("property") .or_else(|| func.child_by_field_name("field")) .or_else(|| func.named_child(1)); if let Some(property) = property { let method_name = self.text(property); let receiver = func .child_by_field_name("object") .or_else(|| func.child_by_field_name("operand")) .or_else(|| func.child_by_field_name("argument")) .or_else(|| func.named_child(0)); if let Some(r) = receiver { if is_literal_receiver(r.kind()) { return; // #1230: literal receivers emit nothing } } match receiver.map(|r| r.kind()) { Some("identifier") | Some("simple_identifier") | Some("field_identifier") => { let receiver_name = self.text(receiver.unwrap()); if !matches!(receiver_name, "self" | "this" | "cls" | "super") { callee_name = format!("{receiver_name}.{method_name}"); } else { callee_name = method_name.to_string(); } } Some("call_expression") => { // Call-result receiver (#645/#608): re-encode as // `().` — C/C++ re-encode any inner. let inner_fn = receiver.unwrap().child_by_field_name("function"); let inner_callee = inner_fn.map(|f| arrow_dot_no_ws(self.text(f))).unwrap_or_default(); if !inner_callee.is_empty() { callee_name = format!("{inner_callee}().{method_name}"); } else { callee_name = method_name.to_string(); } } _ => { callee_name = method_name.to_string(); } } } } else { // Bare / qualified / templated / parenthesized callee. callee_name = self.text(func).to_string(); } } if !callee_name.is_empty() { // `(*fp)(x)` → `fp` (parenthesized-conversion normalization). if let Some(c) = util::paren_conversion().captures(&callee_name) { callee_name = c[1].to_string(); } } // Template-arg strip on callees (`fn(args)`, `ns::fn()`). if !callee_name.is_empty() && callee_name.contains('<') && !callee_name.contains("operator") { callee_name = strip_cpp_template_args(&callee_name); } // Local fn-pointer fan-out: a bare callee bound earlier from `&fn` // emits one calls ref PER recorded target (insertion order). if !callee_name.is_empty() && self.variant == Variant::Cpp && simple_ident_re().is_match(&callee_name) { let targets = self .local_fn_ptrs .get(&caller_row) .and_then(|locals| locals.get(&callee_name)) .cloned(); if let Some(targets) = targets { if !targets.is_empty() { for target in &targets { self.push_ref_at(caller_row, target, calls_kind, node); } return; } } } if !callee_name.is_empty() { self.push_ref_at(caller_row, &callee_name, calls_kind, node); } } /// extractInstantiation: `new Foo(...)` and stack constructions (both /// read the type from the `type` field; template args + qualifiers strip). fn extract_instantiation(&mut self, node: Node<'t>) { if self.stack.is_empty() { return; } let from = self.top_row(); let ctor = node .child_by_field_name("constructor") .or_else(|| node.child_by_field_name("type")) .or_else(|| node.child_by_field_name("name")) .or_else(|| node.named_child(0)); let Some(ctor) = ctor else { return }; let mut class_name = self.text(ctor).to_string(); if let Some(lt) = class_name.find('<') { if lt > 0 { class_name.truncate(lt); } } // Keep the trailing identifier of `ns::Foo` / `a.Foo`. let last_dot = class_name.rfind('.').map(|i| i as isize).unwrap_or(-1); let last_colons = class_name.rfind("::").map(|i| i as isize).unwrap_or(-1); let cut = last_dot.max(last_colons); if cut >= 0 { class_name = class_name[(cut as usize + 1)..].to_string(); if class_name.starts_with(':') || class_name.starts_with('.') { class_name.remove(0); } } let class_name = class_name.trim().to_string(); if !class_name.is_empty() { self.push_ref_at(from, &class_name, edge_kind_index("instantiates").unwrap(), node); } } fn is_cpp_constructor_declaration(&self, node: Node<'t>) -> bool { if self.variant != Variant::Cpp || node.kind() != "declaration" || node.child_by_field_name("type").is_some() { return false; } let Some(owner) = node.parent().and_then(|p| p.parent()) else { return false }; if !matches!(owner.kind(), "class_specifier" | "struct_specifier" | "union_specifier") { return false; } let Some(decl) = node.child_by_field_name("declarator") else { return false }; decl.kind() == "function_declarator" && decl.child_by_field_name("declarator").map(|n| self.text(n)) == owner.child_by_field_name("name").map(|n| self.text(n)) } /// cppExtractor.getSignature (languages/c-cpp.ts): a C++ constructor /// definition or class-body prototype carries its parameter /// list as the signature so a local `T obj(args)` can pick the overload /// by arity (#1839); a trailing semicolon marks a prototype. Macro-shaped definitions whose real name was /// recovered from an argument are excluded. fn constructor_signature(&self, node: Node<'t>) -> Option { if self.variant != Variant::Cpp || (node.kind() != "function_definition" && !self.is_cpp_constructor_declaration(node)) { return None; } if node.child_by_field_name("type").is_some() || self.recover_cpp_macro_defined_name(node).is_some() { return None; } let params = node.child_by_field_name("declarator")?.child_by_field_name("parameters")?; Some(format!("{}{}", self.text(params), if node.kind() == "declaration" { ";" } else { "" })) } /// cppStackConstructions (tree-sitter.ts, #1035 / #1839): whether the /// declaration constructs with arguments (→ `instantiates`), and one /// arity per constructed object (→ `calls T::T/arity`). `extern`, /// pointer / reference / function declarators construct nothing; an /// array's braces hold elements, not constructor arguments. fn cpp_stack_constructions(&self, node: Node<'t>) -> (bool, Vec) { let none = (false, Vec::new()); let Some(type_node) = node.child_by_field_name("type") else { return none }; if !matches!( type_node.kind(), "type_identifier" | "template_type" | "qualified_identifier" ) { return none; } let mut instantiates = false; let mut arities = Vec::new(); for i in 0..node.named_child_count() { let Some(child) = node.named_child(i) else { continue }; if child.kind() == "storage_class_specifier" && self.text(child) == "extern" { return none; } if matches!(child.kind(), "identifier" | "array_declarator") { if child.kind() == "array_declarator" && !self.cpp_object_array(child) { continue; } arities.push(0); continue; } if child.kind() != "init_declarator" { continue; } let Some(declarator) = child.child_by_field_name("declarator") else { continue }; if !matches!(declarator.kind(), "identifier" | "array_declarator") { continue; } let Some(value) = child.child_by_field_name("value") else { continue }; if !matches!(value.kind(), "argument_list" | "initializer_list") { continue; } instantiates = true; if declarator.kind() == "identifier" { let count = (0..value.named_child_count()) .filter(|&j| value.named_child(j).map(|n| n.kind() != "comment").unwrap_or(false)) .count(); arities.push(count); } else if self.cpp_object_array(declarator) { let mut dimensions = Vec::new(); let mut array = Some(declarator); while let Some(n) = array.filter(|n| n.kind() == "array_declarator") { dimensions.insert(0, n.child_by_field_name("size").map(|s| self.text(s)) .filter(|s| s.bytes().all(|b| b.is_ascii_digit())) .and_then(|s| s.parse::().ok()).filter(|&n| n <= 9_007_199_254_740_991)); array = n.child_by_field_name("declarator"); } Self::cpp_array_arities(value, &dimensions, &mut arities); } } (instantiates, arities) } fn cpp_array_arities(list: Node<'t>, dimensions: &[Option], arities: &mut Vec) { let entries: Vec<_> = (0..list.named_child_count()).filter_map(|j| list.named_child(j)) .filter(|n| n.kind() != "comment").collect(); let mut elided = false; for entry in &entries { if dimensions.len() > 1 { if entry.kind() == "initializer_list" { Self::cpp_array_arities(*entry, &dimensions[1..], arities); } else { elided = true; } } else { arities.push(if entry.kind() == "initializer_list" { (0..entry.named_child_count()).filter_map(|j| entry.named_child(j)) .filter(|n| n.kind() != "comment").count() } else { 1 }); } } if !elided && (entries.is_empty() || dimensions[0].map(|n| n > entries.len()).unwrap_or(false)) { arities.push(0); } } fn cpp_object_array(&self, node: Node<'t>) -> bool { let mut element = node.child_by_field_name("declarator"); while element.map(|n| n.kind() == "array_declarator").unwrap_or(false) { element = element.and_then(|n| n.child_by_field_name("declarator")); } element.map(|n| n.kind() == "identifier").unwrap_or(false) } /// recordCppFnPtrBinding (tree-sitter.ts:5089). fn record_cpp_fn_ptr_binding(&mut self, local_name: &str, value: Option) { let Some(value) = value else { return }; if value.kind() != "pointer_expression" { return; } if value.child(0).map(|c| c.kind() != "&").unwrap_or(true) { return; // `*p` dereference, not address-of } let arg = value .child_by_field_name("argument") .or_else(|| value.named_child(0)); let Some(arg) = arg else { return }; if !matches!(arg.kind(), "identifier" | "template_function" | "qualified_identifier") { return; } if self.stack.is_empty() { return; } let caller_row = self.top_row(); let target = strip_cpp_template_args(self.text(arg)); if target.is_empty() || target == local_name { return; } let targets = self .local_fn_ptrs .entry(caller_row) .or_default() .entry(local_name.to_string()) .or_default(); if !targets.contains(&target) { targets.push(target); // Set semantics, insertion-ordered } } /// extractStaticMemberRef — cpp only (c is not in STATIC_MEMBER_LANGS). /// In this grammar the firing shape is `field_expression` (listed in /// MEMBER_ACCESS_TYPES for Scala — same node kind here): a capitalized /// simple receiver's value read. fn extract_static_member_ref(&mut self, node: Node<'t>) { if self.variant != Variant::Cpp { return; } if self.stack.is_empty() { return; } if !matches!(node.kind(), "field_expression" | "qualified_identifier") { return; } // Skip `Type.method()` — the access is a call's callee, already linked. if let Some(parent) = node.parent() { if parent.kind() == "call_expression" { let callee = parent .child_by_field_name("function") .or_else(|| parent.child_by_field_name("method")) .or_else(|| parent.named_child(0)); if let Some(callee) = callee { if callee.start_byte() == node.start_byte() { return; } } } } let recv = node .child_by_field_name("object") .or_else(|| node.child_by_field_name("expression")) .or_else(|| node.child_by_field_name("scope")) .or_else(|| node.named_child(0)); let Some(recv) = recv else { return }; if !matches!( recv.kind(), "identifier" | "type_identifier" | "simple_identifier" | "name" | "scoped_type_identifier" ) { return; } let text = self.text(recv); if capitalized_re().is_match(text) { let owner = self.top_row(); let name = text.to_string(); self.push_ref_at(owner, &name, edge_kind_index("references").unwrap(), recv); } } // --- function bodies ----------------------------------------------------- fn visit_function_body(&mut self, body: Node<'t>) { stack_guard!(); self.visit_for_calls_and_structure(body); } fn visit_for_calls_and_structure(&mut self, node: Node<'t>) { stack_guard!(); let kind = node.kind(); // A function-like macro defined inside a body is still a macro (#1838). if kind == "preproc_function_def" { self.visit_node(node); return; } self.maybe_capture_fn_refs(node); if kind == "call_expression" { self.extract_call(node); } else if kind == "new_expression" { self.extract_instantiation(node); } // C++ stack construction `Calculator calc(0)` / `Widget w{1,2}` (#1035), // plus one constructor ref `ns::T::T/arity` per constructed object (#1839). if kind == "declaration" && self.variant == Variant::Cpp { let (instantiates, arities) = self.cpp_stack_constructions(node); if instantiates { self.extract_instantiation(node); } if !arities.is_empty() && !self.stack.is_empty() { if let Some(type_node) = node.child_by_field_name("type") { let from = self.top_row(); let class_name = strip_cpp_template_args(self.text(type_node)); if let Some(name) = class_name.split("::").filter(|s| !s.is_empty()).last() { let calls = edge_kind_index("calls").unwrap(); for arity in arities { self.push_ref_at(from, &format!("{class_name}::{name}/{arity}"), calls, node); } } } } } // C++ local fn-pointer bindings: declarations and branch reassignments. if self.variant == Variant::Cpp && !self.stack.is_empty() { if kind == "declaration" { for i in 0..node.named_child_count() { let Some(child) = node.named_child(i) else { continue }; if child.kind() != "init_declarator" { continue; } let Some(decl) = child.child_by_field_name("declarator") else { continue }; if decl.kind() != "identifier" { continue; } let local = self.text(decl).to_string(); self.record_cpp_fn_ptr_binding(&local, child.child_by_field_name("value")); } } else if kind == "assignment_expression" { if let Some(left) = node.child_by_field_name("left") { if left.kind() == "identifier" { let local = self.text(left).to_string(); self.record_cpp_fn_ptr_binding(&local, node.child_by_field_name("right")); } } } } // Static-member / value-read: `Foo.BAR`, `Foo->x` (cpp). self.extract_static_member_ref(node); // Nested NAMED functions become their own nodes. if kind == "function_definition" { let nested_name = self.extract_name(node); if !nested_name.is_empty() && nested_name != "" { self.extract_function(node); return; } } // Structural nodes inside bodies (local classes; macro-misparse rescue). if self.variant == Variant::Cpp && kind == "class_specifier" { self.extract_class(node); return; } if kind == "struct_specifier" { self.extract_aggregate(node, "struct"); return; } if kind == "union_specifier" { self.extract_aggregate(node, "union"); return; } if kind == "enum_specifier" { self.extract_enum(node); return; } for i in 0..node.named_child_count() { if let Some(c) = node.named_child(i) { self.visit_for_calls_and_structure(c); } } } // --- inheritance --------------------------------------------------------- /// extractInheritance — the branches whose node kinds occur in the c/cpp /// grammars: base_class_clause (#1043) and the field_declaration_list /// recursion. A member `field_declaration` is never a supertype: the TS /// Go-embedding check is gated to Go, since c/cpp members nest their /// field_identifier inside a pointer/array/function declarator. fn extract_inheritance(&mut self, node: Node<'t>, class_row: u32) { stack_guard!(); let extends_kind = edge_kind_index("extends").unwrap(); for i in 0..node.named_child_count() { let Some(child) = node.named_child(i) else { continue }; match child.kind() { "base_class_clause" => { for j in 0..child.named_child_count() { let Some(t) = child.named_child(j) else { continue }; if matches!( t.kind(), "type_identifier" | "qualified_identifier" | "template_type" ) { let name = strip_cpp_template_args(self.text(t)); self.push_ref_at(class_row, &name, extends_kind, t); } } } "field_declaration_list" | "class_heritage" => { self.extract_inheritance(child, class_row); } _ => {} } } } // --- fn-ref capture (#756, cFamilySpec) ---------------------------------- /// maybeCaptureFnRefs + captureFnRefCandidates for the cFamily dispatch: /// argument_list(args), assignment_expression(rhs:right), /// init_declarator(varinit:value), initializer_list(list), /// initializer_pair(value:value). fn maybe_capture_fn_refs(&mut self, node: Node<'t>) { let mode = match node.kind() { "argument_list" => Mode::Args, "assignment_expression" => Mode::Rhs, "init_declarator" => Mode::Varinit, "initializer_list" => Mode::List, "initializer_pair" => Mode::Value, _ => return, }; if self.stack.is_empty() { return; } let from = self.top_row(); let mut values: Vec> = Vec::new(); match mode { Mode::Args | Mode::List => { for i in 0..node.named_child_count() { if let Some(c) = node.named_child(i) { values.push(c); } } } Mode::Rhs => { if let Some(rhs) = node.child_by_field_name("right") { // Param-storage skip: `o->cb = cb` (LHS last name == RHS). let lhs_text = node .child_by_field_name("left") .map(|l| self.text(l)) .unwrap_or(""); let lhs_last = util::lhs_last_name() .captures(lhs_text) .and_then(|c| c.get(1)) .map(|m| m.as_str()); if !(lhs_last.is_some() && lhs_last == Some(self.text(rhs).trim())) { values.push(rhs); } } } Mode::Value => { let v = node.child_by_field_name("value").or_else(|| { if node.named_child_count() > 0 { node.named_child(node.named_child_count() - 1) } else { None } }); if let Some(v) = v { values.push(v); } } Mode::Varinit => { // (init_declarator has no name/pattern field — no destructure skip) if let Some(v) = node.child_by_field_name("value") { values.push(v); } } } for v in values { let explicit_ref = v.kind() != "identifier"; // !idTypes.has(type) self.normalize_fn_ref_value(v, from, mode, explicit_ref, 0); } } /// normalizeValue for cFamilySpec: bare identifiers, and the /// pointer_expression unwrap (`&fn`; `&Cls::m` keeps the qualified name). fn normalize_fn_ref_value(&mut self, v: Node<'t>, from: u32, mode: Mode, explicit_ref: bool, depth: u32) { stack_guard!(); if depth > 4 { return; } match v.kind() { "identifier" => { let name = self.text(v); if name.is_empty() || is_stoplisted(name) { return; } self.push_fn_ref_cand(from, name.to_string(), mode, explicit_ref, v); } "pointer_expression" => { // `&x` is a function value; `*x` is a data read. if v.child(0).map(|c| c.kind() != "&").unwrap_or(true) { return; } let Some(inner) = v.child_by_field_name("argument") else { return }; if inner.kind() == "qualified_identifier" { let text = self.text(inner).trim(); if qualified_ref_re().is_match(text) && !is_stoplisted(text) { self.push_fn_ref_cand(from, text.to_string(), mode, explicit_ref, inner); } return; } self.normalize_fn_ref_value(inner, from, mode, explicit_ref, depth + 1); } _ => {} } } fn push_fn_ref_cand(&mut self, from: u32, name: String, mode: Mode, explicit_ref: bool, node: Node) { let p = node.start_position(); self.fn_ref_cands.push(Cand { from, name, mode, explicit_ref, line: p.row as u32 + 1, column_byte: node.start_byte(), row: p.row, }); } /// scanFnRefSubtree: capture-only walk of subtrees the main walkers skip /// (variable-declaration initializers). Halts at nested functions/lambdas. fn scan_fn_ref_subtree(&mut self, node: Node<'t>, depth: u32) { stack_guard!(); if depth > 12 { return; } if depth > 0 && matches!( node.kind(), "function_definition" | "arrow_function" | "function_expression" | "lambda_literal" | "lambda_expression" ) { return; } self.maybe_capture_fn_refs(node); for i in 0..node.named_child_count() { if let Some(c) = node.named_child(i) { self.scan_fn_ref_subtree(c, depth + 1); } } } /// flushFnRefCandidates with the cFamily gate policy: value/list positions /// at FILE scope skip the same-file/import gate (C has no symbol imports); /// cpp additionally requires explicit `&` forms outside those positions. fn flush_fn_ref_candidates(&mut self) { let cands = std::mem::take(&mut self.fn_ref_cands); if cands.is_empty() || util::is_generated_file(self.file_path) { return; } let address_of_only = self.variant == Variant::Cpp; let mut seen: HashSet<(String, String)> = HashSet::new(); for c in cands { let at_file_scope = self.node_ids[c.from as usize].starts_with("file:"); if address_of_only && !c.explicit_ref && !(at_file_scope && matches!(c.mode, Mode::Value | Mode::List)) { continue; } if !c.name.starts_with("this.") && !c.name.contains("::") { let skip_gate = matches!(c.mode, Mode::Value | Mode::List) && at_file_scope; if !skip_gate && !self.defined_fn_names.contains(&c.name) && !self.imported_names.contains(&c.name) { continue; } } if !seen.insert((self.node_ids[c.from as usize].clone(), c.name.clone())) { continue; } let column = util::col16(self.src, &self.line_starts, c.row, c.column_byte); let name_ref = self.arena.put(&c.name); self.tables.push_ref(&RefRow { from_idx: c.from, kind: FUNCTION_REF_CODE, line: c.line, column, reference_name: name_ref, candidates: NONE_STR, from_id_str: NONE_STR, }); } } // --- value refs (C only: VALUE_REF_LANGS has 'c', not 'cpp') ------------- fn flush_value_refs(&mut self, root: Node<'t>) { let scopes = std::mem::take(&mut self.value_scopes); let mut targets = std::mem::take(&mut self.fs_values); let counts = std::mem::take(&mut self.fs_value_counts); if self.variant != Variant::C { return; } if std::env::var("CODEGRAPH_VALUE_REFS").as_deref() == Ok("0") { return; } if targets.is_empty() || scopes.is_empty() || util::is_generated_file(self.file_path) { return; } // Shadow prune — the C declarator shape is init_declarator (a // file-scope const AND the local that shadows it both count). let mut decl_counts: HashMap<&str, u32> = HashMap::new(); let mut dstack: Vec = vec![root]; let mut dvisited = 0usize; while let Some(n) = dstack.pop() { if dvisited >= MAX_VALUE_REF_NODES { break; } dvisited += 1; if n.kind() == "init_declarator" { if let Some(name_node) = c_declarator_identifier(n) { if matches!(name_node.kind(), "identifier" | "simple_identifier") { let nm = self.text(name_node); if targets.contains_key(nm) { *decl_counts.entry(nm).or_insert(0) += 1; } } } } for i in 0..n.named_child_count() { if let Some(c) = n.named_child(i) { dstack.push(c); } } } let shadowed: Vec = decl_counts .iter() .filter(|(nm, c)| **c > counts.get(**nm).copied().unwrap_or(1)) .map(|(nm, _)| nm.to_string()) .collect(); for nm in shadowed { targets.remove(&nm); } if targets.is_empty() { return; } let refs_kind = edge_kind_index("references").unwrap(); for scope in &scopes { let mut seen: HashSet<&str> = HashSet::new(); let mut stack: Vec = vec![scope.node]; // (No Dart/Pascal sibling-body pull-in: c/cpp bodies are children.) let mut visited = 0usize; while let Some(n) = stack.pop() { if visited >= MAX_VALUE_REF_NODES { break; } visited += 1; if matches!(n.kind(), "identifier" | "constant" | "name" | "simple_identifier") { let ref_name = self.text(n); if let Some(&target_row) = targets.get(ref_name) { let target_id = self.node_ids[target_row as usize].as_str(); if target_id != self.node_ids[scope.row as usize] && ref_name != scope.name && !seen.contains(&target_id) { seen.insert(target_id); let meta = self.arena.put(r#"{"valueRef":true}"#); self.tables.push_edge(&EdgeRow { source_idx: scope.row, target_idx: target_row, kind: refs_kind, provenance: 0, line: NONE, column: NONE, metadata_json: meta, source_id_str: NONE_STR, target_id_str: NONE_STR, }); } } } for i in 0..n.named_child_count() { if let Some(c) = n.named_child(i) { stack.push(c); } } } } } } // --- free helpers ------------------------------------------------------------ /// findDeclaratorQualifiedId (languages/c-cpp.ts:13): BFS for the declarator's /// `qualified_identifier`, skipping parameter_list + trailing_return_type so a /// qualified PARAMETER type can't be mistaken for the method name. fn find_declarator_qualified_id(declarator: Node) -> Option { let mut queue: VecDeque = VecDeque::new(); queue.push_back(declarator); while let Some(current) = queue.pop_front() { if current.kind() == "qualified_identifier" { return Some(current); } for i in 0..current.named_child_count() { if let Some(child) = current.named_child(i) { if child.kind() != "parameter_list" && child.kind() != "trailing_return_type" { queue.push_back(child); } } } } None } /// cDeclaratorIdentifier (languages/c-cpp.ts): resolve the declared identifier /// through init/pointer/array/parenthesized declarator wrappers; a /// function_declarator means prototype/fn-ptr — null. (The C grammar's /// parenthesized_declarator exposes no `declarator` field, so that arm always /// terminates — bug-for-bug with getChildByField returning null there.) fn c_declarator_identifier(node: Node) -> Option { let mut cur = Some(node); let mut guard = 0; while let Some(n) = cur { guard += 1; if guard > 12 { return None; } match n.kind() { "identifier" => return Some(n), "function_declarator" => return None, "init_declarator" | "pointer_declarator" | "array_declarator" | "parenthesized_declarator" => { cur = n.child_by_field_name("declarator"); } _ => return None, } } None } /// isClassLikeDefinition (languages/c-cpp.ts): a class/struct/union/enum /// specifier that carries a body defines the type; `struct Foo *p` and the /// forward declaration `class Foo;` only name it. fn is_class_like_definition(node: Node) -> bool { matches!(node.kind(), "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier") && node.child_by_field_name("body").is_some() } /// definingDeclaration (languages/c-cpp.ts): the `declaration` a class-like /// definition is the type of (`struct Foo { … } foo;`). Its comment documents /// the type, and an unnamed type takes the name of its first variable. fn defining_declaration(node: Node) -> Option { if !is_class_like_definition(node) { return None; } let parent = node.parent()?; let is_type = parent.child_by_field_name("type").map(|t| t.id()) == Some(node.id()); (parent.kind() == "declaration" && is_type).then_some(parent) } /// isMacroMisparsedTypeDecl (languages/c-cpp.ts:261): `class MACRO Name {…}` /// misparse residue — bodyless class/struct specifier in `type` + a /// non-function_declarator declarator. fn is_macro_misparsed_type_decl(node: Node) -> bool { let Some(type_node) = node.child_by_field_name("type") else { return false }; if type_node.kind() != "class_specifier" && type_node.kind() != "struct_specifier" { return false; } let has_body = (0..type_node.named_child_count()) .filter_map(|i| type_node.named_child(i)) .any(|c| c.kind() == "field_declaration_list"); if has_body { return false; } if let Some(declarator) = node.child_by_field_name("declarator") { if declarator.kind() == "function_declarator" { return false; } } true } /// hasFunctionAncestor (tree-sitter.ts:295). fn has_function_ancestor(node: Node) -> bool { let mut p = node.parent(); while let Some(n) = p { if n.kind() == "function_definition" { return true; } p = n.parent(); } false } fn opt_str(arena: &mut Arena, s: Option<&str>) -> StrRef { match s { Some(s) => arena.put(s), None => NONE_STR, } }