From 4cc66cc6a3454dffb7d437d373a60e6848f0683a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 25 Aug 2026 11:18:23 +0200 Subject: [PATCH 1/2] Don't ignore all generic self tail calls when collecting the return type of a function --- tsc/internal/checker/checker.go | 11 +- .../genericRecursiveFunctionReturn.errors.txt | 52 ++++++++ .../genericRecursiveFunctionReturn.js | 76 ++++++++++++ .../genericRecursiveFunctionReturn.symbols | 107 ++++++++++++++++ .../genericRecursiveFunctionReturn.types | 117 ++++++++++++++++++ .../genericRecursiveFunctionReturn.ts | 36 ++++++ 6 files changed, 395 insertions(+), 4 deletions(-) create mode 100644 tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.js create mode 100644 tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.types create mode 100644 tsc/testdata/tests/cases/compiler/genericRecursiveFunctionReturn.ts diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index ba4c3fa2718b6..08e2e1c112b12 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -20382,15 +20382,18 @@ func (c *Checker) checkAndAggregateReturnExpressionTypes(fn *ast.Node, checkMode return false } expr = ast.SkipParentheses(expr) - // Bare calls to this same function don't contribute to inference - // and `return await` is also safe to unwrap here + // Bare calls to this same function that don't require a new generic instantiation + // don't contribute to inference, and `return await` is also safe to unwrap here. if functionFlags&ast.FunctionFlagsAsync != 0 && ast.IsAwaitExpression(expr) { expr = ast.SkipParentheses(expr.Expression()) } if ast.IsCallExpression(expr) && ast.IsIdentifier(expr.Expression()) && c.checkExpressionCached(expr.Expression()).symbol == c.getMergedSymbol(fn.Symbol()) && (!ast.IsFunctionExpressionOrArrowFunction(fn.Symbol().ValueDeclaration) || c.isConstantReference(expr.Expression())) { - hasReturnOfTypeNever = true - return false + signature := c.getSignatureFromDeclaration(fn) + if len(signature.typeParameters) == 0 || len(expr.TypeArguments()) == 0 && c.isSignatureApplicable(expr, expr.Arguments(), signature, c.assignableRelation, checkMode, false /*reportErrors*/, nil /*diagnosticOutput*/) { + hasReturnOfTypeNever = true + return false + } } t := c.checkExpressionCachedEx(expr, checkMode & ^CheckModeSkipGenericFunctions) if functionFlags&ast.FunctionFlagsAsync != 0 { diff --git a/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.errors.txt b/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.errors.txt new file mode 100644 index 0000000000000..c209c4c5af31e --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.errors.txt @@ -0,0 +1,52 @@ +genericRecursiveFunctionReturn.ts(2,7): error TS7023: 'f' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. +genericRecursiveFunctionReturn.ts(12,10): error TS7023: 'g' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. +genericRecursiveFunctionReturn.ts(26,10): error TS7023: 'nested' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. +genericRecursiveFunctionReturn.ts(32,10): error TS7023: 'swap' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. + + +==== genericRecursiveFunctionReturn.ts (4 errors) ==== + // https://github.com/microsoft/TypeScript/issues/63990 + const f = (a: T, p: number) => { + ~ +!!! error TS7023: 'f' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. + if (!p) return a; + // this recursive call can't be ignored as that would lead to inferring T when the number return type is possible here + return f(p, p - 1); + }; + + const x = f("foo", 5); + + declare const condition: boolean; + + function g(value: T) { + ~ +!!! error TS7023: 'g' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. + if (condition) return value; + // this recursive call can't be ignored as that would lead to inferring T when the number return type is possible here + return g(value); + } + + function h(value: T, count: number) { + if (!count) return value; + // this recursive call can be ignored because its arguments are assignable to the current generic signature + return h(value, count - 1); + } + + const y = h("foo", 1); + + function nested(value: T) { + ~~~~~~ +!!! error TS7023: 'nested' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. + if (condition) return value; + // this recursive call can't be ignored as that would lead to inferring T when the T[] return type is possible here + return nested([value]); + } + + function swap(left: T, right: U, shouldSwap: boolean) { + ~~~~ +!!! error TS7023: 'swap' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. + if (!shouldSwap) return [left, right] as const; + // this recursive call can't be ignored as that would lead to inferring readonly [T, U] when the readonly [U, T] return type is possible here + return swap(right, left, false); + } + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.js b/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.js new file mode 100644 index 0000000000000..e7692831e15c8 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.js @@ -0,0 +1,76 @@ +//// [tests/cases/compiler/genericRecursiveFunctionReturn.ts] //// + +//// [genericRecursiveFunctionReturn.ts] +// https://github.com/microsoft/TypeScript/issues/63990 +const f = (a: T, p: number) => { + if (!p) return a; + // this recursive call can't be ignored as that would lead to inferring T when the number return type is possible here + return f(p, p - 1); +}; + +const x = f("foo", 5); + +declare const condition: boolean; + +function g(value: T) { + if (condition) return value; + // this recursive call can't be ignored as that would lead to inferring T when the number return type is possible here + return g(value); +} + +function h(value: T, count: number) { + if (!count) return value; + // this recursive call can be ignored because its arguments are assignable to the current generic signature + return h(value, count - 1); +} + +const y = h("foo", 1); + +function nested(value: T) { + if (condition) return value; + // this recursive call can't be ignored as that would lead to inferring T when the T[] return type is possible here + return nested([value]); +} + +function swap(left: T, right: U, shouldSwap: boolean) { + if (!shouldSwap) return [left, right] as const; + // this recursive call can't be ignored as that would lead to inferring readonly [T, U] when the readonly [U, T] return type is possible here + return swap(right, left, false); +} + + +//// [genericRecursiveFunctionReturn.js] +"use strict"; +// https://github.com/microsoft/TypeScript/issues/63990 +const f = (a, p) => { + if (!p) + return a; + // this recursive call can't be ignored as that would lead to inferring T when the number return type is possible here + return f(p, p - 1); +}; +const x = f("foo", 5); +function g(value) { + if (condition) + return value; + // this recursive call can't be ignored as that would lead to inferring T when the number return type is possible here + return g(value); +} +function h(value, count) { + if (!count) + return value; + // this recursive call can be ignored because its arguments are assignable to the current generic signature + return h(value, count - 1); +} +const y = h("foo", 1); +function nested(value) { + if (condition) + return value; + // this recursive call can't be ignored as that would lead to inferring T when the T[] return type is possible here + return nested([value]); +} +function swap(left, right, shouldSwap) { + if (!shouldSwap) + return [left, right]; + // this recursive call can't be ignored as that would lead to inferring readonly [T, U] when the readonly [U, T] return type is possible here + return swap(right, left, false); +} diff --git a/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.symbols b/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.symbols new file mode 100644 index 0000000000000..2d429f2c8aee5 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.symbols @@ -0,0 +1,107 @@ +//// [tests/cases/compiler/genericRecursiveFunctionReturn.ts] //// + +=== genericRecursiveFunctionReturn.ts === +// https://github.com/microsoft/TypeScript/issues/63990 +const f = (a: T, p: number) => { +>f : Symbol(f, Decl(genericRecursiveFunctionReturn.ts, 1, 5)) +>T : Symbol(T, Decl(genericRecursiveFunctionReturn.ts, 1, 11)) +>a : Symbol(a, Decl(genericRecursiveFunctionReturn.ts, 1, 14)) +>T : Symbol(T, Decl(genericRecursiveFunctionReturn.ts, 1, 11)) +>p : Symbol(p, Decl(genericRecursiveFunctionReturn.ts, 1, 19)) + + if (!p) return a; +>p : Symbol(p, Decl(genericRecursiveFunctionReturn.ts, 1, 19)) +>a : Symbol(a, Decl(genericRecursiveFunctionReturn.ts, 1, 14)) + + // this recursive call can't be ignored as that would lead to inferring T when the number return type is possible here + return f(p, p - 1); +>f : Symbol(f, Decl(genericRecursiveFunctionReturn.ts, 1, 5)) +>p : Symbol(p, Decl(genericRecursiveFunctionReturn.ts, 1, 19)) +>p : Symbol(p, Decl(genericRecursiveFunctionReturn.ts, 1, 19)) + +}; + +const x = f("foo", 5); +>x : Symbol(x, Decl(genericRecursiveFunctionReturn.ts, 7, 5)) +>f : Symbol(f, Decl(genericRecursiveFunctionReturn.ts, 1, 5)) + +declare const condition: boolean; +>condition : Symbol(condition, Decl(genericRecursiveFunctionReturn.ts, 9, 13)) + +function g(value: T) { +>g : Symbol(g, Decl(genericRecursiveFunctionReturn.ts, 9, 33)) +>T : Symbol(T, Decl(genericRecursiveFunctionReturn.ts, 11, 11)) +>value : Symbol(value, Decl(genericRecursiveFunctionReturn.ts, 11, 29)) +>T : Symbol(T, Decl(genericRecursiveFunctionReturn.ts, 11, 11)) + + if (condition) return value; +>condition : Symbol(condition, Decl(genericRecursiveFunctionReturn.ts, 9, 13)) +>value : Symbol(value, Decl(genericRecursiveFunctionReturn.ts, 11, 29)) + + // this recursive call can't be ignored as that would lead to inferring T when the number return type is possible here + return g(value); +>g : Symbol(g, Decl(genericRecursiveFunctionReturn.ts, 9, 33)) +>value : Symbol(value, Decl(genericRecursiveFunctionReturn.ts, 11, 29)) +} + +function h(value: T, count: number) { +>h : Symbol(h, Decl(genericRecursiveFunctionReturn.ts, 15, 1)) +>T : Symbol(T, Decl(genericRecursiveFunctionReturn.ts, 17, 11)) +>value : Symbol(value, Decl(genericRecursiveFunctionReturn.ts, 17, 14)) +>T : Symbol(T, Decl(genericRecursiveFunctionReturn.ts, 17, 11)) +>count : Symbol(count, Decl(genericRecursiveFunctionReturn.ts, 17, 23)) + + if (!count) return value; +>count : Symbol(count, Decl(genericRecursiveFunctionReturn.ts, 17, 23)) +>value : Symbol(value, Decl(genericRecursiveFunctionReturn.ts, 17, 14)) + + // this recursive call can be ignored because its arguments are assignable to the current generic signature + return h(value, count - 1); +>h : Symbol(h, Decl(genericRecursiveFunctionReturn.ts, 15, 1)) +>value : Symbol(value, Decl(genericRecursiveFunctionReturn.ts, 17, 14)) +>count : Symbol(count, Decl(genericRecursiveFunctionReturn.ts, 17, 23)) +} + +const y = h("foo", 1); +>y : Symbol(y, Decl(genericRecursiveFunctionReturn.ts, 23, 5)) +>h : Symbol(h, Decl(genericRecursiveFunctionReturn.ts, 15, 1)) + +function nested(value: T) { +>nested : Symbol(nested, Decl(genericRecursiveFunctionReturn.ts, 23, 22)) +>T : Symbol(T, Decl(genericRecursiveFunctionReturn.ts, 25, 16)) +>value : Symbol(value, Decl(genericRecursiveFunctionReturn.ts, 25, 19)) +>T : Symbol(T, Decl(genericRecursiveFunctionReturn.ts, 25, 16)) + + if (condition) return value; +>condition : Symbol(condition, Decl(genericRecursiveFunctionReturn.ts, 9, 13)) +>value : Symbol(value, Decl(genericRecursiveFunctionReturn.ts, 25, 19)) + + // this recursive call can't be ignored as that would lead to inferring T when the T[] return type is possible here + return nested([value]); +>nested : Symbol(nested, Decl(genericRecursiveFunctionReturn.ts, 23, 22)) +>value : Symbol(value, Decl(genericRecursiveFunctionReturn.ts, 25, 19)) +} + +function swap(left: T, right: U, shouldSwap: boolean) { +>swap : Symbol(swap, Decl(genericRecursiveFunctionReturn.ts, 29, 1)) +>T : Symbol(T, Decl(genericRecursiveFunctionReturn.ts, 31, 14)) +>U : Symbol(U, Decl(genericRecursiveFunctionReturn.ts, 31, 16)) +>left : Symbol(left, Decl(genericRecursiveFunctionReturn.ts, 31, 20)) +>T : Symbol(T, Decl(genericRecursiveFunctionReturn.ts, 31, 14)) +>right : Symbol(right, Decl(genericRecursiveFunctionReturn.ts, 31, 28)) +>U : Symbol(U, Decl(genericRecursiveFunctionReturn.ts, 31, 16)) +>shouldSwap : Symbol(shouldSwap, Decl(genericRecursiveFunctionReturn.ts, 31, 38)) + + if (!shouldSwap) return [left, right] as const; +>shouldSwap : Symbol(shouldSwap, Decl(genericRecursiveFunctionReturn.ts, 31, 38)) +>left : Symbol(left, Decl(genericRecursiveFunctionReturn.ts, 31, 20)) +>right : Symbol(right, Decl(genericRecursiveFunctionReturn.ts, 31, 28)) +>const : Symbol(const) + + // this recursive call can't be ignored as that would lead to inferring readonly [T, U] when the readonly [U, T] return type is possible here + return swap(right, left, false); +>swap : Symbol(swap, Decl(genericRecursiveFunctionReturn.ts, 29, 1)) +>right : Symbol(right, Decl(genericRecursiveFunctionReturn.ts, 31, 28)) +>left : Symbol(left, Decl(genericRecursiveFunctionReturn.ts, 31, 20)) +} + diff --git a/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.types b/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.types new file mode 100644 index 0000000000000..b02f2891471f6 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.types @@ -0,0 +1,117 @@ +//// [tests/cases/compiler/genericRecursiveFunctionReturn.ts] //// + +=== genericRecursiveFunctionReturn.ts === +// https://github.com/microsoft/TypeScript/issues/63990 +const f = (a: T, p: number) => { +>f : (a: T, p: number) => any +>(a: T, p: number) => { if (!p) return a; // this recursive call can't be ignored as that would lead to inferring T when the number return type is possible here return f(p, p - 1);} : (a: T, p: number) => any +>a : T +>p : number + + if (!p) return a; +>!p : boolean +>p : number +>a : T + + // this recursive call can't be ignored as that would lead to inferring T when the number return type is possible here + return f(p, p - 1); +>f(p, p - 1) : any +>f : (a: T, p: number) => any +>p : number +>p - 1 : number +>p : number +>1 : 1 + +}; + +const x = f("foo", 5); +>x : any +>f("foo", 5) : any +>f : (a: T, p: number) => any +>"foo" : "foo" +>5 : 5 + +declare const condition: boolean; +>condition : boolean + +function g(value: T) { +>g : (value: T) => any +>value : T + + if (condition) return value; +>condition : boolean +>value : T + + // this recursive call can't be ignored as that would lead to inferring T when the number return type is possible here + return g(value); +>g(value) : any +>g : (value: T) => any +>value : T +} + +function h(value: T, count: number) { +>h : (value: T, count: number) => T +>value : T +>count : number + + if (!count) return value; +>!count : boolean +>count : number +>value : T + + // this recursive call can be ignored because its arguments are assignable to the current generic signature + return h(value, count - 1); +>h(value, count - 1) : T +>h : (value: T, count: number) => T +>value : T +>count - 1 : number +>count : number +>1 : 1 +} + +const y = h("foo", 1); +>y : "foo" +>h("foo", 1) : "foo" +>h : (value: T, count: number) => T +>"foo" : "foo" +>1 : 1 + +function nested(value: T) { +>nested : (value: T) => any +>value : T + + if (condition) return value; +>condition : boolean +>value : T + + // this recursive call can't be ignored as that would lead to inferring T when the T[] return type is possible here + return nested([value]); +>nested([value]) : any +>nested : (value: T) => any +>[value] : T[] +>value : T +} + +function swap(left: T, right: U, shouldSwap: boolean) { +>swap : (left: T, right: U, shouldSwap: boolean) => any +>left : T +>right : U +>shouldSwap : boolean + + if (!shouldSwap) return [left, right] as const; +>!shouldSwap : boolean +>shouldSwap : boolean +>[left, right] as const : readonly [T, U] +>[left, right] : readonly [T, U] +>left : T +>right : U + + // this recursive call can't be ignored as that would lead to inferring readonly [T, U] when the readonly [U, T] return type is possible here + return swap(right, left, false); +>swap(right, left, false) : any +>swap : (left: T, right: U, shouldSwap: boolean) => any +>right : U +>left : T +>false : false +} + diff --git a/tsc/testdata/tests/cases/compiler/genericRecursiveFunctionReturn.ts b/tsc/testdata/tests/cases/compiler/genericRecursiveFunctionReturn.ts new file mode 100644 index 0000000000000..26476277819d7 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/genericRecursiveFunctionReturn.ts @@ -0,0 +1,36 @@ +// https://github.com/microsoft/TypeScript/issues/63990 +const f = (a: T, p: number) => { + if (!p) return a; + // this recursive call can't be ignored as that would lead to inferring T when the number return type is possible here + return f(p, p - 1); +}; + +const x = f("foo", 5); + +declare const condition: boolean; + +function g(value: T) { + if (condition) return value; + // this recursive call can't be ignored as that would lead to inferring T when the number return type is possible here + return g(value); +} + +function h(value: T, count: number) { + if (!count) return value; + // this recursive call can be ignored because its arguments are assignable to the current generic signature + return h(value, count - 1); +} + +const y = h("foo", 1); + +function nested(value: T) { + if (condition) return value; + // this recursive call can't be ignored as that would lead to inferring T when the T[] return type is possible here + return nested([value]); +} + +function swap(left: T, right: U, shouldSwap: boolean) { + if (!shouldSwap) return [left, right] as const; + // this recursive call can't be ignored as that would lead to inferring readonly [T, U] when the readonly [U, T] return type is possible here + return swap(right, left, false); +} From 0a566206b17c1e46c8c892a826a1a17d1fb95cbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 25 Aug 2026 12:28:55 +0200 Subject: [PATCH 2/2] Handle tuple spreads in recursive return inference --- tsc/internal/checker/checker.go | 2 +- .../genericRecursiveFunctionReturn.errors.txt | 12 +++- .../genericRecursiveFunctionReturn.js | 15 +++++ .../genericRecursiveFunctionReturn.symbols | 65 +++++++++++++------ .../genericRecursiveFunctionReturn.types | 30 +++++++++ .../genericRecursiveFunctionReturn.ts | 8 +++ 6 files changed, 108 insertions(+), 24 deletions(-) diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 08e2e1c112b12..01966171d45da 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -20390,7 +20390,7 @@ func (c *Checker) checkAndAggregateReturnExpressionTypes(fn *ast.Node, checkMode if ast.IsCallExpression(expr) && ast.IsIdentifier(expr.Expression()) && c.checkExpressionCached(expr.Expression()).symbol == c.getMergedSymbol(fn.Symbol()) && (!ast.IsFunctionExpressionOrArrowFunction(fn.Symbol().ValueDeclaration) || c.isConstantReference(expr.Expression())) { signature := c.getSignatureFromDeclaration(fn) - if len(signature.typeParameters) == 0 || len(expr.TypeArguments()) == 0 && c.isSignatureApplicable(expr, expr.Arguments(), signature, c.assignableRelation, checkMode, false /*reportErrors*/, nil /*diagnosticOutput*/) { + if len(signature.typeParameters) == 0 || len(expr.TypeArguments()) == 0 && c.isSignatureApplicable(expr, c.getEffectiveCallArguments(expr), signature, c.assignableRelation, checkMode, false /*reportErrors*/, nil /*diagnosticOutput*/) { hasReturnOfTypeNever = true return false } diff --git a/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.errors.txt b/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.errors.txt index c209c4c5af31e..824501a255f1c 100644 --- a/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.errors.txt @@ -1,7 +1,7 @@ genericRecursiveFunctionReturn.ts(2,7): error TS7023: 'f' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. genericRecursiveFunctionReturn.ts(12,10): error TS7023: 'g' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. -genericRecursiveFunctionReturn.ts(26,10): error TS7023: 'nested' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. -genericRecursiveFunctionReturn.ts(32,10): error TS7023: 'swap' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. +genericRecursiveFunctionReturn.ts(34,10): error TS7023: 'nested' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. +genericRecursiveFunctionReturn.ts(40,10): error TS7023: 'swap' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. ==== genericRecursiveFunctionReturn.ts (4 errors) ==== @@ -34,6 +34,14 @@ genericRecursiveFunctionReturn.ts(32,10): error TS7023: 'swap' implicitly has re const y = h("foo", 1); + function tupleSpread(value: T, count: number) { + if (!count) return value; + // tuple spreads are expanded into effective arguments before checking assignability + return tupleSpread(...[value, count - 1] as const); + } + + const z = tupleSpread("foo", 1); + function nested(value: T) { ~~~~~~ !!! error TS7023: 'nested' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. diff --git a/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.js b/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.js index e7692831e15c8..38c31a48c1de9 100644 --- a/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.js +++ b/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.js @@ -26,6 +26,14 @@ function h(value: T, count: number) { const y = h("foo", 1); +function tupleSpread(value: T, count: number) { + if (!count) return value; + // tuple spreads are expanded into effective arguments before checking assignability + return tupleSpread(...[value, count - 1] as const); +} + +const z = tupleSpread("foo", 1); + function nested(value: T) { if (condition) return value; // this recursive call can't be ignored as that would lead to inferring T when the T[] return type is possible here @@ -62,6 +70,13 @@ function h(value, count) { return h(value, count - 1); } const y = h("foo", 1); +function tupleSpread(value, count) { + if (!count) + return value; + // tuple spreads are expanded into effective arguments before checking assignability + return tupleSpread(...[value, count - 1]); +} +const z = tupleSpread("foo", 1); function nested(value) { if (condition) return value; diff --git a/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.symbols b/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.symbols index 2d429f2c8aee5..3a62559672798 100644 --- a/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.symbols +++ b/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.symbols @@ -66,42 +66,65 @@ const y = h("foo", 1); >y : Symbol(y, Decl(genericRecursiveFunctionReturn.ts, 23, 5)) >h : Symbol(h, Decl(genericRecursiveFunctionReturn.ts, 15, 1)) +function tupleSpread(value: T, count: number) { +>tupleSpread : Symbol(tupleSpread, Decl(genericRecursiveFunctionReturn.ts, 23, 22)) +>T : Symbol(T, Decl(genericRecursiveFunctionReturn.ts, 25, 21)) +>value : Symbol(value, Decl(genericRecursiveFunctionReturn.ts, 25, 24)) +>T : Symbol(T, Decl(genericRecursiveFunctionReturn.ts, 25, 21)) +>count : Symbol(count, Decl(genericRecursiveFunctionReturn.ts, 25, 33)) + + if (!count) return value; +>count : Symbol(count, Decl(genericRecursiveFunctionReturn.ts, 25, 33)) +>value : Symbol(value, Decl(genericRecursiveFunctionReturn.ts, 25, 24)) + + // tuple spreads are expanded into effective arguments before checking assignability + return tupleSpread(...[value, count - 1] as const); +>tupleSpread : Symbol(tupleSpread, Decl(genericRecursiveFunctionReturn.ts, 23, 22)) +>value : Symbol(value, Decl(genericRecursiveFunctionReturn.ts, 25, 24)) +>count : Symbol(count, Decl(genericRecursiveFunctionReturn.ts, 25, 33)) +>const : Symbol(const) +} + +const z = tupleSpread("foo", 1); +>z : Symbol(z, Decl(genericRecursiveFunctionReturn.ts, 31, 5)) +>tupleSpread : Symbol(tupleSpread, Decl(genericRecursiveFunctionReturn.ts, 23, 22)) + function nested(value: T) { ->nested : Symbol(nested, Decl(genericRecursiveFunctionReturn.ts, 23, 22)) ->T : Symbol(T, Decl(genericRecursiveFunctionReturn.ts, 25, 16)) ->value : Symbol(value, Decl(genericRecursiveFunctionReturn.ts, 25, 19)) ->T : Symbol(T, Decl(genericRecursiveFunctionReturn.ts, 25, 16)) +>nested : Symbol(nested, Decl(genericRecursiveFunctionReturn.ts, 31, 32)) +>T : Symbol(T, Decl(genericRecursiveFunctionReturn.ts, 33, 16)) +>value : Symbol(value, Decl(genericRecursiveFunctionReturn.ts, 33, 19)) +>T : Symbol(T, Decl(genericRecursiveFunctionReturn.ts, 33, 16)) if (condition) return value; >condition : Symbol(condition, Decl(genericRecursiveFunctionReturn.ts, 9, 13)) ->value : Symbol(value, Decl(genericRecursiveFunctionReturn.ts, 25, 19)) +>value : Symbol(value, Decl(genericRecursiveFunctionReturn.ts, 33, 19)) // this recursive call can't be ignored as that would lead to inferring T when the T[] return type is possible here return nested([value]); ->nested : Symbol(nested, Decl(genericRecursiveFunctionReturn.ts, 23, 22)) ->value : Symbol(value, Decl(genericRecursiveFunctionReturn.ts, 25, 19)) +>nested : Symbol(nested, Decl(genericRecursiveFunctionReturn.ts, 31, 32)) +>value : Symbol(value, Decl(genericRecursiveFunctionReturn.ts, 33, 19)) } function swap(left: T, right: U, shouldSwap: boolean) { ->swap : Symbol(swap, Decl(genericRecursiveFunctionReturn.ts, 29, 1)) ->T : Symbol(T, Decl(genericRecursiveFunctionReturn.ts, 31, 14)) ->U : Symbol(U, Decl(genericRecursiveFunctionReturn.ts, 31, 16)) ->left : Symbol(left, Decl(genericRecursiveFunctionReturn.ts, 31, 20)) ->T : Symbol(T, Decl(genericRecursiveFunctionReturn.ts, 31, 14)) ->right : Symbol(right, Decl(genericRecursiveFunctionReturn.ts, 31, 28)) ->U : Symbol(U, Decl(genericRecursiveFunctionReturn.ts, 31, 16)) ->shouldSwap : Symbol(shouldSwap, Decl(genericRecursiveFunctionReturn.ts, 31, 38)) +>swap : Symbol(swap, Decl(genericRecursiveFunctionReturn.ts, 37, 1)) +>T : Symbol(T, Decl(genericRecursiveFunctionReturn.ts, 39, 14)) +>U : Symbol(U, Decl(genericRecursiveFunctionReturn.ts, 39, 16)) +>left : Symbol(left, Decl(genericRecursiveFunctionReturn.ts, 39, 20)) +>T : Symbol(T, Decl(genericRecursiveFunctionReturn.ts, 39, 14)) +>right : Symbol(right, Decl(genericRecursiveFunctionReturn.ts, 39, 28)) +>U : Symbol(U, Decl(genericRecursiveFunctionReturn.ts, 39, 16)) +>shouldSwap : Symbol(shouldSwap, Decl(genericRecursiveFunctionReturn.ts, 39, 38)) if (!shouldSwap) return [left, right] as const; ->shouldSwap : Symbol(shouldSwap, Decl(genericRecursiveFunctionReturn.ts, 31, 38)) ->left : Symbol(left, Decl(genericRecursiveFunctionReturn.ts, 31, 20)) ->right : Symbol(right, Decl(genericRecursiveFunctionReturn.ts, 31, 28)) +>shouldSwap : Symbol(shouldSwap, Decl(genericRecursiveFunctionReturn.ts, 39, 38)) +>left : Symbol(left, Decl(genericRecursiveFunctionReturn.ts, 39, 20)) +>right : Symbol(right, Decl(genericRecursiveFunctionReturn.ts, 39, 28)) >const : Symbol(const) // this recursive call can't be ignored as that would lead to inferring readonly [T, U] when the readonly [U, T] return type is possible here return swap(right, left, false); ->swap : Symbol(swap, Decl(genericRecursiveFunctionReturn.ts, 29, 1)) ->right : Symbol(right, Decl(genericRecursiveFunctionReturn.ts, 31, 28)) ->left : Symbol(left, Decl(genericRecursiveFunctionReturn.ts, 31, 20)) +>swap : Symbol(swap, Decl(genericRecursiveFunctionReturn.ts, 37, 1)) +>right : Symbol(right, Decl(genericRecursiveFunctionReturn.ts, 39, 28)) +>left : Symbol(left, Decl(genericRecursiveFunctionReturn.ts, 39, 20)) } diff --git a/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.types b/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.types index b02f2891471f6..66cd3359917bd 100644 --- a/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.types +++ b/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.types @@ -76,6 +76,36 @@ const y = h("foo", 1); >"foo" : "foo" >1 : 1 +function tupleSpread(value: T, count: number) { +>tupleSpread : (value: T, count: number) => T +>value : T +>count : number + + if (!count) return value; +>!count : boolean +>count : number +>value : T + + // tuple spreads are expanded into effective arguments before checking assignability + return tupleSpread(...[value, count - 1] as const); +>tupleSpread(...[value, count - 1] as const) : T +>tupleSpread : (value: T, count: number) => T +>...[value, count - 1] as const : number | T +>[value, count - 1] as const : readonly [T, number] +>[value, count - 1] : readonly [T, number] +>value : T +>count - 1 : number +>count : number +>1 : 1 +} + +const z = tupleSpread("foo", 1); +>z : "foo" +>tupleSpread("foo", 1) : "foo" +>tupleSpread : (value: T, count: number) => T +>"foo" : "foo" +>1 : 1 + function nested(value: T) { >nested : (value: T) => any >value : T diff --git a/tsc/testdata/tests/cases/compiler/genericRecursiveFunctionReturn.ts b/tsc/testdata/tests/cases/compiler/genericRecursiveFunctionReturn.ts index 26476277819d7..095889b915de3 100644 --- a/tsc/testdata/tests/cases/compiler/genericRecursiveFunctionReturn.ts +++ b/tsc/testdata/tests/cases/compiler/genericRecursiveFunctionReturn.ts @@ -23,6 +23,14 @@ function h(value: T, count: number) { const y = h("foo", 1); +function tupleSpread(value: T, count: number) { + if (!count) return value; + // tuple spreads are expanded into effective arguments before checking assignability + return tupleSpread(...[value, count - 1] as const); +} + +const z = tupleSpread("foo", 1); + function nested(value: T) { if (condition) return value; // this recursive call can't be ignored as that would lead to inferring T when the T[] return type is possible here