Skip to content

fix(resolution): ties that decide a link break by code unit, not the host's locale - #2927

Merged
colbymchenry merged 1 commit into
mainfrom
claude/pr-1983-code-unit-ties
Oct 27, 2026
Merged

colbymchenry merged 1 commit into
mainfrom
claude/pr-1983-code-unit-ties

Conversation

@colbymchenry

Copy link
Copy Markdown
Owner

Summary

Indexes built on machines set to different languages now agree. Four tie-breaks that decide what a reference links to sorted paths or ids with a bare localeCompare, which follows the host's locale, so the same repository could index differently on two machines and a sync on one could disagree with an init on another.

Root cause

String.prototype.localeCompare with no locale uses the host's ICU collation. Under Czech ch/ sorts after h/; under Danish aa sorts after z; locale collation puts alpha/ before Beta/ while code-unit order does the opposite. Node on Windows takes the locale from the system settings, not LANG. The four sites:

site what the tie decides
tsFieldCall in name-matcher.ts (the PR's) this.field.method() on a type declared in two equally near apps
cppClassNamed in cpp-type-aliases.ts the declaration an alias two headers declare differently resolves to, for a C++ base class named through it (rocksdb's using DBWithTTLImplBase = … per #if arm)
the Swift extern-module map in frameworks/react-native.ts which Swift copy of a native module's class a JS call reaches
wiringOrder in go-func-field-synthesizer.ts its last tie-break, by node id, decides which wired values a capped channel keeps (its site comparison already names 'en' and stays)

All four now compare by code unit (the inline a < b ? -1 : a > b ? 1 : 0 already used in workspace-packages.ts and entrypoints.ts). The other localeCompare calls only order output (CLI and MCP listings, the viewer's module pairs, dead-code ranking, type-hierarchy order, ContextBuilder's copybook entries, the skip summary) and are left alone.

What came from the contributor, and what we changed

From @bompus (#1983): the tsFieldCall fix and its test in ts-this-field-call.test.ts.

Ours: the same fix at the three other sites with a test each (cpp-base-class-lookup.test.ts, react-native-bridge.test.ts; the Go synthesizer's tie needs two values wired at one site with ids that collate differently, which a fixture can't choose), and the CHANGELOG bullet moved from the released [1.6.1] section to [Unreleased], reworded to cover all of them, with Thanks @bompus.

Validation

Tests: the PR's and our two fail on main (English host: alpha/ and a/ win) and pass here, re-checked on the rebased commit.

The bug itself, before and after. Fixtures with ch/ and h/ copies for the TypeScript, C++ and React Native cases, indexed under a simulated English and a simulated Czech host (a preload that sets localeCompare's default locale, which reaches the worker threads too):

fixture main, en main, cs this change, en this change, cs
TS this.users.load() apps/ch/ apps/h/ apps/ch/ apps/ch/
C++ base through an alias ch/config_b.h h/config_a.h ch/config_b.h ch/config_b.h
RN NativeModules.CameraModule.capture() ios/ch/ ios/h/ ios/ch/ ios/ch/

A/B, main vs this change, fresh indexes on this English-locale Windows host, 12 repos: excalidraw, zod, vben, bulletproof-react (TypeScript), jq (C), cobra, gin, etcd, prometheus (Go), okhttp (Kotlin), with flask and spring-modulith as controls. Every dump (nodes, edges, unresolved refs, files) is byte-identical, 273,760 edges in all.

The same repos indexed again under a simulated foreign host, Danish for the Go repos (it sorts aa after z, which reaches the synthesizer's id tie-break) and Czech for the TypeScript repos (ch after h, which reaches path ties): main's dumps and this change's both equal their English-host dumps. So these repositories never reach a tie that the host's locale decides, and the fix changes nothing for them; it matters for layouts like the fixtures above. None of them has a C++ alias that two headers declare differently or a React Native module with two Swift copies, so those two sites rest on their tests and the fixtures.

Kernel parity doesn't apply: TypeScript resolution only, same kernel binary in both arms.

The A/B ran on the base the branch had then (4c572e0). Since then main gained #1951, #1939 and #1985, none of which touches tsFieldCall or the other three sites, so the branch was rebased (cleanly) and its tests and the suite re-run.

Suite on this commit (CODEGRAPH_KERNEL_EXPECT=1 npx vitest run --maxWorkers=4 --minWorkers=1, Windows): 760 files passed, 6 skipped; 9,045 tests passed, 83 skipped, 0 failed.

Lands #1983 by @bompus.

🤖 Generated with Claude Code

…host's locale

Several resolution tie-breaks sorted paths or ids with a bare
localeCompare, which follows the host's locale, so two machines could
index the same repository differently: under Czech collation `ch/` sorts
after `h/`, under Danish `aa` sorts after `z`, and locale collation puts
`alpha/` before `Beta/` where code-unit order does the opposite.

The PR fixes the nearest-declaration tie in tsFieldCall, for
`this.field.method()` on a type declared in two equally near apps. On
top, the same bug at the three other sites that decide resolution:
- cpp-type-aliases: the declaration an alias resolves to when two
  headers declare it differently, for a C++ base class named through it;
- frameworks/react-native: the Swift copy of a module's class that a
  JS-to-native call reaches;
- go-func-field-synthesizer: the last tie-break of wiringOrder, which
  decides the values a capped channel keeps (its site comparison already
  names 'en' and stays).
All compare by code unit now. Comparators that only order output (CLI,
MCP and viewer listings, dead-code and hierarchy order, the context
builder's copybook entries) are left alone. The CHANGELOG bullet moves
from the released 1.6.1 section to Unreleased.

Validation: a test each for the TS, C++ and React Native sites; all 3
fail on main and pass here. Indexing `ch/` vs `h/` fixtures for those
three under a simulated English and a simulated Czech host (a
localeCompare preload that reaches the worker threads), main links
different targets in all three, this commit the same in both. A/B, main
vs this commit on an English-locale host, 12 repos (excalidraw, zod,
vben, bulletproof-react, jq, cobra, gin, etcd, prometheus, okhttp, with
flask and spring-modulith as controls): every dump byte-identical.
Indexed again under a simulated Danish host (the Go repos) and Czech
host (the TS repos), both arms match their English dumps, so these
repos reach no tie the locale decides; the fix matters where one does.

Original PR #1983 by @bompus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants