@@ -15250,12 +15250,9 @@ TEST(clsp_tier2_shared_registry_readonly_c) {
1525015250 PASS();
1525115251}
1525215252
15253- /* Direct guard for the finalize-time short-name / embedded-type indexes and their
15254- * iterators (type_registry.c), which the Rust trait/free-func fast paths rely on.
15255- * Verifies: embed index yields exactly the types whose embedded_types carry a
15256- * matching BARE name, in ascending registry order, deduped when a type lists the
15257- * same bare twice; free-func index yields only free funcs (receiver_type==NULL) with
15258- * the given short_name, not methods. */
15253+ /* Direct guard for the type-name / embedded-type / free-function registry
15254+ * indexes and their iterators (type_registry.c). Verifies that every iterator
15255+ * preserves ascending registry order, which is part of resolver tie-breaking. */
1525915256TEST(registry_short_name_indexes) {
1526015257 CBMArena arena;
1526115258 cbm_arena_init(&arena);
@@ -15288,6 +15285,25 @@ TEST(registry_short_name_indexes) {
1528815285 t.short_name = "C";
1528915286 t.embedded_types = c_emb;
1529015287 cbm_registry_add_type(®, t);
15288+ memset(&t, 0, sizeof(t));
15289+ t.qualified_name = "other.Trait";
15290+ t.short_name = "Trait";
15291+ cbm_registry_add_type(®, t);
15292+ memset(&t, 0, sizeof(t));
15293+ t.qualified_name = "Trait";
15294+ t.short_name = "Trait";
15295+ cbm_registry_add_type(®, t);
15296+
15297+ /* Populate enough distinct names to force many hash-bucket collisions, with
15298+ * repeated short names spread across the registry. The differential check
15299+ * below compares the index against the exact former linear-scan order. */
15300+ for (int i = 0; i < 512; i++) {
15301+ memset(&t, 0, sizeof(t));
15302+ t.qualified_name = cbm_arena_sprintf(&arena, "bulk.mod%d.Name%d", i, i % 23);
15303+ ASSERT_NOT_NULL(t.qualified_name);
15304+ t.short_name = strrchr(t.qualified_name, '.') + 1;
15305+ cbm_registry_add_type(®, t);
15306+ }
1529115307
1529215308 /* free func "helper" (x2 — different QNs), method "M.helper", free func "other". */
1529315309 CBMRegisteredFunc f;
@@ -15310,6 +15326,77 @@ TEST(registry_short_name_indexes) {
1531015326 cbm_registry_add_func(®, f);
1531115327
1531215328 cbm_registry_finalize(®);
15329+ cbm_registry_build_type_short_index(®);
15330+
15331+ /* Qualified-name bare segment "Trait": types 0, 4, then the bare-QN type
15332+ * 5. The iterator is a hash prefilter, so apply the exact predicate before
15333+ * asserting its order just like production consumers do. */
15334+ CBMTypeNameIter nit;
15335+ cbm_registry_types_by_short_name(®, "Trait", &nit);
15336+ {
15337+ int expected[] = {0, 4, 5};
15338+ int exact_count = 0;
15339+ int candidate;
15340+ while ((candidate = cbm_type_name_iter_next(&nit)) >= 0) {
15341+ const char *qn = reg.types[candidate].qualified_name;
15342+ const char *last_dot = qn ? strrchr(qn, '.') : NULL;
15343+ const char *candidate_short = last_dot ? last_dot + 1 : qn;
15344+ if (!candidate_short || strcmp(candidate_short, "Trait") != 0)
15345+ continue;
15346+ ASSERT_TRUE(exact_count < 3);
15347+ ASSERT_EQ(candidate, expected[exact_count++]);
15348+ }
15349+ ASSERT_EQ(exact_count, 3);
15350+ }
15351+ cbm_registry_types_by_short_name(®, "Missing", &nit);
15352+ {
15353+ int exact_count = 0;
15354+ int candidate;
15355+ while ((candidate = cbm_type_name_iter_next(&nit)) >= 0) {
15356+ const char *qn = reg.types[candidate].qualified_name;
15357+ const char *last_dot = qn ? strrchr(qn, '.') : NULL;
15358+ const char *candidate_short = last_dot ? last_dot + 1 : qn;
15359+ if (candidate_short && strcmp(candidate_short, "Missing") == 0)
15360+ exact_count++;
15361+ }
15362+ ASSERT_EQ(exact_count, 0);
15363+ }
15364+
15365+ for (int name_i = 0; name_i < 23; name_i++) {
15366+ char short_name[32];
15367+ snprintf(short_name, sizeof(short_name), "Name%d", name_i);
15368+ cbm_registry_types_by_short_name(®, short_name, &nit);
15369+ int linear_i = 0;
15370+ for (;;) {
15371+ int expected = -1;
15372+ while (linear_i < reg.type_count) {
15373+ int candidate = linear_i++;
15374+ const char *qn = reg.types[candidate].qualified_name;
15375+ const char *last_dot = qn ? strrchr(qn, '.') : NULL;
15376+ const char *candidate_short = last_dot ? last_dot + 1 : qn;
15377+ if (candidate_short && strcmp(candidate_short, short_name) == 0) {
15378+ expected = candidate;
15379+ break;
15380+ }
15381+ }
15382+
15383+ int actual;
15384+ for (;;) {
15385+ actual = cbm_type_name_iter_next(&nit);
15386+ if (actual < 0)
15387+ break;
15388+ const char *qn = reg.types[actual].qualified_name;
15389+ const char *last_dot = qn ? strrchr(qn, '.') : NULL;
15390+ const char *candidate_short = last_dot ? last_dot + 1 : qn;
15391+ if (candidate_short && strcmp(candidate_short, short_name) == 0)
15392+ break;
15393+ }
15394+
15395+ ASSERT_EQ(actual, expected);
15396+ if (expected < 0)
15397+ break;
15398+ }
15399+ }
1531315400
1531415401 /* Embed index for bare "Trait": types 1 (A) then 2 (B), ascending, B once. */
1531515402 CBMTypeEmbedIter it;
@@ -15339,6 +15426,70 @@ TEST(registry_short_name_indexes) {
1533915426 ASSERT_EQ(cbm_free_func_iter_next(&fit), 3);
1534015427 ASSERT_EQ(cbm_free_func_iter_next(&fit), -1);
1534115428
15429+ /* A type appended after finalize is covered by the iterator tail. If the
15430+ * auxiliary allocation is unavailable, the iterator falls back to the same
15431+ * complete linear scan rather than silently dropping candidates. Keep this
15432+ * after the other iterator assertions because their tails intentionally
15433+ * expose post-finalize candidates for caller-side exact filtering too. */
15434+ int tail_type_i = reg.type_count;
15435+ memset(&t, 0, sizeof(t));
15436+ t.qualified_name = "tail.Trait";
15437+ t.short_name = "Trait";
15438+ cbm_registry_add_type(®, t);
15439+ cbm_registry_types_by_short_name(®, "Trait", &nit);
15440+ ASSERT_EQ(cbm_type_name_iter_next(&nit), 0);
15441+ ASSERT_EQ(cbm_type_name_iter_next(&nit), 4);
15442+ ASSERT_EQ(cbm_type_name_iter_next(&nit), 5);
15443+ ASSERT_EQ(cbm_type_name_iter_next(&nit), tail_type_i);
15444+ ASSERT_EQ(cbm_type_name_iter_next(&nit), -1);
15445+
15446+ int *saved_type_short_buckets = reg.type_short_buckets;
15447+ reg.type_short_buckets = NULL;
15448+ cbm_registry_types_by_short_name(®, "Trait", &nit);
15449+ int fallback_expected[] = {0, 4, 5, tail_type_i};
15450+ int fallback_count = 0;
15451+ int candidate;
15452+ while ((candidate = cbm_type_name_iter_next(&nit)) >= 0) {
15453+ const char *qn = reg.types[candidate].qualified_name;
15454+ const char *last_dot = qn ? strrchr(qn, '.') : NULL;
15455+ const char *candidate_short = last_dot ? last_dot + 1 : qn;
15456+ if (!candidate_short || strcmp(candidate_short, "Trait") != 0)
15457+ continue;
15458+ ASSERT_TRUE(fallback_count < 4);
15459+ ASSERT_EQ(candidate, fallback_expected[fallback_count++]);
15460+ }
15461+ ASSERT_EQ(fallback_count, 4);
15462+ reg.type_short_buckets = saved_type_short_buckets;
15463+
15464+ /* A failed rebuild must discard the previous auxiliary index. Re-finalize
15465+ * after adding the tail type so the QN boundary advances, then force arena
15466+ * exhaustion for the optional rebuild. The iterator must fall back to the
15467+ * complete linear scan instead of retaining the stale pre-tail index. */
15468+ cbm_registry_finalize(®);
15469+ int saved_nblocks = arena.nblocks;
15470+ size_t saved_used = arena.used;
15471+ arena.nblocks = CBM_ARENA_MAX_BLOCKS;
15472+ arena.used = arena.block_size;
15473+ cbm_registry_build_type_short_index(®);
15474+ arena.nblocks = saved_nblocks;
15475+ arena.used = saved_used;
15476+ ASSERT_NULL(reg.type_short_buckets);
15477+ ASSERT_NULL(reg.type_short_entries);
15478+ ASSERT_EQ(reg.type_short_bucket_count, 0);
15479+
15480+ cbm_registry_types_by_short_name(®, "Trait", &nit);
15481+ fallback_count = 0;
15482+ while ((candidate = cbm_type_name_iter_next(&nit)) >= 0) {
15483+ const char *qn = reg.types[candidate].qualified_name;
15484+ const char *last_dot = qn ? strrchr(qn, '.') : NULL;
15485+ const char *candidate_short = last_dot ? last_dot + 1 : qn;
15486+ if (!candidate_short || strcmp(candidate_short, "Trait") != 0)
15487+ continue;
15488+ ASSERT_TRUE(fallback_count < 4);
15489+ ASSERT_EQ(candidate, fallback_expected[fallback_count++]);
15490+ }
15491+ ASSERT_EQ(fallback_count, 4);
15492+
1534215493 cbm_arena_destroy(&arena);
1534315494 PASS();
1534415495}
0 commit comments