diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6bb0efc2c8322..6bf46ea15bebd 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11741,7 +11741,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } type = anyType; } - links.type = type; + links.type ??= type; } return links.type; } @@ -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; } @@ -11849,7 +11849,7 @@ 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) @@ -11857,7 +11857,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!popTypeResolution()) { reportCircularityError(exportSymbol ?? symbol); - return links.type = errorType; + return links.type ??= errorType; } } return links.type; @@ -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)); @@ -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; } @@ -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; } @@ -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; } @@ -14266,7 +14266,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } result = circularConstraintType; } - t.immediateBaseConstraint = result || noConstraintType; + t.immediateBaseConstraint ??= result || noConstraintType; } return t.immediateBaseConstraint; } @@ -15392,7 +15392,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } type = anyType; } - signature.resolvedReturnType = type; + signature.resolvedReturnType ??= type; } return signature.resolvedReturnType; } @@ -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, @@ -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; @@ -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) }); @@ -29080,7 +29081,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return true; } - links.parameterInitializerContainsUndefined = containsUndefined; + links.parameterInitializerContainsUndefined ??= containsUndefined; } return links.parameterInitializerContainsUndefined; @@ -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) { diff --git a/tests/baselines/reference/circularReferenceInReturnType.errors.txt b/tests/baselines/reference/circularReferenceInReturnType.errors.txt index d90cf8523f5a4..bae89c5c1520c 100644 --- a/tests/baselines/reference/circularReferenceInReturnType.errors.txt +++ b/tests/baselines/reference/circularReferenceInReturnType.errors.txt @@ -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(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(): (cb: () => any) => (a: T) => void; const res2 = fn2()(() => res2); @@ -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. \ No newline at end of file diff --git a/tests/baselines/reference/circularReferenceInReturnType2.errors.txt b/tests/baselines/reference/circularReferenceInReturnType2.errors.txt index 4f075e69a6814..7ddffe8c8edbc 100644 --- a/tests/baselines/reference/circularReferenceInReturnType2.errors.txt +++ b/tests/baselines/reference/circularReferenceInReturnType2.errors.txt @@ -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 = { kind: "object"; __source: (source: Source) => void; @@ -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() { diff --git a/tests/baselines/reference/circularReferenceInReturnType2.types b/tests/baselines/reference/circularReferenceInReturnType2.types index e17b09aff8094..665f114bc9b93 100644 --- a/tests/baselines/reference/circularReferenceInReturnType2.types +++ b/tests/baselines/reference/circularReferenceInReturnType2.types @@ -104,16 +104,16 @@ type Something = { foo: number }; // inference fails here, but ideally should not const A = object()({ ->A : any -> : ^^^ +>A : ObjectType +> : ^^^^^^^^^^^^^^^^^^^^^ >object()({ name: "A", fields: () => ({ a: field({ type: A, resolve() { return { foo: 100, }; }, }), }),}) : ObjectType > : ^^^^^^^^^^^^^^^^^^^^^ >object() : ; }>(config: { name: string; fields: Fields | (() => Fields); }) => ObjectType > : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >object : () => ; }>(config: { name: string; fields: Fields | (() => Fields); }) => ObjectType > : ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^ ->{ name: "A", fields: () => ({ a: field({ type: A, resolve() { return { foo: 100, }; }, }), }),} : { name: string; fields: () => { a: Field; }; } -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>{ name: "A", fields: () => ({ a: field({ type: A, resolve() { return { foo: 100, }; }, }), }),} : { name: string; fields: () => any; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ name: "A", >name : string @@ -122,10 +122,10 @@ const A = object()({ > : ^^^ fields: () => ({ ->fields : () => { a: Field; } -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ->() => ({ a: field({ type: A, resolve() { return { foo: 100, }; }, }), }) : () => { a: Field; } -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>fields : () => any +> : ^^^^^^^^^ +>() => ({ a: field({ type: A, resolve() { return { foo: 100, }; }, }), }) : () => any +> : ^^^^^^^^^ >({ a: field({ type: A, resolve() { return { foo: 100, }; }, }), }) : { a: Field; } > : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >{ a: field({ type: A, resolve() { return { foo: 100, }; }, }), } : { a: Field; } @@ -138,14 +138,14 @@ const A = object()({ > : ^^^^^^^^^^^^^^^^^^^^^ >field : , Key extends string>(field: FieldFuncArgs) => Field > : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^ ->{ type: A, resolve() { return { foo: 100, }; }, } : { type: any; resolve(): { foo: number; }; } -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>{ type: A, resolve() { return { foo: 100, }; }, } : { type: ObjectType; resolve(): { foo: number; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type: A, ->type : any -> : ^^^ ->A : any -> : ^^^ +>type : ObjectType +> : ^^^^^^^^^^^^^^^^^^^^^ +>A : ObjectType +> : ^^^^^^^^^^^^^^^^^^^^^ resolve() { >resolve : () => { foo: number; } diff --git a/tests/baselines/reference/recursiveExportAssignmentAndFindAliasedType7.types b/tests/baselines/reference/recursiveExportAssignmentAndFindAliasedType7.types index d3ffbceb83c01..9b93f1a1db073 100644 --- a/tests/baselines/reference/recursiveExportAssignmentAndFindAliasedType7.types +++ b/tests/baselines/reference/recursiveExportAssignmentAndFindAliasedType7.types @@ -20,7 +20,7 @@ import self = require("recursiveExportAssignmentAndFindAliasedType7_moduleD"); var selfVar = self; >selfVar : any ->self : error +>self : any export = selfVar; >selfVar : any diff --git a/tests/cases/fourslash/issue57429.ts b/tests/cases/fourslash/issue57429.ts new file mode 100644 index 0000000000000..793feec879e40 --- /dev/null +++ b/tests/cases/fourslash/issue57429.ts @@ -0,0 +1,26 @@ +/// + +// @strict: true + +//// function Builder(def: I) { +//// return def; +//// } +//// +//// interface IThing { +//// doThing: (args: { value: object }) => string +//// doAnotherThing: () => void +//// } +//// +//// Builder({ +//// 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, +}]); diff --git a/tests/cases/fourslash/issue57585-2.ts b/tests/cases/fourslash/issue57585-2.ts new file mode 100644 index 0000000000000..d5eef775fa2d8 --- /dev/null +++ b/tests/cases/fourslash/issue57585-2.ts @@ -0,0 +1,76 @@ +/// + +// @strict: true +// @target: esnext +// @lib: esnext + +//// declare const EffectTypeId: unique symbol; +//// +//// type Covariant = (_: never) => A; +//// +//// interface VarianceStruct { +//// readonly _V: string; +//// readonly _A: Covariant; +//// readonly _E: Covariant; +//// readonly _R: Covariant; +//// } +//// +//// interface Variance { +//// readonly [EffectTypeId]: VarianceStruct; +//// } +//// +//// type Success> = [T] extends [ +//// Effect, +//// ] +//// ? _A +//// : never; +//// +//// declare const YieldWrapTypeId: unique symbol; +//// +//// class YieldWrap { +//// readonly #value: T; +//// constructor(value: T) { +//// this.#value = value; +//// } +//// [YieldWrapTypeId](): T { +//// return this.#value; +//// } +//// } +//// +//// interface EffectGenerator> { +//// next(...args: ReadonlyArray): IteratorResult, Success>; +//// } +//// +//// interface Effect +//// extends Variance { +//// [Symbol.iterator](): EffectGenerator>; +//// } +//// +//// declare const gen: { +//// >, AEff>( +//// f: () => Generator, +//// ): Effect< +//// AEff, +//// [Eff] extends [never] +//// ? never +//// : [Eff] extends [YieldWrap>] +//// ? E +//// : never, +//// [Eff] extends [never] +//// ? never +//// : [Eff] extends [YieldWrap>] +//// ? R +//// : never +//// >; +//// }; +//// +//// declare const succeed: (value: A) => Effect; +//// +//// gen(function* () { +//// const a = yield* succeed(1); +//// const b/*1*/ = yield* succeed(2); +//// return a + b; +//// }); + +verify.quickInfoAt("1", "const b: number"); +verify.getSemanticDiagnostics([]); \ No newline at end of file diff --git a/tests/cases/fourslash/issue57585.ts b/tests/cases/fourslash/issue57585.ts new file mode 100644 index 0000000000000..7c5ac8ecde514 --- /dev/null +++ b/tests/cases/fourslash/issue57585.ts @@ -0,0 +1,29 @@ +/// + +// @strict: true +// @target: esnext +// @lib: esnext + +//// export interface Result { +//// mapErr(fn: (error: E) => F): Result; +//// [Symbol.iterator](): Generator; +//// } +//// +//// declare const okIfObject: ( +//// value: unknown, +//// ) => Result, "ERR_NOT_AN_OBJECT">; +//// +//// declare const okIfInt: (value: unknown) => Result; +//// +//// export declare function Do2(job: () => Generator): void; +//// +//// declare let value: unknown; +//// +//// Do2(function* () { +//// const object = yield* okIfObject(value).mapErr((error) => 0); +//// const age = yield* okIfInt(object.age).mapErr((error) => 0); +//// return { age }; +//// }); + +verify.encodedSemanticClassificationsLength('2020', 132); +verify.getSemanticDiagnostics([]);