Repository navigation
fix(sync): a member call on a local typed by an imported class links when the class's module changes - #2637
Merged
Conversation
…when the class's module changes A failed `cache.get(k)` after `const cache = new LRUCache(10)` (or an annotated local, a typed parameter, a default or namespace import of the class) was parked under its member's name, `get`, which more failed refs share than the retry's ceiling, so a sync that added or restored the class's module never retried it. It now waits under the key of the module the type's binding imports, as a member of the binding itself does (#2630). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…log entry Resolution types a receiver by its declaration for a member call; a method passed as a value off `this` is looked up in the enclosing class whatever `this` is declared as, so only calls wait for the module of the receiver's type. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…to its member's tail Resolution reads `results: URI[]` as a `URI`, but `results.push(…)` is the array's method, which no edit of the class's module links. On vscode those calls were 60% of what the typed-local parking added (8,192 of 13,704), and retrying them nearly doubled what an edit of `uri.ts` retried. Inference now reports the line it read the declaration from, and a local annotated as an array of the type keeps its member's tail, as on main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
What
codegraph sync(and the watcher) left a member call on a local typed by an imported class unlinked. This happened when the class's module was added later, came back after a delete, or gained the class in an edit:A fresh index links
cache.gettoLRUCache.get(instance-method, 0.9) by reading the local's declaration. While the module was missing or empty, the call failed and was parked under its member's name,get. A name that common is past the retry's 500 ceiling, so no sync ever retried it. Seen in folo (cache.get×11 inlru-cache.test.ts,newChatInstance.resumeStream) and milo-ui (astCache.get).This is a follow-up to #2630, which did the same for a member of the import binding itself (
store.get).Fix
In
importBindingTail(import-resolver.ts), when a direct member call's receiver is not an import binding, the parking decision now reads the receiver's declared or constructed type the way resolution does. It usesinferLocalReceiverType, now exported. That function is memoized per file, scope and receiver, so the call the matcher just tried costs a lookup. The decision then takes the import binding the type is written through:LRUCache, orliboflib.LRUCache.The call is parked under that module's key, exactly as #2630 parks a binding member. It keeps #2630's other two triggers: its member's name under the shared ceiling, and its receiver.
Exceptions:
thispassed as a value is looked up in the enclosing class, whateverthisis declared as.results: URI[]) keeps its member's tail, as on main. Resolution reads the element type there too, butresults.push(…)is the array's method, which no edit of the class's module links. To tell, inference now reports the line it read the declaration from (decl.line). On vscode these calls were 60% of what a first version parked (8,192 of 13,704), and they nearly doubled what auri.tsedit retries (1,228 → 1,996 refs, below).Fresh indexes are unchanged. Only
name_tailof failed calls moves.Validation
Fresh-index A/B (byte-identical controls)
name_tailof failed calls: folo 23, milo-ui 86, vben 47, takenote 9, proshop 6, ghostfolio 6, infrahub 2; the other 12 repos are unchanged.Sync vs a fresh index
Every 7th file is held out, emptied, or deleted and then restored. Each arm's synced graph is compared with its own fresh index; numbers are differing dump lines.
newChatInstance.resumeStreamcache.get×11,newChatInstance.resumeStreamcache.get×11astCache.getastCache.getAll other scenarios are unchanged: 19 hold, 17 gut and 17 drop per arm. None got worse, and the residue buckets are otherwise identical. The arrays exclusion changed none of these (fix with and without it: same buckets in all 27 runs on the affected repos).
Left over, on both arms:
offsetCache.get/measurementsCache.getinentry-column/list.tsx. Their class comes from@follow/utils/lru-cache, a workspace package the workspace loader never finds: pnpm'spackages/**/*is expanded one level only. So the import counts as an outside package, and 2,411 of folo's@follow/*imports stay unresolved. This is a separate bug, flagged as a follow-up.FOREIGN KEY constraint failedon some syncs: vben in every mode, and the delete step of folo's and milo-ui's drop runs. This iscodegraph syncfails with "FOREIGN KEY constraint failed" after d0bb9be5 when a file lazy-loads a workspace package #2565, fixed in fix(sync): record only indexed files as synthesis inputs (#2565) #2574, which is still open.Cost on vscode
Parking decisions. 817,145 failed JS-family refs, each first attempted as resolution does, single thread, two runs per arm: 1,376 / 1,371 ms → 2,229 / 2,241 ms, so +0.86 s per full index. For scale, the attempts themselves take ~68 s and the index takes 219 s on both arms.
Parked under a module key. 6,336 → 12,070 refs, under 194 → 662 keys. None crosses the 5,000 module ceiling.
What a sync retries. The changed files' names and module keys go through the retry lookup, and what it finds is re-attempted. Nothing changed, so nothing resolves.
uri.tslifecycle.tsevent.tseditor/common/model.tsdom.tsPer changed file, over all 15,358 files: mean 302.5 → 326.2, p50 98 → 112, p90 800 → 860, p99 2,341 → 2,521.
A real
codegraph syncof auri.tsedit, four syncs per arm (the edit and its restore, twice): the failed-ref retry phase took 687 / 696 / 698 / 732 ms on main (1,217 refs) and 989 / 1,014 / 989 / 974 ms with the fix (1,574 refs), so +0.3 s. The whole sync took 98–108 s on both arms; whole-graph synthesis takes most of it.Array exclusion trade-off. A fresh index does link a few array calls to the element class's method: 15 on vscode, such as
distinctElements.findon anExplorerItem[]→ExplorerItem::find. These are wrong 0.9 edges from the same mis-inference. With this PR, a sync doesn't re-create them when the class's module returns. Stopping the mis-inference itself is a separate fresh-edge change, flagged as a follow-up.Tests
__tests__/sync-typed-local-member-retry.test.tshas 4 tests, all of which fail on main 986de95 (run in a detached worktree):extApi.getcalls putgetpast the ceiling.module:lru-cacheormodule:store:new, an annotation, a typed parameter, a namespace import and a default import;new Map(), a package's class, a same-file class, a factory's result, a local and a parameter declared as arrays of the class, and athis.peekmethod value.Mutations:
callsrestriction,this.peekgoes tomodule:factory.caches.pushgoes tomodule:factory.Each fails the tails test.
Full suite (Windows), on the final code: 592 files passed (6 skipped), 7,255 tests passed, 0 failed (82 skipped).
🤖 Generated with Claude Code