Skip to content
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

Fix ghost errors resulting from out-of-order type checking #58337

Merged
merged 9 commits into from Apr 29, 2024
42 changes: 27 additions & 15 deletions src/compiler/checker.ts
Expand Up @@ -11741,7 +11741,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
type = anyType;
}
links.type = type;
links.type ??= type;
}
return links.type;
}
Expand All @@ -11763,7 +11763,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
writeType = anyType;
}
// Absent an explicit setter type annotation we use the read type of the accessor.
links.writeType = writeType || getTypeOfAccessors(symbol);
links.writeType ??= writeType || getTypeOfAccessors(symbol);
}
return links.writeType;
}
Expand Down Expand Up @@ -11849,15 +11849,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// type symbol, call getDeclaredTypeOfSymbol.
// This check is important because without it, a call to getTypeOfSymbol could end
// up recursively calling getTypeOfAlias, causing a stack overflow.
links.type = exportSymbol?.declarations && isDuplicatedCommonJSExport(exportSymbol.declarations) && symbol.declarations!.length ? getFlowTypeFromCommonJSExport(exportSymbol)
links.type ??= exportSymbol?.declarations && isDuplicatedCommonJSExport(exportSymbol.declarations) && symbol.declarations!.length ? getFlowTypeFromCommonJSExport(exportSymbol)
: isDuplicatedCommonJSExport(symbol.declarations) ? autoType
: declaredType ? declaredType
: getSymbolFlags(targetSymbol) & SymbolFlags.Value ? getTypeOfSymbol(targetSymbol)
: errorType;

if (!popTypeResolution()) {
reportCircularityError(exportSymbol ?? symbol);
return links.type = errorType;
return links.type ??= errorType;
}
}
return links.type;
Expand Down Expand Up @@ -12199,7 +12199,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
if (!popTypeResolution()) {
error(type.symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_base_expression, symbolToString(type.symbol));
return type.resolvedBaseConstructorType = errorType;
return type.resolvedBaseConstructorType ??= errorType;
}
if (!(baseConstructorType.flags & TypeFlags.Any) && baseConstructorType !== nullWideningType && !isConstructorType(baseConstructorType)) {
const err = error(baseTypeNode.expression, Diagnostics.Type_0_is_not_a_constructor_function_type, typeToString(baseConstructorType));
Expand All @@ -12216,9 +12216,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
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)));
}
}
return type.resolvedBaseConstructorType = errorType;
return type.resolvedBaseConstructorType ??= errorType;
}
type.resolvedBaseConstructorType = baseConstructorType;
type.resolvedBaseConstructorType ??= baseConstructorType;
}
return type.resolvedBaseConstructorType;
}
Expand Down Expand Up @@ -12500,7 +12500,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
error(isNamedDeclaration(declaration) ? declaration.name || declaration : declaration, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol));
}
}
links.declaredType = type;
links.declaredType ??= type;
}
return links.declaredType;
}
Expand Down Expand Up @@ -13814,7 +13814,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
error(currentNode, Diagnostics.Type_of_property_0_circularly_references_itself_in_mapped_type_1, symbolToString(symbol), typeToString(mappedType));
type = errorType;
}
symbol.links.type = type;
symbol.links.type ??= type;
}
return symbol.links.type;
}
Expand Down Expand Up @@ -14266,7 +14266,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
result = circularConstraintType;
}
t.immediateBaseConstraint = result || noConstraintType;
t.immediateBaseConstraint ??= result || noConstraintType;
}
return t.immediateBaseConstraint;
}
Expand Down Expand Up @@ -15392,7 +15392,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
type = anyType;
}
signature.resolvedReturnType = type;
signature.resolvedReturnType ??= type;
}
return signature.resolvedReturnType;
}
Expand Down Expand Up @@ -15824,10 +15824,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
node.kind === SyntaxKind.ArrayType ? [getTypeFromTypeNode(node.elementType)] :
map(node.elements, getTypeFromTypeNode);
if (popTypeResolution()) {
type.resolvedTypeArguments = type.mapper ? instantiateTypes(typeArguments, type.mapper) : typeArguments;
type.resolvedTypeArguments ??= type.mapper ? instantiateTypes(typeArguments, type.mapper) : typeArguments;
}
else {
type.resolvedTypeArguments = type.target.localTypeParameters?.map(() => errorType) || emptyArray;
type.resolvedTypeArguments ??= type.target.localTypeParameters?.map(() => errorType) || emptyArray;
error(
type.node || currentNode,
type.target.symbol ? Diagnostics.Type_arguments_for_0_circularly_reference_themselves : Diagnostics.Tuple_type_arguments_circularly_reference_themselves,
Expand Down Expand Up @@ -23761,6 +23761,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!links.variances) {
tracing?.push(tracing.Phase.CheckTypes, "getVariancesWorker", { arity: typeParameters.length, id: getTypeId(getDeclaredTypeOfSymbol(symbol)) });
const oldVarianceComputation = inVarianceComputation;
const saveResolutionStart = resolutionStart;
if (!inVarianceComputation) {
inVarianceComputation = true;
resolutionStart = resolutionTargets.length;
Expand Down Expand Up @@ -23805,7 +23806,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
if (!oldVarianceComputation) {
inVarianceComputation = false;
resolutionStart = 0;
resolutionStart = saveResolutionStart;
}
links.variances = variances;
tracing?.pop({ variances: variances.map(Debug.formatVariance) });
Expand Down Expand Up @@ -29080,7 +29081,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return true;
}

links.parameterInitializerContainsUndefined = containsUndefined;
links.parameterInitializerContainsUndefined ??= containsUndefined;
}

return links.parameterInitializerContainsUndefined;
Expand Down Expand Up @@ -35748,8 +35749,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (cached && cached !== resolvingSignature && !candidatesOutArray) {
return cached;
}
const saveResolutionStart = resolutionStart;
if (!cached) {
// If we haven't already done so, temporarily reset the resolution stack. This allows us to
// handle "inverted" situations where, for example, an API client asks for the type of a symbol
// containined in a function call argument whose contextual type depends on the symbol itself
// through resolution of the containing function call. By resetting the resolution stack we'll
// retry the symbol type resolution with the resolvingSignature marker in place to suppress
// the contextual type circularity.
resolutionStart = resolutionTargets.length;
}
links.resolvedSignature = resolvingSignature;
let result = resolveSignature(node, candidatesOutArray, checkMode || CheckMode.Normal);
resolutionStart = saveResolutionStart;
// When CheckMode.SkipGenericFunctions is set we use resolvingSignature to indicate that call
// resolution should be deferred.
if (result !== resolvingSignature) {
Expand Down
@@ -1,13 +1,17 @@
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(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.
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(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.


==== circularReferenceInReturnType.ts (2 errors) ====
==== circularReferenceInReturnType.ts (4 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.
~~~~~~~~~~
!!! 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.

declare function fn2<T>(): (cb: () => any) => (a: T) => void;
const res2 = fn2()(() => res2);
Expand All @@ -16,4 +20,6 @@ circularReferenceInReturnType.ts(9,7): error TS7022: 'res3' implicitly has type
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.
~~~~~~~~~~
!!! 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.

@@ -1,7 +1,8 @@
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(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.


==== circularReferenceInReturnType2.ts (1 errors) ====
==== circularReferenceInReturnType2.ts (2 errors) ====
type ObjectType<Source> = {
kind: "object";
__source: (source: Source) => void;
Expand Down Expand Up @@ -45,6 +46,8 @@ circularReferenceInReturnType2.ts(39,7): error TS7022: 'A' implicitly has type '
!!! 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: () => ({
~~~~~~
!!! 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.
a: field({
type: A,
resolve() {
Expand Down
28 changes: 14 additions & 14 deletions tests/baselines/reference/circularReferenceInReturnType2.types
Expand Up @@ -104,16 +104,16 @@ type Something = { foo: number };

// inference fails here, but ideally should not
const A = object<Something>()({
>A : any
> : ^^^
>A : ObjectType<Something>
> : ^^^^^^^^^^^^^^^^^^^^^
>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>
> : ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^
>{ name: "A", fields: () => ({ a: field({ type: A, resolve() { return { foo: 100, }; }, }), }),} : { name: string; fields: () => { a: Field<Something, "a">; }; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>{ name: "A", fields: () => ({ a: field({ type: A, resolve() { return { foo: 100, }; }, }), }),} : { name: string; fields: () => any; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

name: "A",
>name : string
Expand All @@ -122,10 +122,10 @@ const A = object<Something>()({
> : ^^^

fields: () => ({
>fields : () => { a: Field<Something, "a">; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>() => ({ a: field({ type: A, resolve() { return { foo: 100, }; }, }), }) : () => { a: Field<Something, "a">; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>fields : () => any
> : ^^^^^^^^^
>() => ({ a: field({ type: A, resolve() { return { foo: 100, }; }, }), }) : () => any
> : ^^^^^^^^^
>({ a: field({ type: A, resolve() { return { foo: 100, }; }, }), }) : { a: Field<Something, "a">; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>{ a: field({ type: A, resolve() { return { foo: 100, }; }, }), } : { a: Field<Something, "a">; }
Expand All @@ -138,14 +138,14 @@ const A = object<Something>()({
> : ^^^^^^^^^^^^^^^^^^^^^
>field : <Source, Type extends ObjectType<any>, Key extends string>(field: FieldFuncArgs<Source, Type>) => Field<Source, Key>
> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^
>{ type: A, resolve() { return { foo: 100, }; }, } : { type: any; resolve(): { foo: number; }; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>{ type: A, resolve() { return { foo: 100, }; }, } : { type: ObjectType<Something>; resolve(): { foo: number; }; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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

resolve() {
>resolve : () => { foo: number; }
Expand Down
Expand Up @@ -20,7 +20,7 @@ import self = require("recursiveExportAssignmentAndFindAliasedType7_moduleD");

var selfVar = self;
>selfVar : any
>self : error
>self : any

export = selfVar;
>selfVar : any
Expand Down
26 changes: 26 additions & 0 deletions tests/cases/fourslash/issue57429.ts
@@ -0,0 +1,26 @@
/// <reference path="fourslash.ts" />

// @strict: true

//// function Builder<I>(def: I) {
//// return def;
//// }
////
//// interface IThing {
//// doThing: (args: { value: object }) => string
//// doAnotherThing: () => void
//// }
////
//// Builder<IThing>({
//// doThing(args: { value: object }) {
//// const { v/*1*/alue } = this.[|args|]
//// return `${value}`
//// },
//// doAnotherThing() { },
//// })

verify.quickInfoAt("1", "const value: any");
verify.getSemanticDiagnostics([{
message: "Property 'args' does not exist on type 'IThing'.",
code: 2339,
}]);
76 changes: 76 additions & 0 deletions tests/cases/fourslash/issue57585-2.ts
@@ -0,0 +1,76 @@
/// <reference path="fourslash.ts" />

// @strict: true
// @target: esnext
// @lib: esnext

//// declare const EffectTypeId: unique symbol;
////
//// type Covariant<A> = (_: never) => A;
////
//// interface VarianceStruct<out A, out E, out R> {
//// readonly _V: string;
//// readonly _A: Covariant<A>;
//// readonly _E: Covariant<E>;
//// readonly _R: Covariant<R>;
//// }
////
//// interface Variance<out A, out E, out R> {
//// readonly [EffectTypeId]: VarianceStruct<A, E, R>;
//// }
////
//// type Success<T extends Effect<any, any, any>> = [T] extends [
//// Effect<infer _A, infer _E, infer _R>,
//// ]
//// ? _A
//// : never;
////
//// declare const YieldWrapTypeId: unique symbol;
////
//// class YieldWrap<T> {
//// readonly #value: T;
//// constructor(value: T) {
//// this.#value = value;
//// }
//// [YieldWrapTypeId](): T {
//// return this.#value;
//// }
//// }
////
//// interface EffectGenerator<T extends Effect<any, any, any>> {
//// next(...args: ReadonlyArray<any>): IteratorResult<YieldWrap<T>, Success<T>>;
//// }
////
//// interface Effect<out A, out E = never, out R = never>
//// extends Variance<A, E, R> {
//// [Symbol.iterator](): EffectGenerator<Effect<A, E, R>>;
//// }
////
//// declare const gen: {
//// <Eff extends YieldWrap<Effect<any, any, any>>, AEff>(
//// f: () => Generator<Eff, AEff, never>,
//// ): Effect<
//// AEff,
//// [Eff] extends [never]
//// ? never
//// : [Eff] extends [YieldWrap<Effect<infer _A, infer E, infer _R>>]
//// ? E
//// : never,
//// [Eff] extends [never]
//// ? never
//// : [Eff] extends [YieldWrap<Effect<infer _A, infer _E, infer R>>]
//// ? R
//// : never
//// >;
//// };
////
//// declare const succeed: <A>(value: A) => Effect<A>;
////
//// gen(function* () {
//// const a = yield* succeed(1);
//// const b/*1*/ = yield* succeed(2);
//// return a + b;
//// });

verify.quickInfoAt("1", "const b: number");
verify.getSemanticDiagnostics([]);