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
5 changes: 5 additions & 0 deletions internal/cbm/extract_defs.c
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,11 @@ static void extract_elixir_call(CBMExtractCtx *ctx, TSNode node, const CBMLangSp

// Get "name" field from a node
static TSNode func_name_node(TSNode node) {
const char *node_type = ts_node_type(node);
if (strcmp(node_type, "init_declaration") == 0 ||
strcmp(node_type, "deinit_declaration") == 0) {
return node;
}
TSNode name = ts_node_child_by_field_name(node, TS_FIELD("name"));
if (ts_node_is_null(name)) {
/* Protobuf rpc: name is in rpc_name child, not "name" field */
Expand Down
10 changes: 10 additions & 0 deletions internal/cbm/helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,16 @@ TSNode cbm_resolve_c_declarator_name_node(TSNode func_node) {
// const", and a member lookup for "operator bool" (the implicit call in
// `if (obj)`) misses.
char *cbm_func_name_node_text(CBMArena *a, TSNode name_node, const char *source, CBMLanguage lang) {
if (lang == CBM_LANG_SWIFT) {
const char *kind = ts_node_type(name_node);
if (strcmp(kind, "init_declaration") == 0) {
return cbm_arena_strdup(a, "init");
}
if (strcmp(kind, "deinit_declaration") == 0) {
return cbm_arena_strdup(a, "deinit");
}
}

char *text = cbm_node_text(a, name_node, source);
if (text && strcmp(ts_node_type(name_node), "operator_cast") == 0) {
char *paren = strchr(text, '(');
Expand Down
3 changes: 2 additions & 1 deletion internal/cbm/lang_specs.c
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,8 @@ static const char *objc_assign_types[] = {"assignment_expression", NULL};
// well as in the name resolvers — extract_class_methods gates on this set, so
// without the entry the requirement is walked and then discarded.
static const char *swift_func_types[] = {"function_declaration", "protocol_function_declaration",
"macro_declaration", NULL};
"macro_declaration", "init_declaration",
"deinit_declaration", NULL};
// KNOWN GAP: struct_declaration and enum_declaration are not node types in the
// vendored tree-sitter-swift grammar — it models both as class_declaration — so
// these two entries never match anything, and a bare Swift `enum` is labeled
Expand Down
283 changes: 273 additions & 10 deletions tests/test_extraction.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,30 @@ static int has_def_qn(CBMFileResult *r, const char *qn) {
return 0;
}

/* Return the definition with the given qualified name, or NULL. Distinct from
* find_def_by_name for the same reason has_def_qn is distinct from has_def. */
/* Find a definition by its qualified name relative to the file's module QN
* (e.g. "Box.init" resolves to "<module_qn>.Box.init"). */
static const CBMDefinition *find_def_in_module(CBMFileResult *r, const char *rel_qn) {
char qn[512];
snprintf(qn, sizeof(qn), "%s.%s", r->module_qn, rel_qn);
for (int i = 0; i < r->defs.count; i++) {
if (r->defs.items[i].qualified_name && strcmp(r->defs.items[i].qualified_name, qn) == 0)
return &r->defs.items[i];
}
return NULL;
}

/* Find an in-body call by its raw callee text; returns the call or NULL. */
static const CBMCall *find_call_by_callee(CBMFileResult *r, const char *callee) {
for (int i = 0; i < r->calls.count; i++) {
if (r->calls.items[i].callee_name && strcmp(r->calls.items[i].callee_name, callee) == 0) {
return &r->calls.items[i];
}
}
return NULL;
}

static int count_defs_with_label(CBMFileResult *r, const char *label) {
int count = 0;
for (int i = 0; i < r->defs.count; i++) {
Expand Down Expand Up @@ -2004,6 +2028,247 @@ TEST(swift_force_unwrap_scanner_shift) {
PASS();
}

/* #2379: a struct's plain `init` is a callable, not a name-less hole -- a call
* inside it must source from the initializer, not fall back to the file's
* Module scope. */
TEST(swift_init_struct_issue2379) {
CBMFileResult *r = extract("func helper() -> Int { 1 }\n"
"struct Box {\n"
" let value: Int\n"
" init() {\n"
" value = helper()\n"
" }\n"
"}\n",
CBM_LANG_SWIFT, "t", "Box.swift");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->has_error);

const CBMDefinition *init_def = find_def_in_module(r, "Box.init");
ASSERT_NOT_NULL(init_def);
ASSERT_STR_EQ(init_def->label, "Method");
ASSERT_STR_EQ(init_def->name, "init");

const CBMCall *call = find_call_by_callee(r, "helper");
ASSERT_NOT_NULL(call);
ASSERT_NOT_NULL(call->enclosing_func_qn);
ASSERT_STR_EQ(call->enclosing_func_qn, init_def->qualified_name);
ASSERT_STR_NEQ(call->enclosing_func_qn, r->module_qn);

cbm_free_result(r);
PASS();
}

/* #2379: a class's designated init takes parameters -- coverage that a
* parameterized init is extracted the same way as a bare one. */
TEST(swift_init_class_designated_issue2379) {
CBMFileResult *r = extract("func helper() -> Int { 1 }\n"
"final class Holder {\n"
" let value: Int\n"
" init(seed: Int) {\n"
" value = helper() + seed\n"
" }\n"
"}\n",
CBM_LANG_SWIFT, "t", "Holder.swift");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->has_error);

const CBMDefinition *init_def = find_def_in_module(r, "Holder.init");
ASSERT_NOT_NULL(init_def);
ASSERT_STR_EQ(init_def->label, "Method");

const CBMCall *call = find_call_by_callee(r, "helper");
ASSERT_NOT_NULL(call);
ASSERT_NOT_NULL(call->enclosing_func_qn);
ASSERT_STR_EQ(call->enclosing_func_qn, init_def->qualified_name);
ASSERT_STR_NEQ(call->enclosing_func_qn, r->module_qn);

cbm_free_result(r);
PASS();
}

/* #2379: a convenience init delegates via `self.init(...)`. Its call to
* `other()` (an argument of the delegating call) must source from the
* initializer, not from the file. */
TEST(swift_init_convenience_delegates_issue2379) {
CBMFileResult *r = extract("func other() -> Int { 2 }\n"
"final class Holder {\n"
" let value: Int\n"
" init(seed: Int) {\n"
" value = seed\n"
" }\n"
" convenience init() {\n"
" self.init(seed: other())\n"
" }\n"
"}\n",
CBM_LANG_SWIFT, "t", "Holder.swift");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->has_error);

const CBMDefinition *init_def = find_def_in_module(r, "Holder.init");
ASSERT_NOT_NULL(init_def);

/* self.init(...) is a plain member call today, same as any other
* `receiver.member(...)` invocation -- no dedicated delegation handling. */
ASSERT(has_call(r, "self.init"));

const CBMCall *call = find_call_by_callee(r, "other");
ASSERT_NOT_NULL(call);
ASSERT_NOT_NULL(call->enclosing_func_qn);
ASSERT_STR_EQ(call->enclosing_func_qn, init_def->qualified_name);
ASSERT_STR_NEQ(call->enclosing_func_qn, r->module_qn);

cbm_free_result(r);
PASS();
}

/* #2379: two initializers in the same type calling the SAME callee must both
* attribute to the type's init, not to the file's Module scope. Both share
* the QN `Holder.init` -- codebase-memory-mcp already collapses overloaded
* Swift methods onto one node (DeusData/codebase-memory-mcp#2061), and an
* init is no exception; distinguishing overloads by signature is that
* issue's concern, not this one's. */
TEST(swift_init_multiple_not_collapsed_issue2379) {
CBMFileResult *r = extract("func shared() -> Int { 3 }\n"
"final class Holder {\n"
" let value: Int\n"
" init(seed: Int) {\n"
" value = shared()\n"
" }\n"
" convenience init() {\n"
" self.init(seed: shared())\n"
" }\n"
"}\n",
CBM_LANG_SWIFT, "t", "Holder.swift");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->has_error);

const CBMDefinition *init_def = find_def_in_module(r, "Holder.init");
ASSERT_NOT_NULL(init_def);

int from_init = 0;
for (int i = 0; i < r->calls.count; i++) {
const CBMCall *c = &r->calls.items[i];
if (!c->callee_name || strcmp(c->callee_name, "shared") != 0 || !c->enclosing_func_qn) {
continue;
}
ASSERT_STR_EQ(c->enclosing_func_qn, init_def->qualified_name);
ASSERT_STR_NEQ(c->enclosing_func_qn, r->module_qn);
from_init++;
}
ASSERT_EQ(from_init, 2);

cbm_free_result(r);
PASS();
}

/* #2379: a failable `init?` in an enum is still a callable initializer. */
TEST(swift_init_failable_enum_issue2379) {
CBMFileResult *r = extract("func helper() -> Int { 1 }\n"
"enum Mode {\n"
" case on\n"
" init?(flag: Bool) {\n"
" if helper() > 0 { self = .on } else { return nil }\n"
" }\n"
"}\n",
CBM_LANG_SWIFT, "t", "Mode.swift");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->has_error);

const CBMDefinition *init_def = find_def_in_module(r, "Mode.init");
ASSERT_NOT_NULL(init_def);

const CBMCall *call = find_call_by_callee(r, "helper");
ASSERT_NOT_NULL(call);
ASSERT_NOT_NULL(call->enclosing_func_qn);
ASSERT_STR_EQ(call->enclosing_func_qn, init_def->qualified_name);
ASSERT_STR_NEQ(call->enclosing_func_qn, r->module_qn);

cbm_free_result(r);
PASS();
}

/* #2379: `extension X { }` is modeled by the vendored grammar as a
* class_declaration named X, so an init declared in an extension goes through
* the same class-method path as one declared in the primary type. */
TEST(swift_init_extension_issue2379) {
CBMFileResult *r = extract("func other() -> Int { 2 }\n"
"extension Box {\n"
" init(raw: Int) {\n"
" value = other()\n"
" }\n"
"}\n",
CBM_LANG_SWIFT, "t", "Box+Raw.swift");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->has_error);

const CBMDefinition *init_def = find_def_in_module(r, "Box.init");
ASSERT_NOT_NULL(init_def);

const CBMCall *call = find_call_by_callee(r, "other");
ASSERT_NOT_NULL(call);
ASSERT_NOT_NULL(call->enclosing_func_qn);
ASSERT_STR_EQ(call->enclosing_func_qn, init_def->qualified_name);
ASSERT_STR_NEQ(call->enclosing_func_qn, r->module_qn);

cbm_free_result(r);
PASS();
}

/* #2379: regression guard -- an ordinary method in the same type keeps its own
* attribution unchanged (this already worked pre-fix; the initializer fix
* must not disturb it). */
TEST(swift_method_still_attributed_issue2379) {
CBMFileResult *r = extract("func other() -> Int { 2 }\n"
"struct Box {\n"
" init() {}\n"
" func read() -> Int {\n"
" other()\n"
" }\n"
"}\n",
CBM_LANG_SWIFT, "t", "Box.swift");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->has_error);

const CBMDefinition *read_def = find_def_in_module(r, "Box.read");
ASSERT_NOT_NULL(read_def);

const CBMCall *call = find_call_by_callee(r, "other");
ASSERT_NOT_NULL(call);
ASSERT_NOT_NULL(call->enclosing_func_qn);
ASSERT_STR_EQ(call->enclosing_func_qn, read_def->qualified_name);

cbm_free_result(r);
PASS();
}

/* #2379: a `deinit` has the same gap as `init` -- no source-level name, so a
* call inside its body attributed to the file's Module scope instead of the
* type. */
TEST(swift_deinit_issue2379) {
CBMFileResult *r = extract("func helper() -> Int { 1 }\n"
"final class Deinitable {\n"
" deinit {\n"
" _ = helper()\n"
" }\n"
"}\n",
CBM_LANG_SWIFT, "t", "Deinitable.swift");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->has_error);

const CBMDefinition *deinit_def = find_def_in_module(r, "Deinitable.deinit");
ASSERT_NOT_NULL(deinit_def);
ASSERT_STR_EQ(deinit_def->name, "deinit");

const CBMCall *call = find_call_by_callee(r, "helper");
ASSERT_NOT_NULL(call);
ASSERT_NOT_NULL(call->enclosing_func_qn);
ASSERT_STR_EQ(call->enclosing_func_qn, deinit_def->qualified_name);
ASSERT_STR_NEQ(call->enclosing_func_qn, r->module_qn);

cbm_free_result(r);
PASS();
}

/* --- Objective-C --- */
TEST(objc_interface) {
CBMFileResult *r =
Expand Down Expand Up @@ -4528,16 +4793,6 @@ TEST(extract_ts_decorators_survive_interleaved_comment) {
PASS();
}

/* Find an in-body call by its raw callee text; returns the call or NULL. */
static const CBMCall *find_call_by_callee(CBMFileResult *r, const char *callee) {
for (int i = 0; i < r->calls.count; i++) {
if (r->calls.items[i].callee_name && strcmp(r->calls.items[i].callee_name, callee) == 0) {
return &r->calls.items[i];
}
}
return NULL;
}

/* #1892: the Swift grammar declares no "arguments" field, so the generic field
* lookup read nothing and every Swift call lost its arguments. Without the URL
* the service-pattern table cannot raise an HTTP_CALLS edge or a Route node,
Expand Down Expand Up @@ -8442,6 +8697,14 @@ SUITE(extraction) {
RUN_TEST(swift_constructor_call);
RUN_TEST(swift_chained_call);
RUN_TEST(swift_force_unwrap_scanner_shift);
RUN_TEST(swift_init_struct_issue2379);
RUN_TEST(swift_init_class_designated_issue2379);
RUN_TEST(swift_init_convenience_delegates_issue2379);
RUN_TEST(swift_init_multiple_not_collapsed_issue2379);
RUN_TEST(swift_init_failable_enum_issue2379);
RUN_TEST(swift_init_extension_issue2379);
RUN_TEST(swift_method_still_attributed_issue2379);
RUN_TEST(swift_deinit_issue2379);
RUN_TEST(swift_call_string_arg_issue1892);
RUN_TEST(swift_nested_url_constructor_issue1892);
RUN_TEST(swift_nested_url_no_bang_issue1892);
Expand Down
Loading