diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 80b61edd3657a..11326c3ad83b8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1367,6 +1367,12 @@ const enum MappedTypeModifiers { ExcludeOptional = 1 << 3, } +const enum MappedTypeModifierChange { + Stripped = -1, + Unchanged = 0, + Added = 1, +} + const enum MappedTypeNameTypeKind { None, Filtering, @@ -14323,25 +14329,36 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { (declaration.questionToken ? declaration.questionToken.kind === SyntaxKind.MinusToken ? MappedTypeModifiers.ExcludeOptional : MappedTypeModifiers.IncludeOptional : 0); } - // Return -1, 0, or 1, where -1 means optionality is stripped (i.e. -?), 0 means optionality is unchanged, and 1 means - // optionality is added (i.e. +?). - function getMappedTypeOptionality(type: MappedType): number { + function getMappedTypeOptionality(type: MappedType): MappedTypeModifierChange { + const modifiers = getMappedTypeModifiers(type); + return modifiers & MappedTypeModifiers.ExcludeOptional ? MappedTypeModifierChange.Stripped : modifiers & MappedTypeModifiers.IncludeOptional ? MappedTypeModifierChange.Added : MappedTypeModifierChange.Unchanged; + } + + function getMappedTypeReadonlyness(type: MappedType): MappedTypeModifierChange { const modifiers = getMappedTypeModifiers(type); - return modifiers & MappedTypeModifiers.ExcludeOptional ? -1 : modifiers & MappedTypeModifiers.IncludeOptional ? 1 : 0; + return modifiers & MappedTypeModifiers.ExcludeReadonly ? MappedTypeModifierChange.Stripped : modifiers & MappedTypeModifiers.IncludeReadonly ? MappedTypeModifierChange.Added : MappedTypeModifierChange.Unchanged; } - // Return -1, 0, or 1, for stripped, unchanged, or added optionality respectively. When a homomorphic mapped type doesn't - // modify optionality, recursively consult the optionality of the type being mapped over to see if it strips or adds optionality. - // For intersections, return -1 or 1 when all constituents strip or add optionality, otherwise return 0. - function getCombinedMappedTypeOptionality(type: Type): number { + // Return -1, 0, or 1, for stripped, unchanged, or added modifier kind respectively. When a homomorphic mapped type doesn't + // modify a modifier, recursively consult the modifier change of the type being mapped over to see if it strips or adds it. + // For intersections, return -1 or 1 when all constituents strip or add a specific modifier, otherwise return 0. + function getCombinedMappedTypeModifierChange(type: Type, getMappedTypeModifierChange: (type: MappedType) => MappedTypeModifierChange): MappedTypeModifierChange { if (getObjectFlags(type) & ObjectFlags.Mapped) { - return getMappedTypeOptionality(type as MappedType) || getCombinedMappedTypeOptionality(getModifiersTypeFromMappedType(type as MappedType)); + return getMappedTypeModifierChange(type as MappedType) || getCombinedMappedTypeModifierChange(getModifiersTypeFromMappedType(type as MappedType), getMappedTypeModifierChange); } if (type.flags & TypeFlags.Intersection) { - const optionality = getCombinedMappedTypeOptionality((type as IntersectionType).types[0]); - return every((type as IntersectionType).types, (t, i) => i === 0 || getCombinedMappedTypeOptionality(t) === optionality) ? optionality : 0; + const modifierChange = getCombinedMappedTypeModifierChange((type as IntersectionType).types[0], getMappedTypeModifierChange); + return every((type as IntersectionType).types, (t, i) => i === 0 || getCombinedMappedTypeModifierChange(t, getMappedTypeModifierChange) === modifierChange) ? modifierChange : MappedTypeModifierChange.Unchanged; } - return 0; + return MappedTypeModifierChange.Unchanged; + } + + function getCombinedMappedTypeOptionality(type: Type): MappedTypeModifierChange { + return getCombinedMappedTypeModifierChange(type, getMappedTypeOptionality); + } + + function getCombinedMappedTypeReadonlyness(type: Type): MappedTypeModifierChange { + return getCombinedMappedTypeModifierChange(type, getMappedTypeReadonlyness); } function isPartialMappedType(type: Type) { @@ -18244,6 +18261,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const nameType = getNameTypeFromMappedType(type.target as MappedType || type); if (!nameType && !(indexFlags & IndexFlags.NoIndexSignatures)) { // no mapping and no filtering required, just quickly bail to returning the constraint in the common case + // andarist return constraintType; } const keyTypes: Type[] = []; @@ -18352,7 +18370,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function shouldDeferIndexType(type: Type, indexFlags = IndexFlags.None) { return !!(type.flags & TypeFlags.InstantiableNonPrimitive || isGenericTupleType(type) || - isGenericMappedType(type) && (!hasDistributiveNameType(type) || getMappedTypeNameTypeKind(type) === MappedTypeNameTypeKind.Remapping) || + isGenericMappedType(type) && (getHomomorphicTypeVariable(type.target as MappedType ?? type) && !getNameTypeFromMappedType(type) && getCombinedMappedTypeReadonlyness(type) || (!hasDistributiveNameType(type) || getMappedTypeNameTypeKind(type) === MappedTypeNameTypeKind.Remapping)) || type.flags & TypeFlags.Union && !(indexFlags & IndexFlags.NoReducibleCheck) && isGenericReducibleType(type) || type.flags & TypeFlags.Intersection && maybeTypeOfKind(type, TypeFlags.Instantiable) && some((type as IntersectionType).types, isEmptyAnonymousObjectType)); } diff --git a/tests/baselines/reference/indexTypeOnHomomorphicMappedTypesWithReadonly1.symbols b/tests/baselines/reference/indexTypeOnHomomorphicMappedTypesWithReadonly1.symbols new file mode 100644 index 0000000000000..96fbef28a7d93 --- /dev/null +++ b/tests/baselines/reference/indexTypeOnHomomorphicMappedTypesWithReadonly1.symbols @@ -0,0 +1,108 @@ +//// [tests/cases/compiler/indexTypeOnHomomorphicMappedTypesWithReadonly1.ts] //// + +=== indexTypeOnHomomorphicMappedTypesWithReadonly1.ts === +type T1 = Readonly; +>T1 : Symbol(T1, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 0, 0)) +>Readonly : Symbol(Readonly, Decl(lib.es5.d.ts, --, --)) + +type R1 = keyof T1; // keyof readonly string[] +>R1 : Symbol(R1, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 0, 29)) +>T1 : Symbol(T1, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 0, 0)) + +type KeyOfReadonly = keyof Readonly; +>KeyOfReadonly : Symbol(KeyOfReadonly, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 1, 19)) +>T : Symbol(T, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 2, 19)) +>Readonly : Symbol(Readonly, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 2, 19)) + +type R2 = KeyOfReadonly; // keyof readonly string[] +>R2 : Symbol(R2, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 2, 42)) +>KeyOfReadonly : Symbol(KeyOfReadonly, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 1, 19)) + +type Identity = { [K in keyof T]: T[K] }; +>Identity : Symbol(Identity, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 3, 34)) +>T : Symbol(T, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 5, 14)) +>K : Symbol(K, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 5, 22)) +>T : Symbol(T, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 5, 14)) +>T : Symbol(T, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 5, 14)) +>K : Symbol(K, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 5, 22)) + +type KeyOfReadonly2 = keyof Identity>; +>KeyOfReadonly2 : Symbol(KeyOfReadonly2, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 5, 44)) +>T : Symbol(T, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 7, 20)) +>Identity : Symbol(Identity, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 3, 34)) +>Readonly : Symbol(Readonly, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 7, 20)) + +type R3 = KeyOfReadonly2; // keyof readonly string[] +>R3 : Symbol(R3, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 7, 53)) +>KeyOfReadonly2 : Symbol(KeyOfReadonly2, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 5, 44)) + +type KeyOfReadonly3 = keyof Readonly>; +>KeyOfReadonly3 : Symbol(KeyOfReadonly3, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 8, 35)) +>T : Symbol(T, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 9, 20)) +>Readonly : Symbol(Readonly, Decl(lib.es5.d.ts, --, --)) +>Identity : Symbol(Identity, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 3, 34)) +>T : Symbol(T, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 9, 20)) + +type R4 = KeyOfReadonly3; // keyof readonly string[] +>R4 : Symbol(R4, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 9, 53)) +>KeyOfReadonly3 : Symbol(KeyOfReadonly3, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 8, 35)) + +type Writable = { -readonly [K in keyof T]: T[K] }; +>Writable : Symbol(Writable, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 10, 35)) +>T : Symbol(T, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 12, 14)) +>K : Symbol(K, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 12, 32)) +>T : Symbol(T, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 12, 14)) +>T : Symbol(T, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 12, 14)) +>K : Symbol(K, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 12, 32)) + +type KeyOfWritable = keyof Writable; +>KeyOfWritable : Symbol(KeyOfWritable, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 12, 54)) +>T : Symbol(T, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 14, 19)) +>Writable : Symbol(Writable, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 10, 35)) +>T : Symbol(T, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 14, 19)) + +type R5 = KeyOfWritable; // keyof string[] +>R5 : Symbol(R5, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 14, 42)) +>KeyOfWritable : Symbol(KeyOfWritable, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 12, 54)) + +type KeyOfWritable2 = keyof Writable>; +>KeyOfWritable2 : Symbol(KeyOfWritable2, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 15, 43)) +>T : Symbol(T, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 16, 20)) +>Writable : Symbol(Writable, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 10, 35)) +>Readonly : Symbol(Readonly, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 16, 20)) + +type R6 = KeyOfWritable2; // keyof string[] +>R6 : Symbol(R6, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 16, 53)) +>KeyOfWritable2 : Symbol(KeyOfWritable2, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 15, 43)) + +type KeyOfWritable3 = keyof Identity>>; +>KeyOfWritable3 : Symbol(KeyOfWritable3, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 17, 44)) +>T : Symbol(T, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 18, 20)) +>Identity : Symbol(Identity, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 3, 34)) +>Writable : Symbol(Writable, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 10, 35)) +>Readonly : Symbol(Readonly, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 18, 20)) + +type R7 = KeyOfWritable3; // keyof string[] +>R7 : Symbol(R7, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 18, 63)) +>KeyOfWritable3 : Symbol(KeyOfWritable3, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 17, 44)) + +type R8 = KeyOfWritable3; // keyof string[] +>R8 : Symbol(R8, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 19, 35)) +>KeyOfWritable3 : Symbol(KeyOfWritable3, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 17, 44)) + +type KeyOfReadonly4 = keyof Identity>>; +>KeyOfReadonly4 : Symbol(KeyOfReadonly4, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 20, 44)) +>T : Symbol(T, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 22, 20)) +>Identity : Symbol(Identity, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 3, 34)) +>Readonly : Symbol(Readonly, Decl(lib.es5.d.ts, --, --)) +>Writable : Symbol(Writable, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 10, 35)) +>T : Symbol(T, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 22, 20)) + +type R9 = KeyOfReadonly4; // keyof readonly string[] +>R9 : Symbol(R9, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 22, 63)) +>KeyOfReadonly4 : Symbol(KeyOfReadonly4, Decl(indexTypeOnHomomorphicMappedTypesWithReadonly1.ts, 20, 44)) + diff --git a/tests/baselines/reference/indexTypeOnHomomorphicMappedTypesWithReadonly1.types b/tests/baselines/reference/indexTypeOnHomomorphicMappedTypesWithReadonly1.types new file mode 100644 index 0000000000000..04cab26738129 --- /dev/null +++ b/tests/baselines/reference/indexTypeOnHomomorphicMappedTypesWithReadonly1.types @@ -0,0 +1,79 @@ +//// [tests/cases/compiler/indexTypeOnHomomorphicMappedTypesWithReadonly1.ts] //// + +=== indexTypeOnHomomorphicMappedTypesWithReadonly1.ts === +type T1 = Readonly; +>T1 : readonly string[] +> : ^^^^^^^^^^^^^^^^^ + +type R1 = keyof T1; // keyof readonly string[] +>R1 : keyof readonly string[] +> : ^^^^^^^^^^^^^^^^^^^^^^^ + +type KeyOfReadonly = keyof Readonly; +>KeyOfReadonly : keyof Readonly +> : ^^^^^^^^^^^^^^^^^ + +type R2 = KeyOfReadonly; // keyof readonly string[] +>R2 : keyof readonly string[] +> : ^^^^^^^^^^^^^^^^^^^^^^^ + +type Identity = { [K in keyof T]: T[K] }; +>Identity : Identity +> : ^^^^^^^^^^^ + +type KeyOfReadonly2 = keyof Identity>; +>KeyOfReadonly2 : keyof Identity> +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +type R3 = KeyOfReadonly2; // keyof readonly string[] +>R3 : keyof readonly string[] +> : ^^^^^^^^^^^^^^^^^^^^^^^ + +type KeyOfReadonly3 = keyof Readonly>; +>KeyOfReadonly3 : keyof Readonly> +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +type R4 = KeyOfReadonly3; // keyof readonly string[] +>R4 : keyof readonly string[] +> : ^^^^^^^^^^^^^^^^^^^^^^^ + +type Writable = { -readonly [K in keyof T]: T[K] }; +>Writable : Writable +> : ^^^^^^^^^^^ + +type KeyOfWritable = keyof Writable; +>KeyOfWritable : keyof Writable +> : ^^^^^^^^^^^^^^^^^ + +type R5 = KeyOfWritable; // keyof string[] +>R5 : keyof string[] +> : ^^^^^^^^^^^^^^ + +type KeyOfWritable2 = keyof Writable>; +>KeyOfWritable2 : keyof Writable> +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +type R6 = KeyOfWritable2; // keyof string[] +>R6 : keyof string[] +> : ^^^^^^^^^^^^^^ + +type KeyOfWritable3 = keyof Identity>>; +>KeyOfWritable3 : keyof Identity>> +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +type R7 = KeyOfWritable3; // keyof string[] +>R7 : keyof string[] +> : ^^^^^^^^^^^^^^ + +type R8 = KeyOfWritable3; // keyof string[] +>R8 : keyof string[] +> : ^^^^^^^^^^^^^^ + +type KeyOfReadonly4 = keyof Identity>>; +>KeyOfReadonly4 : keyof Identity>> +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +type R9 = KeyOfReadonly4; // keyof readonly string[] +>R9 : keyof readonly string[] +> : ^^^^^^^^^^^^^^^^^^^^^^^ + diff --git a/tests/cases/compiler/indexTypeOnHomomorphicMappedTypesWithReadonly1.ts b/tests/cases/compiler/indexTypeOnHomomorphicMappedTypesWithReadonly1.ts new file mode 100644 index 0000000000000..fcbe2f345ae9e --- /dev/null +++ b/tests/cases/compiler/indexTypeOnHomomorphicMappedTypesWithReadonly1.ts @@ -0,0 +1,27 @@ +// @strict: true +// @noEmit: true + +type T1 = Readonly; +type R1 = keyof T1; // keyof readonly string[] +type KeyOfReadonly = keyof Readonly; +type R2 = KeyOfReadonly; // keyof readonly string[] + +type Identity = { [K in keyof T]: T[K] }; + +type KeyOfReadonly2 = keyof Identity>; +type R3 = KeyOfReadonly2; // keyof readonly string[] +type KeyOfReadonly3 = keyof Readonly>; +type R4 = KeyOfReadonly3; // keyof readonly string[] + +type Writable = { -readonly [K in keyof T]: T[K] }; + +type KeyOfWritable = keyof Writable; +type R5 = KeyOfWritable; // keyof string[] +type KeyOfWritable2 = keyof Writable>; +type R6 = KeyOfWritable2; // keyof string[] +type KeyOfWritable3 = keyof Identity>>; +type R7 = KeyOfWritable3; // keyof string[] +type R8 = KeyOfWritable3; // keyof string[] + +type KeyOfReadonly4 = keyof Identity>>; +type R9 = KeyOfReadonly4; // keyof readonly string[]