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
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
42 changes: 27 additions & 15 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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.

Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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([]);
Loading