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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 85 additions & 35 deletions src/pipeline/registry.c
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,12 @@ 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;
}

/* The cached candidate languages, under the same coverage rule: both side
* arrays grow together and share is_test_cap. */
static const uint8_t *qn_lang_flags(const qn_array_t *arr) {
return arr->lang && arr->is_test_cap >= arr->count ? arr->lang : NULL;
}

/* 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)
Expand All @@ -237,16 +243,37 @@ static int candidate_score(const char *candidate_qn, const char *module_qn, int
return score;
}

/* Number of '.'-separated segments in a QN: how deeply the definition is
* nested (file depth + enclosing types). */
static int qn_depth(const char *qn) {
int depth = 1;
for (const char *p = qn; *p; p++) {
if (*p == '.') {
depth++;
}
/* How plainly a candidate is something a call can target, lower = better: a
* function or method; then any other definition (a class for a constructor
* call, a macro, a type); then a candidate whose label is unknown; last a
* variable or field, which a call reaches only when it holds a function. */
enum {
REG_RANK_CALLABLE = 0,
REG_RANK_OTHER_DEF = 1,
REG_RANK_UNKNOWN = 2,
REG_RANK_DATA = 3,
};

static int callable_rank(const cbm_registry_t *r, const char *qn) {
const char *label = r ? cbm_registry_label_of(r, qn) : NULL;
if (!label) {
return REG_RANK_UNKNOWN;
}
if (strcmp(label, "Function") == 0 || strcmp(label, "Method") == 0) {
return REG_RANK_CALLABLE;
}
if (strcmp(label, "Variable") == 0 || strcmp(label, "Field") == 0) {
return REG_RANK_DATA;
}
return depth;
return REG_RANK_OTHER_DEF;
}

/* A .NET reference assembly (`src/libraries/<Asm>/ref/<Asm>.cs`) declares an
* assembly's public surface with `throw null` bodies in one shallow file; a
* call that could equally mean the implementation means the implementation.
* C# only: elsewhere a `ref` directory is an ordinary name. */
static bool csharp_reference_stub(const char *qn, uint8_t lang) {
return lang == CBM_LANG_CSHARP && strstr(qn, ".ref.") != NULL;
}

/* Total order among candidates that tie on candidate_score. The registry's
Expand All @@ -255,33 +282,49 @@ static int qn_depth(const char *qn) {
* that order (kernel: 632 CALLS edges differed between two indexes of the
* same tree, `dev_name` flipping between twenty same-named struct fields
* and unresolved, `sg_set_buf` between include/linux and tools/virtio).
* The rule: the least nested definition wins (a top-level function over a
* same-named member two types deep, include/linux over tools/virtio/linux),
* then the lexicographically smaller QN — a pure function of the candidate
* set, never of the order it was built in (O9). */
static bool candidate_outranks_on_tie(const char *candidate, const char *best) {
int cd = qn_depth(candidate);
int bd = qn_depth(best);
if (cd != bd) {
return cd < bd;
* The rule, a pure function of the candidate set (O9):
* 1. the more plainly callable candidate (`dev_name` the function over the
* struct fields; Kotlin `where(...)` the method over the property);
* 2. for C#, an implementation over a reference-assembly stub;
* 3. the lexicographically smaller QN (include/linux over tools/virtio).
* Nesting depth is deliberately not a criterion: "least nested wins" sent
* django's `Model.objects.filter` to the template `Library.filter`, one
* segment shallower than `QuerySet.filter` (2026-10-10 audit). */
static bool candidate_outranks_on_tie(const cbm_registry_t *r, const char *candidate,
uint8_t candidate_lang, const char *best, uint8_t best_lang) {
int ck = callable_rank(r, candidate);
int bk = callable_rank(r, best);
if (ck != bk) {
return ck < bk;
}
bool cs = csharp_reference_stub(candidate, candidate_lang);
bool bs = csharp_reference_stub(best, best_lang);
if (cs != bs) {
return !cs;
}
return strcmp(candidate, best) < 0;
}

/* Pick candidate with highest composite score (test-deprioritization + namespace
* proximity). `is_test_flags` is the candidates' cached test verdicts, or NULL
* when the caller has none (a filtered subset carries its own copy). */
static const char *best_by_import_distance(const char **candidates, const uint8_t *is_test_flags,
* 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). */
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) {
const char *best = NULL;
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);
uint8_t lang = lang_flags ? lang_flags[i] : (uint8_t)CBM_LANG_COUNT;
if (score > best_score ||
(score == best_score && best && candidate_outranks_on_tie(candidates[i], best))) {
(score == best_score && best &&
candidate_outranks_on_tie(r, candidates[i], lang, best, best_lang))) {
best_score = score;
best = candidates[i];
best_lang = lang;
}
}
return best;
Expand Down Expand Up @@ -1409,17 +1452,23 @@ static cbm_resolution_t resolve_same_module(const cbm_registry_t *r, const char
}

/* Strategy 4: multiple candidates with import filtering. */
static cbm_resolution_t resolve_multi_with_imports(const qn_array_t *arr, const char *module_qn,
const char **import_vals, int import_count) {
static cbm_resolution_t resolve_multi_with_imports(const cbm_registry_t *r, const qn_array_t *arr,
const char *module_qn, const char **import_vals,
int import_count) {
const char *filtered[CBM_SZ_256];
uint8_t filtered_test[CBM_SZ_256];
uint8_t filtered_lang[CBM_SZ_256];
const uint8_t *flags = qn_test_flags(arr);
const uint8_t *langs = qn_lang_flags(arr);
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[fcount] = arr->items[i];
fcount++;
}
Expand All @@ -1429,16 +1478,16 @@ static cbm_resolution_t resolve_multi_with_imports(const qn_array_t *arr, const
return (cbm_resolution_t){filtered[0], "suffix_match", conf, arr->count};
}
if (fcount > SKIP_ONE) {
const char *best =
best_by_import_distance(filtered, flags ? filtered_test : NULL, fcount, module_qn);
const char *best = best_by_import_distance(r, filtered, flags ? filtered_test : NULL,
langs ? filtered_lang : NULL, fcount, module_qn);
if (best) {
double conf = candidate_count_penalty(CONF_SUFFIX_MATCH, fcount);
return (cbm_resolution_t){best, "suffix_match", conf, fcount};
}
}
/* No import-reachable — use all candidates with penalty */
const char *best = best_by_import_distance((const char **)arr->items, qn_test_flags(arr),
arr->count, module_qn);
const char *best = best_by_import_distance(r, (const char **)arr->items, qn_test_flags(arr),
qn_lang_flags(arr), arr->count, module_qn);
if (best) {
double conf = candidate_count_penalty(CONF_SUFFIX_MATCH * REG_HALF_PENALTY, arr->count);
return (cbm_resolution_t){best, "suffix_match", conf, arr->count};
Expand Down Expand Up @@ -1632,10 +1681,10 @@ static cbm_resolution_t resolve_name_lookup(const cbm_registry_t *r, const char

/* Strategy 4: multiple candidates */
if (import_vals && import_count > 0) {
return resolve_multi_with_imports(arr, module_qn, import_vals, import_count);
return resolve_multi_with_imports(r, arr, module_qn, import_vals, import_count);
}
const char *best = best_by_import_distance((const char **)arr->items, qn_test_flags(arr),
arr->count, module_qn);
const char *best = best_by_import_distance(r, (const char **)arr->items, qn_test_flags(arr),
qn_lang_flags(arr), arr->count, module_qn);
if (best) {
if (!receiver_chain_admits(callee_name, best)) {
return empty_result();
Expand Down Expand Up @@ -2011,8 +2060,8 @@ cbm_fuzzy_result_t cbm_registry_fuzzy_resolve(const cbm_registry_t *r, const cha

if (fcount == 0) {
/* No import-reachable — use originals with penalty */
const char *best = best_by_import_distance((const char **)arr->items, qn_test_flags(arr),
arr->count, module_qn);
const char *best = best_by_import_distance(r, (const char **)arr->items, qn_test_flags(arr),
qn_lang_flags(arr), arr->count, module_qn);
if (!best) {
return no_match;
}
Expand All @@ -2026,8 +2075,9 @@ 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 char *best = best_by_import_distance(
fptr, fptr == (const char **)arr->items ? qn_test_flags(arr) : NULL, fcount, module_qn);
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);
if (!best) {
return no_match;
}
Expand Down
106 changes: 101 additions & 5 deletions tests/test_registry.c
Original file line number Diff line number Diff line change
Expand Up @@ -919,9 +919,9 @@ TEST(cross_language_config_caller_drops_unique_name_too) {
* to be settled by bucket position, i.e. by the order files were registered:
* two indexes of one kernel tree differed by 632 CALLS edges, `dev_name`
* landing on any of twenty same-named struct fields or on nothing at all.
* The tie is now a function of the candidate set: the least nested
* definition, then the smaller QN — whichever order the registry was built
* in (O9). */
* The tie is now a function of the candidate set: the more plainly callable
* candidate (the function over the fields), then the smaller QN, whichever
* order the registry was built in (O9). */
TEST(registry_tie_break_is_independent_of_registration_order) {
const char *cands[] = {
"proj.drivers.media.cec.i2c.ch7322.ch7322_conn_match.dev_name", /* struct field */
Expand Down Expand Up @@ -951,7 +951,7 @@ TEST(registry_tie_break_is_independent_of_registration_order) {
ASSERT_STR_EQ(f.strategy, "suffix_match");
ASSERT_STR_EQ(b.strategy, "suffix_match");

/* Equal depth: the smaller QN, from either 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");
Expand All @@ -965,7 +965,7 @@ TEST(registry_tie_break_is_independent_of_registration_order) {
ASSERT_STR_EQ(la.qualified_name, "proj.include.linux.scatterlist.sg_next");
ASSERT_STR_EQ(lb.qualified_name, "proj.include.linux.scatterlist.sg_next");

/* Proximity still outranks depth: the sibling wins over a shallower stranger. */
/* Proximity outranks the tie order: the sibling wins over a shallower stranger. */
cbm_registry_t *near = cbm_registry_new();
cbm_registry_add(near, "vnic_rq_free", "proj.lib.vnic_rq_free", "Function");
cbm_registry_add(near, "vnic_rq_free", "proj.drivers.scsi.fnic.vnic_rq.vnic_rq_free",
Expand All @@ -982,6 +982,99 @@ TEST(registry_tie_break_is_independent_of_registration_order) {
PASS();
}

/* A call names something callable. When same-named candidates tie, a method
* must not lose to a property or field that merely shares its name. Exposed
* (Kotlin): `cities.selectAll().where { ... }` bound to the property
* `AbstractQuery.where` instead of the jdbc `Query.where` function, because the
* two QNs have the same depth and "exposed-core" sorts first; main moved 785 ->
* 1,648 Kotlin CALLS edges onto Variable/Field targets this way. */
TEST(registry_tie_prefers_callable_over_property) {
cbm_registry_t *r = cbm_registry_new();
cbm_registry_add(
r, "where",
"proj.exposed-core.src.main.kotlin.org.jetbrains.exposed.v1.core.AbstractQuery.where",
"Variable");
cbm_registry_add(r, "where",
"proj.exposed-jdbc.src.main.kotlin.org.jetbrains.exposed.v1.jdbc.Query.where",
"Method");
/* 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");

const char *caller =
"proj.exposed-tests.src.test.kotlin.org.jetbrains.exposed.v1.tests.SelectTests";
cbm_resolution_t w = cbm_registry_resolve(r, "where", caller, NULL, NULL, 0);
cbm_resolution_t l = cbm_registry_resolve(r, "limit", caller, NULL, NULL, 0);
ASSERT_STR_EQ(w.qualified_name,
"proj.exposed-jdbc.src.main.kotlin.org.jetbrains.exposed.v1.jdbc.Query.where");
ASSERT_STR_EQ(l.qualified_name, "proj.exposed-jdbc.src.main.kotlin.jdbc.Query.limit");
cbm_registry_free(r);
PASS();
}

/* Nesting depth says nothing about which same-named method a call means.
* django: `Model.objects.filter(...)` from tests/ tied between
* `QuerySet.filter` and the template `Library.filter`; "least nested wins"
* picked Library (one segment shallower) for most of the ~5,600 `objects.X`
* calls (QuerySet share 57 % -> 24 %). Equal-kind ties now fall to the QN
* 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_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");
ASSERT_STR_EQ(f.strategy, "suffix_match");
cbm_registry_free(r);
PASS();
}

/* .NET reference assemblies (`src/libraries/<Asm>/ref/<Asm>.cs`) declare every
* public member of an assembly with a `throw null` body in one shallow file.
* A call that ties between such a stub and the implementation must reach the
* implementation: dotnet/runtime had 14.1 % of its CALLS on ref/ stubs (4.9 %
* in v0.11.0) once ties preferred the shallower, then the smaller ("ref" <
* "src") QN. The preference is C#'s alone: elsewhere a `ref` directory is an
* ordinary name. */
TEST(registry_tie_prefers_csharp_implementation_over_ref_stub) {
cbm_registry_t *r = cbm_registry_new();
/* Shallower stub vs deeper implementation (MemoryStream). */
cbm_registry_add_lang(
r, "MemoryStream",
"proj.src.libraries.System.Runtime.ref.System.Runtime.MemoryStream.MemoryStream", "Method",
CBM_LANG_CSHARP);
cbm_registry_add_lang(
r, "MemoryStream",
"proj.src.libraries.System.Private.CoreLib.src.System.IO.MemoryStream.MemoryStream",
"Method", CBM_LANG_CSHARP);
/* 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);
cbm_registry_add_lang(r, "WithCancellation",
"proj.src.libraries.System.Linq.Parallel.src.System.Linq."
"ParallelEnumerable.WithCancellation",
"Method", CBM_LANG_CSHARP);
/* 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);

const char *caller = "proj.src.libraries.System.Security.Cryptography.src.CapiHelper";
cbm_resolution_t ms = cbm_registry_resolve(r, "MemoryStream", caller, NULL, NULL, 0);
cbm_resolution_t wc = cbm_registry_resolve(r, "WithCancellation", caller, NULL, NULL, 0);
cbm_resolution_t jv = cbm_registry_resolve(r, "resolve", "proj.app.Main", NULL, NULL, 0);
ASSERT_STR_EQ(
ms.qualified_name,
"proj.src.libraries.System.Private.CoreLib.src.System.IO.MemoryStream.MemoryStream");
ASSERT_STR_EQ(wc.qualified_name, "proj.src.libraries.System.Linq.Parallel.src.System.Linq."
"ParallelEnumerable.WithCancellation");
ASSERT_STR_EQ(jv.qualified_name, "proj.lib.ref.Resolver.resolve");
cbm_registry_free(r);
PASS();
}

TEST(lang_resolution_compatible_families) {
/* Same language and the interop families the resolvers already honour. */
ASSERT_TRUE(cbm_lang_resolution_compatible(CBM_LANG_PYTHON, CBM_LANG_PYTHON));
Expand Down Expand Up @@ -1643,6 +1736,9 @@ SUITE(registry) {
RUN_TEST(same_module_vetoed_across_languages);
RUN_TEST(cross_language_config_caller_drops_unique_name_too);
RUN_TEST(registry_tie_break_is_independent_of_registration_order);
RUN_TEST(registry_tie_prefers_callable_over_property);
RUN_TEST(registry_tie_ignores_nesting_depth);
RUN_TEST(registry_tie_prefers_csharp_implementation_over_ref_stub);
RUN_TEST(cross_language_ref_drops_go_vs_c);
RUN_TEST(go_bare_ref_never_binds_field);
RUN_TEST(call_onto_field_follows_language_and_shape);
Expand Down
Loading