typescript-go: Infinite recursion in inferFromTypes via getReturnTypeOfSignature (tsc 5.9 OK)
Maintainer antworten meist innerhalb von 1 Tag
Dieses Issue hat noch niemand übernommen.
Bewertung
- Schwierigkeit
- 5/5
- Geschätzter Aufwand
- Über eine Woche
- Anfängerfreundlichkeit
- 12/100
- Issue-Typ
- Bug
- Klarheit
- Größtenteils klar
- Aktivitätsstatus
- Aktiv
- Tech-Stack
- go, typescript
- Bereich
- compilers
Rechercherichtung
Start from the stack in the issue: getReturnTypeOfSignature in the checker, then inferFromTypes and inferFromTypeArguments in inference.go (around lines 89-98 and 311), and fillMissingTypeArguments in checker.go. Reproduce with the linked repo (npx oxlint --type-aware src) and api-probe.cjs. Done means the recursion terminates, ideally by hitting an instantiation or depth limit, with tsc and tsgo agreeing on the result.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
🔎 Search Terms
Infinite Recursion, getReturnTypeOfSignature, IsEqual
🕗 Version & Regression Information
Versions
- typescript-go as bundled in
[email protected](also 0.24.0, 0.25.0);@typescript/[email protected]fortsgo --noEmit - typescript 5.9.3 for comparison
- macOS arm64
⏯ Playground Link
https://github.com/jandolezal71/typescript-go-issue-64701-repro
💻 Code
Repro
https://github.com/jandolezal71/typescript-go-issue-64701-repro
npm i
npx tsc --noEmit # ok, ~1 s
npx oxlint --type-aware src # never finishes
package.json devDependencies: [email protected], [email protected], [email protected], [email protected], [email protected]
.oxlintrc.json
{"plugins":["typescript"],"categories":{"correctness":"off"},"rules":{"typescript/no-misused-promises":"error"}}
tsconfig.json
{"compilerOptions":{"target":"ES2022","module":"ESNext","moduleResolution":"Bundler","strict":true,"noEmit":true,"skipLibCheck":true},"include":["src"]}
src/repro.spec.ts
import type { Asyncify, IsEqual, PartialDeep } from 'type-fest';
import { expect, vi } from 'vitest';
type Fn = (...args: any) => any;
type FirstOverload<TFn extends Fn> = (...args: Parameters<TFn>) => ReturnType<TFn>;
type NextOverload<TFn extends Fn, TStack extends Fn> = (...args: Parameters<TStack & TFn>) => ReturnType<TStack & TFn>;
type SafeAsyncify<TFn extends Fn> = ReturnType<TFn> extends Promise<unknown> ? TFn : Asyncify<TFn>;
// Collects all overloads of TFn; recursion stops once the next overload equals the previous one.
type PromisifyInner<TFn extends Fn, TAcc extends Fn, TStack extends Fn, TPrevious extends Fn, TCurrent extends Fn = NextOverload<TFn, TStack>> =
IsEqual<TCurrent, TPrevious> extends true ? TAcc : PromisifyInner<TFn, TAcc & SafeAsyncify<TCurrent>, TCurrent & TStack, TCurrent>;
type Promisify<TFn extends Fn, TFirst extends Fn = FirstOverload<TFn>> = PromisifyInner<TFn, SafeAsyncify<TFirst>, TFirst, TFirst>;
type IfThenElse<C, T, F> = false extends C ? F : T;
type OwnProperties<T> = { [K in keyof T]: T[K] };
type AddOptionalRequestOptions<TFn> = TFn extends (...args: [...infer TArgs]) => infer TReturn
? ((...args: [...TArgs, requestOptions?: { timeout?: number }]) => TReturn) & TFn
: TFn;
type PromisifyProcedureTree<T> = T extends Fn
? ReturnType<T> extends Promise<unknown>
? AddOptionalRequestOptions<T>
: IfThenElse<
keyof OwnProperties<T> extends never ? true : false,
AddOptionalRequestOptions<Promisify<T>>,
AddOptionalRequestOptions<Promisify<T>> & PromisifyProcedureTree<OwnProperties<T>>
>
: T extends Record<string, unknown>
? { readonly [K in keyof T]: PromisifyProcedureTree<T[K]> }
: T;
type ProtocolValue = null | string | number | boolean | Date | readonly ProtocolValue[] | { readonly [key: string]: ProtocolValue };
type ProcedureTree<TProtocolValue> = {
readonly [key: string | number]:
| ProcedureTree<TProtocolValue>
| ((...args: never[]) => void | PartialDeep<TProtocolValue> | Promise<void | PartialDeep<TProtocolValue>>);
};
declare class Client<TTree> {
get call(): PromisifyProcedureTree<TTree>;
}
declare function createHandler<TTree extends ProcedureTree<ProtocolValue>>(): (client: Client<TTree>) => void;
expect(vi.mocked(createHandler)).toHaveBeenCalledTimes(1);
tsc API comparison (node api-probe.cjs, finishes in ~80 ms)
const ts = require('typescript');
const cfg = ts.getParsedCommandLineOfConfigFile('./tsconfig.json', {}, { ...ts.sys, onUnRecoverableConfigFileDiagnostic() {} });
const program = ts.createProgram(cfg.fileNames, cfg.options);
const checker = program.getTypeChecker();
const sf = program.getSourceFile('./src/repro.spec.ts');
let n = 0;
const t0 = Date.now();
(function visit(node) {
if (ts.isExpression(node)) {
const type = checker.getTypeAtLocation(node);
for (const part of type.isUnion() ? type.types : [type]) {
for (const sig of part.getCallSignatures()) { checker.getReturnTypeOfSignature(sig); n++; }
for (const prop of part.getProperties()) {
const pt = checker.getTypeOfSymbolAtLocation(prop, node);
for (const sig of pt.getCallSignatures()) { checker.getReturnTypeOfSignature(sig); n++; }
}
}
}
ts.forEachChild(node, visit);
})(sf);
console.log(`tsc ${ts.version} API: ${n} return types resolved in ${Date.now() - t0} ms`);
🙁 Actual behavior
Checker never terminates in getReturnTypeOfSignature (inference recursion) on overload-walking type; tsc 5.9 resolves it in milliseconds
What happens
checker.getReturnTypeOfSignaturenever returns: 100 % CPU indefinitely (observed >20 min).- It is reached through the type-aware linter tsgolint, rule
no-misused-promises(alsostrict-void-return). The rule only asks for the return type of a call signature. tsgo --noEmiton the same project finishes in ~1 s with no errors, because the plain check never requests this return type.
tsc 5.9.3 does not hang on the same requests
Through the TS API, I requested the return type of every call signature of every expression and its properties in the file. That is a superset of what the lint rule requests. All 1709 resolved in 79 ms (script below).
The type itself terminates
Forcing Promisify on concrete overloaded functions (1, 2 and 3 overloads, generic overloads) type-checks correctly in both tsc and tsgo. tsc reports 4174 instantiations.
Trigger
A recursive type walks function overloads and stops when IsEqual<Current, Previous> is true.
- With the type-fest 4.31+
IsEqual,<G>() => G extends A & G | G ? 1 : 2, it hangs. - With the older form,
<G>() => G extends A ? 1 : 2, it finishes in ~1 s.
All of these are needed to reproduce. Removing any one makes it finish:
- the recursive
PromisifyInner, - the
Clientclass with acallgetter, - the generic
createHandlerconstrained toProcedureTree, expect(vi.mocked(createHandler))(vitest'sMockedFunctiontype).
Stack (SIGQUIT on the hung process, trimmed; full dump attached as stack.txt):
tsgolint/internal/rules/no_misused_promises ...
typescript-go/internal/checker.(*Checker).getReturnTypeOfSignature
.instantiateType → .instantiateTypeWithAlias → .instantiateTypeWorker
.getConditionalTypeInstantiation → .getConditionalType
...458 frames elided...
.inferFromTypes (inference.go:89 / :98)
.inferFromTypeArguments (inference.go:311)
.inferFromTypes
.inferFromTypeArguments
... (repeats, same source/target pointers on each inferFromTypes call)
.fillMissingTypeArguments (checker.go:21859)
🙂 Expected behavior
Expected
The checker terminates like tsc does. If the type is too deep, it should report or bail out at the instantiation and depth limits, not loop.
Additional information about the issue
attaching stacktrace
- Vorherrschende Sprache
- Go
- Sterne
- 112k
- Forks
- 15.4k
- Ø Merge
- 3 T. 1 Std.
- Gemergte PRs (30 T.)
- 136
Entwicklungsumgebung
Startet den Dev-Container des Projekts im Browser, mit Ihrem eigenen GitHub-Konto.
- Kein Dockerfile und keine Docker-Compose-Datei
- Hat eine Pull-Request-Vorlage
- Beitragsleitfaden lesen
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus microsoft/TypeScript
-
Won't Fix
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 62/100
microsoft/TypeScript#65107 · 1 Kommentar ·
Maintainer antworten meist innerhalb von 1 Tag
-
Native API: formatDiagnosticsWithColorAndContext drops related information without a file locationEvtl. vergeben @timurzvural hat das heute übernommen. Offen
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 75/100
microsoft/TypeScript#65087 ·
Maintainer antworten meist innerhalb von 1 Tag
-
TS7 LSP: documentHighlight returns nothing when the file path contains '@'Evtl. wieder frei Ein Pull Request dazu wurde ohne Merge geschlossen. OffenBug
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
microsoft/TypeScript#64758 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Language server (tsc --lsp) exits with code 1 after `shutdown` and `exit`Evtl. vergeben @rajnish-verma-22172 hat das vor 13 Tagen übernommen. OffenBug
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 76/100
microsoft/TypeScript#64742 · 1 Kommentar ·
Maintainer antworten meist innerhalb von 1 Tag
-
Class names `eval` and `arguments` are not reported as invalid strict mode bindingsEvtl. vergeben @dmety hat das vor 26 Tagen übernommen. OffenBug
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 68/100
microsoft/TypeScript#64661 ·
Maintainer antworten meist innerhalb von 1 Tag
Alle Issues in microsoft/TypeScript
Ähnliche Issues
-
area: frontend priority: normal
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
Maintainer antworten meist innerhalb von 1 Tag
-
TestAutonomousStop_ReportsObservedState fails under host load: 1s wall-clock ceiling vs 250ms cycleOffentype:bug
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 65/100
nightgauge/nightgauge#2909 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 62/100
jasonfen/terminal-space-program#842 ·
Maintainer antworten meist innerhalb von 1 Tag
-
enhancement good first issue
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
MustacheCase/zanadir#521 ·
Maintainer antworten meist innerhalb von 1 Tag
-
v3.1.0: spread placement groups fail — label key uses the reserved hetzner.cloud prefixEvtl. vergeben @ScrimReaper hat das heute übernommen. Offen
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 86/100
stubbi/karpenter-provider-hetzner#120 ·
Maintainer antworten meist innerhalb von 4 Tagen