Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions internal/cbm/extract_unified.c
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,44 @@ static const char *compute_wolfram_func_qn(CBMExtractCtx *ctx, TSNode node) {
return NULL;
}

/* C/C++/CUDA/GLSL: function_definition has no `name` field — the name is nested
* in the declarator chain. Descend the `declarator` field to the innermost name
* node. Without this, the enclosing-function scope for calls made inside a C
* function resolves to NULL and the call is attributed to the module rather than
* the function (issue #438). Mirrors resolve_c_declarator_name() in extract_defs.c. */
#ifndef CBM_DECLARATOR_DEPTH_LIMIT
#define CBM_DECLARATOR_DEPTH_LIMIT 8 /* matches DECLARATOR_DEPTH_LIMIT in extract_defs.c */
#endif
static TSNode resolve_c_declarator_name_node(TSNode node) {
TSNode decl = ts_node_child_by_field_name(node, TS_FIELD("declarator"));
for (int depth = 0; depth < CBM_DECLARATOR_DEPTH_LIMIT && !ts_node_is_null(decl); depth++) {
const char *dk = ts_node_type(decl);
if (strcmp(dk, "identifier") == 0 || strcmp(dk, "field_identifier") == 0 ||
strcmp(dk, "type_identifier") == 0 || strcmp(dk, "destructor_name") == 0 ||
strcmp(dk, "operator_name") == 0 || strcmp(dk, "operator_cast") == 0) {
return decl;
}
if (strcmp(dk, "qualified_identifier") == 0 || strcmp(dk, "scoped_identifier") == 0) {
TSNode nm = ts_node_child_by_field_name(decl, TS_FIELD("name"));
if (!ts_node_is_null(nm)) {
decl = nm;
continue;
}
return decl;
}
TSNode inner = ts_node_child_by_field_name(decl, TS_FIELD("declarator"));
if (ts_node_is_null(inner) && ts_node_named_child_count(decl) > 0) {
inner = ts_node_named_child(decl, 0);
}
if (ts_node_is_null(inner)) {
break;
}
decl = inner;
}
TSNode null_node = {0};
return null_node;
}

// Resolve the name node for a function, handling arrow functions.
static TSNode resolve_func_name_node(TSNode node) {
TSNode name_node = ts_node_child_by_field_name(node, TS_FIELD("name"));
Expand All @@ -101,6 +139,10 @@ static TSNode resolve_func_name_node(TSNode node) {
if (ts_node_is_null(name_node) && strcmp(ts_node_type(node), "function_declaration") == 0) {
name_node = cbm_find_child_by_kind(node, "simple_identifier");
}
/* C/C++/CUDA/GLSL: function_definition name lives in the declarator chain. */
if (ts_node_is_null(name_node) && strcmp(ts_node_type(node), "function_definition") == 0) {
name_node = resolve_c_declarator_name_node(node);
}
return name_node;
}

Expand Down
47 changes: 47 additions & 0 deletions internal/cbm/helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,46 @@ TSNode cbm_find_enclosing_func(TSNode node, CBMLanguage lang) {
}

// Get the name of a function node (basic: try "name" field)
// C/C++/CUDA/GLSL: function_definition has no "name" field — the function name is
// nested in the declarator chain (pointer/function/parenthesized/array
// declarators wrap it). Descend the `declarator` field to the innermost name
// node. Without this, calls made inside C functions are attributed to the module
// rather than the enclosing function (issue #438). Mirrors resolve_c_declarator_name()
// in extract_defs.c.
#ifndef CBM_DECLARATOR_DEPTH_LIMIT
#define CBM_DECLARATOR_DEPTH_LIMIT 8 /* matches DECLARATOR_DEPTH_LIMIT in extract_defs.c */
#endif
static TSNode c_declarator_name_node(TSNode func_node) {
TSNode decl = ts_node_child_by_field_name(func_node, TS_FIELD("declarator"));
for (int depth = 0; depth < CBM_DECLARATOR_DEPTH_LIMIT && !ts_node_is_null(decl); depth++) {
const char *dk = ts_node_type(decl);
if (strcmp(dk, "identifier") == 0 || strcmp(dk, "field_identifier") == 0 ||
strcmp(dk, "type_identifier") == 0 || strcmp(dk, "destructor_name") == 0 ||
strcmp(dk, "operator_name") == 0 || strcmp(dk, "operator_cast") == 0) {
return decl;
}
if (strcmp(dk, "qualified_identifier") == 0 || strcmp(dk, "scoped_identifier") == 0) {
// out-of-line method def (Foo::bar): take the rightmost name segment
TSNode nm = ts_node_child_by_field_name(decl, TS_FIELD("name"));
if (!ts_node_is_null(nm)) {
decl = nm;
continue;
}
return decl;
}
TSNode inner = ts_node_child_by_field_name(decl, TS_FIELD("declarator"));
if (ts_node_is_null(inner) && ts_node_named_child_count(decl) > 0) {
inner = ts_node_named_child(decl, 0);
}
if (ts_node_is_null(inner)) {
break;
}
decl = inner;
}
TSNode null_node = {0};
return null_node;
}

static const char *func_node_name(CBMArena *a, TSNode func_node, const char *source,
CBMLanguage lang) {
// Wolfram: set_delayed_top/set_top/set_delayed/set — LHS is apply(user_symbol("f"), ...)
Expand Down Expand Up @@ -752,6 +792,13 @@ static const char *func_node_name(CBMArena *a, TSNode func_node, const char *sou
}
}
}
// C/C++/CUDA/GLSL: function_definition carries its name in the declarator chain.
if (strcmp(ts_node_type(func_node), "function_definition") == 0) {
TSNode dn = c_declarator_name_node(func_node);
if (!ts_node_is_null(dn)) {
return cbm_node_text(a, dn, source);
}
}
return NULL;
}

Expand Down
26 changes: 26 additions & 0 deletions tests/test_extraction.c
Original file line number Diff line number Diff line change
Expand Up @@ -1787,6 +1787,31 @@ TEST(wolfram_caller_attribution) {
PASS();
}

/* Issue #438: a C function_definition has no `name` field — the name lives in the
* declarator chain. Calls inside a C function must be attributed to the enclosing
* function, not the module. Pre-fix, enclosing_func_qn fell back to the module QN. */
TEST(c_caller_attribution) {
CBMFileResult *r = extract("int helper(int x) { return x; }\n"
"int caller(void) { return helper(1); }\n",
CBM_LANG_C, "t", "main.c");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->has_error);
ASSERT_GT(r->calls.count, 0);
int saw_helper = 0;
for (int i = 0; i < r->calls.count; i++) {
if (strcmp(r->calls.items[i].callee_name, "helper") == 0) {
saw_helper = 1;
/* enclosing_func_qn must be the function, NOT empty and NOT the module QN. */
ASSERT_NOT_NULL(r->calls.items[i].enclosing_func_qn);
ASSERT_FALSE(strcmp(r->calls.items[i].enclosing_func_qn, "") == 0);
ASSERT_FALSE(strcmp(r->calls.items[i].enclosing_func_qn, "t.main") == 0);
}
}
ASSERT(saw_helper);
cbm_free_result(r);
PASS();
}

/* --- Wolfram parse (simple assignment) --- */
TEST(wolfram_parse) {
CBMFileResult *r = extract("x = 42;\ny = x + 1;\n", CBM_LANG_WOLFRAM, "t", "simple.wl");
Expand Down Expand Up @@ -2963,6 +2988,7 @@ SUITE(extraction) {
RUN_TEST(wolfram_function_extended);
RUN_TEST(wolfram_call);
RUN_TEST(wolfram_caller_attribution);
RUN_TEST(c_caller_attribution);
RUN_TEST(wolfram_parse);
RUN_TEST(wolfram_import);
RUN_TEST(wolfram_nested_def);
Expand Down
Loading