refactor(extract): dedup C declarator-name walker into one shared helper

Addresses #463 review: the declarator-chain name resolver was copied into
helpers.c, extract_unified.c, and extract_defs.c, and CBM_DECLARATOR_DEPTH_LIMIT
was #defined twice -- the same triplication drift that caused #438.

- Add cbm_resolve_c_declarator_name_node() to helpers.{c,h} as the single
  source of truth, carrying is_c_terminal_name/resolve_qualified_name with it.
- Route the defs, calls, and unified extractors through it.
- Hoist CBM_DECLARATOR_DEPTH_LIMIT into helpers.h; extract_defs.c's
  DECLARATOR_DEPTH_LIMIT now derives from it.

Canonicalizes on the original extract_defs.c logic (operator/destructor aware)
so defs behavior is unchanged and calls/unified now agree with it.

Test: full suite green except an unrelated ASan RSS-budget check; clang-format clean.

Signed-off-by: Kris Kersey <kris@kerseyfabrications.com>
This commit is contained in:
Kris Kersey committed 2026-06-24 12:42:38 -04:00
1 parent 13c3429651
commit adc8304c57
4 files changed
+45 -108

No files matched your search

+3 -47
View File
@@ -23,7 +23,8 @@
// Tree traversal limits.
enum {
TEMPLATE_DEPTH_LIMIT = 4,
DECLARATOR_DEPTH_LIMIT = 8,
DECLARATOR_DEPTH_LIMIT = CBM_DECLARATOR_DEPTH_LIMIT, // shared define in helpers.h
EXPORT_ANCESTOR_DEPTH = 4,
DECORATOR_SCAN_LIMIT = 3,
C_RETURN_WALK_DEPTH = 5,
@@ -457,27 +458,6 @@ static TSNode resolve_func_name_fp(TSNode node, CBMLanguage lang, const char *ki
return null_node;
}
// Check if a node type is a terminal C declarator name.
static bool is_c_terminal_name(const char *dk) {
return strcmp(dk, "identifier") == 0 || strcmp(dk, "field_identifier") == 0 ||
strcmp(dk, "operator_name") == 0 || strcmp(dk, "operator_cast") == 0 ||
strcmp(dk, "destructor_name") == 0;
}
// Resolve name from a C++ qualified_identifier/scoped_identifier.
static TSNode resolve_qualified_name(TSNode decl) {
static const char *name_kinds[] = {"operator_name", "operator_cast", "destructor_name",
"identifier", "field_identifier", NULL};
for (const char **k = name_kinds; *k; k++) {
TSNode found = cbm_find_child_by_kind(decl, *k);
if (!ts_node_is_null(found)) {
return found;
}
}
TSNode null_node = {0};
return null_node;
}
// C++/CUDA: out-of-line method definitions name the function with a qualified
// declarator (`Foo::bar`, or `ns::Foo::bar`). Return the immediate enclosing
// class name (the scope segment directly left of the function name, e.g. "Foo"),
@@ -528,30 +508,6 @@ static char *cpp_out_of_line_parent_class(CBMArena *a, TSNode node, const char *
return (text && text[0]) ? text : NULL;
}
// Resolve function name from C/C++/CUDA/GLSL declarator chain.
static TSNode resolve_c_declarator_name(TSNode node) {
TSNode decl = ts_node_child_by_field_name(node, TS_FIELD("declarator"));
for (int depth = 0; depth < DECLARATOR_DEPTH_LIMIT && !ts_node_is_null(decl); depth++) {
const char *dk = ts_node_type(decl);
if (is_c_terminal_name(dk)) {
return decl;
}
if (strcmp(dk, "qualified_identifier") == 0 || strcmp(dk, "scoped_identifier") == 0) {
return resolve_qualified_name(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;
}
// R: resolve function_definition name from parent binary_operator lhs.
static TSNode resolve_r_func_name(TSNode node) {
TSNode parent = ts_node_parent(node);
@@ -675,7 +631,7 @@ static TSNode resolve_func_name_c_family(TSNode *node_ptr, CBMLanguage lang, con
lang == CBM_LANG_GLSL || lang == CBM_LANG_HLSL || lang == CBM_LANG_ISPC ||
lang == CBM_LANG_SLANG) &&
strcmp(kind, "function_definition") == 0) {
return resolve_c_declarator_name(*node_ptr);
return cbm_resolve_c_declarator_name_node(*node_ptr);
}
TSNode null_node = {0};
return null_node;
+1 -39
View File
@@ -87,44 +87,6 @@ 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"));
@@ -141,7 +103,7 @@ static TSNode resolve_func_name_node(TSNode node) {
}
/* 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);
name_node = cbm_resolve_c_declarator_name_node(node);
}
return name_node;
}
+27 -22
View File
@@ -717,33 +717,38 @@ TSNode cbm_find_enclosing_func(TSNode node, CBMLanguage lang) {
return null_node;
}
// 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) {
// Check if a node type is a terminal C declarator name.
static bool is_c_terminal_name(const char *dk) {
return strcmp(dk, "identifier") == 0 || strcmp(dk, "field_identifier") == 0 ||
strcmp(dk, "operator_name") == 0 || strcmp(dk, "operator_cast") == 0 ||
strcmp(dk, "destructor_name") == 0;
}
// Resolve name from a C++ qualified_identifier/scoped_identifier.
static TSNode resolve_qualified_name(TSNode decl) {
static const char *name_kinds[] = {"operator_name", "operator_cast", "destructor_name",
"identifier", "field_identifier", NULL};
for (const char **k = name_kinds; *k; k++) {
TSNode found = cbm_find_child_by_kind(decl, *k);
if (!ts_node_is_null(found)) {
return found;
}
}
TSNode null_node = {0};
return null_node;
}
// Resolve function name from C/C++/CUDA/GLSL declarator chain. Shared canonical
// implementation — see the header for the full rationale (#438).
TSNode cbm_resolve_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) {
if (is_c_terminal_name(dk)) {
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;
return resolve_qualified_name(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) {
@@ -794,7 +799,7 @@ 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);
TSNode dn = cbm_resolve_c_declarator_name_node(func_node);
if (!ts_node_is_null(dn)) {
return cbm_node_text(a, dn, source);
}
+14
View File
@@ -36,6 +36,20 @@ const char *cbm_enclosing_func_qn(CBMArena *a, TSNode node, CBMLanguage lang, co
// Cached version: uses ctx->ef_cache to avoid repeated parent-chain walks.
const char *cbm_enclosing_func_qn_cached(CBMExtractCtx *ctx, TSNode node);
// Max declarator-chain descent depth for C/C++/CUDA/GLSL function-name
// resolution. Single source of truth — extract_defs.c's DECLARATOR_DEPTH_LIMIT
// is derived from this so the three extractors cannot drift.
#define CBM_DECLARATOR_DEPTH_LIMIT 8
// Resolve the function-name node for a C/C++/CUDA/GLSL `function_definition`.
// Such nodes have no `name` field — the name is nested in the declarator chain
// (pointer/function/parenthesized/array declarators wrap it; out-of-line method
// definitions name it with a qualified_identifier). Descends the `declarator`
// field to the innermost name node and returns it, or a null node if none is
// found. Shared by the defs, calls, and unified extractors so all three agree on
// enclosing-function attribution — drift between private copies caused #438.
TSNode cbm_resolve_c_declarator_name_node(TSNode func_node);
// Find a child node by kind string.
TSNode cbm_find_child_by_kind(TSNode parent, const char *kind);