You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is the behavior in every version I tried: 7.1.0-dev.20261003.1 and 7.1.0-dev.20261004.1 (commit 50d70a3, the newest typescript@next), the synchronous API (typescript/unstable/sync), linux-x64, Node 26.8.2. Module resolver callbacks arrived in [api] Provide module resolution overrides #64299, so earlier versions do not have this API.
⏯ Playground Link
The API is not in the playground. The reproduction is one file.
repro.mjs writes 300 files that each import three siblings, then loads them with a resolveModuleName callback that asks a default resolver for the answer with the in-progress snapshot, as the "module resolver callbacks can delegate to another resolver" API test does. The callback always returns the right file itself and only counts whether the nested answer was right.
$ node repro.mjs
nested answers: 16 right, 884 for another request; program errors: 893 (first: Module '"./m1.js"' has no exported member 'v1'.)
$ node repro.mjs
nested answers: 27 right, 873 for another request; program errors: 889 (first: Module '"./m1.js"' has no exported member 'v1'.)
$ node repro.mjs local
no nested request; program errors: 0
$ GOMAXPROCS=1 node repro.mjs
nested answers: 900 right, 0 for another request; program errors: 0
Two answers go astray, and only when the callback makes a nested request. The nested resolveModuleName returns the answer to a different import, and the callback's own return value reaches the wrong import too: every callback returns the correct file, yet almost every import in the program is bound to another module.
🙂 Expected behavior
Each nested request gets its own answer and each callback answer reaches the import it was asked for, so the program has 0 errors with any number of threads.
Additional information about the issue
Impact: a resolver callback that delegates to another resolver, the pattern the API's own test shows, binds most imports to the wrong module once the loader uses more than one thread. Every type and diagnostic the tool reads from that program is then wrong, and no error says so. In the reproduction, 873 of 900 nested answers went to another request.
The program loader resolves imports from several goroutines, and each one calls the callback through SyncConn.Call. Call matches a response by method name, so it relies on c.mu to keep its request and its response together. When the client's callback makes a nested request, Callreleases c.mu while it handles that request. Another goroutine's Call then writes its own callback request, which the synchronous client handles inside its pending nested call. When the first nested request finishes, its response reaches the client while the client waits for the second callback's nested answer, and the two callback responses then reach the wrong goroutines because they share a method name.
I met this while building deadset-ts, the TypeScript analyzer of deadset, on the TypeScript 7 API. A pull request with a fix and a test follows.
🔎 Search Terms
createModuleResolver, resolveModuleName callback, nested request, inProgressSnapshot, SyncConn, wrong module
🕗 Version & Regression Information
7.1.0-dev.20261003.1and7.1.0-dev.20261004.1(commit50d70a3, the newesttypescript@next), the synchronous API (typescript/unstable/sync), linux-x64, Node 26.8.2. Module resolver callbacks arrived in [api] Provide module resolution overrides #64299, so earlier versions do not have this API.⏯ Playground Link
The API is not in the playground. The reproduction is one file.
💻 Code
package.json:{ "private": true, "type": "module", "dependencies": { "typescript": "7.1.0-dev.20261004.1" } }repro.mjswrites 300 files that each import three siblings, then loads them with aresolveModuleNamecallback that asks a default resolver for the answer with the in-progress snapshot, as the "module resolver callbacks can delegate to another resolver" API test does. The callback always returns the right file itself and only counts whether the nested answer was right.🙁 Actual behavior
On
7.1.0-dev.20261004.1:Two answers go astray, and only when the callback makes a nested request. The nested
resolveModuleNamereturns the answer to a different import, and the callback's own return value reaches the wrong import too: every callback returns the correct file, yet almost every import in the program is bound to another module.🙂 Expected behavior
Each nested request gets its own answer and each callback answer reaches the import it was asked for, so the program has 0 errors with any number of threads.
Additional information about the issue
Impact: a resolver callback that delegates to another resolver, the pattern the API's own test shows, binds most imports to the wrong module once the loader uses more than one thread. Every type and diagnostic the tool reads from that program is then wrong, and no error says so. In the reproduction, 873 of 900 nested answers went to another request.
The program loader resolves imports from several goroutines, and each one calls the callback through
SyncConn.Call.Callmatches a response by method name, so it relies onc.muto keep its request and its response together. When the client's callback makes a nested request,Callreleasesc.muwhile it handles that request. Another goroutine'sCallthen writes its own callback request, which the synchronous client handles inside its pending nested call. When the first nested request finishes, its response reaches the client while the client waits for the second callback's nested answer, and the two callback responses then reach the wrong goroutines because they share a method name.I met this while building deadset-ts, the TypeScript analyzer of deadset, on the TypeScript 7 API. A pull request with a fix and a test follows.