From e01e77fc0b7e5b3f1b02a5c6f0bec4f0b95dac77 Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Sun, 28 Jun 2026 07:36:06 +0200 Subject: [PATCH] fix(dart): attribute in-body calls to the enclosing function (signature/body siblings) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dart models a function as a function_signature plus a SIBLING function_body (both children of program/class_body), not a single node containing the body. So a scope pushed at the signature never covers the body, and the signature's name was also unresolved by the shared resolver — in-body calls sourced to the Module (enclosing-func drift). Two parts in the unified walk: - compute_func_qn resolves a dart function_signature/method_signature name from its identifier child (method_signature wraps a function_signature). - push_boundary_scopes pushes the SCOPE_FUNC at the function_body, computing the QN from the preceding signature sibling, so the body's children attribute to the function. grammar_scripting and enclosing_parity dart cases now green. Signed-off-by: Martin Vogel --- internal/cbm/extract_unified.c | 44 ++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/internal/cbm/extract_unified.c b/internal/cbm/extract_unified.c index 4937b401a..5ad6f460d 100644 --- a/internal/cbm/extract_unified.c +++ b/internal/cbm/extract_unified.c @@ -322,6 +322,32 @@ static const char *compute_func_qn(CBMExtractCtx *ctx, TSNode node, const CBMLan } } + /* Dart: function_signature / method_signature have no `name` field; the name + * is an `identifier` child (method_signature wraps a function_signature). The + * shared resolver doesn't cover them, so resolve here for call-scope so an + * in-body call sources to the function, not the Module. */ + if (ctx->language == CBM_LANG_DART && (strcmp(ts_node_type(node), "function_signature") == 0 || + strcmp(ts_node_type(node), "method_signature") == 0)) { + TSNode sig = node; + if (strcmp(ts_node_type(node), "method_signature") == 0) { + TSNode fs = cbm_find_child_by_kind(node, "function_signature"); + if (!ts_node_is_null(fs)) { + sig = fs; + } + } + TSNode id = cbm_find_child_by_kind(sig, "identifier"); + if (!ts_node_is_null(id)) { + char *nm = cbm_node_text(ctx->arena, id, ctx->source); + if (nm && nm[0]) { + if (state->enclosing_class_qn) { + return cbm_arena_sprintf(ctx->arena, "%s.%s", state->enclosing_class_qn, nm); + } + return cbm_fqn_compute_source_lang(ctx->arena, ctx->project, ctx->rel_path, nm, + ctx->language); + } + } + } + /* Resolve the function name via the single shared resolver (extract_defs) so * call-scope attribution agrees with definition extraction across all ~130 * grammars. The old private 4-case copy returned NULL for Fortran subroutine, @@ -1071,6 +1097,24 @@ static void push_boundary_scopes(CBMExtractCtx *ctx, TSNode node, const CBMLangS push_scope(state, SCOPE_CLASS, depth, tqn); } } + } else if (ctx->language == CBM_LANG_DART && strcmp(ts_node_type(node), "function_body") == 0) { + /* Dart models a function as `function_signature` + `function_body` SIBLINGS + * (the signature node does not contain the body). A scope pushed at the + * signature never covers the body, so in-body calls source to the Module. + * Push the function scope at the BODY using the preceding signature + * sibling's QN, so the body's children attribute to the function. */ + TSNode prev = ts_node_prev_sibling(node); + while (!ts_node_is_null(prev) && + strcmp(ts_node_type(prev), "function_signature") != 0 && + strcmp(ts_node_type(prev), "method_signature") != 0) { + prev = ts_node_prev_sibling(prev); + } + if (!ts_node_is_null(prev)) { + const char *fqn = compute_func_qn(ctx, prev, spec, state); + if (fqn) { + push_scope(state, SCOPE_FUNC, depth, fqn); + } + } } if (spec->call_node_types && cbm_kind_in_set(node, spec->call_node_types)) {