Skip to content

Commit c763c27

Browse files
authored
Fix ghost errors resulting from out-of-order type checking (#58337)
1 parent cd566ba commit c763c27

8 files changed

+184
-32
lines changed

src/compiler/checker.ts

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11746,7 +11746,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1174611746
}
1174711747
type = anyType;
1174811748
}
11749-
links.type = type;
11749+
links.type ??= type;
1175011750
}
1175111751
return links.type;
1175211752
}
@@ -11768,7 +11768,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1176811768
writeType = anyType;
1176911769
}
1177011770
// Absent an explicit setter type annotation we use the read type of the accessor.
11771-
links.writeType = writeType || getTypeOfAccessors(symbol);
11771+
links.writeType ??= writeType || getTypeOfAccessors(symbol);
1177211772
}
1177311773
return links.writeType;
1177411774
}
@@ -11854,15 +11854,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1185411854
// type symbol, call getDeclaredTypeOfSymbol.
1185511855
// This check is important because without it, a call to getTypeOfSymbol could end
1185611856
// up recursively calling getTypeOfAlias, causing a stack overflow.
11857-
links.type = exportSymbol?.declarations && isDuplicatedCommonJSExport(exportSymbol.declarations) && symbol.declarations!.length ? getFlowTypeFromCommonJSExport(exportSymbol)
11857+
links.type ??= exportSymbol?.declarations && isDuplicatedCommonJSExport(exportSymbol.declarations) && symbol.declarations!.length ? getFlowTypeFromCommonJSExport(exportSymbol)
1185811858
: isDuplicatedCommonJSExport(symbol.declarations) ? autoType
1185911859
: declaredType ? declaredType
1186011860
: getSymbolFlags(targetSymbol) & SymbolFlags.Value ? getTypeOfSymbol(targetSymbol)
1186111861
: errorType;
1186211862

1186311863
if (!popTypeResolution()) {
1186411864
reportCircularityError(exportSymbol ?? symbol);
11865-
return links.type = errorType;
11865+
return links.type ??= errorType;
1186611866
}
1186711867
}
1186811868
return links.type;
@@ -12204,7 +12204,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1220412204
}
1220512205
if (!popTypeResolution()) {
1220612206
error(type.symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_base_expression, symbolToString(type.symbol));
12207-
return type.resolvedBaseConstructorType = errorType;
12207+
return type.resolvedBaseConstructorType ??= errorType;
1220812208
}
1220912209
if (!(baseConstructorType.flags & TypeFlags.Any) && baseConstructorType !== nullWideningType && !isConstructorType(baseConstructorType)) {
1221012210
const err = error(baseTypeNode.expression, Diagnostics.Type_0_is_not_a_constructor_function_type, typeToString(baseConstructorType));
@@ -12221,9 +12221,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1222112221
addRelatedInfo(err, createDiagnosticForNode(baseConstructorType.symbol.declarations[0], Diagnostics.Did_you_mean_for_0_to_be_constrained_to_type_new_args_Colon_any_1, symbolToString(baseConstructorType.symbol), typeToString(ctorReturn)));
1222212222
}
1222312223
}
12224-
return type.resolvedBaseConstructorType = errorType;
12224+
return type.resolvedBaseConstructorType ??= errorType;
1222512225
}
12226-
type.resolvedBaseConstructorType = baseConstructorType;
12226+
type.resolvedBaseConstructorType ??= baseConstructorType;
1222712227
}
1222812228
return type.resolvedBaseConstructorType;
1222912229
}
@@ -12505,7 +12505,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1250512505
error(isNamedDeclaration(declaration) ? declaration.name || declaration : declaration, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol));
1250612506
}
1250712507
}
12508-
links.declaredType = type;
12508+
links.declaredType ??= type;
1250912509
}
1251012510
return links.declaredType;
1251112511
}
@@ -13819,7 +13819,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1381913819
error(currentNode, Diagnostics.Type_of_property_0_circularly_references_itself_in_mapped_type_1, symbolToString(symbol), typeToString(mappedType));
1382013820
type = errorType;
1382113821
}
13822-
symbol.links.type = type;
13822+
symbol.links.type ??= type;
1382313823
}
1382413824
return symbol.links.type;
1382513825
}
@@ -14271,7 +14271,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1427114271
}
1427214272
result = circularConstraintType;
1427314273
}
14274-
t.immediateBaseConstraint = result || noConstraintType;
14274+
t.immediateBaseConstraint ??= result || noConstraintType;
1427514275
}
1427614276
return t.immediateBaseConstraint;
1427714277
}
@@ -15397,7 +15397,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1539715397
}
1539815398
type = anyType;
1539915399
}
15400-
signature.resolvedReturnType = type;
15400+
signature.resolvedReturnType ??= type;
1540115401
}
1540215402
return signature.resolvedReturnType;
1540315403
}
@@ -15829,10 +15829,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1582915829
node.kind === SyntaxKind.ArrayType ? [getTypeFromTypeNode(node.elementType)] :
1583015830
map(node.elements, getTypeFromTypeNode);
1583115831
if (popTypeResolution()) {
15832-
type.resolvedTypeArguments = type.mapper ? instantiateTypes(typeArguments, type.mapper) : typeArguments;
15832+
type.resolvedTypeArguments ??= type.mapper ? instantiateTypes(typeArguments, type.mapper) : typeArguments;
1583315833
}
1583415834
else {
15835-
type.resolvedTypeArguments = type.target.localTypeParameters?.map(() => errorType) || emptyArray;
15835+
type.resolvedTypeArguments ??= type.target.localTypeParameters?.map(() => errorType) || emptyArray;
1583615836
error(
1583715837
type.node || currentNode,
1583815838
type.target.symbol ? Diagnostics.Type_arguments_for_0_circularly_reference_themselves : Diagnostics.Tuple_type_arguments_circularly_reference_themselves,
@@ -23780,6 +23780,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2378023780
if (!links.variances) {
2378123781
tracing?.push(tracing.Phase.CheckTypes, "getVariancesWorker", { arity: typeParameters.length, id: getTypeId(getDeclaredTypeOfSymbol(symbol)) });
2378223782
const oldVarianceComputation = inVarianceComputation;
23783+
const saveResolutionStart = resolutionStart;
2378323784
if (!inVarianceComputation) {
2378423785
inVarianceComputation = true;
2378523786
resolutionStart = resolutionTargets.length;
@@ -23824,7 +23825,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2382423825
}
2382523826
if (!oldVarianceComputation) {
2382623827
inVarianceComputation = false;
23827-
resolutionStart = 0;
23828+
resolutionStart = saveResolutionStart;
2382823829
}
2382923830
links.variances = variances;
2383023831
tracing?.pop({ variances: variances.map(Debug.formatVariance) });
@@ -29099,7 +29100,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2909929100
return true;
2910029101
}
2910129102

29102-
links.parameterInitializerContainsUndefined = containsUndefined;
29103+
links.parameterInitializerContainsUndefined ??= containsUndefined;
2910329104
}
2910429105

2910529106
return links.parameterInitializerContainsUndefined;
@@ -35767,8 +35768,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3576735768
if (cached && cached !== resolvingSignature && !candidatesOutArray) {
3576835769
return cached;
3576935770
}
35771+
const saveResolutionStart = resolutionStart;
35772+
if (!cached) {
35773+
// If we haven't already done so, temporarily reset the resolution stack. This allows us to
35774+
// handle "inverted" situations where, for example, an API client asks for the type of a symbol
35775+
// containined in a function call argument whose contextual type depends on the symbol itself
35776+
// through resolution of the containing function call. By resetting the resolution stack we'll
35777+
// retry the symbol type resolution with the resolvingSignature marker in place to suppress
35778+
// the contextual type circularity.
35779+
resolutionStart = resolutionTargets.length;
35780+
}
3577035781
links.resolvedSignature = resolvingSignature;
3577135782
let result = resolveSignature(node, candidatesOutArray, checkMode || CheckMode.Normal);
35783+
resolutionStart = saveResolutionStart;
3577235784
// When CheckMode.SkipGenericFunctions is set we use resolvingSignature to indicate that call
3577335785
// resolution should be deferred.
3577435786
if (result !== resolvingSignature) {
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
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.
2+
circularReferenceInReturnType.ts(3,18): error TS7024: Function 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.
23
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.
4+
circularReferenceInReturnType.ts(9,20): error TS7024: Function 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.
35

46

5-
==== circularReferenceInReturnType.ts (2 errors) ====
7+
==== circularReferenceInReturnType.ts (4 errors) ====
68
// inference fails for res1 and res2, but ideally should not
79
declare function fn1<T>(cb: () => T): string;
810
const res1 = fn1(() => res1);
911
~~~~
1012
!!! 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.
13+
~~~~~~~~~~
14+
!!! error TS7024: Function 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.
1115

1216
declare function fn2<T>(): (cb: () => any) => (a: T) => void;
1317
const res2 = fn2()(() => res2);
@@ -16,4 +20,6 @@ circularReferenceInReturnType.ts(9,7): error TS7022: 'res3' implicitly has type
1620
const res3 = fn3()(() => res3);
1721
~~~~
1822
!!! 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.
23+
~~~~~~~~~~
24+
!!! error TS7024: Function 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.
1925

tests/baselines/reference/circularReferenceInReturnType2.errors.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
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.
2+
circularReferenceInReturnType2.ts(41,3): error TS7023: 'fields' 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.
23

34

4-
==== circularReferenceInReturnType2.ts (1 errors) ====
5+
==== circularReferenceInReturnType2.ts (2 errors) ====
56
type ObjectType<Source> = {
67
kind: "object";
78
__source: (source: Source) => void;
@@ -45,6 +46,8 @@ circularReferenceInReturnType2.ts(39,7): error TS7022: 'A' implicitly has type '
4546
!!! 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.
4647
name: "A",
4748
fields: () => ({
49+
~~~~~~
50+
!!! error TS7023: 'fields' 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.
4851
a: field({
4952
type: A,
5053
resolve() {

tests/baselines/reference/circularReferenceInReturnType2.types

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -104,16 +104,16 @@ type Something = { foo: number };
104104

105105
// inference fails here, but ideally should not
106106
const A = object<Something>()({
107-
>A : any
108-
> : ^^^
107+
>A : ObjectType<Something>
108+
> : ^^^^^^^^^^^^^^^^^^^^^
109109
>object<Something>()({ name: "A", fields: () => ({ a: field({ type: A, resolve() { return { foo: 100, }; }, }), }),}) : ObjectType<Something>
110110
> : ^^^^^^^^^^^^^^^^^^^^^
111111
>object<Something>() : <Fields extends { [Key in keyof Fields]: Field<Something, Key & string>; }>(config: { name: string; fields: Fields | (() => Fields); }) => ObjectType<Something>
112112
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
113113
>object : <Source>() => <Fields extends { [Key in keyof Fields]: Field<Source, Key & string>; }>(config: { name: string; fields: Fields | (() => Fields); }) => ObjectType<Source>
114114
> : ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^
115-
>{ name: "A", fields: () => ({ a: field({ type: A, resolve() { return { foo: 100, }; }, }), }),} : { name: string; fields: () => { a: Field<Something, "a">; }; }
116-
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
115+
>{ name: "A", fields: () => ({ a: field({ type: A, resolve() { return { foo: 100, }; }, }), }),} : { name: string; fields: () => any; }
116+
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
117117

118118
name: "A",
119119
>name : string
@@ -122,10 +122,10 @@ const A = object<Something>()({
122122
> : ^^^
123123

124124
fields: () => ({
125-
>fields : () => { a: Field<Something, "a">; }
126-
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
127-
>() => ({ a: field({ type: A, resolve() { return { foo: 100, }; }, }), }) : () => { a: Field<Something, "a">; }
128-
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
125+
>fields : () => any
126+
> : ^^^^^^^^^
127+
>() => ({ a: field({ type: A, resolve() { return { foo: 100, }; }, }), }) : () => any
128+
> : ^^^^^^^^^
129129
>({ a: field({ type: A, resolve() { return { foo: 100, }; }, }), }) : { a: Field<Something, "a">; }
130130
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
131131
>{ a: field({ type: A, resolve() { return { foo: 100, }; }, }), } : { a: Field<Something, "a">; }
@@ -138,14 +138,14 @@ const A = object<Something>()({
138138
> : ^^^^^^^^^^^^^^^^^^^^^
139139
>field : <Source, Type extends ObjectType<any>, Key extends string>(field: FieldFuncArgs<Source, Type>) => Field<Source, Key>
140140
> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^
141-
>{ type: A, resolve() { return { foo: 100, }; }, } : { type: any; resolve(): { foo: number; }; }
142-
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
141+
>{ type: A, resolve() { return { foo: 100, }; }, } : { type: ObjectType<Something>; resolve(): { foo: number; }; }
142+
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
143143

144144
type: A,
145-
>type : any
146-
> : ^^^
147-
>A : any
148-
> : ^^^
145+
>type : ObjectType<Something>
146+
> : ^^^^^^^^^^^^^^^^^^^^^
147+
>A : ObjectType<Something>
148+
> : ^^^^^^^^^^^^^^^^^^^^^
149149

150150
resolve() {
151151
>resolve : () => { foo: number; }

tests/baselines/reference/recursiveExportAssignmentAndFindAliasedType7.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import self = require("recursiveExportAssignmentAndFindAliasedType7_moduleD");
2020

2121
var selfVar = self;
2222
>selfVar : any
23-
>self : error
23+
>self : any
2424

2525
export = selfVar;
2626
>selfVar : any

tests/cases/fourslash/issue57429.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @strict: true
4+
5+
//// function Builder<I>(def: I) {
6+
//// return def;
7+
//// }
8+
////
9+
//// interface IThing {
10+
//// doThing: (args: { value: object }) => string
11+
//// doAnotherThing: () => void
12+
//// }
13+
////
14+
//// Builder<IThing>({
15+
//// doThing(args: { value: object }) {
16+
//// const { v/*1*/alue } = this.[|args|]
17+
//// return `${value}`
18+
//// },
19+
//// doAnotherThing() { },
20+
//// })
21+
22+
verify.quickInfoAt("1", "const value: any");
23+
verify.getSemanticDiagnostics([{
24+
message: "Property 'args' does not exist on type 'IThing'.",
25+
code: 2339,
26+
}]);

tests/cases/fourslash/issue57585-2.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @strict: true
4+
// @target: esnext
5+
// @lib: esnext
6+
7+
//// declare const EffectTypeId: unique symbol;
8+
////
9+
//// type Covariant<A> = (_: never) => A;
10+
////
11+
//// interface VarianceStruct<out A, out E, out R> {
12+
//// readonly _V: string;
13+
//// readonly _A: Covariant<A>;
14+
//// readonly _E: Covariant<E>;
15+
//// readonly _R: Covariant<R>;
16+
//// }
17+
////
18+
//// interface Variance<out A, out E, out R> {
19+
//// readonly [EffectTypeId]: VarianceStruct<A, E, R>;
20+
//// }
21+
////
22+
//// type Success<T extends Effect<any, any, any>> = [T] extends [
23+
//// Effect<infer _A, infer _E, infer _R>,
24+
//// ]
25+
//// ? _A
26+
//// : never;
27+
////
28+
//// declare const YieldWrapTypeId: unique symbol;
29+
////
30+
//// class YieldWrap<T> {
31+
//// readonly #value: T;
32+
//// constructor(value: T) {
33+
//// this.#value = value;
34+
//// }
35+
//// [YieldWrapTypeId](): T {
36+
//// return this.#value;
37+
//// }
38+
//// }
39+
////
40+
//// interface EffectGenerator<T extends Effect<any, any, any>> {
41+
//// next(...args: ReadonlyArray<any>): IteratorResult<YieldWrap<T>, Success<T>>;
42+
//// }
43+
////
44+
//// interface Effect<out A, out E = never, out R = never>
45+
//// extends Variance<A, E, R> {
46+
//// [Symbol.iterator](): EffectGenerator<Effect<A, E, R>>;
47+
//// }
48+
////
49+
//// declare const gen: {
50+
//// <Eff extends YieldWrap<Effect<any, any, any>>, AEff>(
51+
//// f: () => Generator<Eff, AEff, never>,
52+
//// ): Effect<
53+
//// AEff,
54+
//// [Eff] extends [never]
55+
//// ? never
56+
//// : [Eff] extends [YieldWrap<Effect<infer _A, infer E, infer _R>>]
57+
//// ? E
58+
//// : never,
59+
//// [Eff] extends [never]
60+
//// ? never
61+
//// : [Eff] extends [YieldWrap<Effect<infer _A, infer _E, infer R>>]
62+
//// ? R
63+
//// : never
64+
//// >;
65+
//// };
66+
////
67+
//// declare const succeed: <A>(value: A) => Effect<A>;
68+
////
69+
//// gen(function* () {
70+
//// const a = yield* succeed(1);
71+
//// const b/*1*/ = yield* succeed(2);
72+
//// return a + b;
73+
//// });
74+
75+
verify.quickInfoAt("1", "const b: number");
76+
verify.getSemanticDiagnostics([]);

0 commit comments

Comments
 (0)