diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index ba4c3fa2718b6..01966171d45da 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, c.getEffectiveCallArguments(expr), 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..824501a255f1c --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.errors.txt @@ -0,0 +1,60 @@ +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(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) ==== + // 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 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. + 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..38c31a48c1de9 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.js @@ -0,0 +1,91 @@ +//// [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 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 + 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 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; + // 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..3a62559672798 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.symbols @@ -0,0 +1,130 @@ +//// [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 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, 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, 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, 31, 32)) +>value : Symbol(value, Decl(genericRecursiveFunctionReturn.ts, 33, 19)) +} + +function swap(left: T, right: U, shouldSwap: boolean) { +>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, 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, 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 new file mode 100644 index 0000000000000..66cd3359917bd --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/genericRecursiveFunctionReturn.types @@ -0,0 +1,147 @@ +//// [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 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 + + 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..095889b915de3 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/genericRecursiveFunctionReturn.ts @@ -0,0 +1,44 @@ +// 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 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 + 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); +}