Skip to content

Revert #54477 but keep the tests #57160

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
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
13 changes: 5 additions & 8 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39347,20 +39347,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}

function getNonGenericReturnTypeOfSingleCallSignature(funcType: Type) {
function getReturnTypeOfSingleNonGenericCallSignature(funcType: Type) {
const signature = getSingleCallSignature(funcType);
if (signature) {
const returnType = getReturnTypeOfSignature(signature);
if (!signature.typeParameters || !couldContainTypeVariables(returnType)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I try to understand how to use couldContainTypeVariables appropriately and I wonder how this was different from how checkFunctionExpressionOrObjectLiteralMethod uses it here?

        // The identityMapper object is used to indicate that function expressions are wildcards
        if (checkMode && checkMode & CheckMode.SkipContextSensitive && isContextSensitive(node)) {
            // Skip parameters, return signature with return type that retains noncontextual parts so inferences can still be drawn in an early stage
            if (!getEffectiveReturnTypeNode(node) && !hasContextSensitiveParameters(node)) {
                // Return plain anyFunctionType if there is no possibility we'll make inferences from the return type
                const contextualSignature = getContextualSignature(node);
                if (contextualSignature && couldContainTypeVariables(getReturnTypeOfSignature(contextualSignature))) {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see how this is part of the inference algorithm and the check here was kinda not - but I still fail to understand how those 2 situations are fundamentally different so it's OK to call it within one context and not OK to call it within another.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that other use is correct either...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also tried to repro the regression caused by this cause I wanted to add a test case for it but I failed. It has been mentioned that it was only visible in the IDE but I assume that this particular report was reported by a bot reporting tsc-related changes. I couldn't repro with either though.

return returnType;
}
if (signature && !signature.typeParameters) {
return getReturnTypeOfSignature(signature);
}
}

function getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr: CallChain) {
const funcType = checkExpression(expr.expression);
const nonOptionalType = getOptionalExpressionType(funcType, expr.expression);
const returnType = getNonGenericReturnTypeOfSingleCallSignature(funcType);
const returnType = getReturnTypeOfSingleNonGenericCallSignature(funcType);
return returnType && propagateOptionalTypeMarker(returnType, expr, nonOptionalType !== funcType);
}

Expand Down Expand Up @@ -39409,7 +39406,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// signature where we can just fetch the return type without checking the arguments.
if (isCallExpression(expr) && expr.expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(expr, /*requireStringLiteralLikeArgument*/ true) && !isSymbolOrSymbolForCall(expr)) {
return isCallChain(expr) ? getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr) :
getNonGenericReturnTypeOfSingleCallSignature(checkNonNullExpression(expr.expression));
getReturnTypeOfSingleNonGenericCallSignature(checkNonNullExpression(expr.expression));
}
else if (isAssertionExpression(expr) && !isConstTypeReference(expr.type)) {
return getTypeFromTypeNode((expr as TypeAssertion).type);
Expand Down
6 changes: 3 additions & 3 deletions tests/baselines/reference/arrayFrom.types
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const inputALike: ArrayLike<A> = { length: 0 };
const inputARand = getEither(inputA, inputALike);
>inputARand : ArrayLike<A> | Iterable<A>
>getEither(inputA, inputALike) : ArrayLike<A> | Iterable<A>
>getEither : <T>(in1: Iterable<T>, in2: ArrayLike<T>) => Iterable<T> | ArrayLike<T>
>getEither : <T>(in1: Iterable<T>, in2: ArrayLike<T>) => ArrayLike<T> | Iterable<T>
>inputA : A[]
>inputALike : ArrayLike<A>

Expand Down Expand Up @@ -163,12 +163,12 @@ const result11: B[] = Array.from(inputASet, ({ a }): B => ({ b: a }));
// the ?: as always taking the false branch, narrowing to ArrayLike<T>,
// even when the type is written as : Iterable<T>|ArrayLike<T>
function getEither<T> (in1: Iterable<T>, in2: ArrayLike<T>) {
>getEither : <T>(in1: Iterable<T>, in2: ArrayLike<T>) => Iterable<T> | ArrayLike<T>
>getEither : <T>(in1: Iterable<T>, in2: ArrayLike<T>) => ArrayLike<T> | Iterable<T>
>in1 : Iterable<T>
>in2 : ArrayLike<T>

return Math.random() > 0.5 ? in1 : in2;
>Math.random() > 0.5 ? in1 : in2 : Iterable<T> | ArrayLike<T>
>Math.random() > 0.5 ? in1 : in2 : ArrayLike<T> | Iterable<T>
>Math.random() > 0.5 : boolean
>Math.random() : number
>Math.random : () => number
Expand Down
19 changes: 19 additions & 0 deletions tests/baselines/reference/circularReferenceInReturnType.errors.txt
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a minimal repro for the break, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
circularReferenceInReturnType.ts(3,7): error TS7022: 'res1' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
circularReferenceInReturnType.ts(9,7): error TS7022: 'res3' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.


==== circularReferenceInReturnType.ts (2 errors) ====
// inference fails for res1 and res2, but ideally should not
declare function fn1<T>(cb: () => T): string;
const res1 = fn1(() => res1);
~~~~
!!! error TS7022: 'res1' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.

declare function fn2<T>(): (cb: () => any) => (a: T) => void;
const res2 = fn2()(() => res2);

declare function fn3<T>(): <T2>(cb: (arg: T2) => any) => (a: T) => void;
const res3 = fn3()(() => res3);
~~~~
!!! error TS7022: 'res3' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.

49 changes: 25 additions & 24 deletions tests/baselines/reference/circularReferenceInReturnType.symbols
Original file line number Diff line number Diff line change
@@ -1,41 +1,42 @@
//// [tests/cases/compiler/circularReferenceInReturnType.ts] ////

=== circularReferenceInReturnType.ts ===
// inference fails for res1 and res2, but ideally should not
declare function fn1<T>(cb: () => T): string;
>fn1 : Symbol(fn1, Decl(circularReferenceInReturnType.ts, 0, 0))
>T : Symbol(T, Decl(circularReferenceInReturnType.ts, 0, 21))
>cb : Symbol(cb, Decl(circularReferenceInReturnType.ts, 0, 24))
>T : Symbol(T, Decl(circularReferenceInReturnType.ts, 0, 21))
>T : Symbol(T, Decl(circularReferenceInReturnType.ts, 1, 21))
>cb : Symbol(cb, Decl(circularReferenceInReturnType.ts, 1, 24))
>T : Symbol(T, Decl(circularReferenceInReturnType.ts, 1, 21))

const res1 = fn1(() => res1);
>res1 : Symbol(res1, Decl(circularReferenceInReturnType.ts, 1, 5))
>res1 : Symbol(res1, Decl(circularReferenceInReturnType.ts, 2, 5))
>fn1 : Symbol(fn1, Decl(circularReferenceInReturnType.ts, 0, 0))
>res1 : Symbol(res1, Decl(circularReferenceInReturnType.ts, 1, 5))
>res1 : Symbol(res1, Decl(circularReferenceInReturnType.ts, 2, 5))

declare function fn2<T>(): (cb: () => any) => (a: T) => void;
>fn2 : Symbol(fn2, Decl(circularReferenceInReturnType.ts, 1, 29))
>T : Symbol(T, Decl(circularReferenceInReturnType.ts, 3, 21))
>cb : Symbol(cb, Decl(circularReferenceInReturnType.ts, 3, 28))
>a : Symbol(a, Decl(circularReferenceInReturnType.ts, 3, 47))
>T : Symbol(T, Decl(circularReferenceInReturnType.ts, 3, 21))
>fn2 : Symbol(fn2, Decl(circularReferenceInReturnType.ts, 2, 29))
>T : Symbol(T, Decl(circularReferenceInReturnType.ts, 4, 21))
>cb : Symbol(cb, Decl(circularReferenceInReturnType.ts, 4, 28))
>a : Symbol(a, Decl(circularReferenceInReturnType.ts, 4, 47))
>T : Symbol(T, Decl(circularReferenceInReturnType.ts, 4, 21))

const res2 = fn2()(() => res2);
>res2 : Symbol(res2, Decl(circularReferenceInReturnType.ts, 4, 5))
>fn2 : Symbol(fn2, Decl(circularReferenceInReturnType.ts, 1, 29))
>res2 : Symbol(res2, Decl(circularReferenceInReturnType.ts, 4, 5))
>res2 : Symbol(res2, Decl(circularReferenceInReturnType.ts, 5, 5))
>fn2 : Symbol(fn2, Decl(circularReferenceInReturnType.ts, 2, 29))
>res2 : Symbol(res2, Decl(circularReferenceInReturnType.ts, 5, 5))

declare function fn3<T>(): <T2>(cb: (arg: T2) => any) => (a: T) => void;
>fn3 : Symbol(fn3, Decl(circularReferenceInReturnType.ts, 4, 31))
>T : Symbol(T, Decl(circularReferenceInReturnType.ts, 6, 21))
>T2 : Symbol(T2, Decl(circularReferenceInReturnType.ts, 6, 28))
>cb : Symbol(cb, Decl(circularReferenceInReturnType.ts, 6, 32))
>arg : Symbol(arg, Decl(circularReferenceInReturnType.ts, 6, 37))
>T2 : Symbol(T2, Decl(circularReferenceInReturnType.ts, 6, 28))
>a : Symbol(a, Decl(circularReferenceInReturnType.ts, 6, 58))
>T : Symbol(T, Decl(circularReferenceInReturnType.ts, 6, 21))
>fn3 : Symbol(fn3, Decl(circularReferenceInReturnType.ts, 5, 31))
>T : Symbol(T, Decl(circularReferenceInReturnType.ts, 7, 21))
>T2 : Symbol(T2, Decl(circularReferenceInReturnType.ts, 7, 28))
>cb : Symbol(cb, Decl(circularReferenceInReturnType.ts, 7, 32))
>arg : Symbol(arg, Decl(circularReferenceInReturnType.ts, 7, 37))
>T2 : Symbol(T2, Decl(circularReferenceInReturnType.ts, 7, 28))
>a : Symbol(a, Decl(circularReferenceInReturnType.ts, 7, 58))
>T : Symbol(T, Decl(circularReferenceInReturnType.ts, 7, 21))

const res3 = fn3()(() => res3);
>res3 : Symbol(res3, Decl(circularReferenceInReturnType.ts, 7, 5))
>fn3 : Symbol(fn3, Decl(circularReferenceInReturnType.ts, 4, 31))
>res3 : Symbol(res3, Decl(circularReferenceInReturnType.ts, 7, 5))
>res3 : Symbol(res3, Decl(circularReferenceInReturnType.ts, 8, 5))
>fn3 : Symbol(fn3, Decl(circularReferenceInReturnType.ts, 5, 31))
>res3 : Symbol(res3, Decl(circularReferenceInReturnType.ts, 8, 5))

13 changes: 7 additions & 6 deletions tests/baselines/reference/circularReferenceInReturnType.types
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
//// [tests/cases/compiler/circularReferenceInReturnType.ts] ////

=== circularReferenceInReturnType.ts ===
// inference fails for res1 and res2, but ideally should not
declare function fn1<T>(cb: () => T): string;
>fn1 : <T>(cb: () => T) => string
>cb : () => T

const res1 = fn1(() => res1);
>res1 : string
>res1 : any
>fn1(() => res1) : string
>fn1 : <T>(cb: () => T) => string
>() => res1 : () => string
>res1 : string
>() => res1 : () => any
>res1 : any

declare function fn2<T>(): (cb: () => any) => (a: T) => void;
>fn2 : <T>() => (cb: () => any) => (a: T) => void
Expand All @@ -32,10 +33,10 @@ declare function fn3<T>(): <T2>(cb: (arg: T2) => any) => (a: T) => void;
>a : T

const res3 = fn3()(() => res3);
>res3 : (a: unknown) => void
>res3 : any
>fn3()(() => res3) : (a: unknown) => void
>fn3() : <T2>(cb: (arg: T2) => any) => (a: unknown) => void
>fn3 : <T>() => <T2>(cb: (arg: T2) => any) => (a: T) => void
>() => res3 : () => (a: unknown) => void
>res3 : (a: unknown) => void
>() => res3 : () => any
>res3 : any

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
circularReferenceInReturnType2.ts(39,7): error TS7022: 'A' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.


==== circularReferenceInReturnType2.ts (1 errors) ====
type ObjectType<Source> = {
kind: "object";
__source: (source: Source) => void;
};

type Field<Source, Key extends string> = {
__key: (key: Key) => void;
__source: (source: Source) => void;
};

declare const object: <Source>() => <
Fields extends {
[Key in keyof Fields]: Field<Source, Key & string>;
}
>(config: {
name: string;
fields: Fields | (() => Fields);
}) => ObjectType<Source>;

type InferValueFromObjectType<Type extends ObjectType<any>> =
Type extends ObjectType<infer Source> ? Source : never;

type FieldResolver<Source, TType extends ObjectType<any>> = (
source: Source
) => InferValueFromObjectType<TType>;

type FieldFuncArgs<Source, Type extends ObjectType<any>> = {
type: Type;
resolve: FieldResolver<Source, Type>;
};

declare const field: <Source, Type extends ObjectType<any>, Key extends string>(
field: FieldFuncArgs<Source, Type>
) => Field<Source, Key>;

type Something = { foo: number };

// inference fails here, but ideally should not
const A = object<Something>()({
~
!!! error TS7022: 'A' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
name: "A",
fields: () => ({
a: field({
type: A,
resolve() {
return {
foo: 100,
};
},
}),
}),
});

17 changes: 9 additions & 8 deletions tests/baselines/reference/circularReferenceInReturnType2.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -126,31 +126,32 @@ type Something = { foo: number };
>Something : Symbol(Something, Decl(circularReferenceInReturnType2.ts, 33, 24))
>foo : Symbol(foo, Decl(circularReferenceInReturnType2.ts, 35, 18))

// inference fails here, but ideally should not
const A = object<Something>()({
>A : Symbol(A, Decl(circularReferenceInReturnType2.ts, 37, 5))
>A : Symbol(A, Decl(circularReferenceInReturnType2.ts, 38, 5))
>object : Symbol(object, Decl(circularReferenceInReturnType2.ts, 10, 13))
>Something : Symbol(Something, Decl(circularReferenceInReturnType2.ts, 33, 24))

name: "A",
>name : Symbol(name, Decl(circularReferenceInReturnType2.ts, 37, 31))
>name : Symbol(name, Decl(circularReferenceInReturnType2.ts, 38, 31))

fields: () => ({
>fields : Symbol(fields, Decl(circularReferenceInReturnType2.ts, 38, 12))
>fields : Symbol(fields, Decl(circularReferenceInReturnType2.ts, 39, 12))

a: field({
>a : Symbol(a, Decl(circularReferenceInReturnType2.ts, 39, 18))
>a : Symbol(a, Decl(circularReferenceInReturnType2.ts, 40, 18))
>field : Symbol(field, Decl(circularReferenceInReturnType2.ts, 31, 13))

type: A,
>type : Symbol(type, Decl(circularReferenceInReturnType2.ts, 40, 14))
>A : Symbol(A, Decl(circularReferenceInReturnType2.ts, 37, 5))
>type : Symbol(type, Decl(circularReferenceInReturnType2.ts, 41, 14))
>A : Symbol(A, Decl(circularReferenceInReturnType2.ts, 38, 5))

resolve() {
>resolve : Symbol(resolve, Decl(circularReferenceInReturnType2.ts, 41, 14))
>resolve : Symbol(resolve, Decl(circularReferenceInReturnType2.ts, 42, 14))

return {
foo: 100,
>foo : Symbol(foo, Decl(circularReferenceInReturnType2.ts, 43, 16))
>foo : Symbol(foo, Decl(circularReferenceInReturnType2.ts, 44, 16))

};
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,9 @@ type Something = { foo: number };
>Something : { foo: number; }
>foo : number

// inference fails here, but ideally should not
const A = object<Something>()({
>A : ObjectType<Something>
>A : any
>object<Something>()({ name: "A", fields: () => ({ a: field({ type: A, resolve() { return { foo: 100, }; }, }), }),}) : ObjectType<Something>
>object<Something>() : <Fields extends { [Key in keyof Fields]: Field<Something, Key & string>; }>(config: { name: string; fields: Fields | (() => Fields); }) => ObjectType<Something>
>object : <Source>() => <Fields extends { [Key in keyof Fields]: Field<Source, Key & string>; }>(config: { name: string; fields: Fields | (() => Fields); }) => ObjectType<Source>
Expand All @@ -100,11 +101,11 @@ const A = object<Something>()({
>a : Field<Something, "a">
>field({ type: A, resolve() { return { foo: 100, }; }, }) : Field<Something, "a">
>field : <Source, Type extends ObjectType<any>, Key extends string>(field: FieldFuncArgs<Source, Type>) => Field<Source, Key>
>{ type: A, resolve() { return { foo: 100, }; }, } : { type: ObjectType<Something>; resolve(): { foo: number; }; }
>{ type: A, resolve() { return { foo: 100, }; }, } : { type: any; resolve(): { foo: number; }; }

type: A,
>type : ObjectType<Something>
>A : ObjectType<Something>
>type : any
>A : any

resolve() {
>resolve : () => { foo: number; }
Expand Down
1 change: 1 addition & 0 deletions tests/cases/compiler/circularReferenceInReturnType.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @strict: true
// @noEmit: true

// inference fails for res1 and res2, but ideally should not
declare function fn1<T>(cb: () => T): string;
const res1 = fn1(() => res1);

Expand Down
1 change: 1 addition & 0 deletions tests/cases/compiler/circularReferenceInReturnType2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ declare const field: <Source, Type extends ObjectType<any>, Key extends string>(

type Something = { foo: number };

// inference fails here, but ideally should not
const A = object<Something>()({
name: "A",
fields: () => ({
Expand Down