From 33aeffb5cb3e1ed61a620b3fd579ff8e5818387d Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 3 May 2023 08:34:00 -0700 Subject: [PATCH 1/4] Improve type inference to allow HKT technique function again --- src/compiler/checker.ts | 11 ++++++++++- src/compiler/types.ts | 19 ++++++++++--------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1bb5c6a297765..63f6fda4225f1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17685,7 +17685,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // eagerly using the constraint type of 'this' at the given location. if (isGenericIndexType(indexType) || (accessNode && accessNode.kind !== SyntaxKind.IndexedAccessType ? isGenericTupleType(objectType) && !indexTypeLessThan(indexType, objectType.target.fixedLength) : - isGenericObjectType(objectType) && !(isTupleType(objectType) && indexTypeLessThan(indexType, objectType.target.fixedLength)) || isGenericReducibleType(objectType))) { + isGenericObjectType(objectType) && !(isTupleType(objectType) && indexTypeLessThan(indexType, objectType.target.fixedLength)) || !(accessFlags & AccessFlags.ResolveReducibleTypes) && isGenericReducibleType(objectType))) { if (objectType.flags & TypeFlags.AnyOrUnknown) { return objectType; } @@ -24511,6 +24511,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } } + // The following is a targeted fix to allow higher-kinded types to be emulated using the technique in #53970. + // Specifically, when an indexed access type was deferred because it has a reducible object type, here we force + // resolution of the type and then infer to the result. + if (target.flags & TypeFlags.IndexedAccess && isGenericReducibleType((target as IndexedAccessType).objectType)) { + const instantiated = getIndexedAccessType((target as IndexedAccessType).objectType, (target as IndexedAccessType).indexType, AccessFlags.ResolveReducibleTypes); + if (instantiated && instantiated !== target) { + inferFromTypes(source, instantiated); + } + } } if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && ( (source as TypeReference).target === (target as TypeReference).target || isArrayType(source) && isArrayType(target)) && diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c97bbaffa2834..122ed908792fa 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6593,15 +6593,16 @@ export interface TypeParameter extends InstantiableType { export const enum AccessFlags { None = 0, IncludeUndefined = 1 << 0, - NoIndexSignatures = 1 << 1, - Writing = 1 << 2, - CacheSymbol = 1 << 3, - NoTupleBoundsCheck = 1 << 4, - ExpressionPosition = 1 << 5, - ReportDeprecated = 1 << 6, - SuppressNoImplicitAnyError = 1 << 7, - Contextual = 1 << 8, - Persistent = IncludeUndefined, + ResolveReducibleTypes = 1 << 1, + NoIndexSignatures = 1 << 2, + Writing = 1 << 3, + CacheSymbol = 1 << 4, + NoTupleBoundsCheck = 1 << 5, + ExpressionPosition = 1 << 6, + ReportDeprecated = 1 << 7, + SuppressNoImplicitAnyError = 1 << 8, + Contextual = 1 << 9, + Persistent = IncludeUndefined | ResolveReducibleTypes, } // Indexed access types (TypeFlags.IndexedAccess) From dd882290801573e05d412e9a140ad9875e736e50 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 3 May 2023 08:38:22 -0700 Subject: [PATCH 2/4] Add regression test --- .../reference/inferenceAndHKTs.symbols | 100 ++++++++++++++++++ .../reference/inferenceAndHKTs.types | 64 +++++++++++ tests/cases/compiler/inferenceAndHKTs.ts | 33 ++++++ 3 files changed, 197 insertions(+) create mode 100644 tests/baselines/reference/inferenceAndHKTs.symbols create mode 100644 tests/baselines/reference/inferenceAndHKTs.types create mode 100644 tests/cases/compiler/inferenceAndHKTs.ts diff --git a/tests/baselines/reference/inferenceAndHKTs.symbols b/tests/baselines/reference/inferenceAndHKTs.symbols new file mode 100644 index 0000000000000..8542a0b36222e --- /dev/null +++ b/tests/baselines/reference/inferenceAndHKTs.symbols @@ -0,0 +1,100 @@ +=== tests/cases/compiler/inferenceAndHKTs.ts === +// Repro from #53970 + +export interface TypeLambda { +>TypeLambda : Symbol(TypeLambda, Decl(inferenceAndHKTs.ts, 0, 0)) + + readonly A: unknown; +>A : Symbol(TypeLambda.A, Decl(inferenceAndHKTs.ts, 2, 29)) +} + +export interface TypeClass { +>TypeClass : Symbol(TypeClass, Decl(inferenceAndHKTs.ts, 4, 1)) +>F : Symbol(F, Decl(inferenceAndHKTs.ts, 6, 27)) +>TypeLambda : Symbol(TypeLambda, Decl(inferenceAndHKTs.ts, 0, 0)) + + readonly _F: F; +>_F : Symbol(TypeClass._F, Decl(inferenceAndHKTs.ts, 6, 50)) +>F : Symbol(F, Decl(inferenceAndHKTs.ts, 6, 27)) +} + +export type Apply = F extends { readonly type: unknown } +>Apply : Symbol(Apply, Decl(inferenceAndHKTs.ts, 8, 1)) +>F : Symbol(F, Decl(inferenceAndHKTs.ts, 10, 18)) +>TypeLambda : Symbol(TypeLambda, Decl(inferenceAndHKTs.ts, 0, 0)) +>A : Symbol(A, Decl(inferenceAndHKTs.ts, 10, 39)) +>F : Symbol(F, Decl(inferenceAndHKTs.ts, 10, 18)) +>type : Symbol(type, Decl(inferenceAndHKTs.ts, 10, 56)) + + ? (F & { readonly A: A })['type'] +>F : Symbol(F, Decl(inferenceAndHKTs.ts, 10, 18)) +>A : Symbol(A, Decl(inferenceAndHKTs.ts, 11, 12)) +>A : Symbol(A, Decl(inferenceAndHKTs.ts, 10, 39)) + + : { readonly F: F, readonly A: A }; +>F : Symbol(F, Decl(inferenceAndHKTs.ts, 12, 7)) +>F : Symbol(F, Decl(inferenceAndHKTs.ts, 10, 18)) +>A : Symbol(A, Decl(inferenceAndHKTs.ts, 12, 22)) +>A : Symbol(A, Decl(inferenceAndHKTs.ts, 10, 39)) + +export interface T { +>T : Symbol(T, Decl(inferenceAndHKTs.ts, 12, 39)) +>A : Symbol(A, Decl(inferenceAndHKTs.ts, 14, 19)) + + value: A; +>value : Symbol(T.value, Decl(inferenceAndHKTs.ts, 14, 23)) +>A : Symbol(A, Decl(inferenceAndHKTs.ts, 14, 19)) +} + +export interface TTypeLambda extends TypeLambda { +>TTypeLambda : Symbol(TTypeLambda, Decl(inferenceAndHKTs.ts, 16, 1)) +>TypeLambda : Symbol(TypeLambda, Decl(inferenceAndHKTs.ts, 0, 0)) + + readonly type: T; +>type : Symbol(TTypeLambda.type, Decl(inferenceAndHKTs.ts, 18, 49)) +>T : Symbol(T, Decl(inferenceAndHKTs.ts, 12, 39)) +} + +export declare const map: (F: TypeClass) => (a: Apply, f: (a: A) => B) => Apply; +>map : Symbol(map, Decl(inferenceAndHKTs.ts, 22, 20)) +>F : Symbol(F, Decl(inferenceAndHKTs.ts, 22, 27), Decl(inferenceAndHKTs.ts, 22, 49)) +>TypeLambda : Symbol(TypeLambda, Decl(inferenceAndHKTs.ts, 0, 0)) +>F : Symbol(F, Decl(inferenceAndHKTs.ts, 22, 27), Decl(inferenceAndHKTs.ts, 22, 49)) +>TypeClass : Symbol(TypeClass, Decl(inferenceAndHKTs.ts, 4, 1)) +>F : Symbol(F, Decl(inferenceAndHKTs.ts, 22, 27), Decl(inferenceAndHKTs.ts, 22, 49)) +>A : Symbol(A, Decl(inferenceAndHKTs.ts, 22, 70)) +>B : Symbol(B, Decl(inferenceAndHKTs.ts, 22, 72)) +>a : Symbol(a, Decl(inferenceAndHKTs.ts, 22, 76)) +>Apply : Symbol(Apply, Decl(inferenceAndHKTs.ts, 8, 1)) +>F : Symbol(F, Decl(inferenceAndHKTs.ts, 22, 27), Decl(inferenceAndHKTs.ts, 22, 49)) +>A : Symbol(A, Decl(inferenceAndHKTs.ts, 22, 70)) +>f : Symbol(f, Decl(inferenceAndHKTs.ts, 22, 91)) +>a : Symbol(a, Decl(inferenceAndHKTs.ts, 22, 96)) +>A : Symbol(A, Decl(inferenceAndHKTs.ts, 22, 70)) +>B : Symbol(B, Decl(inferenceAndHKTs.ts, 22, 72)) +>Apply : Symbol(Apply, Decl(inferenceAndHKTs.ts, 8, 1)) +>F : Symbol(F, Decl(inferenceAndHKTs.ts, 22, 27), Decl(inferenceAndHKTs.ts, 22, 49)) +>B : Symbol(B, Decl(inferenceAndHKTs.ts, 22, 72)) + +declare const typeClass: TypeClass; +>typeClass : Symbol(typeClass, Decl(inferenceAndHKTs.ts, 24, 13)) +>TypeClass : Symbol(TypeClass, Decl(inferenceAndHKTs.ts, 4, 1)) +>TTypeLambda : Symbol(TTypeLambda, Decl(inferenceAndHKTs.ts, 16, 1)) + +declare const a: T; +>a : Symbol(a, Decl(inferenceAndHKTs.ts, 26, 13)) +>T : Symbol(T, Decl(inferenceAndHKTs.ts, 12, 39)) + +const x1 = map(typeClass); +>x1 : Symbol(x1, Decl(inferenceAndHKTs.ts, 28, 5)) +>map : Symbol(map, Decl(inferenceAndHKTs.ts, 22, 20)) +>typeClass : Symbol(typeClass, Decl(inferenceAndHKTs.ts, 24, 13)) + +const x2 = map(typeClass)(a, (_) => _); // T +>x2 : Symbol(x2, Decl(inferenceAndHKTs.ts, 29, 5)) +>map : Symbol(map, Decl(inferenceAndHKTs.ts, 22, 20)) +>typeClass : Symbol(typeClass, Decl(inferenceAndHKTs.ts, 24, 13)) +>a : Symbol(a, Decl(inferenceAndHKTs.ts, 26, 13)) +>_ : Symbol(_, Decl(inferenceAndHKTs.ts, 29, 30)) +>_ : Symbol(_, Decl(inferenceAndHKTs.ts, 29, 30)) + diff --git a/tests/baselines/reference/inferenceAndHKTs.types b/tests/baselines/reference/inferenceAndHKTs.types new file mode 100644 index 0000000000000..2b055133e3edc --- /dev/null +++ b/tests/baselines/reference/inferenceAndHKTs.types @@ -0,0 +1,64 @@ +=== tests/cases/compiler/inferenceAndHKTs.ts === +// Repro from #53970 + +export interface TypeLambda { + readonly A: unknown; +>A : unknown +} + +export interface TypeClass { + readonly _F: F; +>_F : F +} + +export type Apply = F extends { readonly type: unknown } +>Apply : Apply +>type : unknown + + ? (F & { readonly A: A })['type'] +>A : A + + : { readonly F: F, readonly A: A }; +>F : F +>A : A + +export interface T { + value: A; +>value : A +} + +export interface TTypeLambda extends TypeLambda { + readonly type: T; +>type : T +} + +export declare const map: (F: TypeClass) => (a: Apply, f: (a: A) => B) => Apply; +>map : (F: TypeClass) => (a: Apply, f: (a: A) => B) => Apply +>F : TypeClass +>a : Apply +>f : (a: A) => B +>a : A + +declare const typeClass: TypeClass; +>typeClass : TypeClass + +declare const a: T; +>a : T + +const x1 = map(typeClass); +>x1 : (a: (TTypeLambda & { readonly A: A; })["type"], f: (a: A) => B) => (TTypeLambda & { readonly A: B; })["type"] +>map(typeClass) : (a: (TTypeLambda & { readonly A: A; })["type"], f: (a: A) => B) => (TTypeLambda & { readonly A: B; })["type"] +>map : (F: TypeClass) => (a: Apply, f: (a: A) => B) => Apply +>typeClass : TypeClass + +const x2 = map(typeClass)(a, (_) => _); // T +>x2 : T +>map(typeClass)(a, (_) => _) : T +>map(typeClass) : (a: (TTypeLambda & { readonly A: A; })["type"], f: (a: A) => B) => (TTypeLambda & { readonly A: B; })["type"] +>map : (F: TypeClass) => (a: Apply, f: (a: A) => B) => Apply +>typeClass : TypeClass +>a : T +>(_) => _ : (_: number) => number +>_ : number +>_ : number + diff --git a/tests/cases/compiler/inferenceAndHKTs.ts b/tests/cases/compiler/inferenceAndHKTs.ts new file mode 100644 index 0000000000000..00887c7fa9157 --- /dev/null +++ b/tests/cases/compiler/inferenceAndHKTs.ts @@ -0,0 +1,33 @@ +// @strict: true +// @noEmit: true + +// Repro from #53970 + +export interface TypeLambda { + readonly A: unknown; +} + +export interface TypeClass { + readonly _F: F; +} + +export type Apply = F extends { readonly type: unknown } + ? (F & { readonly A: A })['type'] + : { readonly F: F, readonly A: A }; + +export interface T { + value: A; +} + +export interface TTypeLambda extends TypeLambda { + readonly type: T; +} + +export declare const map: (F: TypeClass) => (a: Apply, f: (a: A) => B) => Apply; + +declare const typeClass: TypeClass; + +declare const a: T; + +const x1 = map(typeClass); +const x2 = map(typeClass)(a, (_) => _); // T From f104b53c050725f72a19f4d631817a5feb1e3529 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 4 May 2023 16:56:20 -0700 Subject: [PATCH 3/4] Revert changes and instead fix isGenericObjectType to be more conservative --- src/compiler/checker.ts | 13 ++----------- src/compiler/types.ts | 19 +++++++++---------- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 63f6fda4225f1..bf49a02fc963f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14078,7 +14078,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { else if (type !== firstType) { checkFlags |= CheckFlags.HasNonUniformType; } - if (isLiteralType(type) || isPatternLiteralType(type) || type === uniqueLiteralType) { + if (isLiteralType(type) || isPatternLiteralType(type)) { checkFlags |= CheckFlags.HasLiteralType; } if (type.flags & TypeFlags.Never && type !== uniqueLiteralType) { @@ -17685,7 +17685,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // eagerly using the constraint type of 'this' at the given location. if (isGenericIndexType(indexType) || (accessNode && accessNode.kind !== SyntaxKind.IndexedAccessType ? isGenericTupleType(objectType) && !indexTypeLessThan(indexType, objectType.target.fixedLength) : - isGenericObjectType(objectType) && !(isTupleType(objectType) && indexTypeLessThan(indexType, objectType.target.fixedLength)) || !(accessFlags & AccessFlags.ResolveReducibleTypes) && isGenericReducibleType(objectType))) { + isGenericObjectType(objectType) && !(isTupleType(objectType) && indexTypeLessThan(indexType, objectType.target.fixedLength)) || isGenericReducibleType(objectType))) { if (objectType.flags & TypeFlags.AnyOrUnknown) { return objectType; } @@ -24511,15 +24511,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } } - // The following is a targeted fix to allow higher-kinded types to be emulated using the technique in #53970. - // Specifically, when an indexed access type was deferred because it has a reducible object type, here we force - // resolution of the type and then infer to the result. - if (target.flags & TypeFlags.IndexedAccess && isGenericReducibleType((target as IndexedAccessType).objectType)) { - const instantiated = getIndexedAccessType((target as IndexedAccessType).objectType, (target as IndexedAccessType).indexType, AccessFlags.ResolveReducibleTypes); - if (instantiated && instantiated !== target) { - inferFromTypes(source, instantiated); - } - } } if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && ( (source as TypeReference).target === (target as TypeReference).target || isArrayType(source) && isArrayType(target)) && diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 122ed908792fa..c97bbaffa2834 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6593,16 +6593,15 @@ export interface TypeParameter extends InstantiableType { export const enum AccessFlags { None = 0, IncludeUndefined = 1 << 0, - ResolveReducibleTypes = 1 << 1, - NoIndexSignatures = 1 << 2, - Writing = 1 << 3, - CacheSymbol = 1 << 4, - NoTupleBoundsCheck = 1 << 5, - ExpressionPosition = 1 << 6, - ReportDeprecated = 1 << 7, - SuppressNoImplicitAnyError = 1 << 8, - Contextual = 1 << 9, - Persistent = IncludeUndefined | ResolveReducibleTypes, + NoIndexSignatures = 1 << 1, + Writing = 1 << 2, + CacheSymbol = 1 << 3, + NoTupleBoundsCheck = 1 << 4, + ExpressionPosition = 1 << 5, + ReportDeprecated = 1 << 6, + SuppressNoImplicitAnyError = 1 << 7, + Contextual = 1 << 8, + Persistent = IncludeUndefined, } // Indexed access types (TypeFlags.IndexedAccess) From 64a864b4d5a48e5a56e6a1199e687efaa13181c9 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 4 May 2023 16:57:02 -0700 Subject: [PATCH 4/4] Accept new baselines --- tests/baselines/reference/inferenceAndHKTs.types | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/baselines/reference/inferenceAndHKTs.types b/tests/baselines/reference/inferenceAndHKTs.types index 2b055133e3edc..51fc87bf76b8d 100644 --- a/tests/baselines/reference/inferenceAndHKTs.types +++ b/tests/baselines/reference/inferenceAndHKTs.types @@ -46,15 +46,15 @@ declare const a: T; >a : T const x1 = map(typeClass); ->x1 : (a: (TTypeLambda & { readonly A: A; })["type"], f: (a: A) => B) => (TTypeLambda & { readonly A: B; })["type"] ->map(typeClass) : (a: (TTypeLambda & { readonly A: A; })["type"], f: (a: A) => B) => (TTypeLambda & { readonly A: B; })["type"] +>x1 : (a: T, f: (a: A) => B) => T +>map(typeClass) : (a: T, f: (a: A) => B) => T >map : (F: TypeClass) => (a: Apply, f: (a: A) => B) => Apply >typeClass : TypeClass const x2 = map(typeClass)(a, (_) => _); // T >x2 : T >map(typeClass)(a, (_) => _) : T ->map(typeClass) : (a: (TTypeLambda & { readonly A: A; })["type"], f: (a: A) => B) => (TTypeLambda & { readonly A: B; })["type"] +>map(typeClass) : (a: T, f: (a: A) => B) => T >map : (F: TypeClass) => (a: Apply, f: (a: A) => B) => Apply >typeClass : TypeClass >a : T