Skip to main content

bonsai_lang_cpp/
lib.rs

1//! C++ language adapter.
2use bonsai_common::{FileId, Span};
3use bonsai_lang_api::{
4    decl_index_with_handler, extract_imports_via,
5    kit::{
6        c_family_preproc_imports, collect_kinds, collect_param_type_aliases,
7        expression_operand_names_with_handler, first_named_child_of_kind, language_from_pack,
8        named_child_call_args_with_handler, node_text, parse_with, span_of, walk_flow_events,
9    },
10    AdapterContext, AdapterError, AggregateLayout, ArgumentPassingMode, CallKind, CallTargetExtraction,
11    DeclIndex, DeclKind, FieldWrite, FlowEvent, GrammarHandler, ImportIndex, ImportScope, ImportSpec,
12    LanguageAdapter, LanguageCapabilities, LanguageId, TypeAliasBinding, TypeAliasVocabulary, Visibility,
13    EMPTY_HANDLER,
14};
15use std::sync::OnceLock;
16use tree_sitter::{Language, Node, Tree};
17
18/// C++ parameter shape: `parameter_declaration` carries `type` and
19/// `declarator` fields (the declarator may be a pointer / array /
20/// reference wrapper around the binding identifier). The kit
21/// helper drops back to `child_by_field_name("declarator")` when
22/// `name` isn't present, then walks down to the inner identifier.
23// `parameter_declaration` covers the function's formal parameters;
24// `declaration` covers local stack-allocated bindings inside the
25// body (`Box obj;`, `Logger log = ...;`). Both shapes carry a
26// `type` field and a `declarator` field, so the kit's generic
27// param-alias extractor pulls a `name : Type` binding from either.
28const CPP_TYPE_ALIASES: TypeAliasVocabulary = TypeAliasVocabulary {
29    fn_kinds: &["function_definition"],
30    param_kinds: &["parameter_declaration", "declaration"],
31    name_field: "declarator",
32    type_field: "type",
33};
34
35fn cpp_foreach_binding(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
36    (node.kind() == "for_range_loop")
37        .then(|| {
38            Some((
39                node.child_by_field_name("declarator")?,
40                node.child_by_field_name("right")?,
41            ))
42        })
43        .flatten()
44}
45
46pub const LANG_ID: LanguageId = LanguageId::new("cpp");
47const PACK_NAME: &str = "cpp";
48const CPP_CALL_KINDS: &[&str] = &["call_expression", "new_expression"];
49
50fn cpp_indirect_place_operand(node: Node<'_>) -> Option<Node<'_>> {
51    if node.kind() != "pointer_expression" {
52        return None;
53    }
54    let mut cursor = node.walk();
55    let has_indirection = node
56        .children(&mut cursor)
57        .any(|child| matches!(child.kind(), "*" | "&"));
58    has_indirection
59        .then(|| node.child_by_field_name("argument"))
60        .flatten()
61}
62
63/// C++ call targets are grammar-delimited `function`/`type` nodes. Preserve
64/// the complete callable path (`absl::GetFlag`, `object.method`, and operator
65/// calls), while removing parsed template-argument nodes: `tokenize<T>` and
66/// its declaration `tokenize` are one compiler callable identity. The adapter
67/// owns this CST normalization so shared resolution never parses `<...>`.
68fn cpp_call_target<'tree>(node: Node<'tree>, src: &[u8]) -> Option<CallTargetExtraction<'tree>> {
69    let target = match node.kind() {
70        "call_expression" => node.child_by_field_name("function")?,
71        "new_expression" => node.child_by_field_name("type")?,
72        _ => return None,
73    };
74    let full_text = cpp_call_target_without_template_arguments(target, src);
75    (!full_text.is_empty()).then_some(CallTargetExtraction {
76        node: target,
77        full_text,
78    })
79}
80
81fn cpp_call_target_without_template_arguments(target: Node<'_>, src: &[u8]) -> String {
82    let mut argument_ranges = Vec::new();
83    let mut stack = vec![target];
84    while let Some(node) = stack.pop() {
85        if node.kind() == "template_argument_list" {
86            argument_ranges.push(node.byte_range());
87            continue;
88        }
89        let mut cursor = node.walk();
90        stack.extend(node.named_children(&mut cursor));
91    }
92    if argument_ranges.is_empty() {
93        return node_text(&target, src).trim().to_string();
94    }
95    argument_ranges.sort_by_key(|range| (range.start, range.end));
96    let mut out = String::new();
97    let mut cursor = target.start_byte();
98    for range in argument_ranges {
99        if range.start > cursor {
100            out.push_str(std::str::from_utf8(&src[cursor..range.start]).unwrap_or_default());
101        }
102        cursor = cursor.max(range.end);
103    }
104    if cursor < target.end_byte() {
105        out.push_str(std::str::from_utf8(&src[cursor..target.end_byte()]).unwrap_or_default());
106    }
107    out.trim().to_string()
108}
109
110const HANDLER: GrammarHandler = GrammarHandler {
111    literal_value_kinds: &["null", "nullptr", "true", "false"],
112    string_literal_kinds: &[
113        "string_literal",
114        "raw_string_literal",
115        "char_literal",
116        "concatenated_string",
117    ],
118    comment_kinds: &["comment"],
119    doc_comment_prefixes: &["///", "//!", "/**"],
120    decorator_kinds: &["attribute"],
121    parameter_container_kinds: &["parameter_list"],
122    parameter_kinds: &["parameter_declaration", "optional_parameter_declaration"],
123    parameter_annotation_kinds: &["attribute"],
124    variadic_parameter_kinds: &["variadic_parameter", "variadic_declaration"],
125    binding_identifier_kinds: &["identifier"],
126    anonymous_variadic_token: Some("..."),
127    identifier_kinds: &["identifier"],
128    named_aggregate_kinds: &["initializer_list"],
129    positional_aggregate_kinds: &["initializer_list"],
130    aggregate_pair_kinds: &["initializer_pair"],
131    aggregate_key_field_names: &["designator"],
132    aggregate_value_field_names: &["value"],
133    static_field_name_kinds: &["field_identifier"],
134    aggregate_syntax_only_kinds: &["type_identifier"],
135    transparent_call_wrapper_kinds: &[
136        "field_expression",
137        "scoped_identifier",
138        "parenthesized_expression",
139        "await_expression",
140        "co_await_expression",
141    ],
142    single_expression_group_kinds: &["expression_list"],
143    assignment_target_wrapper_kinds: &[
144        "init_declarator",
145        "declarator",
146        "function_declarator",
147        "pointer_declarator",
148        "reference_declarator",
149        "parenthesized_declarator",
150    ],
151    binding_declaration_keyword_spellings: &["auto", "const"],
152    fn_kinds: &["function_definition"],
153    call_kinds: CPP_CALL_KINDS,
154    constructor_call_kinds: &["new_expression"],
155    call_callee_field_names: &["function"],
156    constructor_type_field_names: &["type"],
157    call_target_extractor: Some(cpp_call_target),
158    call_argument_field_names: &["arguments"],
159    call_argument_container_kinds: &["argument_list"],
160    writeback_operand_field_names: &["argument"],
161    indirect_place_operand_extractor: Some(cpp_indirect_place_operand),
162    lambda_body_field_names: &["body"],
163    pseudo_call_extractor: Some(extract_cpp_pseudo_call),
164    syntax_event_extractor: Some(extract_cpp_syntax_event),
165    argument_passing_mode_extractor: Some(cpp_argument_passing_mode),
166    expression_value_kind_extractor: Some(cpp_expression_value_kind),
167    call_ref_kinds: CPP_CALL_KINDS,
168    member_expression_kinds: &["field_expression", "qualified_identifier", "scoped_identifier"],
169    subscript_expression_kinds: &["subscript_expression"],
170    member_base_field_names: &["argument", "scope"],
171    member_name_field_names: &["field", "name"],
172    subscript_base_field_names: &["argument"],
173    subscript_index_field_names: &["index"],
174    syntax_error_tolerant_call_names: &["va_arg", "__builtin_va_arg"],
175    value_free_expression_kinds: &["sizeof_expression", "alignof_expression"],
176    class_kinds: &["class_specifier", "struct_specifier", "union_specifier"],
177    class_decl_kinds: &[
178        ("class_specifier", DeclKind::Class),
179        ("struct_specifier", DeclKind::Struct),
180        ("union_specifier", DeclKind::Struct),
181    ],
182    method_context_kinds: &["class_specifier", "struct_specifier", "union_specifier"],
183    if_kinds: &["if_statement", "conditional_expression", "switch_statement"],
184    branch_then_field_names: &["consequence", "body"],
185    branch_else_field_names: &["alternative"],
186    branch_condition_field_names: &["condition", "value"],
187    loop_body_field_names: &["body"],
188    loop_body_kinds: &["compound_statement", "expression_statement"],
189    branch_arm_kinds: &["compound_statement", "expression_statement"],
190    for_kinds: &["for_statement"],
191    foreach_kinds: &["for_range_loop"],
192    foreach_binding_extractor: Some(cpp_foreach_binding),
193    while_kinds: &["while_statement"],
194    do_kinds: &["do_statement"],
195    assignment_kinds: &["assignment_expression", "init_declarator"],
196    compound_assignment_operators: &["+=", "-=", "*=", "/=", "%=", "<<=", ">>=", "&=", "^=", "|="],
197    positional_aggregate_assignment_kinds: &["init_declarator"],
198    positional_aggregate_value_kinds: &["initializer_list"],
199    return_kinds: &["return_statement", "co_return_statement"],
200    throw_kinds: &["throw_statement"],
201    lambda_kinds: &["lambda_expression"],
202    try_kinds: &["try_statement"],
203    catch_kinds: &["catch_clause"],
204    break_kinds: &["break_statement"],
205    continue_kinds: &["continue_statement"],
206    control_label_field_names: &[],
207    yield_kinds: &["co_yield_statement"],
208    yield_value_field_names: &["argument", "value"],
209    try_body_field_names: &["body"],
210    await_kinds: &["co_await_expression"],
211    // `this` for instance methods; C++ has no `super` keyword, but
212    // `Base::method()` is a qualified call that the resolver
213    // already narrows by qualified-name matching, so the explicit
214    // implicit-receiver list stays at `this`.
215    constructor_names: bonsai_lang_api::NO_CONSTRUCTOR_METHOD_NAMES,
216    implicit_receiver_names: &["this"],
217    ..EMPTY_HANDLER
218};
219
220fn cpp_expression_value_kind(node: Node<'_>, _src: &[u8]) -> Option<bonsai_lang_api::AssignValueKind> {
221    matches!(
222        node.kind(),
223        "string_literal" | "char_literal" | "number_literal" | "true" | "false" | "nullptr"
224    )
225    .then_some(bonsai_lang_api::AssignValueKind::Literal)
226}
227
228fn cpp_argument_passing_mode(argument: Node<'_>, value: Node<'_>) -> ArgumentPassingMode {
229    if [argument, value].into_iter().any(|node| {
230        matches!(node.kind(), "pointer_expression" | "unary_expression") && {
231            let mut cursor = node.walk();
232            let has_address_of = node.children(&mut cursor).any(|child| child.kind() == "&");
233            has_address_of
234        }
235    }) {
236        ArgumentPassingMode::WriteBack
237    } else {
238        ArgumentPassingMode::Value
239    }
240}
241
242fn extract_cpp_pseudo_call(
243    node: Node<'_>,
244    file: FileId,
245    src: &[u8],
246    handler: &GrammarHandler,
247) -> Option<FlowEvent> {
248    if node.kind() != "delete_expression" {
249        return None;
250    }
251    Some(FlowEvent::Call {
252        span: span_of(file, &node),
253        receiver: None,
254        receiver_types: Vec::new(),
255        name: "delete".to_string(),
256        call_kind: CallKind::Operator,
257        args: named_child_call_args_with_handler(&node, file, src, handler),
258    })
259}
260
261/// Lower C++ direct initialization (`Type value(args)`) as the constructor
262/// call it denotes. Tree-sitter represents this as an `init_declarator` whose
263/// value is an `argument_list`, not as a `call_expression`; without this
264/// adapter-owned CST rule the compiler sees the assignment and nested
265/// argument calls but loses the constructor boundary itself.
266fn extract_cpp_syntax_event(
267    node: Node<'_>,
268    file: FileId,
269    src: &[u8],
270    handler: &GrammarHandler,
271) -> Option<FlowEvent> {
272    let (name, value) = match node.kind() {
273        "init_declarator" => {
274            let value = node.child_by_field_name("value")?;
275            if value.kind() != "argument_list" {
276                return None;
277            }
278            let declaration = node.parent().filter(|parent| parent.kind() == "declaration")?;
279            let type_node = declaration.child_by_field_name("type")?;
280            (cpp_type_descriptor_name(&type_node, src)?, value)
281        }
282        // A constructor's member/base initializer list lives outside its
283        // compound body. The adapter explicitly walks that list below; base
284        // identifiers resolve to constructor declarations, while member
285        // identifiers remain unresolved unless their own typed declaration
286        // provides a callable identity.
287        "field_initializer" => {
288            let name_node = node.named_child(0)?;
289            let value = first_named_child_of_kind(&node, "argument_list")?;
290            (node_text(&name_node, src).trim().to_string(), value)
291        }
292        _ => return None,
293    };
294    if name.is_empty() {
295        return None;
296    }
297    Some(FlowEvent::Call {
298        span: span_of(file, &value),
299        receiver: None,
300        receiver_types: Vec::new(),
301        name,
302        call_kind: CallKind::Constructor,
303        args: named_child_call_args_with_handler(&value, file, src, handler),
304    })
305}
306
307/// Zero-sized adapter handle; all state lives in the shared parser pack.
308#[derive(Debug, Default, Copy, Clone)]
309pub struct CppAdapter;
310
311impl CppAdapter {
312    /// Construct a fresh adapter handle.
313    #[must_use]
314    pub fn new() -> Self {
315        Self
316    }
317}
318
319fn cpp_tree_proves_language(tree: &Tree) -> bool {
320    static C_GRAMMAR: OnceLock<Option<Language>> = OnceLock::new();
321    let Some(c_grammar) = C_GRAMMAR.get_or_init(|| language_from_pack("c").ok()) else {
322        return false;
323    };
324    let mut stack = vec![tree.root_node()];
325    while let Some(node) = stack.pop() {
326        if node.is_named()
327            && !node.is_error()
328            && (!grammar_has_named_kind(c_grammar, node.kind()) || is_cpp_braced_construction(node))
329        {
330            return true;
331        }
332        let mut cursor = node.walk();
333        stack.extend(node.named_children(&mut cursor));
334    }
335    false
336}
337
338fn grammar_has_named_kind(grammar: &Language, kind: &str) -> bool {
339    let id = grammar.id_for_node_kind(kind, true);
340    id != 0 && grammar.node_kind_is_named(id) && grammar.node_kind_for_id(id) == Some(kind)
341}
342
343/// Distinguish C++ uniform construction (`Type { ... }`) from C's standard
344/// compound literal (`(Type) { ... }`) using the grammar's typed child span.
345/// Both grammars call the parent a `compound_literal_expression`, so the node
346/// kind alone is not language proof.
347fn is_cpp_braced_construction(node: Node<'_>) -> bool {
348    node.kind() == "compound_literal_expression"
349        && node
350            .child_by_field_name("type")
351            .is_some_and(|ty| ty.start_byte() == node.start_byte())
352}
353
354impl LanguageAdapter for CppAdapter {
355    fn language_id(&self) -> LanguageId {
356        LANG_ID
357    }
358    fn display_name(&self) -> &'static str {
359        "C++"
360    }
361    fn file_extensions(&self) -> &'static [&'static str] {
362        // `.h` is shared with C and Objective-C. Grammar-owned C++ constructs
363        // prove this specialized frontend; a C-compatible header with no C++
364        // syntax stays with the generic C adapter.
365        &["cpp", "cc", "cxx", "hpp", "hh", "hxx", "h"]
366    }
367    fn tree_sitter_language(&self) -> Result<Language, AdapterError> {
368        language_from_pack(PACK_NAME)
369    }
370    fn source_syntax_proves_language(
371        &self,
372        _snapshot: &bonsai_lang_api::FileSnapshot,
373        tree: &Tree,
374    ) -> bonsai_lang_api::LanguageOwnershipEvidence {
375        if cpp_tree_proves_language(tree) {
376            bonsai_lang_api::LanguageOwnershipEvidence::Proven
377        } else {
378            bonsai_lang_api::LanguageOwnershipEvidence::Excluded
379        }
380    }
381    fn parse_recovery_edits(
382        &self,
383        snapshot: &bonsai_lang_api::FileSnapshot,
384        vfs: &bonsai_lang_api::Vfs,
385        tree: &Tree,
386    ) -> Vec<bonsai_lang_api::ParseRecoveryEdit> {
387        let mut edits = bonsai_lang_api::branch_free_conditional_recovery_edits(
388            snapshot,
389            tree,
390            bonsai_lang_api::ConditionalDirectiveSyntax {
391                openings_with_condition: &["#if", "#ifdef", "#ifndef"],
392                alternatives_with_condition: &["#elif", "#elifdef", "#elifndef"],
393                alternatives_without_condition: &["#else"],
394                closing: "#endif",
395                trailing_comment_prefixes: &["//", "/*"],
396            },
397        );
398        edits.extend(bonsai_lang_api::c_family_declaration_macro_recovery_edits(
399            snapshot,
400            vfs,
401            tree,
402            &["va_arg", "__builtin_va_arg"],
403        ));
404        edits
405    }
406    fn capabilities(&self) -> LanguageCapabilities {
407        // Macros: same story as C — tree-sitter-cpp parses
408        // `STR_CPY(...)` / `LOG(...)` / `assert(...)` as ordinary
409        // call expressions and the engine narrows them by name.
410        // `#define` expansion is not performed.
411        LanguageCapabilities {
412            macros: bonsai_lang_api::CapabilityLevel::Partial,
413            receiver_types: bonsai_lang_api::CapabilityLevel::Partial,
414            module_default_export_names: &[],
415            universal_type_names: &[],
416            module_path_syntax: bonsai_lang_api::ModulePathSyntax {
417                rooted_prefixes: &["::"],
418                repeatable_rooted_prefixes: &[],
419            },
420            // C++ constructors are class-named; the kind-based
421            // `DeclKind::Constructor` lookup is authoritative.
422            constructor_method_names: bonsai_lang_api::NO_CONSTRUCTOR_METHOD_NAMES,
423            super_receiver_tokens: &[],
424            implicit_receiver_tokens: &["this"],
425            same_directory_unqualified_calls: true,
426            build_target_linkage: true,
427            ..LanguageCapabilities::partial_baseline()
428        }
429    }
430    fn extract_declarations(&self, file: FileId, ctx: &AdapterContext<'_>) -> DeclIndex {
431        let mut decl_index = decl_index_with_handler(PACK_NAME, file, ctx, &HANDLER);
432        mark_cpp_constructors(&mut decl_index);
433        // Populate qualified_name + module_path + visibility per the
434        // semantic-identity contract
435        // (`docs/contributing/design-patterns.mdx::Semantic Resolution Always`).
436        // Two TU-private surfaces in C++:
437        //   - `static` storage class on a free function (C-inherited).
438        //   - Definition inside an anonymous namespace.
439        // Both must surface as `Visibility::Private` so the resolver
440        // refuses cross-TU linking by name.
441        bonsai_lang_api::apply_file_stem_semantic_identity(&mut decl_index, ctx);
442        let private_function_names = collect_tu_private_function_names(file, ctx);
443        for decl in &mut decl_index.defs {
444            if private_function_names.contains(&decl.name) {
445                decl.visibility = Visibility::Private;
446            }
447        }
448        // Per-class `bases`: `class C : public Base, private Other {…}`
449        // → ["Base", "Other"]. C++ exposes them as a single
450        // `base_class_clause` whose access_specifier+type_identifier
451        // pairs alternate. Per-decl `type_aliases` from typed
452        // parameters bring C++ in lockstep with the rest per
453        // docs/contributing/design-patterns.mdx::Semantic Resolution Always.
454        if let Some((snapshot, tree)) = parse_with(PACK_NAME, file, ctx) {
455            let src = snapshot.text.as_bytes();
456            // Phase-6 return-type extraction: `T foo() {}` populates
457            // `Decl.return_type` for `apply_assign_call_result_types`.
458            bonsai_lang_api::populate_decl_return_types(&mut decl_index, &tree, src, &HANDLER);
459            let bases_by_span = collect_cpp_class_bases(&tree, file, src);
460            let fields_by_class = collect_cpp_class_fields(&tree, file, src);
461            decl_index.aggregate_layouts = fields_by_class
462                .iter()
463                .map(|(_, type_name, fields)| AggregateLayout {
464                    type_name: type_name.clone(),
465                    fields: fields.clone(),
466                })
467                .collect();
468            let fields_by_parent = cpp_fields_by_parent_symbol(&decl_index, &fields_by_class);
469            let access_by_span = collect_cpp_member_visibility(&tree, file, src);
470            let alias_map = collect_param_type_aliases(&tree, file, src, &CPP_TYPE_ALIASES);
471            // WS2: `auto c = static_cast<Foo>(x)` / `auto c = (Foo) x` — the
472            // kit types declared-type locals (`Foo c = make()`) but not the
473            // inferred-`auto` form, where the type lives only on the cast.
474            let cast_aliases = collect_cpp_cast_aliases(&tree, file, src).into_iter().fold(
475                std::collections::HashMap::<Span, Vec<TypeAliasBinding>>::new(),
476                |mut by_span, (span, binding)| {
477                    by_span.entry(span).or_default().push(binding);
478                    by_span
479                },
480            );
481            let initializer_specs = collect_cpp_initializer_field_specs(&tree, file, src)
482                .into_iter()
483                .collect::<std::collections::HashMap<_, _>>();
484            let initializer_events = collect_cpp_constructor_initializer_events(&tree, file, src)
485                .into_iter()
486                .collect::<std::collections::HashMap<_, _>>();
487            for decl in &mut decl_index.defs {
488                if let Some(events) = initializer_events.get(&decl.span) {
489                    let mut ordered = events.clone();
490                    ordered.append(&mut decl.flow_events);
491                    decl.flow_events = ordered;
492                }
493                if let Some(fields) = decl.parent.and_then(|parent| fields_by_parent.get(&parent)) {
494                    bonsai_lang_api::qualify_receiver_field_expression_flows(
495                        &mut decl.flow_events,
496                        fields,
497                        "this",
498                    );
499                }
500                if let Some(visibility) = access_by_span.get(&decl.span).copied() {
501                    decl.visibility = visibility;
502                }
503                if let Some(aliases) = alias_map.get(&decl.span) {
504                    decl.type_aliases = aliases.clone();
505                }
506                if let Some(bindings) = cast_aliases.get(&decl.span) {
507                    decl.type_aliases.extend(bindings.iter().cloned());
508                }
509                collapse_cpp_same_type_copy_initializers(&mut decl.flow_events, &decl.type_aliases);
510                // Repair catch-param bindings: the kit's generic
511                // extractor picks the first identifier descendant of
512                // the catch clause, which on C++ `catch (const T& e)`
513                // is the type identifier rather than the binding.
514                fix_cpp_catch_params(&mut decl.flow_events, &tree, src);
515                // Bases only attach to class-shaped decls; skip
516                // free functions, methods, vars, etc.
517                if let Some(specs) = initializer_specs.get(&decl.span) {
518                    for spec in specs {
519                        let source_param_indices = decl
520                            .params
521                            .iter()
522                            .enumerate()
523                            .filter_map(|(idx, param)| {
524                                spec.sources
525                                    .iter()
526                                    .any(|source| cpp_source_mentions_param(source, param))
527                                    .then_some(idx)
528                            })
529                            .collect::<Vec<_>>();
530                        if source_param_indices.is_empty() {
531                            continue;
532                        }
533                        decl.receiver_field_writes.push(FieldWrite {
534                            span: spec.span,
535                            target: format!("this.{}", spec.field),
536                            source_param_indices,
537                        });
538                    }
539                    decl.receiver_field_writes
540                        .sort_by_key(|write| (write.span.start, write.target.clone()));
541                    decl.receiver_field_writes.dedup_by(|a, b| {
542                        a.span == b.span
543                            && a.target == b.target
544                            && a.source_param_indices == b.source_param_indices
545                    });
546                }
547                if !is_class_like(decl.kind) {
548                    continue;
549                }
550                if let Some(bases) = bases_by_span.iter().find_map(|(span, name, bases)| {
551                    (*span == decl.span || name == &decl.name).then_some(bases)
552                }) {
553                    decl.bases = bases.clone();
554                }
555            }
556        }
557        for decl in &mut decl_index.defs {
558            bonsai_lang_api::normalize_call_result_assignment_sources(&mut decl.flow_events);
559            let has_variadic_param = decl
560                .params
561                .iter()
562                .any(|param| param == bonsai_lang_api::kit::SYNTHETIC_VARARGS_PARAM);
563            bonsai_lang_api::kit::normalize_variadic_builtin_flow(
564                &mut decl.flow_events,
565                has_variadic_param,
566                &["va_start", "__builtin_va_start"],
567                &["va_arg", "__builtin_va_arg"],
568            );
569            apply_cpp_moved_argument_places(&mut decl.flow_events);
570        }
571        // Precompute `self.<field> → Type` bindings from each
572        // class's constructor `receiver_field_writes` so receiver-
573        // typed dispatch through stable instance state is an O(1)
574        // lookup against the method's `type_aliases` instead of a
575        // per-call walk over sibling decls.
576        // Local constructor-result receiver typing follows constructor CST
577        // nodes and declaration resolution, never identifier capitalization.
578        bonsai_lang_api::apply_constructor_result_type_aliases(&mut decl_index);
579        bonsai_lang_api::apply_class_field_type_aliases(&mut decl_index);
580        decl_index
581    }
582    fn extract_imports(&self, file: FileId, ctx: &AdapterContext<'_>) -> ImportIndex {
583        extract_imports_via(PACK_NAME, file, ctx, parse_imports)
584    }
585}
586
587/// C++ direct-list initialization with one value of the declared type is copy
588/// construction, not positional aggregate initialization:
589/// `Envelope valid{env}` carries the whole object. Tree-sitter deliberately
590/// uses the same `initializer_list` node as `Envelope env{kind, cmd}`, so the
591/// adapter resolves the distinction from its parsed declaration types before
592/// shared aggregate lowering assigns positional field names.
593fn collapse_cpp_same_type_copy_initializers(events: &mut Vec<FlowEvent>, aliases: &[TypeAliasBinding]) {
594    for event in events.iter_mut() {
595        match event {
596            FlowEvent::Branch {
597                then_events,
598                else_events,
599                ..
600            } => {
601                collapse_cpp_same_type_copy_initializers(then_events, aliases);
602                collapse_cpp_same_type_copy_initializers(else_events, aliases);
603            }
604            FlowEvent::Loop { body, .. } | FlowEvent::Defer { body, .. } | FlowEvent::Using { body, .. } => {
605                collapse_cpp_same_type_copy_initializers(body, aliases);
606            }
607            FlowEvent::Try {
608                body,
609                catch_events,
610                finally_events,
611                ..
612            } => {
613                collapse_cpp_same_type_copy_initializers(body, aliases);
614                collapse_cpp_same_type_copy_initializers(catch_events, aliases);
615                collapse_cpp_same_type_copy_initializers(finally_events, aliases);
616            }
617            _ => {}
618        }
619    }
620    events.retain(|event| {
621        let FlowEvent::AggregateAssign {
622            target,
623            type_name,
624            value_flow,
625            ..
626        } = event
627        else {
628            return true;
629        };
630        if !value_flow.aggregate_fields.is_empty()
631            || !value_flow.spreads.is_empty()
632            || value_flow.tuple_items.len() != 1
633        {
634            return true;
635        }
636        let Some(source) = value_flow.tuple_items[0].place.as_deref() else {
637            return true;
638        };
639        let declared_type = type_name.as_deref().or_else(|| {
640            aliases
641                .iter()
642                .find(|alias| alias.name == *target)
643                .map(|alias| alias.type_name.as_str())
644        });
645        let source_type = aliases
646            .iter()
647            .find(|alias| alias.name == source)
648            .map(|alias| alias.type_name.as_str());
649        let (Some(declared_type), Some(source_type)) = (declared_type, source_type) else {
650            return true;
651        };
652        bonsai_lang_api::kit::canonical_simple_type_name(declared_type)
653            != bonsai_lang_api::kit::canonical_simple_type_name(source_type)
654    });
655}
656
657fn collect_cpp_constructor_initializer_events(
658    tree: &Tree,
659    file: FileId,
660    src: &[u8],
661) -> Vec<(Span, Vec<FlowEvent>)> {
662    let mut out = Vec::new();
663    for function in collect_kinds(tree, &["function_definition"]) {
664        let Some(initializers) = first_named_child_of_kind(&function, "field_initializer_list") else {
665            continue;
666        };
667        let events = walk_flow_events(initializers, file, src, &HANDLER, &[]);
668        if !events.is_empty() {
669            out.push((span_of(file, &function), events));
670        }
671    }
672    out
673}
674
675/// Preserve object identity through a parsed move expression nested inside a
676/// larger call argument (`run(std::move(env))`). Lifecycle injection has
677/// already classified the inner call from the adapter-owned C++ semantics;
678/// this pass uses only that semantic event plus AST spans to mark the outer
679/// argument as the same addressable place. The IDG can then forward exact
680/// descendant fields without knowing any library function names.
681fn apply_cpp_moved_argument_places(events: &mut [FlowEvent]) {
682    let mut moved = Vec::new();
683    collect_cpp_moved_events(events, &mut moved);
684    apply_cpp_moved_argument_places_with_events(events, &moved);
685}
686
687fn collect_cpp_moved_events(events: &[FlowEvent], out: &mut Vec<(Span, String)>) {
688    for event in events {
689        match event {
690            FlowEvent::Lifecycle {
691                span,
692                name,
693                transition,
694            } if transition == "moved" && !name.is_empty() => out.push((*span, name.clone())),
695            FlowEvent::Branch {
696                then_events,
697                else_events,
698                ..
699            } => {
700                collect_cpp_moved_events(then_events, out);
701                collect_cpp_moved_events(else_events, out);
702            }
703            FlowEvent::Loop { body, .. } | FlowEvent::Defer { body, .. } | FlowEvent::Using { body, .. } => {
704                collect_cpp_moved_events(body, out);
705            }
706            FlowEvent::Try {
707                body,
708                catch_events,
709                finally_events,
710                ..
711            } => {
712                collect_cpp_moved_events(body, out);
713                collect_cpp_moved_events(catch_events, out);
714                collect_cpp_moved_events(finally_events, out);
715            }
716            _ => {}
717        }
718    }
719}
720
721fn apply_cpp_moved_argument_places_with_events(events: &mut [FlowEvent], moved: &[(Span, String)]) {
722    for event in events {
723        match event {
724            FlowEvent::Call { args, .. } => {
725                for arg in args {
726                    if arg.place.is_some() {
727                        continue;
728                    }
729                    let candidate = moved.iter().find_map(|(span, name)| {
730                        (arg.span.file == span.file
731                            && arg.span.start <= span.start
732                            && span.end <= arg.span.end
733                            && arg.source_names.iter().any(|source| source == name))
734                        .then_some(name)
735                    });
736                    if let Some(candidate) = candidate {
737                        arg.place = Some(candidate.clone());
738                    }
739                }
740            }
741            FlowEvent::Branch {
742                then_events,
743                else_events,
744                ..
745            } => {
746                apply_cpp_moved_argument_places_with_events(then_events, moved);
747                apply_cpp_moved_argument_places_with_events(else_events, moved);
748            }
749            FlowEvent::Loop { body, .. } | FlowEvent::Defer { body, .. } | FlowEvent::Using { body, .. } => {
750                apply_cpp_moved_argument_places_with_events(body, moved);
751            }
752            FlowEvent::Try {
753                body,
754                catch_events,
755                finally_events,
756                ..
757            } => {
758                apply_cpp_moved_argument_places_with_events(body, moved);
759                apply_cpp_moved_argument_places_with_events(catch_events, moved);
760                apply_cpp_moved_argument_places_with_events(finally_events, moved);
761            }
762            _ => {}
763        }
764    }
765}
766
767/// C++ constructors are identified by the grammar-owned class/member
768/// relationship: a member whose identifier equals its parent class identifier
769/// is a constructor.  This uses declaration identity emitted from the CST;
770/// downstream resolution never guesses from capitalization or a name list.
771fn mark_cpp_constructors(decl_index: &mut DeclIndex) {
772    let class_names = decl_index
773        .defs
774        .iter()
775        .filter(|decl| is_class_like(decl.kind))
776        .map(|decl| (decl.symbol, decl.name.clone()))
777        .collect::<std::collections::HashMap<_, _>>();
778    for decl in &mut decl_index.defs {
779        if !matches!(decl.kind, DeclKind::Function | DeclKind::Method) {
780            continue;
781        }
782        let Some(parent_name) = decl.parent.and_then(|parent| class_names.get(&parent)) else {
783            continue;
784        };
785        if decl.name == *parent_name {
786            decl.kind = DeclKind::Constructor;
787            if decl.implicit_receiver_names.is_empty() {
788                decl.implicit_receiver_names.push("this".to_string());
789            }
790        }
791    }
792}
793
794fn collect_cpp_class_fields(tree: &Tree, file: FileId, src: &[u8]) -> Vec<(Span, String, Vec<String>)> {
795    let mut out = Vec::new();
796    for class_node in collect_kinds(tree, &["class_specifier", "struct_specifier", "union_specifier"]) {
797        let Some(name_node) = class_node
798            .child_by_field_name("name")
799            .or_else(|| first_named_child_of_kind(&class_node, "type_identifier"))
800        else {
801            continue;
802        };
803        let Some(body) = class_node.child_by_field_name("body") else {
804            continue;
805        };
806        let mut fields = Vec::new();
807        let mut body_cursor = body.walk();
808        for field_decl in body
809            .named_children(&mut body_cursor)
810            .filter(|child| child.kind() == "field_declaration")
811        {
812            for child_index in 0..field_decl.child_count() {
813                if field_decl.field_name_for_child(child_index as u32) != Some("declarator") {
814                    continue;
815                }
816                let Some(child) = field_decl.child(child_index as u32) else {
817                    continue;
818                };
819                if !child.is_named() || cpp_declarator_is_function(child) {
820                    continue;
821                }
822                if let Some(identifier) = cpp_binding_identifier(child) {
823                    let name = node_text(&identifier, src).trim();
824                    if !name.is_empty() && !fields.iter().any(|field| field == name) {
825                        fields.push(name.to_string());
826                    }
827                }
828            }
829        }
830        if !fields.is_empty() {
831            out.push((
832                span_of(file, &class_node),
833                node_text(&name_node, src).trim().to_string(),
834                fields,
835            ));
836        }
837    }
838    out
839}
840
841fn cpp_declarator_is_function(node: Node<'_>) -> bool {
842    let mut stack = vec![node];
843    while let Some(current) = stack.pop() {
844        if current.kind() == "function_declarator" {
845            return true;
846        }
847        let mut cursor = current.walk();
848        stack.extend(current.named_children(&mut cursor));
849    }
850    false
851}
852
853fn cpp_binding_identifier(node: Node<'_>) -> Option<Node<'_>> {
854    if matches!(node.kind(), "identifier" | "field_identifier") {
855        return Some(node);
856    }
857    for field in ["declarator", "name"] {
858        if let Some(child) = node.child_by_field_name(field) {
859            if let Some(identifier) = cpp_binding_identifier(child) {
860                return Some(identifier);
861            }
862        }
863    }
864    let mut cursor = node.walk();
865    for child in node.named_children(&mut cursor) {
866        if let Some(identifier) = cpp_binding_identifier(child) {
867            return Some(identifier);
868        }
869    }
870    None
871}
872
873fn cpp_fields_by_parent_symbol(
874    index: &DeclIndex,
875    fields_by_class: &[(Span, String, Vec<String>)],
876) -> std::collections::HashMap<bonsai_common::SymbolId, std::collections::HashSet<String>> {
877    index
878        .defs
879        .iter()
880        .filter(|decl| is_class_like(decl.kind))
881        .filter_map(|decl| {
882            fields_by_class
883                .iter()
884                .find(|(span, name, _)| *span == decl.span || *name == decl.name)
885                .map(|(_, _, fields)| (decl.symbol, fields.iter().cloned().collect()))
886        })
887        .collect()
888}
889
890/// Collect every C++ function name that's TU-private:
891///
892/// - Function definitions with a `static` storage class specifier.
893/// - Function definitions whose body lives inside an anonymous
894///   `namespace { ... }` block (no namespace identifier).
895fn collect_tu_private_function_names(
896    file: FileId,
897    ctx: &AdapterContext<'_>,
898) -> std::collections::HashSet<String> {
899    let mut private_names: std::collections::HashSet<String> = std::collections::HashSet::new();
900    // Bail conservatively on any I/O / parser failure.
901    let Some((snapshot, tree)) = parse_with(PACK_NAME, file, ctx) else {
902        return private_names;
903    };
904    let src = snapshot.text.as_bytes();
905    let root = tree.root_node();
906    walk_for_tu_private(root, src, false, &mut private_names);
907    private_names
908}
909
910#[derive(Clone, Debug)]
911struct CppInitializerFieldSpec {
912    span: Span,
913    field: String,
914    sources: Vec<String>,
915}
916
917fn collect_cpp_initializer_field_specs(
918    tree: &Tree,
919    file: FileId,
920    src: &[u8],
921) -> Vec<(Span, Vec<CppInitializerFieldSpec>)> {
922    let mut out = Vec::new();
923    for fn_node in collect_kinds(tree, &["function_definition"]) {
924        let Some(initializers) = first_named_child_of_kind(&fn_node, "field_initializer_list") else {
925            continue;
926        };
927        let mut specs = Vec::new();
928        let mut cursor = initializers.walk();
929        for init in initializers.named_children(&mut cursor) {
930            if init.kind() != "field_initializer" {
931                continue;
932            }
933            let Some(field_node) = first_named_child_of_kind(&init, "field_identifier") else {
934                continue;
935            };
936            let Some(value_node) = first_named_child_of_kind(&init, "argument_list") else {
937                continue;
938            };
939            let field = node_text(&field_node, src).trim().to_string();
940            let sources = expression_operand_names_with_handler(&value_node, src, &HANDLER);
941            if field.is_empty() || sources.is_empty() {
942                continue;
943            }
944            specs.push(CppInitializerFieldSpec {
945                span: span_of(file, &init),
946                field,
947                sources,
948            });
949        }
950        if !specs.is_empty() {
951            out.push((span_of(file, &fn_node), specs));
952        }
953    }
954    out
955}
956
957fn cpp_source_mentions_param(source: &str, param: &str) -> bool {
958    let source = bonsai_common::normalize_qualified_name(source);
959    let param = bonsai_common::normalize_qualified_name(param);
960    source == param
961        || source
962            .strip_prefix(&param)
963            .is_some_and(|projection| projection.starts_with('.'))
964}
965
966fn collect_cpp_member_visibility(
967    tree: &Tree,
968    file: FileId,
969    src: &[u8],
970) -> std::collections::HashMap<Span, Visibility> {
971    let mut out = std::collections::HashMap::new();
972    for class_node in collect_kinds(tree, &["class_specifier", "struct_specifier"]) {
973        let default_visibility = if class_node.kind() == "struct_specifier" {
974            Visibility::Public
975        } else {
976            Visibility::Private
977        };
978        let mut current_visibility = default_visibility;
979        if let Some(body) = class_node.child_by_field_name("body") {
980            let mut cursor = body.walk();
981            for child in body.named_children(&mut cursor) {
982                if child.kind() == "access_specifier" {
983                    current_visibility = cpp_access_visibility(node_text(&child, src), default_visibility);
984                    continue;
985                }
986                if child.kind() == "function_definition" {
987                    out.insert(span_of(file, &child), current_visibility);
988                }
989            }
990        }
991    }
992    out
993}
994
995fn cpp_access_visibility(raw: &str, default_visibility: Visibility) -> Visibility {
996    match raw.trim().trim_end_matches(':') {
997        "public" => Visibility::Public,
998        "protected" => Visibility::Protected,
999        "private" => Visibility::Private,
1000        _ => default_visibility,
1001    }
1002}
1003
1004/// Recursive walker tracking whether we're currently inside an
1005/// anonymous namespace; when we are, every nested function definition
1006/// counts as TU-private even without a `static` specifier.
1007fn walk_for_tu_private(
1008    root: Node<'_>,
1009    src: &[u8],
1010    inside_anonymous_ns: bool,
1011    private_names: &mut std::collections::HashSet<String>,
1012) {
1013    let mut stack = vec![(root, inside_anonymous_ns)];
1014    while let Some((node, inside_anonymous_ns)) = stack.pop() {
1015        if node.kind() == "function_definition"
1016            && (inside_anonymous_ns || function_has_static_specifier(&node, src))
1017        {
1018            if let Some(name) = function_name(&node, src) {
1019                private_names.insert(name);
1020            }
1021        }
1022        let mut cursor = node.walk();
1023        for child in node.children(&mut cursor) {
1024            // An anonymous namespace child flips the flag for the
1025            // subtree; inner namespaces inherit privacy.
1026            let entering_anonymous = inside_anonymous_ns
1027                || (child.kind() == "namespace_definition" && !namespace_is_named(&child));
1028            stack.push((child, entering_anonymous));
1029        }
1030    }
1031}
1032
1033/// True when a `namespace_definition` has any identifier — a missing
1034/// name means the namespace is anonymous (TU-local).
1035fn namespace_is_named(node: &Node<'_>) -> bool {
1036    if node.child_by_field_name("name").is_some() {
1037        return true;
1038    }
1039    let mut cursor = node.walk();
1040    let has_identifier = node
1041        .children(&mut cursor)
1042        .any(|child| child.kind() == "namespace_identifier" || child.kind() == "identifier");
1043    has_identifier
1044}
1045
1046/// True when `node` (a `function_definition`) carries a `static`
1047/// storage-class specifier as a direct child.
1048fn function_has_static_specifier(node: &Node<'_>, src: &[u8]) -> bool {
1049    let mut cursor = node.walk();
1050    for child in node.children(&mut cursor) {
1051        if child.kind() == "storage_class_specifier" && node_text(&child, src) == "static" {
1052            return true;
1053        }
1054    }
1055    false
1056}
1057
1058/// Resolve the bare function name from a `function_definition`'s
1059/// declarator chain. Falls through pointer / reference declarators.
1060fn function_name(node: &Node<'_>, src: &[u8]) -> Option<String> {
1061    let declarator = node.child_by_field_name("declarator")?;
1062    extract_function_identifier(&declarator, src)
1063}
1064
1065/// Recursively unwrap a declarator subtree until a leaf identifier
1066/// surfaces. Includes destructor / operator names so e.g. `~Foo` or
1067/// `operator==` still produce a name.
1068fn extract_function_identifier(node: &Node<'_>, src: &[u8]) -> Option<String> {
1069    if matches!(
1070        node.kind(),
1071        "identifier" | "field_identifier" | "destructor_name" | "operator_name"
1072    ) {
1073        return Some(node_text(node, src).to_string());
1074    }
1075    let mut cursor = node.walk();
1076    for child in node.children(&mut cursor) {
1077        if let Some(found) = extract_function_identifier(&child, src) {
1078            return Some(found);
1079        }
1080    }
1081    None
1082}
1083
1084/// True for decl kinds that may carry a base list — only those need
1085/// `bases` populated.
1086fn is_class_like(kind: DeclKind) -> bool {
1087    matches!(
1088        kind,
1089        DeclKind::Class | DeclKind::Interface | DeclKind::Trait | DeclKind::Struct | DeclKind::Enum
1090    )
1091}
1092
1093/// Walk C++ class / struct specifiers and collect bare base type
1094/// names. Grammar shape (verified):
1095///
1096///   `class Echo : public Base, private Other { … };` →
1097///     (class_specifier name: (type_identifier)
1098///        (base_class_clause (access_specifier) (type_identifier)
1099///                           (access_specifier) (type_identifier))
1100///        body: (field_declaration_list))
1101///
1102/// Within `base_class_clause`, parents are listed as
1103/// `type_identifier` / `qualified_identifier` / `template_type`
1104/// nodes (alternating with `access_specifier` keywords). Generic /
1105/// qualified bases collapse to the bare tail.
1106fn collect_cpp_class_bases(
1107    tree: &Tree,
1108    file: FileId,
1109    src: &[u8],
1110) -> Vec<(bonsai_common::Span, String, Vec<String>)> {
1111    let mut bases_by_class = Vec::new();
1112    let class_kinds = &["class_specifier", "struct_specifier", "union_specifier"];
1113    for class_node in collect_kinds(tree, class_kinds) {
1114        let Some(name_node) = class_node
1115            .child_by_field_name("name")
1116            .or_else(|| first_named_child_of_kind(&class_node, "type_identifier"))
1117            .or_else(|| first_named_child_of_kind(&class_node, "identifier"))
1118        else {
1119            continue;
1120        };
1121        let class_name = node_text(&name_node, src).trim();
1122        if class_name.is_empty() {
1123            continue;
1124        }
1125        let mut bases: Vec<String> = Vec::new();
1126        let mut class_cursor = class_node.walk();
1127        for class_child in class_node.named_children(&mut class_cursor) {
1128            // Bases live exclusively under the `base_class_clause`
1129            // child; everything else (the body, attributes, etc.) is
1130            // skipped.
1131            if class_child.kind() != "base_class_clause" {
1132                continue;
1133            }
1134            let mut clause_cursor = class_child.walk();
1135            for clause_child in class_child.named_children(&mut clause_cursor) {
1136                match clause_child.kind() {
1137                    "type_identifier"
1138                    | "qualified_identifier"
1139                    | "template_type"
1140                    | "scoped_type_identifier" => {
1141                        if let Some(name) = canonical_cpp_base_name(node_text(&clause_child, src)) {
1142                            // Dedup so `class C : public Base, public Base` collapses.
1143                            if !bases.iter().any(|existing| existing == &name) {
1144                                bases.push(name);
1145                            }
1146                        }
1147                    }
1148                    _ => {}
1149                }
1150            }
1151        }
1152        if !bases.is_empty() {
1153            bases_by_class.push((span_of(file, &class_node), class_name.to_string(), bases));
1154        }
1155    }
1156    bases_by_class
1157}
1158
1159/// WS2 cast typing for `auto`-LHS locals: `auto c = static_cast<Foo>(x)` and
1160/// `auto c = (Foo) x`. The kit's param-alias extractor already types the
1161/// declared-type form `Foo c = make()`, but NOT the inferred-`auto` form where
1162/// the class lives only on the cast initializer. Mirrors the Java/C#
1163/// `var c = (Foo) x` handling. Returns `(enclosing-fn span, binding)` pairs;
1164/// the fn span matches the function decl's `span` so the caller merges into
1165/// `decl.type_aliases`. Only fires when the declared type IS `auto`
1166/// (`placeholder_type_specifier`) — never clobbers a real declared type — and
1167/// reads the init_declarator's DIRECT `value` so a cast nested in a call
1168/// argument cannot mistype the local.
1169fn collect_cpp_cast_aliases(
1170    tree: &Tree,
1171    file: FileId,
1172    src: &[u8],
1173) -> Vec<(bonsai_common::Span, TypeAliasBinding)> {
1174    let mut out = Vec::new();
1175    for decl_node in collect_kinds(tree, &["declaration"]) {
1176        let Some(type_node) = decl_node.child_by_field_name("type") else {
1177            continue;
1178        };
1179        if type_node.kind() != "placeholder_type_specifier" {
1180            continue;
1181        }
1182        let Some(init) = first_named_child_of_kind(&decl_node, "init_declarator") else {
1183            continue;
1184        };
1185        let Some(decl_field) = init.child_by_field_name("declarator") else {
1186            continue;
1187        };
1188        let name_node = if decl_field.kind() == "identifier" {
1189            decl_field
1190        } else {
1191            match cpp_first_descendant_of_kind(&decl_field, "identifier") {
1192                Some(n) => n,
1193                None => continue,
1194            }
1195        };
1196        let name = node_text(&name_node, src).trim().to_string();
1197        if name.is_empty() {
1198            continue;
1199        }
1200        let Some(value) = init.child_by_field_name("value") else {
1201            continue;
1202        };
1203        let Some(type_name) = cpp_cast_type_of_value(&value, src) else {
1204            continue;
1205        };
1206        let Some(fn_span) = cpp_enclosing_fn_span(&decl_node, file) else {
1207            continue;
1208        };
1209        out.push((fn_span, TypeAliasBinding { name, type_name }));
1210    }
1211    out
1212}
1213
1214/// Cast target type of a direct initializer value, or `None` for any non-cast
1215/// shape. Handles C-style `(Foo) x` (`cast_expression`) and the `*_cast<Foo>(x)`
1216/// family (a `call_expression` whose `function` is a `template_function` named
1217/// `static_cast` / `reinterpret_cast` / `dynamic_cast` / `const_cast`).
1218fn cpp_cast_type_of_value(value: &Node<'_>, src: &[u8]) -> Option<String> {
1219    match value.kind() {
1220        "cast_expression" => {
1221            let type_node = value.child_by_field_name("type")?;
1222            cpp_type_descriptor_name(&type_node, src)
1223        }
1224        "call_expression" => {
1225            let func = value.child_by_field_name("function")?;
1226            if func.kind() != "template_function" {
1227                return None;
1228            }
1229            let name = func.child_by_field_name("name")?;
1230            if !matches!(
1231                node_text(&name, src).trim(),
1232                "static_cast" | "reinterpret_cast" | "dynamic_cast" | "const_cast"
1233            ) {
1234                return None;
1235            }
1236            let args = func.child_by_field_name("arguments")?;
1237            cpp_type_descriptor_name(&args, src)
1238        }
1239        _ => None,
1240    }
1241}
1242
1243/// Bare tail name of the first `type_identifier` under a `type_descriptor` /
1244/// `template_argument_list` node (`ns::Foo<T>*` → `Foo`).
1245fn cpp_type_descriptor_name(node: &Node<'_>, src: &[u8]) -> Option<String> {
1246    let ti = if node.kind() == "type_identifier" {
1247        *node
1248    } else {
1249        cpp_first_descendant_of_kind(node, "type_identifier")?
1250    };
1251    canonical_cpp_base_name(node_text(&ti, src))
1252}
1253
1254/// First descendant found by an iterative syntax-tree walk, or `None`.
1255fn cpp_first_descendant_of_kind<'a>(node: &Node<'a>, kind: &str) -> Option<Node<'a>> {
1256    let mut stack = vec![*node];
1257    while let Some(n) = stack.pop() {
1258        let mut cursor = n.walk();
1259        for child in n.named_children(&mut cursor) {
1260            if child.kind() == kind {
1261                return Some(child);
1262            }
1263            stack.push(child);
1264        }
1265    }
1266    None
1267}
1268
1269/// Span of the nearest enclosing `function_definition` (matches the function
1270/// decl's `span` so cast aliases merge into the right method's type_aliases).
1271fn cpp_enclosing_fn_span(node: &Node<'_>, file: FileId) -> Option<bonsai_common::Span> {
1272    let mut cur = node.parent();
1273    while let Some(n) = cur {
1274        if n.kind() == "function_definition" {
1275            return Some(span_of(file, &n));
1276        }
1277        cur = n.parent();
1278    }
1279    None
1280}
1281
1282/// Reduce a base-class type expression to its bare tail identifier:
1283/// strip template arguments and namespace qualifiers so
1284/// `ns::Base<T>` → `Base`.
1285fn canonical_cpp_base_name(raw: &str) -> Option<String> {
1286    let trimmed = raw.trim();
1287    let without_template = trimmed.split('<').next().unwrap_or(trimmed).trim();
1288    let bare = without_template
1289        .rsplit("::")
1290        .next()
1291        .unwrap_or(without_template)
1292        .trim();
1293    if bare.is_empty() {
1294        return None;
1295    }
1296    Some(bare.to_string())
1297}
1298
1299/// Translate `#include` directives and `using` declarations into
1300/// `ImportSpec`s. The two flavours produce indistinguishable
1301/// downstream lookups; `using namespace` is recorded as a wildcard import.
1302fn parse_imports(tree: &Tree, src: &[u8], file: FileId) -> Vec<ImportSpec> {
1303    let mut imports = c_family_preproc_imports(tree, src, file);
1304    // C-style preproc_include + C++ `using namespace X;` / `using X::Y;`.
1305    for using_node in collect_kinds(tree, &["using_declaration"]) {
1306        // The path is the declaration's single named child. The anonymous
1307        // `namespace` token distinguishes the wildcard form, so semantic
1308        // classification never depends on re-tokenizing statement text.
1309        //
1310        //   * `using namespace X::Y;` — wildcard import; brings every
1311        //     name in `X::Y` into scope, no single local binding.
1312        //   * `using X::Y::Z;`        — single-symbol import; binds
1313        //     `Z` locally to `X::Y::Z`.
1314        let is_wildcard_namespace = (0..using_node.child_count())
1315            .filter_map(|index| u32::try_from(index).ok())
1316            .any(|index| {
1317                using_node
1318                    .child(index)
1319                    .is_some_and(|child| child.kind() == "namespace")
1320            });
1321        let mut path_cursor = using_node.walk();
1322        let Some(path_node) = using_node
1323            .named_children(&mut path_cursor)
1324            .find(|child| matches!(child.kind(), "identifier" | "qualified_identifier"))
1325        else {
1326            continue;
1327        };
1328        let mut path_segments = cpp_import_path_segments(path_node, src);
1329        if path_segments.is_empty() {
1330            continue;
1331        }
1332        if is_wildcard_namespace {
1333            imports.push(ImportSpec {
1334                span: span_of(file, &using_node),
1335                module: path_segments.join("::"),
1336                alias: None,
1337                is_wildcard: true,
1338                original_name: None,
1339                scope: ImportScope::Module,
1340            });
1341        } else if let Some(original_name) = path_segments.pop() {
1342            imports.push(ImportSpec {
1343                span: span_of(file, &using_node),
1344                module: path_segments.join("::"),
1345                alias: None,
1346                is_wildcard: false,
1347                original_name: Some(original_name),
1348                scope: ImportScope::Module,
1349            });
1350        }
1351    }
1352    // C++ `namespace h = util;` — explicit namespace alias. The
1353    // `name` field is the local alias (`h`); the `aliased` /
1354    // `value` field is the original namespace identifier (`util`).
1355    // Bind `h` as a `Namespace` target so `h::helper(...)` resolves
1356    // to `util::helper(...)`.
1357    for alias_node in collect_kinds(tree, &["namespace_alias_definition"]) {
1358        let alias_name_node = alias_node.child_by_field_name("name").or_else(|| {
1359            let mut cursor = alias_node.walk();
1360            let mut found = None;
1361            for child in alias_node.named_children(&mut cursor) {
1362                if matches!(child.kind(), "identifier" | "namespace_identifier") {
1363                    found = Some(child);
1364                    break;
1365                }
1366            }
1367            found
1368        });
1369        let module_name_node = alias_node
1370            .child_by_field_name("aliased")
1371            .or_else(|| alias_node.child_by_field_name("value"))
1372            .or_else(|| {
1373                let alias_name_node = alias_name_node?;
1374                let mut cursor = alias_node.walk();
1375                let target = alias_node.named_children(&mut cursor).find(|child| {
1376                    *child != alias_name_node
1377                        && matches!(
1378                            child.kind(),
1379                            "namespace_identifier" | "nested_namespace_specifier"
1380                        )
1381                });
1382                target
1383            });
1384        let (Some(alias_name_node), Some(module_name_node)) = (alias_name_node, module_name_node) else {
1385            continue;
1386        };
1387        let alias_name = node_text(&alias_name_node, src).trim().to_string();
1388        let module = node_text(&module_name_node, src).trim().to_string();
1389        if alias_name.is_empty() || module.is_empty() || alias_name == module {
1390            continue;
1391        }
1392        imports.push(ImportSpec {
1393            span: span_of(file, &alias_node),
1394            module,
1395            alias: Some(alias_name),
1396            is_wildcard: false,
1397            original_name: None,
1398            scope: ImportScope::Module,
1399        });
1400    }
1401    imports
1402}
1403
1404fn cpp_import_path_segments(path: Node<'_>, src: &[u8]) -> Vec<String> {
1405    if path.kind() == "qualified_identifier" {
1406        let mut segments = path
1407            .child_by_field_name("scope")
1408            .map(|scope| cpp_import_path_segments(scope, src))
1409            .unwrap_or_default();
1410        if let Some(name) = path.child_by_field_name("name") {
1411            segments.extend(cpp_import_path_segments(name, src));
1412        }
1413        return segments;
1414    }
1415    if path.kind() == "nested_namespace_specifier" {
1416        let mut segments = Vec::new();
1417        let mut cursor = path.walk();
1418        for child in path.named_children(&mut cursor) {
1419            segments.extend(cpp_import_path_segments(child, src));
1420        }
1421        return segments;
1422    }
1423    let segment = node_text(&path, src).trim();
1424    if segment.is_empty() {
1425        Vec::new()
1426    } else {
1427        vec![segment.to_string()]
1428    }
1429}
1430
1431/// Repair `catch_param` on C++ `Try` events. The kit's generic
1432/// extractor returns the first identifier descendant of the catch
1433/// clause, which on `catch (const std::exception& e)` is the type
1434/// identifier rather than the binding. We re-extract the binding
1435/// from the parse tree via the standard `parameter_declaration` →
1436/// `declarator` → identifier chain.
1437fn fix_cpp_catch_params(events: &mut [bonsai_lang_api::FlowEvent], tree: &Tree, src: &[u8]) {
1438    use bonsai_lang_api::FlowEvent;
1439    for event in events {
1440        match event {
1441            FlowEvent::Try {
1442                span,
1443                body,
1444                catch_events,
1445                finally_events,
1446                catch_param,
1447                ..
1448            } => {
1449                if let Some(node) =
1450                    bonsai_lang_api::kit::node_at_span(tree.root_node(), *span, &["try_statement"])
1451                {
1452                    if let Some(name) = cpp_catch_param_binding(node, src) {
1453                        *catch_param = Some(name);
1454                    }
1455                }
1456                fix_cpp_catch_params(body, tree, src);
1457                fix_cpp_catch_params(catch_events, tree, src);
1458                fix_cpp_catch_params(finally_events, tree, src);
1459            }
1460            FlowEvent::Branch {
1461                then_events,
1462                else_events,
1463                ..
1464            } => {
1465                fix_cpp_catch_params(then_events, tree, src);
1466                fix_cpp_catch_params(else_events, tree, src);
1467            }
1468            FlowEvent::Loop { body, .. } | FlowEvent::Defer { body, .. } | FlowEvent::Using { body, .. } => {
1469                fix_cpp_catch_params(body, tree, src);
1470            }
1471            _ => {}
1472        }
1473    }
1474}
1475
1476fn cpp_catch_param_binding(try_node: Node<'_>, src: &[u8]) -> Option<String> {
1477    let mut tcur = try_node.walk();
1478    for child in try_node.named_children(&mut tcur) {
1479        if child.kind() != "catch_clause" {
1480            continue;
1481        }
1482        // catch_clause > parameter_list > parameter_declaration > declarator > identifier.
1483        let mut ccur = child.walk();
1484        for sub in child.named_children(&mut ccur) {
1485            let target = if sub.kind() == "parameter_list" {
1486                let mut pcur = sub.walk();
1487                let mut found: Option<Node<'_>> = None;
1488                for c in sub.named_children(&mut pcur) {
1489                    if c.kind() == "parameter_declaration" {
1490                        found = Some(c);
1491                        break;
1492                    }
1493                }
1494                found
1495            } else if sub.kind() == "parameter_declaration" {
1496                Some(sub)
1497            } else {
1498                None
1499            };
1500            let Some(pdecl) = target else { continue };
1501            // The `declarator` field of `parameter_declaration` is the
1502            // binding. For `const T& e`, the declarator is a
1503            // reference_declarator → identifier. For bare `T e`, the
1504            // declarator is an identifier.
1505            let decl = pdecl.child_by_field_name("declarator");
1506            if let Some(decl) = decl {
1507                if let Some(ident) = first_identifier_descendant_cpp(decl) {
1508                    return Some(node_text(&ident, src).trim().to_string());
1509                }
1510            }
1511            // Fallback: trailing identifier among the named children.
1512            let mut pcur = pdecl.walk();
1513            let mut last_ident: Option<Node<'_>> = None;
1514            for n in pdecl.named_children(&mut pcur) {
1515                if let Some(found) = first_identifier_descendant_cpp(n) {
1516                    last_ident = Some(found);
1517                }
1518            }
1519            if let Some(n) = last_ident {
1520                return Some(node_text(&n, src).trim().to_string());
1521            }
1522        }
1523    }
1524    None
1525}
1526
1527fn first_identifier_descendant_cpp<'a>(node: Node<'a>) -> Option<Node<'a>> {
1528    if node.kind() == "identifier" || node.kind() == "field_identifier" {
1529        return Some(node);
1530    }
1531    let mut cursor = node.walk();
1532    for child in node.named_children(&mut cursor) {
1533        if let Some(found) = first_identifier_descendant_cpp(child) {
1534            return Some(found);
1535        }
1536    }
1537    None
1538}
1539
1540#[cfg(test)]
1541mod import_tests {
1542    use super::*;
1543
1544    fn parse_import_specs(src: &str) -> Vec<ImportSpec> {
1545        let language = language_from_pack(PACK_NAME).expect("cpp grammar");
1546        let mut parser = tree_sitter::Parser::new();
1547        parser.set_language(&language).expect("set cpp grammar");
1548        let tree = parser.parse(src.as_bytes(), None).expect("parse cpp source");
1549        parse_imports(&tree, src.as_bytes(), FileId::new(0))
1550    }
1551
1552    #[test]
1553    fn using_declarations_are_lowered_from_cst_nodes() {
1554        let imports = parse_import_specs(
1555            "using /* trivia */ namespace alpha::beta;\n\
1556             using alpha::beta::Thing;\n\
1557             namespace short_name = alpha::beta;\n",
1558        );
1559
1560        assert!(imports
1561            .iter()
1562            .any(|spec| spec.module == "alpha::beta" && spec.is_wildcard));
1563        assert!(imports.iter().any(|spec| {
1564            spec.module == "alpha::beta"
1565                && spec.alias.is_none()
1566                && spec.original_name.as_deref() == Some("Thing")
1567        }));
1568        assert!(imports.iter().any(|spec| {
1569            spec.module == "alpha::beta"
1570                && spec.alias.as_deref() == Some("short_name")
1571                && spec.original_name.is_none()
1572        }));
1573    }
1574}