Skip to content

Commit

Permalink
Fix ghost errors resulting from out-of-order type checking (#58337)
Browse files Browse the repository at this point in the history
  • Loading branch information
ahejlsberg committed Apr 29, 2024
1 parent cd566ba commit c763c27
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 32 deletions.
42 changes: 27 additions & 15 deletions src/compiler/checker.ts
Expand Up @@ -11746,7 +11746,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
type = anyType;
}
links.type = type;
links.type ??= type;
}
return links.type;
}
Expand All @@ -11768,7 +11768,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 @@ -11854,15 +11854,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 @@ -12204,7 +12204,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 @@ -12221,9 +12221,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 @@ -12505,7 +12505,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 @@ -13819,7 +13819,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 @@ -14271,7 +14271,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
result = circularConstraintType;
}
t.immediateBaseConstraint = result || noConstraintType;
t.immediateBaseConstraint ??= result || noConstraintType;
}
return t.immediateBaseConstraint;
}
Expand Down Expand Up @@ -15397,7 +15397,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
type = anyType;
}
signature.resolvedReturnType = type;
signature.resolvedReturnType ??= type;
}
return signature.resolvedReturnType;
}
Expand Down Expand Up @@ -15829,10 +15829,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 @@ -23780,6 +23780,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 @@ -23824,7 +23825,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 @@ -29099,7 +29100,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return true;
}

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

return links.parameterInitializerContainsUndefined;
Expand Down Expand Up @@ -35767,8 +35768,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([]);

0 comments on commit c763c27

Please sign in to comment.