diff --git a/src/pipeline/pass_definitions.c b/src/pipeline/pass_definitions.c index bb3954008..44678b76c 100644 --- a/src/pipeline/pass_definitions.c +++ b/src/pipeline/pass_definitions.c @@ -521,13 +521,16 @@ static void process_def(cbm_pipeline_ctx_t *ctx, const CBMDefinition *def, const * field resolution), Variable/Field (READS/WRITES resolution), and Table/View * (SQL FROM/JOIN lineage). pass_parallel.c and pipeline_incremental.c seed * through the same predicate, so the three registries cannot diverge. - * A generated member is registered by qualified name only, at all three. */ + * A generated member is registered by qualified name only, at all three. + * The registry's test verdict is the extractor's OR the test-double path + * rule (cbm_registry_test_double_path) on the node's file, at all three. */ if (node_id > 0 && cbm_label_is_registry_symbol(def->label)) { if (cbm_def_props_generated(def->extra_props)) { cbm_registry_add_qn_only(ctx->registry, def->qualified_name, def->label); } else { cbm_registry_add_lang_in_module(ctx->registry, def->name, def->qualified_name, - def->label, lang, module_prefix_len); + def->label, lang, module_prefix_len, + def->is_test || cbm_registry_test_double_path(def_rel)); } if (def->qn_sig_off) { cbm_registry_set_swift_signature(ctx->registry, def->qualified_name, diff --git a/src/pipeline/pass_parallel.c b/src/pipeline/pass_parallel.c index 670347352..4ae2ea88f 100644 --- a/src/pipeline/pass_parallel.c +++ b/src/pipeline/pass_parallel.c @@ -1816,7 +1816,7 @@ int cbm_parallel_extract(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *files, /* `file_node_id` is the defining file's node id (0 when it has none), looked up * once per file by the caller: computing the file QN and finding its node for * every definition was 700 k allocations and lookups on the Go corpus. */ -static int register_and_link_def(cbm_pipeline_ctx_t *ctx, const CBMDefinition *def, +static int register_and_link_def(cbm_pipeline_ctx_t *ctx, const CBMDefinition *def, const char *rel, int64_t file_node_id, CBMLanguage lang, size_t module_prefix_len, int *reg_entries) { int edges = 0; @@ -1824,14 +1824,17 @@ static int register_and_link_def(cbm_pipeline_ctx_t *ctx, const CBMDefinition *d return 0; } /* Registry membership is defined ONCE by cbm_label_is_registry_symbol - * (helpers.c) — see pass_definitions.c for the per-label rationale, and - * for the generated member registered by qualified name only. */ + * (helpers.c) — see pass_definitions.c for the per-label rationale, for + * the generated member registered by qualified name only, and for the + * test-double path rule, judged on the path the def's node stores. */ if (cbm_label_is_registry_symbol(def->label)) { if (cbm_def_props_generated(def->extra_props)) { cbm_registry_add_qn_only(ctx->registry, def->qualified_name, def->label); } else { + const char *def_rel = def->file_path ? def->file_path : rel; cbm_registry_add_lang_in_module(ctx->registry, def->name, def->qualified_name, - def->label, lang, module_prefix_len); + def->label, lang, module_prefix_len, + def->is_test || cbm_registry_test_double_path(def_rel)); } if (def->qn_sig_off) { cbm_registry_set_swift_signature(ctx->registry, def->qualified_name, @@ -2050,7 +2053,7 @@ int cbm_build_registry_from_cache(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t : 0; for (int d = 0; d < result->defs.count; d++) { defines_edges += - register_and_link_def(ctx, &result->defs.items[d], file_node_id, + register_and_link_def(ctx, &result->defs.items[d], rel, file_node_id, files[i].language, py_prefix_len, ®_entries); } } diff --git a/src/pipeline/pass_tests.c b/src/pipeline/pass_tests.c index a763016fb..9972086ee 100644 --- a/src/pipeline/pass_tests.c +++ b/src/pipeline/pass_tests.c @@ -45,7 +45,7 @@ static const char *itoa_log(int val) { } /* Check if a node has is_test:true in its properties JSON. */ -static bool node_is_test(const cbm_gbuf_node_t *n) { +bool cbm_node_is_test(const cbm_gbuf_node_t *n) { if (!n || !n->properties_json) { return false; } @@ -270,13 +270,13 @@ static int create_tests_edges(cbm_pipeline_ctx_t *ctx) { } bool src_is_test = - node_is_test(src) || (src->file_path && cbm_is_test_path(src->file_path)); + cbm_node_is_test(src) || (src->file_path && cbm_is_test_path(src->file_path)); if (!src_is_test) { continue; } bool tgt_is_test = - node_is_test(tgt) || (tgt->file_path && cbm_is_test_path(tgt->file_path)); + cbm_node_is_test(tgt) || (tgt->file_path && cbm_is_test_path(tgt->file_path)); if (tgt_is_test) { continue; } diff --git a/src/pipeline/pipeline.h b/src/pipeline/pipeline.h index f877cdecc..83907ae4b 100644 --- a/src/pipeline/pipeline.h +++ b/src/pipeline/pipeline.h @@ -283,9 +283,24 @@ cbm_registry_t *cbm_registry_new(void); void cbm_registry_free(cbm_registry_t *r); /* Register a function/method/class. All strings are copied. The entry's - * language is unknown, so name-based resolution never filters it out. */ + * language is unknown, so name-based resolution never filters it out. + * `is_test` is the extractor's verdict for this symbol (CBMDefinition.is_test: + * the file's test-ness OR the definition's own test attributes). The registry + * stores it and scores with it; it does not derive test-ness from the QN. */ void cbm_registry_add(cbm_registry_t *r, const char *name, const char *qualified_name, - const char *label); + const char *label, bool is_test); + +/* The test-double path rule, for the registry's ranking only: true when the + * repo-relative path of a symbol's defining file has a directory segment named + * exactly `fake`, `mock`, `mocks`, `testing` or `testutil`, or its file name + * matches `mock_*.go` or `fake_*.go`. Go projects keep generated fake + * clientsets and mock packages in ordinary files the extractor rightly does + * not call tests; a call should still prefer real code over them. The three + * registry seeders (pass_definitions.c, pass_parallel.c, + * pipeline_incremental.c) pass the extractor's verdict OR this, so full and + * incremental indexes rank alike. It never decides what a node is: the stored + * is_test property, TESTS edges and importance stay with cbm_is_test_path. */ +bool cbm_registry_test_double_path(const char *rel_path); /* Swift-only metadata and label-compatible overload selection. The returned * QNs are borrowed from the registry and all belong to one base callable. @@ -306,7 +321,7 @@ int cbm_registry_swift_candidates_in_file(const cbm_registry_t *r, const CBMCall * function-local symbols: that needs the defining file's module QN, see * cbm_registry_add_lang_in_module. */ void cbm_registry_add_lang(cbm_registry_t *r, const char *name, const char *qualified_name, - const char *label, CBMLanguage lang); + const char *label, CBMLanguage lang, bool is_test); /* As cbm_registry_add_lang. A Python symbol declared inside a function body of * its own file (cbm_registry_qn_is_function_local, bounded by @@ -318,7 +333,7 @@ void cbm_registry_add_lang(cbm_registry_t *r, const char *name, const char *qual * indexes agree. 0 = no local detection. */ void cbm_registry_add_lang_in_module(cbm_registry_t *r, const char *name, const char *qualified_name, const char *label, - CBMLanguage lang, size_t module_prefix_len); + CBMLanguage lang, size_t module_prefix_len, bool is_test); /* Register a symbol for exact (qualified-name) lookups only: it is never * offered to the short-name strategies (unique_name, suffix_match, diff --git a/src/pipeline/pipeline_delta.c b/src/pipeline/pipeline_delta.c index 63c5ef1d1..22573f8c8 100644 --- a/src/pipeline/pipeline_delta.c +++ b/src/pipeline/pipeline_delta.c @@ -623,6 +623,28 @@ static char *delta_generated_props(const char *props, const char *generated) { return out; } +/* `props` (a JSON object) with the extractor's test verdict added as + * `"is_test":true` (CBM_MEM_CLASS_OTHER; the caller frees), NULL on allocation + * failure. The incremental registry seed reads the verdict back + * (cbm_node_is_test) so a re-resolved caller ranks a test definition below + * real code exactly as the full build does; an absent key reads as false. */ +static char *delta_test_props(const char *props) { + static const char member[] = "\"is_test\":true"; + size_t pl = props ? strlen(props) : 0; + bool has_members = pl > PAIR_LEN && props[0] == '{' && props[pl - SKIP_ONE] == '}'; + size_t cap = pl + sizeof(member) + CBM_SZ_4; + char *out = (char *)cbm_alloc(CBM_MEM_CLASS_OTHER, cap); + if (!out) { + return NULL; + } + if (has_members) { + snprintf(out, cap, "%.*s,%s}", (int)(pl - SKIP_ONE), props, member); + } else { + snprintf(out, cap, "{%s}", member); + } + return out; +} + int64_t cbm_delta_preseed(cbm_store_t *store, const char *project, cbm_gbuf_t *gbuf) { sqlite3 *db = cbm_store_get_db(store); if (!db) { @@ -673,7 +695,13 @@ int64_t cbm_delta_preseed(cbm_store_t *store, const char *project, cbm_gbuf_t *g * And a generated member's mark (a Java record's implicit accessor, a * Method node): the registry seed reads it to register the member by * qualified name only, as the full build does (cbm_node_props_generated); - * without it the proxy came back into the short-name index. */ + * without it the proxy came back into the short-name index. + * + * And the extractor's test verdict: the registry seed reads it back + * (cbm_node_is_test) to rank a test definition below real code, as the + * full build does with CBMDefinition.is_test; without it every proxy + * scored as production and a re-resolved caller could bind a test helper + * the full build ranks last. */ sqlite3_stmt *stmt = NULL; if (sqlite3_prepare_v2( db, @@ -702,7 +730,8 @@ int64_t cbm_delta_preseed(cbm_store_t *store, const char *project, cbm_gbuf_t *g " THEN json_extract(properties, '$.parent_class') END," " CASE WHEN label = 'Method'" " AND instr(properties, '\"" CBM_PROP_GENERATED "\"') > 0" - " THEN json_extract(properties, '$." CBM_PROP_GENERATED "') END" + " THEN json_extract(properties, '$." CBM_PROP_GENERATED "') END," + " instr(properties, '\"is_test\":true') > 0" " FROM nodes" " WHERE project = ?1 AND label NOT IN" " ('Macro','Comment','Section','Branch','Commit','Tag')" @@ -727,14 +756,27 @@ int64_t cbm_delta_preseed(cbm_store_t *store, const char *project, cbm_gbuf_t *g int end_line = sqlite3_column_int(stmt, 7); const char *parent = (const char *)sqlite3_column_text(stmt, 8); const char *generated = (const char *)sqlite3_column_text(stmt, 9); + bool is_test = sqlite3_column_int(stmt, 10) != 0; char *owner_props = parent ? delta_owner_props(project, name, qn, parent, client_props) : NULL; const char *props = owner_props ? owner_props : (client_props ? client_props : "{}"); char *generated_props = delta_generated_props(props, generated); + const char *seed_props = generated_props ? generated_props : props; + char *test_props = is_test ? delta_test_props(seed_props) : NULL; + if (is_test && !test_props) { + /* A proxy without its verdict would rank as production code; + * the run cannot be trusted, as with an id mismatch below. */ + cbm_free(CBM_MEM_CLASS_OTHER, generated_props); + cbm_free(CBM_MEM_CLASS_OTHER, owner_props); + sqlite3_finalize(stmt); + cbm_log_error("delta.preseed_verdict_alloc_failed", "qn", qn ? qn : ""); + return -1; + } /* Pin the gbuf id to the database id: proxies ARE their rows. */ cbm_gbuf_set_next_id(gbuf, id); int64_t got = cbm_gbuf_upsert_node(gbuf, label, name, qn, fp ? fp : "", start_line, - end_line, generated_props ? generated_props : props); + end_line, test_props ? test_props : seed_props); + cbm_free(CBM_MEM_CLASS_OTHER, test_props); cbm_free(CBM_MEM_CLASS_OTHER, generated_props); cbm_free(CBM_MEM_CLASS_OTHER, owner_props); if (got != id) { diff --git a/src/pipeline/pipeline_incremental.c b/src/pipeline/pipeline_incremental.c index 58ac46d1a..9324314cd 100644 --- a/src/pipeline/pipeline_incremental.c +++ b/src/pipeline/pipeline_incremental.c @@ -1396,8 +1396,11 @@ static void registry_visitor(const cbm_gbuf_node_t *node, void *userdata) { size_t prefix_len = lang == CBM_LANG_PYTHON ? cbm_pipeline_fqn_symbol_prefix_len(seed->project, node->file_path) : 0; + /* The full index's verdict: the extractor's (stored as is_test) OR the + * test-double path rule on the node's file (pass_definitions.c). */ + bool is_test = cbm_node_is_test(node) || cbm_registry_test_double_path(node->file_path); cbm_registry_add_lang_in_module(seed->registry, node->name, node->qualified_name, node->label, - lang, prefix_len); + lang, prefix_len, is_test); if (node->properties_json) { const char *mask = strstr(node->properties_json, "\"param_default_mask\":\""); const char *count = strstr(node->properties_json, "\"selector_param_count\":"); diff --git a/src/pipeline/pipeline_internal.h b/src/pipeline/pipeline_internal.h index d0e12b8dc..cb8af8f02 100644 --- a/src/pipeline/pipeline_internal.h +++ b/src/pipeline/pipeline_internal.h @@ -657,6 +657,12 @@ static inline bool cbm_pipeline_test_force_def_modules_null(const cbm_pipeline_c typedef bool (*cbm_result_want_fn)(const CBMFileResult *header); CBMFileResult *cbm_pipeline_result_acquire(const cbm_pipeline_ctx_t *ctx, CBMFileResult **cache, int i, cbm_result_want_fn want, bool *loaded); +/* True when a graph-buffer node carries is_test:true in its properties JSON. + * The incremental registry seed has only the persisted node, not the + * CBMDefinition the definition passes hand to cbm_registry_add, and both must + * give the registry the same verdict for the same symbol. */ +bool cbm_node_is_test(const cbm_gbuf_node_t *n); + void cbm_pipeline_result_release(CBMFileResult *r, bool loaded); /* Log the store counters, close and delete the store, drop the latch. */ diff --git a/src/pipeline/registry.c b/src/pipeline/registry.c index 71d77b2bd..ac1b8ce61 100644 --- a/src/pipeline/registry.c +++ b/src/pipeline/registry.c @@ -30,6 +30,7 @@ enum { REG_MAX_CANDIDATES = 256 }; #include "foundation/compat.h" /* CBM_TLS */ #include "foundation/hash_table.h" #include "foundation/arena.h" +#include "foundation/log.h" #include "foundation/dyn_array.h" #include "foundation/mem_core.h" #include "foundation/platform.h" @@ -81,16 +82,16 @@ const char *cbm_confidence_band(double score) { /* ── Internal types ──────────────────────────────────────────────── */ -/* Array of QN strings for byName index, with the test/mock verdict of each - * name cached beside it: the scorer asked is_test_qn for every candidate of - * every unresolved call, and the answer depends only on the name, which never - * changes once registered (waste sanitizer scaling lane, 2026-09-17). Field - * order and names match CBM_DYN_ARRAY(char *) so cbm_da_push works on it. */ +/* Array of QN strings for byName index, with each entry's test verdict beside + * it. The verdict is the extractor's, handed to cbm_registry_add by the caller + * that already computed it from the file path and the definition's attributes; + * it is stored rather than derived, so every entry always has one. Field order + * and names match CBM_DYN_ARRAY(char *) so cbm_da_push works on it. */ typedef struct { char **items; int count; int cap; - uint8_t *is_test; /* is_test_qn(items[i]), one byte per entry */ + uint8_t *is_test; /* the caller's verdict for items[i], one byte per entry */ int is_test_cap; /* Language of the file that defined items[i] (a CBMLanguage; CBM_LANG_COUNT * = unknown, compatible with every caller), one byte per entry. Grown in @@ -204,84 +205,64 @@ static int common_prefix_len(const char *a, const char *b) { enum { REG_TEST_PENALTY = 1000 }; -/* Check if a qualified name looks like a test/mock path: does it contain any of - * Test, test, Mock, mock, Stub, stub, Fake, fake, Fixture, spec? One pass that - * dispatches on the first character instead of ten strstr scans of the whole - * name (this ran for every candidate of every unresolved call: the scaling - * lane's x4.0, waste sanitizer 2026-09-17). */ -static bool is_test_qn(const char *qn) { - if (!qn) { +/* A directory segment that, by name alone, holds test doubles: Go's generated + * fake clientsets and listers (pkg/client/.../fake), mock packages, and the + * testing / testutil helper packages. Whole segments only, compared exactly. */ +static bool test_double_dir_segment(const char *seg, size_t len) { + static const char *const dirs[] = {"fake", "mock", "mocks", "testing", "testutil"}; + for (size_t i = 0; i < sizeof(dirs) / sizeof(dirs[0]); i++) { + if (strlen(dirs[i]) == len && memcmp(seg, dirs[i], len) == 0) { + return true; + } + } + return false; +} + +/* `base` is a Go file named `prefix`*.go. */ +static bool go_file_with_prefix(const char *base, const char *prefix) { + static const char go_ext[] = ".go"; + size_t blen = strlen(base); + size_t plen = strlen(prefix); + size_t elen = sizeof(go_ext) - SKIP_ONE; + return blen >= plen + elen && strncmp(base, prefix, plen) == 0 && + strcmp(base + blen - elen, go_ext) == 0; +} + +bool cbm_registry_test_double_path(const char *rel_path) { + if (!rel_path) { return false; } - for (const char *p = qn; *p; p++) { - switch (*p) { - case 'T': - if (strncmp(p, "Test", 4) == 0) { - return true; - } - break; - case 't': - if (strncmp(p, "test", 4) == 0) { - return true; - } - break; - case 'M': - if (strncmp(p, "Mock", 4) == 0) { - return true; - } - break; - case 'm': - if (strncmp(p, "mock", 4) == 0) { - return true; - } - break; - case 'S': - if (strncmp(p, "Stub", 4) == 0) { - return true; - } - break; - case 's': - if (strncmp(p, "stub", 4) == 0 || strncmp(p, "spec", 4) == 0) { - return true; - } - break; - case 'F': - if (strncmp(p, "Fake", 4) == 0 || strncmp(p, "Fixture", 7) == 0) { - return true; - } - break; - case 'f': - if (strncmp(p, "fake", 4) == 0) { - return true; - } - break; - default: - break; + const char *seg = rel_path; + for (const char *slash = strchr(seg, '/'); slash; slash = strchr(seg, '/')) { + if (test_double_dir_segment(seg, (size_t)(slash - seg))) { + return true; } + seg = slash + SKIP_ONE; } - return false; + /* `seg` is the file name now. */ + return go_file_with_prefix(seg, "mock_") || go_file_with_prefix(seg, "fake_"); } -/* The cached test verdicts, only when they cover every entry: a failed growth - * leaves the cache short, and then the scorer asks per candidate as before. */ +/* The stored test verdicts. index_under_name drops an entry it cannot record a + * verdict for, so this covers every entry of a non-empty bucket. */ static const uint8_t *qn_test_flags(const qn_array_t *arr) { - return arr->is_test && arr->is_test_cap >= arr->count ? arr->is_test : NULL; + return arr->is_test; } -/* The cached candidate languages, under the same coverage rule: both side - * arrays grow together and share is_test_cap. */ +/* The stored candidate languages. Both side arrays grow together and share + * is_test_cap, and index_under_name drops an entry that does not fit both, so + * this covers every entry of a non-empty bucket too. */ static const uint8_t *qn_lang_flags(const qn_array_t *arr) { - return arr->lang && arr->is_test_cap >= arr->count ? arr->lang : NULL; + return arr->lang; } /* Score a candidate for tiebreaking. Higher = better. * Layer 1: Non-test code preferred over test code (+1000) * Layer 2: Namespace proximity via common prefix length (+plen) - * `is_test` is the cached verdict when the caller has one (-1: ask). */ -static int candidate_score(const char *candidate_qn, const char *module_qn, int is_test) { + * `is_test` is the verdict stored for this candidate at registration. */ +static int candidate_score(const char *candidate_qn, const char *module_qn, bool is_test) { int score = 0; - bool test = is_test >= 0 ? is_test != 0 : is_test_qn(candidate_qn); - if (!test) { + if (!is_test) { score += REG_TEST_PENALTY; } score += common_prefix_len(candidate_qn, module_qn); @@ -351,9 +332,10 @@ static bool candidate_outranks_on_tie(const cbm_registry_t *r, const char *candi } /* Pick candidate with highest composite score (test-deprioritization + namespace - * proximity), ties settled by candidate_outranks_on_tie. `is_test_flags` and - * `lang_flags` are the candidates' cached test verdicts and languages, or NULL - * when the caller has none (a filtered subset carries its own copies). */ + * proximity), ties settled by candidate_outranks_on_tie. `is_test_flags` are + * the candidates' stored test verdicts, always given: every by-name entry has + * one, and a filtered subset carries its own copies. `lang_flags` are their + * languages, or NULL when the caller has none. */ static const char *best_by_import_distance(const cbm_registry_t *r, const char **candidates, const uint8_t *is_test_flags, const uint8_t *lang_flags, int count, const char *module_qn) { @@ -361,8 +343,7 @@ static const char *best_by_import_distance(const cbm_registry_t *r, const char * uint8_t best_lang = (uint8_t)CBM_LANG_COUNT; int best_score = CBM_NOT_FOUND; for (int i = 0; i < count; i++) { - int score = - candidate_score(candidates[i], module_qn, is_test_flags ? (int)is_test_flags[i] : -1); + int score = candidate_score(candidates[i], module_qn, is_test_flags[i] != 0); uint8_t lang = lang_flags ? lang_flags[i] : (uint8_t)CBM_LANG_COUNT; if (score > best_score || (score == best_score && best && @@ -1719,11 +1700,11 @@ void cbm_registry_free(cbm_registry_t *r) { /* ── Registration ────────────────────────────────────────────────── */ /* Record `owned_qn` in the by-name bucket for `key`, keeping the bucket's - * is_test flags in step with its entries. + * is_test and lang side arrays in step with its entries. * * No array dedup needed: cbm_registry_add's exact-map check guarantees the QN * is new, and it calls this at most once per distinct key. */ -static void index_under_name(cbm_registry_t *r, const char *key, const char *owned_qn, +static void index_under_name(cbm_registry_t *r, const char *key, const char *owned_qn, bool is_test, CBMLanguage lang) { qn_array_t *arr = cbm_ht_get(r->by_name, key); if (!arr) { @@ -1733,7 +1714,7 @@ static void index_under_name(cbm_registry_t *r, const char *key, const char *own int before = arr->count; cbm_da_push(arr, (char *)owned_qn); if (arr->count == before) { - return; /* the name could not be recorded: no verdict to cache */ + return; /* the name could not be recorded: no verdict to store */ } if (arr->count > arr->is_test_cap) { int want = arr->cap > 0 ? arr->cap : arr->count; @@ -1744,19 +1725,25 @@ static void index_under_name(cbm_registry_t *r, const char *key, const char *own uint8_t *grown_lang = cbm_realloc(CBM_MEM_CLASS_DYN_ARRAY, arr->lang, (size_t)want * sizeof(uint8_t)); /* Both side arrays share is_test_cap: on failure it stays below - * count, so this name serves neither cached test verdicts nor - * language filtering, exactly the unfiltered answer. */ + * count, and the entry is dropped below. */ if (grown_lang) { arr->lang = grown_lang; arr->is_test_cap = want; } } } - if (arr->count <= arr->is_test_cap) { - arr->is_test[arr->count - SKIP_ONE] = is_test_qn(owned_qn) ? 1 : 0; - arr->lang[arr->count - SKIP_ONE] = - (uint8_t)(lang >= 0 && lang < CBM_LANG_COUNT ? lang : CBM_LANG_COUNT); + if (arr->count > arr->is_test_cap) { + /* The verdict can no longer be recomputed from the QN, so an entry + * without one would be scored as non-test and outrank real code. Drop + * it instead: one missing candidate beats a wrong winner. The language + * shares is_test_cap, so the drop covers it as well. */ + arr->count = before; + cbm_log_warn("registry.verdict_alloc_failed", "key", key); + return; } + arr->is_test[arr->count - SKIP_ONE] = is_test ? 1 : 0; + arr->lang[arr->count - SKIP_ONE] = + (uint8_t)(lang >= 0 && lang < CBM_LANG_COUNT ? lang : CBM_LANG_COUNT); } static bool registry_label_is_callable(const char *label) { @@ -1844,13 +1831,13 @@ static const char *registry_add_exact(cbm_registry_t *r, const char *qualified_n } void cbm_registry_add(cbm_registry_t *r, const char *name, const char *qualified_name, - const char *label) { - cbm_registry_add_lang_in_module(r, name, qualified_name, label, CBM_LANG_COUNT, 0); + const char *label, bool is_test) { + cbm_registry_add_lang_in_module(r, name, qualified_name, label, CBM_LANG_COUNT, 0, is_test); } void cbm_registry_add_lang(cbm_registry_t *r, const char *name, const char *qualified_name, - const char *label, CBMLanguage lang) { - cbm_registry_add_lang_in_module(r, name, qualified_name, label, lang, 0); + const char *label, CBMLanguage lang, bool is_test) { + cbm_registry_add_lang_in_module(r, name, qualified_name, label, lang, 0, is_test); } /* Python nests defs: a def or class declared in a function body -- a closure, @@ -1869,7 +1856,7 @@ void cbm_registry_add_lang(cbm_registry_t *r, const char *name, const char *qual * index seconds for nothing. */ void cbm_registry_add_lang_in_module(cbm_registry_t *r, const char *name, const char *qualified_name, const char *label, - CBMLanguage lang, size_t module_prefix_len) { + CBMLanguage lang, size_t module_prefix_len, bool is_test) { if (!r || !qualified_name || !label) { return; } @@ -1913,7 +1900,7 @@ void cbm_registry_add_lang_in_module(cbm_registry_t *r, const char *name, const char *derived = simple_name(qualified_name); const char *primary = name && cbm_qn_callable_base_len_named(owned_qn, name) < strlen(owned_qn) ? name : derived; - index_under_name(r, primary, owned_qn, lang); + index_under_name(r, primary, owned_qn, is_test, lang); /* '#' is a QN fence; no extractor mints one for a REGISTERED definition * today (Rust cfg twins are variants of one plain QN; a C macro's QN ends in * "#macro", but Macro is no registry label, so it never gets here). A @@ -1925,7 +1912,7 @@ void cbm_registry_add_lang_in_module(cbm_registry_t *r, const char *name, * signature-qualified callable is filed under `name` already, and its * capped suffix may carry a '#' of its own. */ if (name && name[0] && strchr(derived, '#') && strcmp(name, primary) != 0) { - index_under_name(r, name, owned_qn, lang); + index_under_name(r, name, owned_qn, is_test, lang); } } @@ -2296,12 +2283,8 @@ static cbm_resolution_t resolve_multi_with_imports(const cbm_registry_t *r, cons int fcount = 0; for (int i = 0; i < arr->count && fcount < CBM_SZ_256; i++) { if (is_import_reachable(arr->items[i], import_vals, import_count)) { - if (flags) { - filtered_test[fcount] = flags[i]; - } - if (langs) { - filtered_lang[fcount] = langs[i]; - } + filtered_test[fcount] = flags[i]; + filtered_lang[fcount] = langs[i]; filtered[fcount] = arr->items[i]; fcount++; } @@ -2311,8 +2294,8 @@ static cbm_resolution_t resolve_multi_with_imports(const cbm_registry_t *r, cons return (cbm_resolution_t){filtered[0], "suffix_match", conf, arr->count}; } if (fcount > SKIP_ONE) { - const char *best = best_by_import_distance(r, filtered, flags ? filtered_test : NULL, - langs ? filtered_lang : NULL, fcount, module_qn); + const char *best = + best_by_import_distance(r, filtered, filtered_test, filtered_lang, fcount, module_qn); if (best) { double conf = candidate_count_penalty(CONF_SUFFIX_MATCH, fcount); return (cbm_resolution_t){best, "suffix_match", conf, fcount}; @@ -2507,14 +2490,13 @@ static bool receiver_chain_admits(const char *callee_name, const char *candidate /* The same-name candidates without the members -- Field and Method nodes -- * in `view` (backed by the caller's buffers, at most REG_MAX_CANDIDATES - * entries). The side arrays are carried over only when the bucket has them - * for every entry. */ + * entries). Every entry of a non-empty bucket has its stored verdict and + * language (index_under_name), and the view carries them over. */ static void candidates_without_members(const cbm_registry_t *r, const qn_array_t *arr, qn_array_t *view, char **items, uint8_t *is_test, uint8_t *lang) { const uint8_t *flags = qn_test_flags(arr); const uint8_t *langs = qn_lang_flags(arr); - bool side = flags && langs; int n = 0; for (int i = 0; i < arr->count; i++) { const char *label = cbm_registry_label_of(r, arr->items[i]); @@ -2522,18 +2504,12 @@ static void candidates_without_members(const cbm_registry_t *r, const qn_array_t continue; } items[n] = arr->items[i]; - if (side) { - is_test[n] = flags[i]; - lang[n] = langs[i]; - } + is_test[n] = flags[i]; + lang[n] = langs[i]; n++; } - *view = (qn_array_t){.items = items, - .count = n, - .cap = n, - .is_test = side ? is_test : NULL, - .is_test_cap = side ? n : 0, - .lang = side ? lang : NULL}; + *view = (qn_array_t){ + .items = items, .count = n, .cap = n, .is_test = is_test, .is_test_cap = n, .lang = lang}; } /* Strategies 3.5, 3 and 4 over one same-name candidate set. `lang` reaches @@ -2669,7 +2645,7 @@ static cbm_resolution_t registry_resolve_chain(const cbm_registry_t *r, const ch * at most REG_MAX_CANDIDATES long whenever a name strategy produced `qn`. */ static CBMLanguage registry_entry_language(const cbm_registry_t *r, const char *qn) { const qn_array_t *arr = cbm_ht_get(r->by_name, simple_name(qn)); - if (!arr || !arr->lang || arr->is_test_cap < arr->count) { + if (!arr) { return CBM_LANG_COUNT; } for (int i = 0; i < arr->count; i++) { @@ -3071,22 +3047,28 @@ static bool swift_signature_matches(const swift_signature_t *meta, const CBMCall } /* Keep receiver choice ahead of general import/name choice. Without a module, - * this path still uses the deterministic distance tie-break, as before. */ -static const char *swift_choose_receiver(const cbm_registry_t *r, const char **matches, int matched, - const char *name, const CBMCall *call, - const char *module_qn, const char **import_vals, - int import_count, const char **scratch) { + * this path still uses the deterministic distance tie-break, as before. + * `match_test` holds the stored test verdict of each of `matches`, and + * `scratch_test` is the matching scratch, so every filtered list the scorer + * sees carries its own verdicts. */ +static const char *swift_choose_receiver(const cbm_registry_t *r, const char **matches, + const uint8_t *match_test, int matched, const char *name, + const CBMCall *call, const char *module_qn, + const char **import_vals, int import_count, + const char **scratch, uint8_t *scratch_test) { if (!strchr(call->callee_name, '.')) { return NULL; } size_t callee_len = strlen(call->callee_name); const char **receiver_matches = scratch; + uint8_t *receiver_test = scratch_test; int receiver_count = 0; for (int i = 0; i < matched; i++) { size_t base = cbm_qn_callable_base_len_named(matches[i], name); if (base >= callee_len && memcmp(matches[i] + base - callee_len, call->callee_name, callee_len) == 0 && (base == callee_len || matches[i][base - callee_len - SKIP_ONE] == '.')) { + receiver_test[receiver_count] = match_test[i]; receiver_matches[receiver_count++] = matches[i]; } } @@ -3094,45 +3076,53 @@ static const char *swift_choose_receiver(const cbm_registry_t *r, const char **m return NULL; } const char **reachable = scratch + matched; + uint8_t *reachable_test = scratch_test + matched; int reachable_count = 0; if (import_vals) { for (int i = 0; i < receiver_count; i++) { if (is_import_reachable(receiver_matches[i], import_vals, import_count)) { + reachable_test[reachable_count] = receiver_test[i]; reachable[reachable_count++] = receiver_matches[i]; } } } const char **preferred = reachable_count > 0 ? reachable : receiver_matches; + const uint8_t *preferred_test = reachable_count > 0 ? reachable_test : receiver_test; int preferred_count = reachable_count > 0 ? reachable_count : receiver_count; - return best_by_import_distance(r, preferred, NULL, NULL, preferred_count, module_qn); + return best_by_import_distance(r, preferred, preferred_test, NULL, preferred_count, module_qn); } static const char *swift_choose_candidate(const cbm_registry_t *r, const char **matches, - int matched, const char *name, const CBMCall *call, - const char *module_qn, const char **import_vals, - int import_count, const char **scratch) { - const char *chosen = swift_choose_receiver(r, matches, matched, name, call, module_qn, - import_vals, import_count, scratch); + const uint8_t *match_test, int matched, const char *name, + const CBMCall *call, const char *module_qn, + const char **import_vals, int import_count, + const char **scratch, uint8_t *scratch_test) { + const char *chosen = + swift_choose_receiver(r, matches, match_test, matched, name, call, module_qn, import_vals, + import_count, scratch, scratch_test); if (chosen) { return chosen; } const char **reachable = scratch; + uint8_t *reachable_test = scratch_test; int reachable_count = 0; if (import_vals) { for (int i = 0; i < matched; i++) { if (is_import_reachable(matches[i], import_vals, import_count)) { + reachable_test[reachable_count] = match_test[i]; reachable[reachable_count++] = matches[i]; } } } if (reachable_count > 0) { - chosen = module_qn - ? best_by_import_distance(r, reachable, NULL, NULL, reachable_count, module_qn) - : reachable[0]; + chosen = module_qn ? best_by_import_distance(r, reachable, reachable_test, NULL, + reachable_count, module_qn) + : reachable[0]; } if (!chosen) { - chosen = module_qn ? best_by_import_distance(r, matches, NULL, NULL, matched, module_qn) - : matches[0]; + chosen = module_qn + ? best_by_import_distance(r, matches, match_test, NULL, matched, module_qn) + : matches[0]; } return chosen; } @@ -3762,15 +3752,20 @@ int cbm_registry_swift_candidates(const cbm_registry_t *r, const CBMCall *call, if (!bucket) { return 0; } + /* Three QN lists (the matches and the choosers' two scratch lists), each + * with a parallel byte per entry for its stored test verdict, in one block. */ enum { SWIFT_MATCH_BUFFERS = 3 }; - if ((size_t)bucket->count > SIZE_MAX / sizeof(const char *) / SWIFT_MATCH_BUFFERS) { + const size_t slot = sizeof(const char *) + sizeof(uint8_t); + if ((size_t)bucket->count > SIZE_MAX / slot / SWIFT_MATCH_BUFFERS) { return SWIFT_INCOMPATIBLE; } - const char **matches = cbm_alloc( - CBM_MEM_CLASS_OTHER, (size_t)bucket->count * SWIFT_MATCH_BUFFERS * sizeof(*matches)); + size_t slots = (size_t)bucket->count * SWIFT_MATCH_BUFFERS; + const char **matches = cbm_alloc(CBM_MEM_CLASS_OTHER, slots * slot); if (!matches) { return SWIFT_INCOMPATIBLE; } + uint8_t *match_test = (uint8_t *)(matches + slots); + const uint8_t *bucket_test = qn_test_flags(bucket); int matched = 0; bool has_swift = false; for (int i = 0; i < bucket->count; i++) { @@ -3779,6 +3774,7 @@ int cbm_registry_swift_candidates(const cbm_registry_t *r, const CBMCall *call, has_swift = has_swift || meta != NULL; if (meta && receiver_chain_admits(call->callee_name, qn, CBM_LANG_SWIFT) && swift_signature_matches(meta, call)) { + match_test[matched] = bucket_test[i]; matches[matched++] = qn; } } @@ -3786,8 +3782,9 @@ int cbm_registry_swift_candidates(const cbm_registry_t *r, const CBMCall *call, cbm_free(CBM_MEM_CLASS_OTHER, matches); return has_swift ? SWIFT_INCOMPATIBLE : 0; } - const char *chosen = swift_choose_candidate(r, matches, matched, name, call, module_qn, - import_vals, import_count, matches + bucket->count); + const char *chosen = + swift_choose_candidate(r, matches, match_test, matched, name, call, module_qn, import_vals, + import_count, matches + bucket->count, match_test + bucket->count); if (!chosen) { cbm_free(CBM_MEM_CLASS_OTHER, matches); return 0; @@ -4760,11 +4757,18 @@ cbm_resolution_t cbm_registry_resolve_call_scoped(const cbm_registry_t *r, const /* ── Fuzzy Resolve ──────────────────────────────────────────────── */ /* Filter candidates by import reachability. Returns count of reachable. */ -static int filter_import_reachable(const char **candidates, int count, const char **import_vals, - int import_count, const char **out, int max_out) { +/* Keeps `out_flags` and `out_langs` in step with `out`: the verdicts are + * stored per entry and cannot be recomputed from the QN, so a filtered view has + * to carry its own, and the languages travel with them. */ +static int filter_import_reachable(const char **candidates, const uint8_t *flags, + const uint8_t *langs, int count, const char **import_vals, + int import_count, const char **out, uint8_t *out_flags, + uint8_t *out_langs, int max_out) { int n = 0; for (int i = 0; i < count && n < max_out; i++) { if (is_import_reachable(candidates[i], import_vals, import_count)) { + out_flags[n] = flags[i]; + out_langs[n] = langs[i]; out[n++] = candidates[i]; } } @@ -4804,13 +4808,21 @@ cbm_fuzzy_result_t cbm_registry_fuzzy_resolve(const cbm_registry_t *r, const cha /* Multiple candidates: filter by import reachability */ const char *filtered[CBM_SZ_256]; + uint8_t filtered_flags[CBM_SZ_256]; + uint8_t filtered_langs[CBM_SZ_256]; int fcount = arr->count; const char **fptr = (const char **)arr->items; + const uint8_t *fflags = qn_test_flags(arr); + const uint8_t *flangs = qn_lang_flags(arr); if (have_imports) { - fcount = filter_import_reachable((const char **)arr->items, arr->count, import_map_vals, - import_map_count, filtered, CBM_SZ_256); + fcount = filter_import_reachable((const char **)arr->items, qn_test_flags(arr), + qn_lang_flags(arr), arr->count, import_map_vals, + import_map_count, filtered, filtered_flags, filtered_langs, + CBM_SZ_256); fptr = filtered; + fflags = filtered_flags; + flangs = filtered_langs; } if (fcount == 0) { @@ -4830,9 +4842,7 @@ cbm_fuzzy_result_t cbm_registry_fuzzy_resolve(const cbm_registry_t *r, const cha {fptr[0], "fuzzy", candidate_count_penalty(CONF_FUZZY_SINGLE, arr->count), arr->count}, true}; } - const bool all = fptr == (const char **)arr->items; - const char *best = best_by_import_distance(r, fptr, all ? qn_test_flags(arr) : NULL, - all ? qn_lang_flags(arr) : NULL, fcount, module_qn); + const char *best = best_by_import_distance(r, fptr, fflags, flangs, fcount, module_qn); if (!best) { return no_match; } diff --git a/tests/test_callable_sig.c b/tests/test_callable_sig.c index 9f09e55d0..e981aa7ff 100644 --- a/tests/test_callable_sig.c +++ b/tests/test_callable_sig.c @@ -434,9 +434,9 @@ TEST(callable_sig_leaf_splitters_skip_suffix) { * keeps its historical key (last '.' or "::" segment). */ TEST(callable_sig_registry_by_name_uses_base_leaf) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "upload", "p.Session.upload(_:Data,to:URL)", "Method"); - cbm_registry_add(r, "upload", "p.Session.upload(_:Data,with:URLRequest)", "Method"); - cbm_registry_add(r, "square", "p.Math::square", "Function"); + cbm_registry_add(r, "upload", "p.Session.upload(_:Data,to:URL)", "Method", false); + cbm_registry_add(r, "upload", "p.Session.upload(_:Data,with:URLRequest)", "Method", false); + cbm_registry_add(r, "square", "p.Math::square", "Function", false); const char **out = NULL; int count = 0; cbm_registry_find_by_name(r, "upload", &out, &count); diff --git a/tests/test_pipeline.c b/tests/test_pipeline.c index 50243e24b..baf2e34b7 100644 --- a/tests/test_pipeline.c +++ b/tests/test_pipeline.c @@ -6619,6 +6619,132 @@ TEST(pipeline_incremental_cross_file_call_reference_matches_fresh_full) { PASS(); } +/* #2427 rank probes: one bare call, two same-named candidates, and the + * registry's test ranking alone decides which one the call binds. Three runs + * on one database: a full index, then a comment-only edit of the caller on + * the closure-repair route, then another on the forced legacy-partial route. + * Both incremental routes re-resolve the caller against a registry seeded from + * stored nodes rather than the extractor's definitions, which is where a + * seeder that loses the verdict shows. */ +typedef struct { + int want[3]; + int other[3]; + cbm_incremental_route_t route[3]; +} rank_probe_t; + +static int rank_probe_count(const char *db, const char *project, const char *source, + const char *target, const char *file) { + cbm_store_t *s = cbm_store_open_path(db); + if (!s) { + return -1; + } + int n = named_edge_to_file_count(s, project, "CALLS", source, target, file); + cbm_store_close(s); + return n; +} + +static void rank_probe(const char *tmp, const char *caller_path, const char *caller_src, + const char *source, const char *target, const char *want_file, + const char *other_file, rank_probe_t *out) { + char db[512]; + snprintf(db, sizeof(db), "%s/rank.db", tmp); + for (int run = 0; run < 3; run++) { + out->want[run] = -1; + out->other[run] = -1; + out->route[run] = CBM_INCREMENTAL_ROUTE_NONE; + if (run > 0) { + char edited[512]; + snprintf(edited, sizeof(edited), "%s// edit %d\n", caller_src, run); + write_temp_file(tmp, caller_path, edited); + } + cbm_pipeline_incremental_test_reset_faults(); +#if defined(CBM_INCREMENTAL_TEST_API) && CBM_INCREMENTAL_TEST_API + if (run == 2) { + cbm_pipeline_incremental_test_force_legacy_partial_once(); + } +#endif + cbm_pipeline_t *p = cbm_pipeline_new(tmp, db, CBM_MODE_FULL); + if (!p) { + return; + } + int rc = cbm_pipeline_run(p); + out->route[run] = cbm_pipeline_incremental_test_last_route(); + char project[256]; + snprintf(project, sizeof(project), "%s", cbm_pipeline_project_name(p)); + cbm_pipeline_free(p); + if (rc != 0) { + return; + } + out->want[run] = rank_probe_count(db, project, source, target, want_file); + out->other[run] = rank_probe_count(db, project, source, target, other_file); + } +} + +/* "latest" spells "test", so the QN scan the extractor's verdict replaced + * ranked the production Parse below a helper in a _test.go file, whose Go QN + * names only its package and so spells nothing. The verdict ranks them the + * right way round, and both incremental routes must read the same verdict + * back from the stored nodes: the closure-repair proxies once carried no + * is_test, every candidate scored as production, and the smaller QN -- the + * test helper -- won. */ +TEST(pipeline_registry_ranks_by_extractor_verdict_full_and_incremental) { + char tmp[256]; + snprintf(tmp, sizeof(tmp), "/tmp/cbm_rank_verdict_XXXXXX"); + ASSERT_NOT_NULL(cbm_mkdtemp(tmp)); + static const char caller[] = "package app\n\nfunc Run() int { return Parse() }\n"; + write_temp_file(tmp, "go.mod", "module example.com/rank\n\ngo 1.22\n"); + write_temp_file(tmp, "latest/parse.go", "package latest\n\nfunc Parse() int { return 1 }\n"); + write_temp_file(tmp, "harness/parse_test.go", + "package harness\n\nfunc Parse() int { return 2 }\n"); + write_temp_file(tmp, "app/run.go", caller); + rank_probe_t r; + rank_probe(tmp, "app/run.go", caller, "Run", "Parse", "latest/parse.go", + "harness/parse_test.go", &r); + th_rmtree(tmp); + ASSERT_EQ(r.want[0], 1); /* full */ + ASSERT_EQ(r.other[0], 0); + ASSERT_EQ(r.route[1], CBM_INCREMENTAL_ROUTE_CLOSURE_REPAIR); + ASSERT_EQ(r.want[1], 1); + ASSERT_EQ(r.other[1], 0); +#if defined(CBM_INCREMENTAL_TEST_API) && CBM_INCREMENTAL_TEST_API + ASSERT_EQ(r.route[2], CBM_INCREMENTAL_ROUTE_LEGACY_PARTIAL); +#endif + ASSERT_EQ(r.want[2], 1); + ASSERT_EQ(r.other[2], 0); + PASS(); +} + +/* A generated fake beside its callers: client/fake shares more of the caller's + * package path than the real store does, so on the extractor's verdict alone + * (a fake is not a test file) the fake wins on proximity. The test-double path + * rule ranks it last in the full index and, through the incremental seed's own + * use of the rule, on both incremental routes. */ +TEST(pipeline_registry_ranks_test_doubles_last_full_and_incremental) { + char tmp[256]; + snprintf(tmp, sizeof(tmp), "/tmp/cbm_rank_double_XXXXXX"); + ASSERT_NOT_NULL(cbm_mkdtemp(tmp)); + static const char caller[] = "package client\n\nfunc Run() int { return Load() }\n"; + write_temp_file(tmp, "go.mod", "module example.com/rank\n\ngo 1.22\n"); + write_temp_file(tmp, "store/load.go", "package store\n\nfunc Load() int { return 1 }\n"); + write_temp_file(tmp, "client/fake/load.go", "package fake\n\nfunc Load() int { return 2 }\n"); + write_temp_file(tmp, "client/run.go", caller); + rank_probe_t r; + rank_probe(tmp, "client/run.go", caller, "Run", "Load", "store/load.go", "client/fake/load.go", + &r); + th_rmtree(tmp); + ASSERT_EQ(r.want[0], 1); /* full */ + ASSERT_EQ(r.other[0], 0); + ASSERT_EQ(r.route[1], CBM_INCREMENTAL_ROUTE_CLOSURE_REPAIR); + ASSERT_EQ(r.want[1], 1); + ASSERT_EQ(r.other[1], 0); +#if defined(CBM_INCREMENTAL_TEST_API) && CBM_INCREMENTAL_TEST_API + ASSERT_EQ(r.route[2], CBM_INCREMENTAL_ROUTE_LEGACY_PARTIAL); +#endif + ASSERT_EQ(r.want[2], 1); + ASSERT_EQ(r.other[2], 0); + PASS(); +} + /* A semantic control file can change an unchanged source file's exact target. * Both target modules deliberately export the same short name: without the * tsconfig path mapping, project-wide unique-name fallback is ambiguous and @@ -21834,7 +21960,7 @@ static int python_typed_field_scope_case(bool external) { if (result && reg && gbuf && result->field_types.count == 1) { for (int i = 0; i < result->defs.count; i++) { const CBMDefinition *def = &result->defs.items[i]; - cbm_registry_add(reg, def->name, def->qualified_name, def->label); + cbm_registry_add(reg, def->name, def->qualified_name, def->label, false); } const char **keys = NULL; const char **vals = NULL; @@ -27801,8 +27927,8 @@ TEST(helm_parse_chart_many_dependencies) { TEST(registry_resolve_single_candidate) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "CreateOrder", "svcA.handlers.CreateOrder", "Function"); - cbm_registry_add(reg, "ValidateOrder", "svcB.validators.ValidateOrder", "Function"); + cbm_registry_add(reg, "CreateOrder", "svcA.handlers.CreateOrder", "Function", false); + cbm_registry_add(reg, "ValidateOrder", "svcB.validators.ValidateOrder", "Function", false); /* Normal resolve unique name */ cbm_resolution_t r = cbm_registry_resolve(reg, "CreateOrder", "svcC.caller", NULL, NULL, 0); @@ -27820,7 +27946,7 @@ TEST(registry_resolve_single_candidate) { TEST(registry_fuzzy_nonexistent) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "CreateOrder", "svcA.handlers.CreateOrder", "Function"); + cbm_registry_add(reg, "CreateOrder", "svcA.handlers.CreateOrder", "Function", false); cbm_fuzzy_result_t fr = cbm_registry_fuzzy_resolve(reg, "NonExistent", "svcC.caller", NULL, NULL, 0); @@ -27832,8 +27958,8 @@ TEST(registry_fuzzy_nonexistent) { TEST(registry_fuzzy_multiple_best_by_distance) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Process", "svcA.handlers.Process", "Function"); - cbm_registry_add(reg, "Process", "svcB.handlers.Process", "Function"); + cbm_registry_add(reg, "Process", "svcA.handlers.Process", "Function", false); + cbm_registry_add(reg, "Process", "svcB.handlers.Process", "Function", false); /* Caller in svcA → prefer svcA */ cbm_fuzzy_result_t fr = @@ -27852,7 +27978,7 @@ TEST(registry_fuzzy_multiple_best_by_distance) { TEST(registry_fuzzy_simple_name_extraction) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "DoWork", "myproject.utils.DoWork", "Function"); + cbm_registry_add(reg, "DoWork", "myproject.utils.DoWork", "Function", false); /* Deeply qualified name → extract "DoWork" */ cbm_fuzzy_result_t fr = cbm_registry_fuzzy_resolve(reg, "some.deep.module.DoWork", @@ -27877,8 +28003,8 @@ TEST(registry_fuzzy_empty) { TEST(registry_exists) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Foo", "pkg.module.Foo", "Function"); - cbm_registry_add(reg, "Bar", "pkg.module.Bar", "Method"); + cbm_registry_add(reg, "Foo", "pkg.module.Foo", "Function", false); + cbm_registry_add(reg, "Bar", "pkg.module.Bar", "Method", false); ASSERT_TRUE(cbm_registry_exists(reg, "pkg.module.Foo")); ASSERT_TRUE(cbm_registry_exists(reg, "pkg.module.Bar")); @@ -27891,7 +28017,7 @@ TEST(registry_exists) { TEST(registry_confidence_import_map) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Foo", "proj.other.Foo", "Function"); + cbm_registry_add(reg, "Foo", "proj.other.Foo", "Function", false); const char *keys[] = {"other"}; const char *vals[] = {"proj.other"}; @@ -27906,7 +28032,7 @@ TEST(registry_confidence_import_map) { TEST(registry_confidence_import_map_suffix) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Foo", "proj.other.sub.Foo", "Function"); + cbm_registry_add(reg, "Foo", "proj.other.sub.Foo", "Function", false); const char *keys[] = {"other"}; const char *vals[] = {"proj.other"}; @@ -27921,7 +28047,7 @@ TEST(registry_confidence_import_map_suffix) { TEST(registry_confidence_same_module) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Foo", "proj.pkg.Foo", "Function"); + cbm_registry_add(reg, "Foo", "proj.pkg.Foo", "Function", false); cbm_resolution_t r = cbm_registry_resolve(reg, "Foo", "proj.pkg", NULL, NULL, 0); ASSERT_STR_EQ(r.qualified_name, "proj.pkg.Foo"); @@ -27934,7 +28060,7 @@ TEST(registry_confidence_same_module) { TEST(registry_confidence_unique_name) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Bar", "proj.pkg.Bar", "Function"); + cbm_registry_add(reg, "Bar", "proj.pkg.Bar", "Function", false); cbm_resolution_t r = cbm_registry_resolve(reg, "Bar", "proj.unrelated", NULL, NULL, 0); ASSERT_STR_EQ(r.qualified_name, "proj.pkg.Bar"); @@ -27947,8 +28073,8 @@ TEST(registry_confidence_unique_name) { TEST(registry_confidence_suffix_match) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Process", "proj.svcA.Process", "Function"); - cbm_registry_add(reg, "Process", "proj.svcB.Process", "Function"); + cbm_registry_add(reg, "Process", "proj.svcA.Process", "Function", false); + cbm_registry_add(reg, "Process", "proj.svcB.Process", "Function", false); cbm_resolution_t r = cbm_registry_resolve(reg, "Process", "proj.svcA.caller", NULL, NULL, 0); ASSERT_STR_EQ(r.qualified_name, "proj.svcA.Process"); @@ -27964,7 +28090,7 @@ TEST(registry_confidence_suffix_match) { * wrong target — and with one candidate it won the top name-only confidence. */ TEST(registry_receiver_chain_refuses_library_unique_name_issue1893) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "data", "HomeboxUI.PickedFile.data", "Variable"); + cbm_registry_add(reg, "data", "HomeboxUI.PickedFile.data", "Variable", false); cbm_resolution_t r = cbm_registry_resolve(reg, "URLSession.shared.data", "HomeboxUI.Net", NULL, NULL, 0); @@ -27978,8 +28104,8 @@ TEST(registry_receiver_chain_refuses_library_unique_name_issue1893) { * the final name and import distance picks the winner. */ TEST(registry_receiver_chain_refuses_library_suffix_match_issue1893) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "data", "HomeboxUI.PickedFile.data", "Variable"); - cbm_registry_add(reg, "data", "HomeboxUI.Payload.data", "Variable"); + cbm_registry_add(reg, "data", "HomeboxUI.PickedFile.data", "Variable", false); + cbm_registry_add(reg, "data", "HomeboxUI.Payload.data", "Variable", false); cbm_resolution_t r = cbm_registry_resolve(reg, "URLSession.shared.data", "HomeboxUI.Net", NULL, NULL, 0); @@ -27993,7 +28119,7 @@ TEST(registry_receiver_chain_refuses_library_suffix_match_issue1893) { * so Calendar really is in the receiver chain. */ TEST(registry_receiver_chain_keeps_project_extension_issue1893) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "startOfDayUTC", "AuthDTOs.Calendar.startOfDayUTC", "Method"); + cbm_registry_add(reg, "startOfDayUTC", "AuthDTOs.Calendar.startOfDayUTC", "Method", false); cbm_resolution_t r = cbm_registry_resolve(reg, "Calendar.utcGregorian.startOfDayUTC", "HomeboxUI.Stats", NULL, NULL, 0); @@ -28013,7 +28139,7 @@ TEST(registry_receiver_chain_keeps_project_extension_issue1893) { TEST(registry_receiver_chain_admits_factory_chain_into_nested_type) { cbm_registry_t *reg = cbm_registry_new(); cbm_registry_add(reg, "putList", "org.elasticsearch.common.settings.Settings.Builder.putList", - "Method"); + "Method", false); cbm_resolution_t r = cbm_registry_resolve( reg, "Settings.builder().putList", "org.elasticsearch.index.IndexSettings", NULL, NULL, 0); @@ -28024,7 +28150,7 @@ TEST(registry_receiver_chain_admits_factory_chain_into_nested_type) { * .toBuilder().setWarningsHandler -> RequestOptions.Builder.setWarningsHandler. */ cbm_registry_add(reg, "setWarningsHandler", "org.elasticsearch.client.RequestOptions.Builder.setWarningsHandler", - "Method"); + "Method", false); r = cbm_registry_resolve(reg, "RequestOptions.DEFAULT.toBuilder().setWarningsHandler", "org.elasticsearch.client.Rest", NULL, NULL, 0); ASSERT_STR_EQ(r.qualified_name, @@ -28040,7 +28166,7 @@ TEST(registry_receiver_chain_admits_factory_chain_into_nested_type) { TEST(registry_receiver_chain_still_refuses_foreign_root_with_ancestry_rule) { cbm_registry_t *reg = cbm_registry_new(); cbm_registry_add(reg, "getEncoder", "org.elasticsearch.index.codec.DocOffsetsCodec.getEncoder", - "Method"); + "Method", false); cbm_resolution_t r = cbm_registry_resolve( reg, "Base64.getEncoder", "org.elasticsearch.index.IndexSettings", NULL, NULL, 0); @@ -28050,7 +28176,7 @@ TEST(registry_receiver_chain_still_refuses_foreign_root_with_ancestry_rule) { * org.elasticsearch.common.Math.toIntExact through "Math" appearing as an * ancestry segment ONLY IF that segment really is the type — here it is, and * that is the correct outcome (the project's own Math.toIntExact). */ - cbm_registry_add(reg, "toIntExact", "org.elasticsearch.common.Math.toIntExact", "Method"); + cbm_registry_add(reg, "toIntExact", "org.elasticsearch.common.Math.toIntExact", "Method", false); r = cbm_registry_resolve(reg, "Math.toIntExact", "org.elasticsearch.index.IndexSettings", NULL, NULL, 0); ASSERT_STR_EQ(r.qualified_name, "org.elasticsearch.common.Math.toIntExact"); @@ -28063,7 +28189,7 @@ TEST(registry_receiver_chain_still_refuses_foreign_root_with_ancestry_rule) { * must not look at it, or every ordinary vm.load style call would be refused. */ TEST(registry_receiver_chain_ignores_lowercase_root_issue1893) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "load", "HomeboxUI.EntityListViewModel.load", "Method"); + cbm_registry_add(reg, "load", "HomeboxUI.EntityListViewModel.load", "Method", false); cbm_resolution_t r = cbm_registry_resolve(reg, "vm.load", "HomeboxUI.Views", NULL, NULL, 0); ASSERT_STR_EQ(r.qualified_name, "HomeboxUI.EntityListViewModel.load"); @@ -28079,7 +28205,7 @@ TEST(registry_receiver_chain_ignores_lowercase_root_issue1893) { * the CALLS entry point both resolvers share, for a C# file. */ TEST(registry_receiver_chain_refuses_lowercase_type_keyword_issue2121) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "TryParse", "tmp.Decoys.TsidNode.TryParse", "Method"); + cbm_registry_add(reg, "TryParse", "tmp.Decoys.TsidNode.TryParse", "Method", false); cbm_resolution_t r = cbm_registry_resolve_call_lang(reg, "int.TryParse", "tmp.Demo", NULL, NULL, 0, CBM_LANG_CSHARP); @@ -28100,7 +28226,7 @@ TEST(registry_receiver_chain_refuses_lowercase_type_keyword_issue2121) { * (which would pass through for the wrong reason). */ TEST(registry_receiver_chain_keeps_type_keyword_when_chain_matches_issue2121) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "TryParse", "tmp.int.TryParse", "Method"); + cbm_registry_add(reg, "TryParse", "tmp.int.TryParse", "Method", false); cbm_resolution_t r = cbm_registry_resolve_call_lang(reg, "int.TryParse", "tmp.Demo", NULL, NULL, 0, CBM_LANG_CSHARP); @@ -28119,7 +28245,7 @@ TEST(registry_receiver_chain_keeps_type_keyword_when_chain_matches_issue2121) { * pinning the other side of the gate. */ TEST(registry_receiver_chain_keyword_list_gated_to_csharp_issue2121) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "TryParse", "tmp.Decoys.TsidNode.TryParse", "Method"); + cbm_registry_add(reg, "TryParse", "tmp.Decoys.TsidNode.TryParse", "Method", false); cbm_resolution_t r = cbm_registry_resolve_call_lang(reg, "object.TryParse", "tmp.Demo", NULL, NULL, 0, CBM_LANG_PYTHON); @@ -28129,7 +28255,7 @@ TEST(registry_receiver_chain_keyword_list_gated_to_csharp_issue2121) { /* A Go call and the language-blind cbm_registry_resolve read a second * keyword spelling as a value root as well. */ cbm_registry_t *reg2 = cbm_registry_new(); - cbm_registry_add(reg2, "Join", "tmp.Decoys.Joiner.Join", "Method"); + cbm_registry_add(reg2, "Join", "tmp.Decoys.Joiner.Join", "Method", false); cbm_resolution_t r2 = cbm_registry_resolve_call_lang(reg2, "string.Join", "tmp.Demo", NULL, NULL, 0, CBM_LANG_GO); ASSERT_STR_EQ(r2.qualified_name, "tmp.Decoys.Joiner.Join"); @@ -28151,7 +28277,7 @@ TEST(registry_receiver_chain_keyword_list_gated_to_csharp_issue2121) { * words that can only name a type. */ TEST(registry_receiver_chain_csharp_dynamic_variable_still_binds_issue2121) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Invoke", "tmp.Handlers.Handler.Invoke", "Method"); + cbm_registry_add(reg, "Invoke", "tmp.Handlers.Handler.Invoke", "Method", false); cbm_resolution_t r = cbm_registry_resolve_call_lang(reg, "dynamic.Invoke", "tmp.Demo", NULL, NULL, 0, CBM_LANG_CSHARP); @@ -28165,7 +28291,7 @@ TEST(registry_receiver_chain_csharp_dynamic_variable_still_binds_issue2121) { /* An unqualified callee has no chain at all and must pass through unchanged. */ TEST(registry_receiver_chain_ignores_bare_name_issue1893) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "helper", "proj.pkg.helper", "Function"); + cbm_registry_add(reg, "helper", "proj.pkg.helper", "Function", false); cbm_resolution_t r = cbm_registry_resolve(reg, "helper", "proj.other", NULL, NULL, 0); ASSERT_STR_EQ(r.qualified_name, "proj.pkg.helper"); @@ -28177,7 +28303,7 @@ TEST(registry_receiver_chain_ignores_bare_name_issue1893) { TEST(registry_fuzzy_confidence_single) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Handler", "proj.svc.Handler", "Function"); + cbm_registry_add(reg, "Handler", "proj.svc.Handler", "Function", false); cbm_fuzzy_result_t fr = cbm_registry_fuzzy_resolve(reg, "unknownPkg.Handler", "proj.caller", NULL, NULL, 0); @@ -28191,8 +28317,8 @@ TEST(registry_fuzzy_confidence_single) { TEST(registry_fuzzy_confidence_distance) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Process", "proj.svcA.Process", "Function"); - cbm_registry_add(reg, "Process", "proj.svcB.Process", "Function"); + cbm_registry_add(reg, "Process", "proj.svcA.Process", "Function", false); + cbm_registry_add(reg, "Process", "proj.svcB.Process", "Function", false); cbm_fuzzy_result_t fr = cbm_registry_fuzzy_resolve(reg, "unknownPkg.Process", "proj.svcA.other", NULL, NULL, 0); @@ -28206,8 +28332,8 @@ TEST(registry_fuzzy_confidence_distance) { TEST(registry_negative_import_rejects) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Process", "proj.billing.Process", "Function"); - cbm_registry_add(reg, "Process", "proj.handler.Process", "Function"); + cbm_registry_add(reg, "Process", "proj.billing.Process", "Function", false); + cbm_registry_add(reg, "Process", "proj.handler.Process", "Function", false); /* Import only handler's module → should prefer handler */ const char *keys[] = {"handler"}; @@ -28221,7 +28347,7 @@ TEST(registry_negative_import_rejects) { TEST(registry_fuzzy_import_penalty) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Handler", "proj.billing.Handler", "Function"); + cbm_registry_add(reg, "Handler", "proj.billing.Handler", "Function", false); /* Has imports but billing not imported → confidence halved */ const char *keys[] = {"other"}; @@ -28238,7 +28364,7 @@ TEST(registry_fuzzy_import_penalty) { TEST(registry_fuzzy_no_import_map_passthrough) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Handler", "proj.billing.Handler", "Function"); + cbm_registry_add(reg, "Handler", "proj.billing.Handler", "Function", false); /* NULL import map → no penalty, full fuzzy confidence */ cbm_fuzzy_result_t fr = @@ -28252,10 +28378,10 @@ TEST(registry_fuzzy_no_import_map_passthrough) { TEST(registry_find_by_name) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Foo", "proj.pkg.Foo", "Function"); - cbm_registry_add(reg, "Bar", "proj.pkg.Bar", "Function"); - cbm_registry_add(reg, "Foo", "proj.other.Foo", "Function"); - cbm_registry_add(reg, "transform", "proj.utils.DataProcessor.transform", "Method"); + cbm_registry_add(reg, "Foo", "proj.pkg.Foo", "Function", false); + cbm_registry_add(reg, "Bar", "proj.pkg.Bar", "Function", false); + cbm_registry_add(reg, "Foo", "proj.other.Foo", "Function", false); + cbm_registry_add(reg, "transform", "proj.utils.DataProcessor.transform", "Method", false); /* FindByName returns all entries for "Foo" */ const char **foos = NULL; @@ -29215,10 +29341,10 @@ TEST(registry_is_import_reachable) { /* Port of FindEndingWith portion from Go TestFunctionRegistry in pipeline_test.go */ TEST(registry_find_ending_with) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Foo", "proj.pkg.Foo", "Function"); - cbm_registry_add(reg, "Bar", "proj.pkg.Bar", "Function"); - cbm_registry_add(reg, "Foo", "proj.other.Foo", "Function"); - cbm_registry_add(reg, "transform", "proj.utils.DataProcessor.transform", "Method"); + cbm_registry_add(reg, "Foo", "proj.pkg.Foo", "Function", false); + cbm_registry_add(reg, "Bar", "proj.pkg.Bar", "Function", false); + cbm_registry_add(reg, "Foo", "proj.other.Foo", "Function", false); + cbm_registry_add(reg, "transform", "proj.utils.DataProcessor.transform", "Method", false); /* FindEndingWith "DataProcessor.transform" → 1 match */ const char **matches = NULL; @@ -34486,6 +34612,8 @@ SUITE(pipeline) { RUN_TEST(pipeline_call_reference_sequential_parallel_edge_set_parity); RUN_TEST(pipeline_complexity_props_independent_of_worker_order); RUN_TEST(pipeline_incremental_cross_file_call_reference_matches_fresh_full); + RUN_TEST(pipeline_registry_ranks_by_extractor_verdict_full_and_incremental); + RUN_TEST(pipeline_registry_ranks_test_doubles_last_full_and_incremental); RUN_TEST(pipeline_incremental_changed_target_invalidates_stale_inbound_call_reference); RUN_TEST(pipeline_incremental_parallel_registry_nodes_advance_shared_ids); #if defined(CBM_INCREMENTAL_TEST_API) && CBM_INCREMENTAL_TEST_API diff --git a/tests/test_registry.c b/tests/test_registry.c index 6e0e18f3a..10c9ef8e5 100644 --- a/tests/test_registry.c +++ b/tests/test_registry.c @@ -193,7 +193,7 @@ TEST(registry_free_null) { TEST(registry_add_and_exists) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "main", "proj.cmd.main", "Function"); + cbm_registry_add(r, "main", "proj.cmd.main", "Function", false); ASSERT_EQ(cbm_registry_size(r), 1); ASSERT_TRUE(cbm_registry_exists(r, "proj.cmd.main")); ASSERT_FALSE(cbm_registry_exists(r, "proj.cmd.other")); @@ -201,10 +201,114 @@ TEST(registry_add_and_exists) { PASS(); } +/* REG_TEST_PENALTY had no direct coverage. Of the registry call sites in this + * suite, none registered a symbol the scorer would treat as a test, so the + * layer that keeps production code ahead of test code was never exercised. + * These three pin it, and pin where the verdict comes from. */ +TEST(registry_prefers_non_test_over_test_candidate) { + cbm_registry_t *r = cbm_registry_new(); + cbm_registry_add(r, "helper", "proj.app.helper", "Function", false); + cbm_registry_add(r, "helper", "proj.harness.helper", "Function", true); + + cbm_resolution_t res = cbm_registry_resolve(r, "helper", "proj.other", NULL, NULL, 0); + ASSERT_STR_EQ(res.qualified_name, "proj.app.helper"); + cbm_registry_free(r); + PASS(); +} + +/* The verdict is the caller's, not the QN's spelling. "latest" contains "test", + * so the scan this replaces read ordinary production code as a test and pushed + * it below a real one; "harness" spells nothing, so the same scan read a test + * as production. Both verdicts here are the extractor's, and both are right. */ +TEST(registry_test_verdict_comes_from_the_caller_not_the_qn) { + cbm_registry_t *r = cbm_registry_new(); + cbm_registry_add(r, "parse", "proj.latest.parse", "Function", false); + cbm_registry_add(r, "parse", "proj.harness.parse", "Function", true); + + cbm_resolution_t res = cbm_registry_resolve(r, "parse", "proj.other", NULL, NULL, 0); + ASSERT_STR_EQ(res.qualified_name, "proj.latest.parse"); + cbm_registry_free(r); + PASS(); +} + +/* A bucket of one is returned whatever its verdict: the penalty orders + * candidates, it does not veto them. */ +TEST(registry_sole_test_candidate_still_resolves) { + cbm_registry_t *r = cbm_registry_new(); + cbm_registry_add(r, "only", "proj.harness.only", "Function", true); + + cbm_resolution_t res = cbm_registry_resolve(r, "only", "proj.other", NULL, NULL, 0); + ASSERT_STR_EQ(res.qualified_name, "proj.harness.only"); + cbm_registry_free(r); + PASS(); +} + +/* The test-double path rule: whole directory segments named fake, mock, mocks, + * testing or testutil, and Go files named mock_*.go or fake_*.go. Anchored, so + * the substrings the old QN scan tripped on (inspect, latest, contest) and + * near-miss names (fakeroot, mockery, testingutil) stay ordinary code. */ +TEST(registry_test_double_path_table) { + static const char *const doubles[] = { + "pkg/client/fake/clientset.go", + "fake/x.go", + "internal/mocks/store.go", + "pkg/mock/store.go", + "pkg/testing/fixtures.go", + "internal/testutil/helpers.go", + "pkg/mock_store.go", + "pkg/fake_clock.go", + "mock_store.go", + "src/fake/deep/nested/util.py", + }; + static const char *const ordinary[] = { + "pkg/inspect/x.go", "pkg/latest/x.go", "pkg/contest/x.go", "pkg/fakeroot/x.go", + "pkg/mockery/x.go", "pkg/testingutil/x.go", "pkg/mocking.go", "pkg/mock_store.py", + "pkg/fake_clock.golang", "pkg/fake.go", "pkg/x/fake", "pkg/Fake/x.go", + "pkg/myfake/x.go", "pkg/fake-client/x.go", "testing.go", "", + }; + char why[256]; + for (size_t i = 0; i < sizeof(doubles) / sizeof(doubles[0]); i++) { + if (!cbm_registry_test_double_path(doubles[i])) { + snprintf(why, sizeof(why), "expected a test-double path: %s", doubles[i]); + FAIL(why); + } + } + for (size_t i = 0; i < sizeof(ordinary) / sizeof(ordinary[0]); i++) { + if (cbm_registry_test_double_path(ordinary[i])) { + snprintf(why, sizeof(why), "expected an ordinary path: %s", ordinary[i]); + FAIL(why); + } + } + ASSERT_FALSE(cbm_registry_test_double_path(NULL)); + PASS(); +} + +/* The Swift overload choosers score with the verdicts stored at registration, + * carried through their own filtered lists. Both overloads fit the call and tie + * on proximity; without the verdicts the smaller QN (the test double) would win + * the tie. */ +TEST(registry_swift_overload_choice_uses_stored_verdicts) { + cbm_registry_t *r = cbm_registry_new(); + const char *double_qn = "proj.Fixtures.Store.load(path:String)"; + const char *real_qn = "proj.Zeta.Store.load(path:String)"; + cbm_registry_add(r, "load", double_qn, "Method", true); + cbm_registry_add(r, "load", real_qn, "Method", false); + cbm_registry_set_swift_signature(r, double_qn, 0, 1, NULL); + cbm_registry_set_swift_signature(r, real_qn, 0, 1, NULL); + + CBMCallArg arg = {.keyword = "path"}; + CBMCall call = {.callee_name = "load", .args = &arg, .arg_count = 1}; + const char *out[4] = {0}; + ASSERT_EQ(cbm_registry_swift_candidates(r, &call, "proj.Caller", NULL, 0, out, 4), 1); + ASSERT_STR_EQ(out[0], real_qn); + cbm_registry_free(r); + PASS(); +} + TEST(registry_label_of) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "Foo", "proj.pkg.Foo", "Class"); - cbm_registry_add(r, "bar", "proj.pkg.bar", "Function"); + cbm_registry_add(r, "Foo", "proj.pkg.Foo", "Class", false); + cbm_registry_add(r, "bar", "proj.pkg.bar", "Function", false); ASSERT_STR_EQ(cbm_registry_label_of(r, "proj.pkg.Foo"), "Class"); ASSERT_STR_EQ(cbm_registry_label_of(r, "proj.pkg.bar"), "Function"); @@ -218,10 +322,10 @@ TEST(registry_label_of) { * method's fenced `#field` twin when the class has one; nothing else moves. */ TEST(registry_value_target_field_twin) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "count", "p.C.count", "Method"); - cbm_registry_add(r, "count", "p.C.count#field", "Field"); - cbm_registry_add(r, "size", "p.C.size", "Method"); - cbm_registry_add(r, "name", "p.C.name", "Field"); + cbm_registry_add(r, "count", "p.C.count", "Method", false); + cbm_registry_add(r, "count", "p.C.count#field", "Field", false); + cbm_registry_add(r, "size", "p.C.size", "Method", false); + cbm_registry_add(r, "name", "p.C.name", "Field", false); ASSERT_STR_EQ(cbm_registry_value_target(r, "p.C.count"), "p.C.count#field"); ASSERT_STR_EQ(cbm_registry_value_target(r, "p.C.size"), "p.C.size"); ASSERT_STR_EQ(cbm_registry_value_target(r, "p.C.name"), "p.C.name"); @@ -237,9 +341,9 @@ TEST(registry_value_target_field_twin) { TEST(registry_find_by_name) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "main", "proj.cmd.main", "Function"); - cbm_registry_add(r, "main", "proj.srv.main", "Function"); - cbm_registry_add(r, "helper", "proj.util.helper", "Function"); + cbm_registry_add(r, "main", "proj.cmd.main", "Function", false); + cbm_registry_add(r, "main", "proj.srv.main", "Function", false); + cbm_registry_add(r, "helper", "proj.util.helper", "Function", false); const char **out = NULL; int count = 0; @@ -258,8 +362,8 @@ TEST(registry_find_by_name) { TEST(registry_no_duplicates) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "main", "proj.cmd.main", "Function"); - cbm_registry_add(r, "main", "proj.cmd.main", "Function"); /* duplicate */ + cbm_registry_add(r, "main", "proj.cmd.main", "Function", false); + cbm_registry_add(r, "main", "proj.cmd.main", "Function", false); /* duplicate */ ASSERT_EQ(cbm_registry_size(r), 1); const char **out = NULL; @@ -275,7 +379,7 @@ TEST(registry_no_duplicates) { TEST(resolve_same_module) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "helper", "proj.pkg.service.helper", "Function"); + cbm_registry_add(r, "helper", "proj.pkg.service.helper", "Function", false); /* Call "helper" from the same module → should resolve */ cbm_resolution_t res = cbm_registry_resolve(r, "helper", "proj.pkg.service", NULL, NULL, 0); @@ -297,7 +401,7 @@ TEST(resolve_same_module) { * (false only for a this/super receiver). */ TEST(resolve_same_module_only_on_self_receiver) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "get", "proj.pkg.service.get", "Function"); + cbm_registry_add(r, "get", "proj.pkg.service.get", "Function", false); const CBMLanguage js = CBM_LANG_JAVASCRIPT; const char *mod = "proj.pkg.service"; CBMImport axios_import = {.local_name = "axios", .module_path = "axios", .is_default = true}; @@ -457,11 +561,11 @@ TEST(resolve_qualified_receiver_awareness) { const CBMLanguage js = CBM_LANG_JAVASCRIPT; /* 1. Register a Method candidate under Class Service: proj.pkg.Service.get */ - cbm_registry_add(r, "Service", "proj.pkg.Service", "Class"); - cbm_registry_add(r, "get", "proj.pkg.Service.get", "Method"); + cbm_registry_add(r, "Service", "proj.pkg.Service", "Class", false); + cbm_registry_add(r, "get", "proj.pkg.Service.get", "Method", false); /* 2. Register a Module axios */ - cbm_registry_add(r, "axios", "proj.axios", "Module"); + cbm_registry_add(r, "axios", "proj.axios", "Module", false); /* Test Case A: Unrelated receiver that is a known module/type -> name-only * guess, dropped by the member guard */ @@ -513,7 +617,7 @@ TEST(resolve_qualified_receiver_awareness) { /* Test Case F: Function candidate behind an unrelated receiver -> name-only * guess, dropped by the member guard */ /* Register unique Function helper.compute */ - cbm_registry_add(r, "compute", "proj.pkg.helper.compute", "Function"); + cbm_registry_add(r, "compute", "proj.pkg.helper.compute", "Function", false); { cbm_resolution_t res = cbm_registry_resolve(r, "unrelated.compute", "proj.app", NULL, NULL, 0); @@ -530,9 +634,9 @@ TEST(resolve_qualified_receiver_awareness) { * Foo::Bar::sub()) where the same sub name exists in multiple packages. */ TEST(resolve_qualified_disambiguates_same_name) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "save", "proj.lib.App.Alpha.save", "Function"); - cbm_registry_add(r, "save", "proj.lib.App.Beta.save", "Function"); - cbm_registry_add(r, "save", "proj.lib.App.Gamma.save", "Function"); + cbm_registry_add(r, "save", "proj.lib.App.Alpha.save", "Function", false); + cbm_registry_add(r, "save", "proj.lib.App.Beta.save", "Function", false); + cbm_registry_add(r, "save", "proj.lib.App.Gamma.save", "Function", false); /* Each fully-qualified call routes to its own package. */ cbm_resolution_t a = @@ -573,8 +677,8 @@ TEST(resolve_qualified_disambiguates_same_name) { * pick arbitrarily under the high-confidence qualified_suffix strategy. */ TEST(resolve_qualified_ambiguous_tail_falls_through) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "run", "proj.svcA.Foo.Bar.run", "Function"); - cbm_registry_add(r, "run", "proj.svcB.Foo.Bar.run", "Function"); + cbm_registry_add(r, "run", "proj.svcA.Foo.Bar.run", "Function", false); + cbm_registry_add(r, "run", "proj.svcB.Foo.Bar.run", "Function", false); /* "Foo::Bar::run" tail matches BOTH candidates → not unique → fall through. */ cbm_resolution_t res = @@ -592,7 +696,7 @@ TEST(resolve_qualified_ambiguous_tail_falls_through) { * `add` callee could ever reach it. */ TEST(registry_indexes_by_passed_name_not_qn_tail) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "add", "proj.lib.add#cfg(test)", "Function"); + cbm_registry_add(r, "add", "proj.lib.add#cfg(test)", "Function", false); cbm_resolution_t res = cbm_registry_resolve(r, "add", "proj.lib.caller", NULL, NULL, 0); ASSERT_STR_EQ(res.qualified_name, "proj.lib.add#cfg(test)"); @@ -617,9 +721,9 @@ TEST(registry_indexes_by_passed_name_not_qn_tail) { * lookup a bare module reference needs. */ TEST(registry_indexes_a_dotted_name_under_its_tail_too) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "resource.aws_instance.web", "proj.main.resource.aws_instance.web", - "Class"); - cbm_registry_add(r, "helper.py", "proj.pkg.helper", "Module"); + cbm_registry_add(r, "resource.aws_instance.web", "proj.main.resource.aws_instance.web", "Class", + false); + cbm_registry_add(r, "helper.py", "proj.pkg.helper", "Module", false); cbm_resolution_t tail = cbm_registry_resolve(r, "web", "proj.main", NULL, NULL, 0); ASSERT_STR_EQ(tail.qualified_name, "proj.main.resource.aws_instance.web"); @@ -637,7 +741,7 @@ TEST(registry_indexes_a_dotted_name_under_its_tail_too) { TEST(resolve_import_map) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "Process", "proj.pkg.worker.Process", "Function"); + cbm_registry_add(r, "Process", "proj.pkg.worker.Process", "Function", false); /* Import map: "worker" → "proj.pkg.worker" */ const char *keys[] = {"worker"}; @@ -660,9 +764,9 @@ TEST(resolve_import_map) { * file. Regression for the @/lib/auth-style import case. */ TEST(resolve_import_map_bare_function) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "requireAdmin", "proj.lib.authorization.requireAdmin", "Function"); + cbm_registry_add(r, "requireAdmin", "proj.lib.authorization.requireAdmin", "Function", false); /* Same name in another module — without the fix this is what gets picked. */ - cbm_registry_add(r, "requireAdmin", "proj.lib.users.requireAdmin", "Function"); + cbm_registry_add(r, "requireAdmin", "proj.lib.users.requireAdmin", "Function", false); const char *keys[] = {"requireAdmin"}; const char *vals[] = {"proj.lib.authorization"}; @@ -683,7 +787,7 @@ TEST(resolve_import_map_bare_function) { * Regression for #875. */ TEST(resolve_import_map_bare_alias) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "scan_bash", "proj.security_scan.scan_bash", "Function"); + cbm_registry_add(r, "scan_bash", "proj.security_scan.scan_bash", "Function", false); /* Import map: alias "_scan_bash" → FULL SYMBOL QN (not the module). */ const char *keys[] = {"_scan_bash"}; const char *vals[] = {"proj.security_scan.scan_bash"}; @@ -700,10 +804,10 @@ TEST(resolve_import_map_bare_alias) { * already on main via #875/#979; this locks the Yui G1 shape. */ TEST(resolve_import_map_aliased_from_import) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "execute", "proj.services.satori_bridge.gate.execute", "Function"); + cbm_registry_add(r, "execute", "proj.services.satori_bridge.gate.execute", "Function", false); /* Alias ghost must not win if somehow registered. */ cbm_registry_add(r, "bridge_execute", "proj.services.satori_bridge.gate.bridge_execute", - "Function"); + "Function", false); const char *keys[] = {"bridge_execute"}; const char *vals[] = {"proj.services.satori_bridge.gate.execute"}; @@ -719,7 +823,7 @@ TEST(resolve_import_map_aliased_from_import) { TEST(resolve_unique_name) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "UniqueFunc", "proj.deep.path.UniqueFunc", "Function"); + cbm_registry_add(r, "UniqueFunc", "proj.deep.path.UniqueFunc", "Function", false); /* Call "UniqueFunc" — only one candidate project-wide */ cbm_resolution_t res = @@ -733,7 +837,7 @@ TEST(resolve_unique_name) { TEST(resolve_unresolved) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "foo", "proj.pkg.foo", "Function"); + cbm_registry_add(r, "foo", "proj.pkg.foo", "Function", false); /* Call "nonexistent" — not in registry */ cbm_resolution_t res = cbm_registry_resolve(r, "nonexistent", "proj.other", NULL, NULL, 0); @@ -750,7 +854,7 @@ TEST(resolve_many_nodes) { char name[32], qn[64]; snprintf(name, sizeof(name), "func_%d", i); snprintf(qn, sizeof(qn), "proj.pkg.func_%d", i); - cbm_registry_add(r, name, qn, "Function"); + cbm_registry_add(r, name, qn, "Function", false); } ASSERT_EQ(cbm_registry_size(r), 500); @@ -788,8 +892,8 @@ TEST(confidence_band_speculative) { TEST(resolve_suffix_match) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "Process", "proj.svcA.Process", "Function"); - cbm_registry_add(r, "Process", "proj.svcB.Process", "Function"); + cbm_registry_add(r, "Process", "proj.svcA.Process", "Function", false); + cbm_registry_add(r, "Process", "proj.svcB.Process", "Function", false); /* Caller in svcA — should prefer svcA via import distance */ cbm_resolution_t res = cbm_registry_resolve(r, "Process", "proj.svcA.caller", NULL, NULL, 0); @@ -812,7 +916,7 @@ TEST(resolve_caps_unresolvably_ambiguous_names) { for (int i = 0; i < 300; i++) { char qn[64]; snprintf(qn, sizeof(qn), "proj.mod%d.flags", i); - cbm_registry_add(r, "flags", qn, "Variable"); + cbm_registry_add(r, "flags", qn, "Variable", false); } cbm_resolution_t res = cbm_registry_resolve(r, "flags", "proj.other.caller", NULL, NULL, 0); ASSERT_TRUE(res.qualified_name == NULL || res.qualified_name[0] == '\0'); @@ -830,7 +934,7 @@ TEST(resolve_caps_unresolvably_ambiguous_names) { TEST(resolve_import_map_suffix) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "Foo", "proj.other.sub.Foo", "Function"); + cbm_registry_add(r, "Foo", "proj.other.sub.Foo", "Function", false); const char *keys[] = {"other"}; const char *vals[] = {"proj.other"}; @@ -852,7 +956,7 @@ TEST(resolve_is_import_reachable) { /* Test import reachability through unique_name confidence penalty. * is_import_reachable is static, so we test it indirectly. */ cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "Helper", "proj.shared.utils.Helper", "Function"); + cbm_registry_add(r, "Helper", "proj.shared.utils.Helper", "Function", false); /* With import covering the module → full confidence */ const char *keys1[] = {"utils"}; @@ -875,7 +979,7 @@ TEST(resolve_is_import_reachable) { TEST(resolve_import_reachable_prefix) { /* "proj.handler.sub.Process" should be reachable via import "proj.handler" */ cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "Process", "proj.handler.sub.Process", "Function"); + cbm_registry_add(r, "Process", "proj.handler.sub.Process", "Function", false); const char *keys[] = {"handler"}; const char *vals[] = {"proj.handler"}; @@ -929,8 +1033,8 @@ TEST(reach_cache_memo_matches_uncached_across_files) { TEST(negative_import_rejects_unimported) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "Process", "proj.billing.Process", "Function"); - cbm_registry_add(r, "Process", "proj.handler.Process", "Function"); + cbm_registry_add(r, "Process", "proj.billing.Process", "Function", false); + cbm_registry_add(r, "Process", "proj.handler.Process", "Function", false); /* Import only handler's module — suffix_match should prefer handler */ const char *keys[] = {"handler"}; @@ -946,8 +1050,8 @@ TEST(negative_import_rejects_unimported) { TEST(fuzzy_resolve_single_candidate) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "CreateOrder", "svcA.handlers.CreateOrder", "Function"); - cbm_registry_add(r, "ValidateOrder", "svcB.validators.ValidateOrder", "Function"); + cbm_registry_add(r, "CreateOrder", "svcA.handlers.CreateOrder", "Function", false); + cbm_registry_add(r, "ValidateOrder", "svcB.validators.ValidateOrder", "Function", false); /* FuzzyResolve should find by simple name even with unknown prefix */ cbm_fuzzy_result_t fr = @@ -961,7 +1065,7 @@ TEST(fuzzy_resolve_single_candidate) { TEST(fuzzy_resolve_nonexistent) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "CreateOrder", "svcA.handlers.CreateOrder", "Function"); + cbm_registry_add(r, "CreateOrder", "svcA.handlers.CreateOrder", "Function", false); cbm_fuzzy_result_t fr = cbm_registry_fuzzy_resolve(r, "NonExistent", "svcC.caller", NULL, NULL, 0); @@ -973,8 +1077,8 @@ TEST(fuzzy_resolve_nonexistent) { TEST(fuzzy_resolve_multiple_best_by_distance) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "Process", "svcA.handlers.Process", "Function"); - cbm_registry_add(r, "Process", "svcB.handlers.Process", "Function"); + cbm_registry_add(r, "Process", "svcA.handlers.Process", "Function", false); + cbm_registry_add(r, "Process", "svcB.handlers.Process", "Function", false); /* Caller in svcA — should prefer svcA */ cbm_fuzzy_result_t fr = @@ -993,7 +1097,7 @@ TEST(fuzzy_resolve_multiple_best_by_distance) { TEST(fuzzy_resolve_deep_name_extraction) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "DoWork", "myproject.utils.DoWork", "Function"); + cbm_registry_add(r, "DoWork", "myproject.utils.DoWork", "Function", false); /* Deeply qualified callee — should extract "DoWork" */ cbm_fuzzy_result_t fr = @@ -1018,7 +1122,7 @@ TEST(fuzzy_resolve_empty_registry) { TEST(fuzzy_resolve_confidence_single) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "Handler", "proj.svc.Handler", "Function"); + cbm_registry_add(r, "Handler", "proj.svc.Handler", "Function", false); cbm_fuzzy_result_t fr = cbm_registry_fuzzy_resolve(r, "unknownPkg.Handler", "proj.caller", NULL, NULL, 0); @@ -1032,8 +1136,8 @@ TEST(fuzzy_resolve_confidence_single) { TEST(fuzzy_resolve_confidence_distance) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "Process", "proj.svcA.Process", "Function"); - cbm_registry_add(r, "Process", "proj.svcB.Process", "Function"); + cbm_registry_add(r, "Process", "proj.svcA.Process", "Function", false); + cbm_registry_add(r, "Process", "proj.svcB.Process", "Function", false); cbm_fuzzy_result_t fr = cbm_registry_fuzzy_resolve(r, "unknownPkg.Process", "proj.svcA.other", NULL, NULL, 0); @@ -1047,7 +1151,7 @@ TEST(fuzzy_resolve_confidence_distance) { TEST(fuzzy_penalty_unreachable_import) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "Handler", "proj.billing.Handler", "Function"); + cbm_registry_add(r, "Handler", "proj.billing.Handler", "Function", false); /* Import for different module → confidence halved */ const char *keys[] = {"other"}; @@ -1064,7 +1168,7 @@ TEST(fuzzy_penalty_unreachable_import) { TEST(fuzzy_no_import_map_passthrough) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "Handler", "proj.billing.Handler", "Function"); + cbm_registry_add(r, "Handler", "proj.billing.Handler", "Function", false); /* nil import map → full confidence */ cbm_fuzzy_result_t fr = @@ -1173,8 +1277,8 @@ TEST(registry_tie_break_is_independent_of_registration_order) { cbm_registry_t *forward = cbm_registry_new(); cbm_registry_t *backward = cbm_registry_new(); for (int i = 0; i < n; i++) { - cbm_registry_add(forward, "dev_name", cands[i], labels[i]); - cbm_registry_add(backward, "dev_name", cands[n - 1 - i], labels[n - 1 - i]); + cbm_registry_add(forward, "dev_name", cands[i], labels[i], false); + cbm_registry_add(backward, "dev_name", cands[n - 1 - i], labels[n - 1 - i], false); } /* A far-away caller with no imports: every candidate scores the same. */ @@ -1192,10 +1296,10 @@ TEST(registry_tie_break_is_independent_of_registration_order) { /* Equal kind: the smaller QN, from either order. */ cbm_registry_t *lex_a = cbm_registry_new(); cbm_registry_t *lex_b = cbm_registry_new(); - cbm_registry_add(lex_a, "sg_next", "proj.tools.virtio.scatterlist.sg_next", "Function"); - cbm_registry_add(lex_a, "sg_next", "proj.include.linux.scatterlist.sg_next", "Function"); - cbm_registry_add(lex_b, "sg_next", "proj.include.linux.scatterlist.sg_next", "Function"); - cbm_registry_add(lex_b, "sg_next", "proj.tools.virtio.scatterlist.sg_next", "Function"); + cbm_registry_add(lex_a, "sg_next", "proj.tools.virtio.scatterlist.sg_next", "Function", false); + cbm_registry_add(lex_a, "sg_next", "proj.include.linux.scatterlist.sg_next", "Function", false); + cbm_registry_add(lex_b, "sg_next", "proj.include.linux.scatterlist.sg_next", "Function", false); + cbm_registry_add(lex_b, "sg_next", "proj.tools.virtio.scatterlist.sg_next", "Function", false); cbm_resolution_t la = cbm_registry_resolve(lex_a, "sg_next", "proj.drivers.scsi.arm_scsi", NULL, NULL, 0); cbm_resolution_t lb = @@ -1205,9 +1309,9 @@ TEST(registry_tie_break_is_independent_of_registration_order) { /* Proximity outranks the tie order: the sibling wins over a shallower stranger. */ cbm_registry_t *proximity = cbm_registry_new(); - cbm_registry_add(proximity, "vnic_rq_free", "proj.lib.vnic_rq_free", "Function"); + cbm_registry_add(proximity, "vnic_rq_free", "proj.lib.vnic_rq_free", "Function", false); cbm_registry_add(proximity, "vnic_rq_free", "proj.drivers.scsi.fnic.vnic_rq.vnic_rq_free", - "Function"); + "Function", false); cbm_resolution_t nr = cbm_registry_resolve(proximity, "vnic_rq_free", "proj.drivers.scsi.fnic.fnic_res", NULL, NULL, 0); ASSERT_STR_EQ(nr.qualified_name, "proj.drivers.scsi.fnic.vnic_rq.vnic_rq_free"); @@ -1231,13 +1335,14 @@ TEST(registry_tie_prefers_callable_over_property) { cbm_registry_add( r, "where", "proj.exposed-core.src.main.kotlin.org.jetbrains.exposed.v1.core.AbstractQuery.where", - "Variable"); + "Variable", false); cbm_registry_add(r, "where", "proj.exposed-jdbc.src.main.kotlin.org.jetbrains.exposed.v1.jdbc.Query.where", - "Method"); + "Method", false); /* A deeper method still beats a shallower field: kind decides before order. */ - cbm_registry_add(r, "limit", "proj.exposed-core.Table.limit", "Field"); - cbm_registry_add(r, "limit", "proj.exposed-jdbc.src.main.kotlin.jdbc.Query.limit", "Method"); + cbm_registry_add(r, "limit", "proj.exposed-core.Table.limit", "Field", false); + cbm_registry_add(r, "limit", "proj.exposed-jdbc.src.main.kotlin.jdbc.Query.limit", "Method", + false); const char *caller = "proj.exposed-tests.src.test.kotlin.org.jetbrains.exposed.v1.tests.SelectTests"; @@ -1258,8 +1363,8 @@ TEST(registry_tie_prefers_callable_over_property) { * order alone, which is still a pure function of the candidate set (O9). */ TEST(registry_tie_ignores_nesting_depth) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "filter", "proj.django.template.library.Library.filter", "Method"); - cbm_registry_add(r, "filter", "proj.django.db.models.query.QuerySet.filter", "Method"); + cbm_registry_add(r, "filter", "proj.django.template.library.Library.filter", "Method", false); + cbm_registry_add(r, "filter", "proj.django.db.models.query.QuerySet.filter", "Method", false); cbm_resolution_t f = cbm_registry_resolve(r, "filter", "proj.tests.lookup.tests", NULL, NULL, 0); ASSERT_STR_EQ(f.qualified_name, "proj.django.db.models.query.QuerySet.filter"); @@ -1281,23 +1386,25 @@ TEST(registry_tie_prefers_csharp_implementation_over_ref_stub) { cbm_registry_add_lang( r, "MemoryStream", "proj.src.libraries.System.Runtime.ref.System.Runtime.MemoryStream.MemoryStream", "Method", - CBM_LANG_CSHARP); + CBM_LANG_CSHARP, false); cbm_registry_add_lang( r, "MemoryStream", "proj.src.libraries.System.Private.CoreLib.src.System.IO.MemoryStream.MemoryStream", - "Method", CBM_LANG_CSHARP); + "Method", CBM_LANG_CSHARP, false); /* Equal depth, "ref" sorts before "src" (WithCancellation). */ cbm_registry_add_lang(r, "WithCancellation", "proj.src.libraries.System.Linq.Parallel.ref.System.Linq." "ParallelEnumerable.WithCancellation", - "Method", CBM_LANG_CSHARP); + "Method", CBM_LANG_CSHARP, false); cbm_registry_add_lang(r, "WithCancellation", "proj.src.libraries.System.Linq.Parallel.src.System.Linq." "ParallelEnumerable.WithCancellation", - "Method", CBM_LANG_CSHARP); + "Method", CBM_LANG_CSHARP, false); /* Not C#: a `ref` directory gets no special treatment. */ - cbm_registry_add_lang(r, "resolve", "proj.lib.ref.Resolver.resolve", "Method", CBM_LANG_JAVA); - cbm_registry_add_lang(r, "resolve", "proj.lib.src.Resolver.resolve", "Method", CBM_LANG_JAVA); + cbm_registry_add_lang(r, "resolve", "proj.lib.ref.Resolver.resolve", "Method", CBM_LANG_JAVA, + false); + cbm_registry_add_lang(r, "resolve", "proj.lib.src.Resolver.resolve", "Method", CBM_LANG_JAVA, + false); const char *caller = "proj.src.libraries.System.Security.Cryptography.src.CapiHelper"; cbm_resolution_t ms = cbm_registry_resolve(r, "MemoryStream", caller, NULL, NULL, 0); @@ -1353,10 +1460,10 @@ static cbm_resolution_t xl_resolve(const cbm_registry_t *r, CBMLanguage lang, co TEST(name_guess_vetoed_across_languages) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add_lang(r, "get", "builtins.dict.get", "Method", CBM_LANG_PYTHON); - cbm_registry_add_lang(r, "patch", "proj.web.Panel.patch", "Function", CBM_LANG_TSX); - cbm_registry_add_lang(r, "patch", "proj.api.client.patch", "Function", CBM_LANG_PYTHON); - cbm_registry_add(r, "legacy", "proj.x.legacy", "Function"); /* unknown language */ + cbm_registry_add_lang(r, "get", "builtins.dict.get", "Method", CBM_LANG_PYTHON, false); + cbm_registry_add_lang(r, "patch", "proj.web.Panel.patch", "Function", CBM_LANG_TSX, false); + cbm_registry_add_lang(r, "patch", "proj.api.client.patch", "Function", CBM_LANG_PYTHON, false); + cbm_registry_add(r, "legacy", "proj.x.legacy", "Function", false); /* unknown language */ /* JS `app.get`: the only "get" is Python's -> EMPTY, not unique_name. */ cbm_resolution_t res = xl_resolve(r, CBM_LANG_JAVASCRIPT, "app.get", "proj.app"); @@ -1402,10 +1509,11 @@ TEST(name_guess_vetoed_across_languages) { TEST(same_module_vetoed_across_languages) { cbm_registry_t *r = cbm_registry_new(); cbm_registry_add_lang(r, "reducer_worker", "proj.tests.cov.reducer_worker", "Function", - CBM_LANG_PYTHON); + CBM_LANG_PYTHON, false); cbm_registry_add_lang(r, "reducer_worker", "proj.other.reducer_worker", "Function", - CBM_LANG_PYTHON); - cbm_registry_add_lang(r, "stat_child", "proj.tests.cov.stat_child", "Function", CBM_LANG_C); + CBM_LANG_PYTHON, false); + cbm_registry_add_lang(r, "stat_child", "proj.tests.cov.stat_child", "Function", CBM_LANG_C, + false); cbm_resolution_t res = cbm_registry_resolve(r, "reducer_worker", "proj.tests.cov", NULL, NULL, 0); @@ -1562,11 +1670,11 @@ TEST(c_bare_call_does_not_weigh_field_namesakes) { * A bare C call never names a member, so its Field namesakes do not * compete: the function is the unique name it was in v0.11.0. */ cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add_lang(r, "put_page", "proj.include.linux.mm.put_page", "Function", - CBM_LANG_CPP); + cbm_registry_add_lang(r, "put_page", "proj.include.linux.mm.put_page", "Function", CBM_LANG_CPP, + false); cbm_registry_add_lang(r, "put_page", "proj.arch.arm64.include.asm.kvm_pgtable.kvm_pgtable_mm_ops.put_page", - "Field", CBM_LANG_CPP); + "Field", CBM_LANG_CPP, false); const char *mod = "proj.arch.arm64.kernel.elfcore"; cbm_resolution_t res = cbm_registry_resolve_call_lang(r, "put_page", mod, NULL, NULL, 0, CBM_LANG_C); @@ -1581,13 +1689,14 @@ TEST(c_bare_call_does_not_weigh_field_namesakes) { /* A name only members carry is no bare C call target at all -- neither a * struct member nor a C++ method: redis's libc `read()` became the unique * name of a Qt adapter's `read` Method once only Fields were set aside. */ - cbm_registry_add_lang(r, "on_close", "proj.net.sock.sock_ops.on_close", "Field", CBM_LANG_C); + cbm_registry_add_lang(r, "on_close", "proj.net.sock.sock_ops.on_close", "Field", CBM_LANG_C, + false); res = cbm_registry_resolve_call_lang(r, "on_close", mod, NULL, NULL, 0, CBM_LANG_C); ASSERT_TRUE(res.qualified_name == NULL || res.qualified_name[0] == '\0'); cbm_registry_add_lang(r, "read", "proj.deps.hiredis.adapters.qt.RedisQtAdapter.read", "Method", - CBM_LANG_CPP); - cbm_registry_add_lang(r, "read", "proj.src.connection.ConnectionType.read", "Field", - CBM_LANG_C); + CBM_LANG_CPP, false); + cbm_registry_add_lang(r, "read", "proj.src.connection.ConnectionType.read", "Field", CBM_LANG_C, + false); res = cbm_registry_resolve_call_lang(r, "read", mod, NULL, NULL, 0, CBM_LANG_C); ASSERT_TRUE(res.qualified_name == NULL || res.qualified_name[0] == '\0'); /* C++ still reaches the method by name. */ @@ -1652,12 +1761,12 @@ TEST(unique_field_qn_needs_exactly_one_field_of_that_name) { * the same name is not a candidate for a member at all. */ cbm_registry_t *r = cbm_registry_new(); ASSERT_NOT_NULL(r); - cbm_registry_add(r, "only_here", "proj.a.Alpha.only_here", "Field"); - cbm_registry_add(r, "count", "proj.a.Alpha.count", "Field"); - cbm_registry_add(r, "count", "proj.b.Beta.count", "Field"); - cbm_registry_add(r, "close", "proj.cb.Callbacks.close", "Field"); - cbm_registry_add(r, "close", "proj.io.close", "Function"); - cbm_registry_add(r, "lonely_fn", "proj.io.lonely_fn", "Function"); + cbm_registry_add(r, "only_here", "proj.a.Alpha.only_here", "Field", false); + cbm_registry_add(r, "count", "proj.a.Alpha.count", "Field", false); + cbm_registry_add(r, "count", "proj.b.Beta.count", "Field", false); + cbm_registry_add(r, "close", "proj.cb.Callbacks.close", "Field", false); + cbm_registry_add(r, "close", "proj.io.close", "Function", false); + cbm_registry_add(r, "lonely_fn", "proj.io.lonely_fn", "Function", false); const char *only = cbm_registry_unique_field_qn(r, "only_here"); ASSERT_NOT_NULL(only); @@ -1746,7 +1855,7 @@ TEST(generated_member_registers_by_qn_only) { ASSERT_EQ(n, 0); cbm_resolution_t res = cbm_registry_resolve(r, "v.y", "proj.app.Caller", NULL, NULL, 0); ASSERT_NULL(res.qualified_name); - cbm_registry_add(r, "x", "proj.geo.Point.Point.x", "Method"); + cbm_registry_add(r, "x", "proj.geo.Point.Point.x", "Method", false); res = cbm_registry_resolve(r, "v.x", "proj.app.Caller", NULL, NULL, 0); ASSERT_STR_EQ(res.qualified_name, "proj.geo.Point.Point.x"); /* Degenerate input registers nothing. */ @@ -2403,8 +2512,8 @@ TEST(go_suppress_keeps_typed_and_import_aware_matches) { * Signal.send). Regression guard for #1000. */ TEST(resolve_import_map_alias_with_suffix_hits_method) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "user_logged_in", "proj.auth.signals.user_logged_in", "Variable"); - cbm_registry_add(r, "send", "proj.auth.signals.user_logged_in.send", "Method"); + cbm_registry_add(r, "user_logged_in", "proj.auth.signals.user_logged_in", "Variable", false); + cbm_registry_add(r, "send", "proj.auth.signals.user_logged_in.send", "Method", false); const char *keys[] = {"user_logged_in"}; const char *vals[] = {"proj.auth.signals.user_logged_in"}; cbm_resolution_t res = @@ -2418,7 +2527,7 @@ TEST(resolve_import_map_alias_with_suffix_hits_method) { TEST(swift_overload_labels_defaults_and_trailing_closure) { cbm_registry_t *r = cbm_registry_new(); ASSERT_NOT_NULL(r); - cbm_registry_add(r, "Service", "proj.Service", "Class"); + cbm_registry_add(r, "Service", "proj.Service", "Class", false); const char *qns[] = { "proj.Service.work(flag:Bool)", "proj.Service.work(name:String)", @@ -2449,7 +2558,7 @@ TEST(swift_overload_labels_defaults_and_trailing_closure) { "combine", "repeated", "repeated", "unlabeled"}; const uint8_t counts[] = {1, 1, 1, 1, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 2, 2, 2, 2}; for (int i = 0; i < 22; i++) { - cbm_registry_add(r, names[i], qns[i], "Method"); + cbm_registry_add(r, names[i], qns[i], "Method", false); uint64_t defaults = (i == 5 || i == 15) ? UINT64_C(1) << 1 : i == 17 || i >= 19 ? UINT64_C(1) : 0; @@ -2460,7 +2569,7 @@ TEST(swift_overload_labels_defaults_and_trailing_closure) { const char *generic_qns[] = {"proj.Service.constrainedVoid>(_:T)", "proj.Service.constrained(_:T)async"}; for (int i = 0; i < 2; i++) { - cbm_registry_add(r, "constrained", generic_qns[i], "Method"); + cbm_registry_add(r, "constrained", generic_qns[i], "Method", false); cbm_registry_set_swift_signature(r, generic_qns[i], 0, 1, NULL); } CBMCallArg generic_arg = {0}; @@ -2468,7 +2577,7 @@ TEST(swift_overload_labels_defaults_and_trailing_closure) { .callee_name = "Service.constrained", .args = &generic_arg, .arg_count = 1}; ASSERT_EQ(cbm_registry_swift_candidates(r, &generic_call, "proj.Caller", NULL, 0, out, 8), 2); const char *nested = "proj.Service.nested,U==()=>Void>(_:T)=>Int"; - cbm_registry_add(r, "nested", nested, "Method"); + cbm_registry_add(r, "nested", nested, "Method", false); cbm_registry_set_swift_signature(r, nested, 0, 1, NULL); generic_call.callee_name = "Service.nested"; ASSERT_EQ(cbm_registry_swift_candidates(r, &generic_call, "proj.Caller", NULL, 0, out, 8), 1); @@ -2476,7 +2585,7 @@ TEST(swift_overload_labels_defaults_and_trailing_closure) { const char *returns[] = {"proj.Service.pick()=>Int", "proj.Service.pick()async=>(A/Item)=>B/Item"}; for (int i = 0; i < 2; i++) { - cbm_registry_add(r, "pick", returns[i], "Method"); + cbm_registry_add(r, "pick", returns[i], "Method", false); cbm_registry_set_swift_signature(r, returns[i], 0, 0, NULL); } CBMCall returned = {.callee_name = "Service.pick"}; @@ -2484,7 +2593,7 @@ TEST(swift_overload_labels_defaults_and_trailing_closure) { const char *malformed[] = {"proj.Service.bad()=>", "proj.Service.bad()=>[Int", "proj.Service.bad()=>Int)"}; for (int i = 0; i < 3; i++) { - cbm_registry_add(r, "bad", malformed[i], "Method"); + cbm_registry_add(r, "bad", malformed[i], "Method", false); cbm_registry_set_swift_signature(r, malformed[i], 0, 0, NULL); } returned.callee_name = "Service.bad"; @@ -2493,7 +2602,7 @@ TEST(swift_overload_labels_defaults_and_trailing_closure) { for (int i = 0; i < 2; i++) { char qn[128]; snprintf(qn, sizeof(qn), "proj.Service.%s(_:A,_:A)=>Bool", operators[i]); - cbm_registry_add(r, operators[i], qn, "Method"); + cbm_registry_add(r, operators[i], qn, "Method", false); cbm_registry_set_swift_signature(r, qn, 0, 2, NULL); CBMCallArg operands[2] = {{0}}; CBMCall operation = {.callee_name = operators[i], .args = operands, .arg_count = 2}; @@ -2657,14 +2766,14 @@ TEST(swift_manifest_root_targets_respect_nested_package_boundaries) { const char *methods[] = {"proj.Root.Ext.Service.run()", "proj.Nested.Ext.Service.run()", "proj.Unknown.Ext.Service.run()"}; for (int i = 0; i < 5; i++) { - cbm_registry_add(r, "Service", qns[i], "Class"); + cbm_registry_add(r, "Service", qns[i], "Class", false); ASSERT_GT(cbm_gbuf_upsert_node(graph, "Class", "Service", qns[i], files[i], 1, 3, i == 0 || i == 2 ? "{\"swift_type_path\":\"Service\"}" : "{\"swift_extension\":\"Service\"}"), 0); } for (int i = 0; i < 3; i++) { - cbm_registry_add(r, "run", methods[i], "Method"); + cbm_registry_add(r, "run", methods[i], "Method", false); cbm_registry_set_swift_signature(r, methods[i], 0, 0, "()"); } cbm_registry_build_swift_owners(r, graph, root, NULL, 0); @@ -2701,12 +2810,12 @@ TEST(swift_receiver_resolution_is_independent_of_registration_order) { for (int reverse = 0; reverse < 2; reverse++) { cbm_registry_t *r = cbm_registry_new(); ASSERT_NOT_NULL(r); - cbm_registry_add(r, "Service", "proj.alpha.Service", "Class"); - cbm_registry_add(r, "Service", "proj.beta.Service", "Class"); + cbm_registry_add(r, "Service", "proj.alpha.Service", "Class", false); + cbm_registry_add(r, "Service", "proj.beta.Service", "Class", false); const char *first = reverse ? beta : alpha; const char *second = reverse ? alpha : beta; - cbm_registry_add(r, "work", first, "Method"); - cbm_registry_add(r, "work", second, "Method"); + cbm_registry_add(r, "work", first, "Method", false); + cbm_registry_add(r, "work", second, "Method", false); cbm_registry_set_swift_signature(r, first, 0, 1, NULL); cbm_registry_set_swift_signature(r, second, 0, 1, NULL); @@ -2725,11 +2834,11 @@ TEST(swift_receiver_resolution_is_independent_of_registration_order) { TEST(swift_multiple_trailing_closures_match_labels) { cbm_registry_t *r = cbm_registry_new(); ASSERT_NOT_NULL(r); - cbm_registry_add(r, "Service", "proj.Service", "Class"); + cbm_registry_add(r, "Service", "proj.Service", "Class", false); const char *one_qn = "proj.Service.handle(completion:()=>Void)"; const char *two_qn = "proj.Service.handle(completion:()=>Void,onError:()=>Void)"; - cbm_registry_add(r, "handle", one_qn, "Method"); - cbm_registry_add(r, "handle", two_qn, "Method"); + cbm_registry_add(r, "handle", one_qn, "Method", false); + cbm_registry_add(r, "handle", two_qn, "Method", false); cbm_registry_set_swift_signature(r, one_qn, 0, 1, NULL); cbm_registry_set_swift_signature(r, two_qn, 0, 2, NULL); @@ -2762,7 +2871,7 @@ TEST(swift_multiple_trailing_closures_match_labels) { * closure can skip an earlier defaulted closure and bind the required one. */ const char *delayed_qn = "proj.Service.delayed(onDismiss:(()=>Void)?,content:()=>Void,onError:(()=>Void)?)"; - cbm_registry_add(r, "delayed", delayed_qn, "Method"); + cbm_registry_add(r, "delayed", delayed_qn, "Method", false); cbm_registry_set_swift_signature(r, delayed_qn, (UINT64_C(1) << 0) | (UINT64_C(1) << 2), 3, NULL); call.callee_name = "Service.delayed"; @@ -2779,9 +2888,9 @@ TEST(swift_multiple_trailing_closures_match_labels) { TEST(swift_repeated_labels_skip_default_before_next_trailing_label) { cbm_registry_t *r = cbm_registry_new(); ASSERT_NOT_NULL(r); - cbm_registry_add(r, "Service", "proj.Service", "Class"); + cbm_registry_add(r, "Service", "proj.Service", "Class", false); const char *qn = "proj.Service.repeated(a:()=>Void,b:()=>Void,a:()=>Void,b:()=>Void)"; - cbm_registry_add(r, "repeated", qn, "Method"); + cbm_registry_add(r, "repeated", qn, "Method", false); cbm_registry_set_swift_signature(r, qn, (UINT64_C(1) << 0) | (UINT64_C(1) << 3), 4, NULL); const char *labels[3] = {NULL, "a", "b"}; CBMCall call = {.callee_name = "Service.repeated", @@ -2800,9 +2909,9 @@ TEST(swift_repeated_labels_skip_default_before_next_trailing_label) { TEST(swift_repeated_labels_do_not_skip_default_past_next_trailing_label) { cbm_registry_t *r = cbm_registry_new(); ASSERT_NOT_NULL(r); - cbm_registry_add(r, "Service", "proj.Service", "Class"); + cbm_registry_add(r, "Service", "proj.Service", "Class", false); const char *qn = "proj.Service.repeated(a:()=>Void,b:()=>Void,a:()=>Void,b:()=>Void)"; - cbm_registry_add(r, "repeated", qn, "Method"); + cbm_registry_add(r, "repeated", qn, "Method", false); cbm_registry_set_swift_signature(r, qn, (UINT64_C(1) << 0) | (UINT64_C(1) << 1), 4, NULL); const char *labels[2] = {NULL, "b"}; CBMCall call = {.callee_name = "Service.repeated", @@ -2822,9 +2931,9 @@ TEST(swift_repeated_labels_do_not_skip_default_past_next_trailing_label) { TEST(swift_variadic_trailing_closures_do_not_precede_parenthesized_argument) { cbm_registry_t *r = cbm_registry_new(); ASSERT_NOT_NULL(r); - cbm_registry_add(r, "Service", "proj.Service", "Class"); + cbm_registry_add(r, "Service", "proj.Service", "Class", false); const char *qn = "proj.Service.f(callbacks:(()=>Void)~,flag:Bool,done:()=>Void)"; - cbm_registry_add(r, "f", qn, "Method"); + cbm_registry_add(r, "f", qn, "Method", false); cbm_registry_set_swift_signature(r, qn, UINT64_C(1) << 1, 3, NULL); CBMCallArg arg = {.keyword = "flag"}; const char *labels[2] = {NULL, "done"}; @@ -2845,17 +2954,17 @@ TEST(swift_variadic_trailing_closures_do_not_precede_parenthesized_argument) { TEST(swift_overloaded_bucket_fails_closed) { cbm_registry_t *r = cbm_registry_new(); ASSERT_NOT_NULL(r); - cbm_registry_add(r, "Service", "proj.Service", "Class"); + cbm_registry_add(r, "Service", "proj.Service", "Class", false); const char *first_qn = "proj.Service.work(flag:Bool)"; - cbm_registry_add(r, "work", first_qn, "Method"); + cbm_registry_add(r, "work", first_qn, "Method", false); cbm_registry_set_swift_signature(r, first_qn, 0, 1, NULL); const char *other = "proj.Other.work(flag:Bool)"; - cbm_registry_add(r, "work", other, "Method"); + cbm_registry_add(r, "work", other, "Method", false); cbm_registry_set_swift_signature(r, other, 0, 1, NULL); char qns[256][64]; for (int i = 1; i < 256; i++) { snprintf(qns[i], sizeof(qns[i]), "proj.Other%d.work(flag:Bool)", i); - cbm_registry_add(r, "work", qns[i], "Method"); + cbm_registry_add(r, "work", qns[i], "Method", false); cbm_registry_set_swift_signature(r, qns[i], 0, 1, NULL); } CBMCall call = {.callee_name = "Service.work"}; @@ -2921,11 +3030,11 @@ TEST(python_symbol_scope_len_preserves_file_identity) { TEST(registry_python_package_scope_is_explicit) { cbm_registry_t *r = cbm_registry_new(); ASSERT_NOT_NULL(r); - cbm_registry_add(r, "Field", "proj.forms.fields.Field", "Class"); - cbm_registry_add(r, "Field", "proj.db.fields.Field", "Class"); - cbm_registry_add(r, "Field", "proj.db.fields.__init__.Field", "Class"); - cbm_registry_add(r, "Field", "proj.db.plain.Field", "Class"); - cbm_registry_add(r, "Field", "proj.db.index.Field", "Class"); + cbm_registry_add(r, "Field", "proj.forms.fields.Field", "Class", false); + cbm_registry_add(r, "Field", "proj.db.fields.Field", "Class", false); + cbm_registry_add(r, "Field", "proj.db.fields.__init__.Field", "Class", false); + cbm_registry_add(r, "Field", "proj.db.plain.Field", "Class", false); + cbm_registry_add(r, "Field", "proj.db.index.Field", "Class", false); const char *keys[] = {"forms"}; const char *vals[] = {"proj.forms"}; const char *module = "proj.db.fields.__init__"; @@ -2957,11 +3066,11 @@ TEST(registry_python_package_scope_is_explicit) { TEST(registry_python_scope_keeps_import_precedence_and_raw_scoring) { cbm_registry_t *r = cbm_registry_new(); ASSERT_NOT_NULL(r); - cbm_registry_add(r, "Field", "proj.vendor.Field", "Class"); - cbm_registry_add(r, "Field", "proj.pkg.Field", "Class"); - cbm_registry_add(r, "Field", "proj.pkg.__init__.Field", "Class"); - cbm_registry_add(r, "choose", "proj.pkg.__init__.child.choose", "Function"); - cbm_registry_add(r, "choose", "proj.pkg.peer.choose", "Function"); + cbm_registry_add(r, "Field", "proj.vendor.Field", "Class", false); + cbm_registry_add(r, "Field", "proj.pkg.Field", "Class", false); + cbm_registry_add(r, "Field", "proj.pkg.__init__.Field", "Class", false); + cbm_registry_add(r, "choose", "proj.pkg.__init__.child.choose", "Function", false); + cbm_registry_add(r, "choose", "proj.pkg.peer.choose", "Function", false); const char *keys[] = {"Field"}; const char *vals[] = {"proj.vendor.Field"}; bool seeded = cbm_registry_size(r) == 5; @@ -2985,14 +3094,14 @@ TEST(registry_python_scope_keeps_import_precedence_and_raw_scoring) { TEST(registry_python_scope_cache_modes_and_raw_lineage) { cbm_registry_t *r = cbm_registry_new(); ASSERT_NOT_NULL(r); - cbm_registry_add(r, "Entry", "proj.pkg.Entry", "Function"); - cbm_registry_add(r, "Entry", "proj.pkg.__init__.Entry", "Function"); - cbm_registry_add(r, "Reject", "proj.pkg.Reject", "Table"); - cbm_registry_add(r, "Reject", "proj.pkg.__init__.Reject", "Function"); - cbm_registry_add(r, "Accept", "proj.pkg.Accept", "Function"); - cbm_registry_add(r, "Accept", "proj.pkg.__init__.Accept", "Table"); - cbm_registry_add(r, "Relation", "proj.pkg.Relation", "View"); - cbm_registry_add(r, "Relation", "proj.pkg.__init__.Relation", "Table"); + cbm_registry_add(r, "Entry", "proj.pkg.Entry", "Function", false); + cbm_registry_add(r, "Entry", "proj.pkg.__init__.Entry", "Function", false); + cbm_registry_add(r, "Reject", "proj.pkg.Reject", "Table", false); + cbm_registry_add(r, "Reject", "proj.pkg.__init__.Reject", "Function", false); + cbm_registry_add(r, "Accept", "proj.pkg.Accept", "Function", false); + cbm_registry_add(r, "Accept", "proj.pkg.__init__.Accept", "Table", false); + cbm_registry_add(r, "Relation", "proj.pkg.Relation", "View", false); + cbm_registry_add(r, "Relation", "proj.pkg.__init__.Relation", "Table", false); bool seeded = cbm_registry_size(r) == 8; bool correct = true; /* Each cache lifetime has one fixed module/import map. Alternate modes in @@ -3070,12 +3179,12 @@ TEST(registry_python_scope_complete_candidate_boundaries) { int tn = snprintf(target, sizeof(target), "%s.%s", scope, name); bool shape = mn > 0 && (size_t)mn < sizeof(module) && tn > 0 && (size_t)tn == lengths[i] && (size_t)tn < sizeof(target); - cbm_registry_add(r, name, target, "Function"); + cbm_registry_add(r, name, target, "Function", false); if (lengths[i] > 511) { char truncated[512]; memcpy(truncated, target, sizeof(truncated) - 1); truncated[sizeof(truncated) - 1] = '\0'; - cbm_registry_add(r, "truncated_decoy", truncated, "Function"); + cbm_registry_add(r, "truncated_decoy", truncated, "Function", false); } const char *canonical = registry_scope_canonical(r, name, target); cbm_resolution_t python = @@ -3102,7 +3211,7 @@ TEST(registry_python_scope_qualified_terminal_bucket_uncapped) { char qn[80]; int n = snprintf(qn, sizeof(qn), "proj.noise%d.perform", i); shape = n > 0 && (size_t)n < sizeof(qn) && shape; - cbm_registry_add(r, "perform", qn, "Method"); + cbm_registry_add(r, "perform", qn, "Method", false); } char scope[1024]; memcpy(scope, "proj.", 5); @@ -3120,7 +3229,7 @@ TEST(registry_python_scope_qualified_terminal_bucket_uncapped) { int pn = snprintf(prefixed, sizeof(prefixed), "receiver.%s", names[i]); shape = tn > 0 && (size_t)tn < sizeof(target) && pn > 0 && (size_t)pn < sizeof(prefixed) && shape; - cbm_registry_add(r, "perform", target, "Method"); + cbm_registry_add(r, "perform", target, "Method", false); const char *canonical = registry_scope_canonical(r, "perform", target); cbm_resolution_t direct = cbm_registry_resolve_lang(r, names[i], module, NULL, NULL, 0, CBM_LANG_PYTHON); @@ -3154,13 +3263,13 @@ TEST(registry_python_function_local_is_exact_only_issue1548) { {"Inner", "proj.web.Outer.Inner", "Class"}, }; for (size_t i = 0; i < sizeof(defs) / sizeof(defs[0]); i++) { - cbm_registry_add_lang_in_module(r, defs[i][0], defs[i][1], defs[i][2], CBM_LANG_PYTHON, - web); + cbm_registry_add_lang_in_module(r, defs[i][0], defs[i][1], defs[i][2], CBM_LANG_PYTHON, web, + false); } cbm_registry_add_lang_in_module(r, "f", "proj.ts.f", "Function", CBM_LANG_TYPESCRIPT, - strlen("proj.ts")); + strlen("proj.ts"), false); cbm_registry_add_lang_in_module(r, "g", "proj.ts.f.g", "Function", CBM_LANG_TYPESCRIPT, - strlen("proj.ts")); + strlen("proj.ts"), false); static const char *const local[] = {"proj.web.create_app.index", "proj.web.create_app.Settings", "proj.web.create_app.Settings.value"}; @@ -3212,7 +3321,7 @@ TEST(registry_resolve_lexical_walks_enclosing_defs_issue1548) { }; for (size_t i = 0; i < sizeof(defs) / sizeof(defs[0]); i++) { cbm_registry_add_lang_in_module(r, defs[i][0], defs[i][1], defs[i][2], CBM_LANG_PYTHON, - strlen("proj.m")); + strlen("proj.m"), false); } /* In method Tree.visit, `visit` is its nested def; so it is inside that @@ -3254,17 +3363,17 @@ TEST(registry_python_local_bound_at_own_file_issue1548) { cbm_registry_t *r = cbm_registry_new(); if (order == 0) { cbm_registry_add_lang_in_module(r, "layout", "proj.pkg.layout", "Function", - CBM_LANG_PYTHON, init_prefix); + CBM_LANG_PYTHON, init_prefix, false); } cbm_registry_add_lang_in_module(r, "Layout", "proj.pkg.layout.Layout", "Class", - CBM_LANG_PYTHON, mod_prefix); + CBM_LANG_PYTHON, mod_prefix, false); cbm_registry_add_lang_in_module(r, "create", "proj.pkg.layout.Layout.create", "Method", - CBM_LANG_PYTHON, mod_prefix); + CBM_LANG_PYTHON, mod_prefix, false); cbm_registry_add_lang_in_module(r, "make", "proj.pkg.layout.make", "Function", - CBM_LANG_PYTHON, mod_prefix); + CBM_LANG_PYTHON, mod_prefix, false); if (order == 1) { cbm_registry_add_lang_in_module(r, "layout", "proj.pkg.layout", "Function", - CBM_LANG_PYTHON, init_prefix); + CBM_LANG_PYTHON, init_prefix, false); } static const char *const nameable[][2] = {{"Layout", "proj.pkg.layout.Layout"}, {"create", "proj.pkg.layout.Layout.create"}, @@ -3332,6 +3441,11 @@ SUITE(registry) { RUN_TEST(registry_create_free); RUN_TEST(registry_free_null); RUN_TEST(registry_add_and_exists); + RUN_TEST(registry_prefers_non_test_over_test_candidate); + RUN_TEST(registry_test_verdict_comes_from_the_caller_not_the_qn); + RUN_TEST(registry_sole_test_candidate_still_resolves); + RUN_TEST(registry_test_double_path_table); + RUN_TEST(registry_swift_overload_choice_uses_stored_verdicts); RUN_TEST(registry_label_of); RUN_TEST(registry_value_target_field_twin); RUN_TEST(registry_find_by_name);