Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 = <T>(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<T extends number>(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<number>(value);
}

function h<T>(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<T>(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<T>(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<T, U>(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);
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//// [tests/cases/compiler/genericRecursiveFunctionReturn.ts] ////

//// [genericRecursiveFunctionReturn.ts]
// https://github.com/microsoft/TypeScript/issues/63990
const f = <T>(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<T extends number>(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<number>(value);
}

function h<T>(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<T>(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<T>(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<T, U>(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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//// [tests/cases/compiler/genericRecursiveFunctionReturn.ts] ////

=== genericRecursiveFunctionReturn.ts ===
// https://github.com/microsoft/TypeScript/issues/63990
const f = <T>(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<T extends number>(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<number>(value);
>g : Symbol(g, Decl(genericRecursiveFunctionReturn.ts, 9, 33))
>value : Symbol(value, Decl(genericRecursiveFunctionReturn.ts, 11, 29))
}

function h<T>(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<T>(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<T>(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<T, U>(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))
}

Loading