Repository navigation
fix(resolution): ties that decide a link break by code unit, not the host's locale - #2927
Merged
Merged
Conversation
…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>
This was referenced Oct 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 asyncon one could disagree with aniniton another.Root cause
String.prototype.localeComparewith no locale uses the host's ICU collation. Under Czechch/sorts afterh/; under Danishaasorts afterz; locale collation putsalpha/beforeBeta/while code-unit order does the opposite. Node on Windows takes the locale from the system settings, notLANG. The four sites:tsFieldCallinname-matcher.ts(the PR's)this.field.method()on a type declared in two equally near appscppClassNamedincpp-type-aliases.tsusing DBWithTTLImplBase = …per#ifarm)frameworks/react-native.tswiringOrderingo-func-field-synthesizer.ts'en'and stays)All four now compare by code unit (the inline
a < b ? -1 : a > b ? 1 : 0already used inworkspace-packages.tsandentrypoints.ts). The otherlocaleComparecalls 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
tsFieldCallfix and its test ints-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, withThanks @bompus.Validation
Tests: the PR's and our two fail on main (English host:
alpha/anda/win) and pass here, re-checked on the rebased commit.The bug itself, before and after. Fixtures with
ch/andh/copies for the TypeScript, C++ and React Native cases, indexed under a simulated English and a simulated Czech host (a preload that setslocaleCompare's default locale, which reaches the worker threads too):this.users.load()apps/ch/apps/h/apps/ch/apps/ch/ch/config_b.hh/config_a.hch/config_b.hch/config_b.hNativeModules.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
aaafterz, which reaches the synthesizer's id tie-break) and Czech for the TypeScript repos (chafterh, 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
tsFieldCallor 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