diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e52570b6f41b0..79446307c87bf 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -404,6 +404,7 @@ import { ImportTypeNode, IndexedAccessType, IndexedAccessTypeNode, + IndexFlags, IndexInfo, IndexKind, indexOfNode, @@ -1448,6 +1449,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { var noImplicitThis = getStrictOptionValue(compilerOptions, "noImplicitThis"); var useUnknownInCatchVariables = getStrictOptionValue(compilerOptions, "useUnknownInCatchVariables"); var keyofStringsOnly = !!compilerOptions.keyofStringsOnly; + var defaultIndexFlags = keyofStringsOnly ? IndexFlags.StringsOnly : IndexFlags.None; var freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : ObjectFlags.FreshLiteral; var exactOptionalPropertyTypes = compilerOptions.exactOptionalPropertyTypes; @@ -14123,6 +14125,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return !prop.valueDeclaration && !!(getCheckFlags(prop) & CheckFlags.ContainsPrivate); } + /** + * A union type which is reducible upon instantiation (meaning some members are removed under certain instantiations) + * must be kept generic, as that instantiation information needs to flow through the type system. By replacing all + * type parameters in the union with a special never type that is treated as a literal in `getReducedType`, we can cause + * the `getReducedType` logic to reduce the resulting type if possible (since only intersections with conflicting + * literal-typed properties are reducible). + */ + function isGenericReducibleType(type: Type): boolean { + return !!(type.flags & TypeFlags.Union && (type as UnionType).objectFlags & ObjectFlags.ContainsIntersections && some((type as UnionType).types, isGenericReducibleType) || + type.flags & TypeFlags.Intersection && isReducibleIntersection(type as IntersectionType)); + } + + function isReducibleIntersection(type: IntersectionType) { + const uniqueFilled = type.uniqueLiteralFilledInstantiation || (type.uniqueLiteralFilledInstantiation = instantiateType(type, uniqueLiteralMapper)); + return getReducedType(uniqueFilled) !== uniqueFilled; + } + function elaborateNeverIntersection(errorInfo: DiagnosticMessageChain | undefined, type: Type) { if (type.flags & TypeFlags.Intersection && getObjectFlags(type) & ObjectFlags.IsNeverIntersection) { const neverProp = find(getPropertiesOfUnionOrIntersectionType(type as IntersectionType), isDiscriminantWithNeverType); @@ -16774,10 +16793,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return links.resolvedType; } - function createIndexType(type: InstantiableType | UnionOrIntersectionType, stringsOnly: boolean) { + function createIndexType(type: InstantiableType | UnionOrIntersectionType, indexFlags: IndexFlags) { const result = createType(TypeFlags.Index) as IndexType; result.type = type; - result.stringsOnly = stringsOnly; + result.indexFlags = indexFlags; return result; } @@ -16787,10 +16806,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return result; } - function getIndexTypeForGenericType(type: InstantiableType | UnionOrIntersectionType, stringsOnly: boolean) { - return stringsOnly ? - type.resolvedStringIndexType || (type.resolvedStringIndexType = createIndexType(type, /*stringsOnly*/ true)) : - type.resolvedIndexType || (type.resolvedIndexType = createIndexType(type, /*stringsOnly*/ false)); + function getIndexTypeForGenericType(type: InstantiableType | UnionOrIntersectionType, indexFlags: IndexFlags) { + return indexFlags & IndexFlags.StringsOnly ? + type.resolvedStringIndexType || (type.resolvedStringIndexType = createIndexType(type, IndexFlags.StringsOnly)) : + type.resolvedIndexType || (type.resolvedIndexType = createIndexType(type, IndexFlags.None)); } /** @@ -16800,11 +16819,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { * reduction in the constraintType) when possible. * @param noIndexSignatures Indicates if _string_ index signatures should be elided. (other index signatures are always reported) */ - function getIndexTypeForMappedType(type: MappedType, stringsOnly: boolean, noIndexSignatures: boolean | undefined) { + function getIndexTypeForMappedType(type: MappedType, indexFlags: IndexFlags) { const typeParameter = getTypeParameterFromMappedType(type); const constraintType = getConstraintTypeFromMappedType(type); const nameType = getNameTypeFromMappedType(type.target as MappedType || type); - if (!nameType && !noIndexSignatures) { + if (!nameType && !(indexFlags & IndexFlags.NoIndexSignatures)) { // no mapping and no filtering required, just quickly bail to returning the constraint in the common case return constraintType; } @@ -16817,12 +16836,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // so we only eagerly manifest the keys if the constraint is nongeneric if (!isGenericIndexType(constraintType)) { const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T' - forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, TypeFlags.StringOrNumberLiteralOrUnique, stringsOnly, addMemberForKeyType); + forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, TypeFlags.StringOrNumberLiteralOrUnique, !!(indexFlags & IndexFlags.StringsOnly), addMemberForKeyType); } else { // we have a generic index and a homomorphic mapping (but a distributive key remapping) - we need to defer the whole `keyof whatever` for later // since it's not safe to resolve the shape of modifier type - return getIndexTypeForGenericType(type, stringsOnly); + return getIndexTypeForGenericType(type, indexFlags); } } else { @@ -16833,7 +16852,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } // we had to pick apart the constraintType to potentially map/filter it - compare the final resulting list with the original constraintType, // so we can return the union that preserves aliases/origin data if possible - const result = noIndexSignatures ? filterType(getUnionType(keyTypes), t => !(t.flags & (TypeFlags.Any | TypeFlags.String))) : getUnionType(keyTypes); + const result = indexFlags & IndexFlags.NoIndexSignatures ? filterType(getUnionType(keyTypes), t => !(t.flags & (TypeFlags.Any | TypeFlags.String))) : getUnionType(keyTypes); if (result.flags & TypeFlags.Union && constraintType.flags & TypeFlags.Union && getTypeListId((result as UnionType).types) === getTypeListId((constraintType as UnionType).types)){ return constraintType; } @@ -16902,36 +16921,25 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, origin); } - /** - * A union type which is reducible upon instantiation (meaning some members are removed under certain instantiations) - * must be kept generic, as that instantiation information needs to flow through the type system. By replacing all - * type parameters in the union with a special never type that is treated as a literal in `getReducedType`, we can cause the `getReducedType` logic - * to reduce the resulting type if possible (since only intersections with conflicting literal-typed properties are reducible). - */ - function isPossiblyReducibleByInstantiation(type: Type): boolean { - const uniqueFilled = getUniqueLiteralFilledInstantiation(type); - return getReducedType(uniqueFilled) !== uniqueFilled; - } - - function shouldDeferIndexType(type: Type) { + function shouldDeferIndexType(type: Type, indexFlags = IndexFlags.None) { return !!(type.flags & TypeFlags.InstantiableNonPrimitive || isGenericTupleType(type) || isGenericMappedType(type) && !hasDistributiveNameType(type) || - type.flags & TypeFlags.Union && some((type as UnionType).types, isPossiblyReducibleByInstantiation) || + type.flags & TypeFlags.Union && !(indexFlags & IndexFlags.NoReducibleCheck) && isGenericReducibleType(type) || type.flags & TypeFlags.Intersection && maybeTypeOfKind(type, TypeFlags.Instantiable) && some((type as IntersectionType).types, isEmptyAnonymousObjectType)); } - function getIndexType(type: Type, stringsOnly = keyofStringsOnly, noIndexSignatures?: boolean): Type { + function getIndexType(type: Type, indexFlags = defaultIndexFlags): Type { type = getReducedType(type); - return shouldDeferIndexType(type) ? getIndexTypeForGenericType(type as InstantiableType | UnionOrIntersectionType, stringsOnly) : - type.flags & TypeFlags.Union ? getIntersectionType(map((type as UnionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) : - type.flags & TypeFlags.Intersection ? getUnionType(map((type as IntersectionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) : - getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(type as MappedType, stringsOnly, noIndexSignatures) : + return shouldDeferIndexType(type, indexFlags) ? getIndexTypeForGenericType(type as InstantiableType | UnionOrIntersectionType, indexFlags) : + type.flags & TypeFlags.Union ? getIntersectionType(map((type as UnionType).types, t => getIndexType(t, indexFlags))) : + type.flags & TypeFlags.Intersection ? getUnionType(map((type as IntersectionType).types, t => getIndexType(t, indexFlags))) : + getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(type as MappedType, indexFlags) : type === wildcardType ? wildcardType : type.flags & TypeFlags.Unknown ? neverType : type.flags & (TypeFlags.Any | TypeFlags.Never) ? keyofConstraintType : - getLiteralTypeFromProperties(type, (noIndexSignatures ? TypeFlags.StringLiteral : TypeFlags.StringLike) | (stringsOnly ? 0 : TypeFlags.NumberLike | TypeFlags.ESSymbolLike), - stringsOnly === keyofStringsOnly && !noIndexSignatures); + getLiteralTypeFromProperties(type, (indexFlags & IndexFlags.NoIndexSignatures ? TypeFlags.StringLiteral : TypeFlags.StringLike) | (indexFlags & IndexFlags.StringsOnly ? 0 : TypeFlags.NumberLike | TypeFlags.ESSymbolLike), + indexFlags === defaultIndexFlags); } function getExtractStringType(type: Type) { @@ -17544,6 +17552,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (objectType === wildcardType || indexType === wildcardType) { return wildcardType; } + objectType = getReducedType(objectType); // If the object type has a string index signature and no other members we know that the result will // always be the type of that index signature and we can simplify accordingly. if (isStringIndexSignatureOnlyType(objectType) && !(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) { @@ -17560,7 +17569,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)))) { + isGenericObjectType(objectType) && !(isTupleType(objectType) && indexTypeLessThan(indexType, objectType.target.fixedLength)) || isGenericReducibleType(objectType))) { if (objectType.flags & TypeFlags.AnyOrUnknown) { return objectType; } @@ -18980,11 +18989,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return type; // Nested invocation of `inferTypeForHomomorphicMappedType` or the `source` instantiated into something unmappable } - function getUniqueLiteralFilledInstantiation(type: Type) { - return type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never) ? type : - type.uniqueLiteralFilledInstantiation || (type.uniqueLiteralFilledInstantiation = instantiateType(type, uniqueLiteralMapper)); - } - function getPermissiveInstantiation(type: Type) { return type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never) ? type : type.permissiveInstantiation || (type.permissiveInstantiation = instantiateType(type, permissiveMapper)); @@ -21273,7 +21277,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // false positives. For example, given 'T extends { [K in keyof T]: string }', // 'keyof T' has itself as its constraint and produces a Ternary.Maybe when // related to other types. - if (isRelatedTo(source, getIndexType(constraint, (target as IndexType).stringsOnly), RecursionFlags.Target, reportErrors) === Ternary.True) { + if (isRelatedTo(source, getIndexType(constraint, (target as IndexType).indexFlags | IndexFlags.NoReducibleCheck), RecursionFlags.Target, reportErrors) === Ternary.True) { return Ternary.True; } } @@ -21373,7 +21377,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // If target has shape `{ [P in Q]: T }`, then its keys have type `Q`. const targetKeys = keysRemapped ? getNameTypeFromMappedType(target)! : getConstraintTypeFromMappedType(target); // Type of the keys of source type `S`, i.e. `keyof S`. - const sourceKeys = getIndexType(source, /*stringsOnly*/ undefined, /*noIndexSignatures*/ true); + const sourceKeys = getIndexType(source, IndexFlags.NoIndexSignatures); const includeOptional = modifiers & MappedTypeModifiers.IncludeOptional; const filteredByApplicability = includeOptional ? intersectTypes(targetKeys, sourceKeys) : undefined; // A source type `S` is related to a target type `{ [P in Q]: T }` if `Q` is related to `keyof S` and `S[Q]` is related to `T`. @@ -38486,7 +38490,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Check if the index type is assignable to 'keyof T' for the object type. const objectType = (type as IndexedAccessType).objectType; const indexType = (type as IndexedAccessType).indexType; - if (isTypeAssignableTo(indexType, getIndexType(objectType, /*stringsOnly*/ false))) { + if (isTypeAssignableTo(indexType, getIndexType(objectType, IndexFlags.None))) { if (accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) && getObjectFlags(objectType) & ObjectFlags.Mapped && getMappedTypeModifiers(objectType as MappedType) & MappedTypeModifiers.IncludeReadonly) { error(accessNode, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType)); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 35e899e351a55..3e2db076a22f7 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6118,8 +6118,6 @@ export interface Type { /** @internal */ restrictiveInstantiation?: Type; // Instantiation with type parameters mapped to unconstrained form /** @internal */ - uniqueLiteralFilledInstantiation?: Type; // Instantiation with type parameters mapped to never type - /** @internal */ immediateBaseConstraint?: Type; // Immediate base constraint cache /** @internal */ widened?: Type; // Cached widened form of the type @@ -6408,6 +6406,8 @@ export interface UnionType extends UnionOrIntersectionType { export interface IntersectionType extends UnionOrIntersectionType { /** @internal */ resolvedApparentType: Type; + /** @internal */ + uniqueLiteralFilledInstantiation?: Type; // Instantiation with type parameters mapped to never type } export type StructuredType = ObjectType | UnionType | IntersectionType; @@ -6557,11 +6557,19 @@ export interface IndexedAccessType extends InstantiableType { export type TypeVariable = TypeParameter | IndexedAccessType; +/** @internal */ +export const enum IndexFlags { + None = 0, + StringsOnly = 1 << 0, + NoIndexSignatures = 1 << 1, + NoReducibleCheck = 1 << 2, +} + // keyof T types (TypeFlags.Index) export interface IndexType extends InstantiableType { type: InstantiableType | UnionOrIntersectionType; /** @internal */ - stringsOnly: boolean; + indexFlags: IndexFlags; } export interface ConditionalRoot { diff --git a/tests/baselines/reference/complicatedIndexedAccessKeyofReliesOnKeyofNeverUpperBound.errors.txt b/tests/baselines/reference/complicatedIndexedAccessKeyofReliesOnKeyofNeverUpperBound.errors.txt index ec5fce9a3825c..819d28a53fb49 100644 --- a/tests/baselines/reference/complicatedIndexedAccessKeyofReliesOnKeyofNeverUpperBound.errors.txt +++ b/tests/baselines/reference/complicatedIndexedAccessKeyofReliesOnKeyofNeverUpperBound.errors.txt @@ -2,12 +2,12 @@ tests/cases/compiler/complicatedIndexedAccessKeyofReliesOnKeyofNeverUpperBound.t Type '{ type: T; localChannelId: string; }' is not assignable to type 'Pick | ChannelOfType, "type">'. Types of property 'type' are incompatible. Type 'T' is not assignable to type 'ChannelOfType["type"] & ChannelOfType["type"]'. - Type '"text" | "email"' is not assignable to type 'ChannelOfType["type"] & ChannelOfType["type"]'. - Type '"text"' is not assignable to type 'ChannelOfType["type"] & ChannelOfType["type"]'. - Type '"text"' is not assignable to type 'ChannelOfType["type"]'. + Type 'string' is not assignable to type 'ChannelOfType["type"] & ChannelOfType["type"]'. + Type 'string' is not assignable to type 'ChannelOfType["type"] & ChannelOfType["type"]'. + Type 'string' is not assignable to type 'ChannelOfType["type"]'. Type 'T' is not assignable to type 'ChannelOfType["type"]'. - Type '"text" | "email"' is not assignable to type 'ChannelOfType["type"]'. - Type '"text"' is not assignable to type 'ChannelOfType["type"]'. + Type 'string' is not assignable to type 'ChannelOfType["type"]'. + Type 'string' is not assignable to type 'ChannelOfType["type"]'. Type '"text"' is not assignable to type 'T & "text"'. Type 'T' is not assignable to type 'T & "text"'. Type '"text" | "email"' is not assignable to type 'T & "text"'. @@ -58,12 +58,12 @@ tests/cases/compiler/complicatedIndexedAccessKeyofReliesOnKeyofNeverUpperBound.t !!! error TS2322: Type '{ type: T; localChannelId: string; }' is not assignable to type 'Pick | ChannelOfType, "type">'. !!! error TS2322: Types of property 'type' are incompatible. !!! error TS2322: Type 'T' is not assignable to type 'ChannelOfType["type"] & ChannelOfType["type"]'. -!!! error TS2322: Type '"text" | "email"' is not assignable to type 'ChannelOfType["type"] & ChannelOfType["type"]'. -!!! error TS2322: Type '"text"' is not assignable to type 'ChannelOfType["type"] & ChannelOfType["type"]'. -!!! error TS2322: Type '"text"' is not assignable to type 'ChannelOfType["type"]'. +!!! error TS2322: Type 'string' is not assignable to type 'ChannelOfType["type"] & ChannelOfType["type"]'. +!!! error TS2322: Type 'string' is not assignable to type 'ChannelOfType["type"] & ChannelOfType["type"]'. +!!! error TS2322: Type 'string' is not assignable to type 'ChannelOfType["type"]'. !!! error TS2322: Type 'T' is not assignable to type 'ChannelOfType["type"]'. -!!! error TS2322: Type '"text" | "email"' is not assignable to type 'ChannelOfType["type"]'. -!!! error TS2322: Type '"text"' is not assignable to type 'ChannelOfType["type"]'. +!!! error TS2322: Type 'string' is not assignable to type 'ChannelOfType["type"]'. +!!! error TS2322: Type 'string' is not assignable to type 'ChannelOfType["type"]'. !!! error TS2322: Type '"text"' is not assignable to type 'T & "text"'. !!! error TS2322: Type 'T' is not assignable to type 'T & "text"'. !!! error TS2322: Type '"text" | "email"' is not assignable to type 'T & "text"'. diff --git a/tests/baselines/reference/reducibleIndexedAccessTypes.symbols b/tests/baselines/reference/reducibleIndexedAccessTypes.symbols new file mode 100644 index 0000000000000..c5bbd17f9a042 --- /dev/null +++ b/tests/baselines/reference/reducibleIndexedAccessTypes.symbols @@ -0,0 +1,135 @@ +=== tests/cases/compiler/reducibleIndexedAccessTypes.ts === +// Repro from 53030 + +enum Type { A, B, C } +>Type : Symbol(Type, Decl(reducibleIndexedAccessTypes.ts, 0, 0)) +>A : Symbol(Type.A, Decl(reducibleIndexedAccessTypes.ts, 2, 11)) +>B : Symbol(Type.B, Decl(reducibleIndexedAccessTypes.ts, 2, 14)) +>C : Symbol(Type.C, Decl(reducibleIndexedAccessTypes.ts, 2, 17)) + +interface PayloadStructure { +>PayloadStructure : Symbol(PayloadStructure, Decl(reducibleIndexedAccessTypes.ts, 2, 21)) + + dataType: Type; +>dataType : Symbol(PayloadStructure.dataType, Decl(reducibleIndexedAccessTypes.ts, 4, 28)) +>Type : Symbol(Type, Decl(reducibleIndexedAccessTypes.ts, 0, 0)) + + data: unknown; +>data : Symbol(PayloadStructure.data, Decl(reducibleIndexedAccessTypes.ts, 5, 19)) +} + +interface PayloadA extends PayloadStructure { +>PayloadA : Symbol(PayloadA, Decl(reducibleIndexedAccessTypes.ts, 7, 1)) +>PayloadStructure : Symbol(PayloadStructure, Decl(reducibleIndexedAccessTypes.ts, 2, 21)) + + dataType: Type.A; +>dataType : Symbol(PayloadA.dataType, Decl(reducibleIndexedAccessTypes.ts, 9, 45)) +>Type : Symbol(Type, Decl(reducibleIndexedAccessTypes.ts, 0, 0)) +>A : Symbol(Type.A, Decl(reducibleIndexedAccessTypes.ts, 2, 11)) + + data: string; +>data : Symbol(PayloadA.data, Decl(reducibleIndexedAccessTypes.ts, 10, 21)) +} + +interface PayloadB extends PayloadStructure { +>PayloadB : Symbol(PayloadB, Decl(reducibleIndexedAccessTypes.ts, 12, 1)) +>PayloadStructure : Symbol(PayloadStructure, Decl(reducibleIndexedAccessTypes.ts, 2, 21)) + + dataType: Type.B; +>dataType : Symbol(PayloadB.dataType, Decl(reducibleIndexedAccessTypes.ts, 14, 45)) +>Type : Symbol(Type, Decl(reducibleIndexedAccessTypes.ts, 0, 0)) +>B : Symbol(Type.B, Decl(reducibleIndexedAccessTypes.ts, 2, 14)) + + data: number; +>data : Symbol(PayloadB.data, Decl(reducibleIndexedAccessTypes.ts, 15, 21)) +} + +interface PayloadC extends PayloadStructure { +>PayloadC : Symbol(PayloadC, Decl(reducibleIndexedAccessTypes.ts, 17, 1)) +>PayloadStructure : Symbol(PayloadStructure, Decl(reducibleIndexedAccessTypes.ts, 2, 21)) + + dataType: Type.C; +>dataType : Symbol(PayloadC.dataType, Decl(reducibleIndexedAccessTypes.ts, 19, 45)) +>Type : Symbol(Type, Decl(reducibleIndexedAccessTypes.ts, 0, 0)) +>C : Symbol(Type.C, Decl(reducibleIndexedAccessTypes.ts, 2, 17)) + + data: { +>data : Symbol(PayloadC.data, Decl(reducibleIndexedAccessTypes.ts, 20, 21)) + + x: number; +>x : Symbol(x, Decl(reducibleIndexedAccessTypes.ts, 21, 11)) + + y: number; +>y : Symbol(y, Decl(reducibleIndexedAccessTypes.ts, 22, 18)) + + }; +} + +type Payload = PayloadA | PayloadB | PayloadC +>Payload : Symbol(Payload, Decl(reducibleIndexedAccessTypes.ts, 25, 1)) +>PayloadA : Symbol(PayloadA, Decl(reducibleIndexedAccessTypes.ts, 7, 1)) +>PayloadB : Symbol(PayloadB, Decl(reducibleIndexedAccessTypes.ts, 12, 1)) +>PayloadC : Symbol(PayloadC, Decl(reducibleIndexedAccessTypes.ts, 17, 1)) + +type MappedPayload2 = { +>MappedPayload2 : Symbol(MappedPayload2, Decl(reducibleIndexedAccessTypes.ts, 27, 45)) + + [K in Type]?: (data: (Payload & { dataType: K })["data"]) => void +>K : Symbol(K, Decl(reducibleIndexedAccessTypes.ts, 30, 5)) +>Type : Symbol(Type, Decl(reducibleIndexedAccessTypes.ts, 0, 0)) +>data : Symbol(data, Decl(reducibleIndexedAccessTypes.ts, 30, 19)) +>Payload : Symbol(Payload, Decl(reducibleIndexedAccessTypes.ts, 25, 1)) +>dataType : Symbol(dataType, Decl(reducibleIndexedAccessTypes.ts, 30, 37)) +>K : Symbol(K, Decl(reducibleIndexedAccessTypes.ts, 30, 5)) +} + +const payloads2: MappedPayload2 = { +>payloads2 : Symbol(payloads2, Decl(reducibleIndexedAccessTypes.ts, 33, 5)) +>MappedPayload2 : Symbol(MappedPayload2, Decl(reducibleIndexedAccessTypes.ts, 27, 45)) + + [Type.A]: data => { console.log(data) } +>[Type.A] : Symbol([Type.A], Decl(reducibleIndexedAccessTypes.ts, 33, 35)) +>Type.A : Symbol(Type.A, Decl(reducibleIndexedAccessTypes.ts, 2, 11)) +>Type : Symbol(Type, Decl(reducibleIndexedAccessTypes.ts, 0, 0)) +>A : Symbol(Type.A, Decl(reducibleIndexedAccessTypes.ts, 2, 11)) +>data : Symbol(data, Decl(reducibleIndexedAccessTypes.ts, 34, 13)) +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>data : Symbol(data, Decl(reducibleIndexedAccessTypes.ts, 34, 13)) +} + +// Additional repro from 53030 + +type GetPayload

= P extends { dataType: K } ? P["data"] : never; +>GetPayload : Symbol(GetPayload, Decl(reducibleIndexedAccessTypes.ts, 35, 1)) +>P : Symbol(P, Decl(reducibleIndexedAccessTypes.ts, 39, 16)) +>Payload : Symbol(Payload, Decl(reducibleIndexedAccessTypes.ts, 25, 1)) +>K : Symbol(K, Decl(reducibleIndexedAccessTypes.ts, 39, 34)) +>P : Symbol(P, Decl(reducibleIndexedAccessTypes.ts, 39, 16)) +>P : Symbol(P, Decl(reducibleIndexedAccessTypes.ts, 39, 16)) +>dataType : Symbol(dataType, Decl(reducibleIndexedAccessTypes.ts, 39, 67)) +>K : Symbol(K, Decl(reducibleIndexedAccessTypes.ts, 39, 34)) +>P : Symbol(P, Decl(reducibleIndexedAccessTypes.ts, 39, 16)) + +// Repro from #51161 + +type AnyOneof = { oneofKind: string; [k: string]: unknown } | { oneofKind: undefined }; +>AnyOneof : Symbol(AnyOneof, Decl(reducibleIndexedAccessTypes.ts, 39, 102)) +>oneofKind : Symbol(oneofKind, Decl(reducibleIndexedAccessTypes.ts, 43, 17)) +>k : Symbol(k, Decl(reducibleIndexedAccessTypes.ts, 43, 38)) +>oneofKind : Symbol(oneofKind, Decl(reducibleIndexedAccessTypes.ts, 43, 63)) + +type AnyOneofKind = T extends { oneofKind: keyof T } +>AnyOneofKind : Symbol(AnyOneofKind, Decl(reducibleIndexedAccessTypes.ts, 43, 87)) +>T : Symbol(T, Decl(reducibleIndexedAccessTypes.ts, 44, 18)) +>AnyOneof : Symbol(AnyOneof, Decl(reducibleIndexedAccessTypes.ts, 39, 102)) +>T : Symbol(T, Decl(reducibleIndexedAccessTypes.ts, 44, 18)) +>oneofKind : Symbol(oneofKind, Decl(reducibleIndexedAccessTypes.ts, 44, 51)) +>T : Symbol(T, Decl(reducibleIndexedAccessTypes.ts, 44, 18)) + + ? T['oneofKind'] +>T : Symbol(T, Decl(reducibleIndexedAccessTypes.ts, 44, 18)) + + : never; + diff --git a/tests/baselines/reference/reducibleIndexedAccessTypes.types b/tests/baselines/reference/reducibleIndexedAccessTypes.types new file mode 100644 index 0000000000000..8378943751738 --- /dev/null +++ b/tests/baselines/reference/reducibleIndexedAccessTypes.types @@ -0,0 +1,102 @@ +=== tests/cases/compiler/reducibleIndexedAccessTypes.ts === +// Repro from 53030 + +enum Type { A, B, C } +>Type : Type +>A : Type.A +>B : Type.B +>C : Type.C + +interface PayloadStructure { + dataType: Type; +>dataType : Type + + data: unknown; +>data : unknown +} + +interface PayloadA extends PayloadStructure { + dataType: Type.A; +>dataType : Type.A +>Type : any + + data: string; +>data : string +} + +interface PayloadB extends PayloadStructure { + dataType: Type.B; +>dataType : Type.B +>Type : any + + data: number; +>data : number +} + +interface PayloadC extends PayloadStructure { + dataType: Type.C; +>dataType : Type.C +>Type : any + + data: { +>data : { x: number; y: number; } + + x: number; +>x : number + + y: number; +>y : number + + }; +} + +type Payload = PayloadA | PayloadB | PayloadC +>Payload : PayloadA | PayloadB | PayloadC + +type MappedPayload2 = { +>MappedPayload2 : { 0?: ((data: string) => void) | undefined; 1?: ((data: number) => void) | undefined; 2?: ((data: { x: number; y: number; }) => void) | undefined; } + + [K in Type]?: (data: (Payload & { dataType: K })["data"]) => void +>data : (Payload & { dataType: K; })["data"] +>dataType : K +} + +const payloads2: MappedPayload2 = { +>payloads2 : MappedPayload2 +>{ [Type.A]: data => { console.log(data) }} : { 0: (data: string) => void; } + + [Type.A]: data => { console.log(data) } +>[Type.A] : (data: string) => void +>Type.A : Type.A +>Type : typeof Type +>A : Type.A +>data => { console.log(data) } : (data: string) => void +>data : string +>console.log(data) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>data : string +} + +// Additional repro from 53030 + +type GetPayload

= P extends { dataType: K } ? P["data"] : never; +>GetPayload : GetPayload +>dataType : K + +// Repro from #51161 + +type AnyOneof = { oneofKind: string; [k: string]: unknown } | { oneofKind: undefined }; +>AnyOneof : { [k: string]: unknown; oneofKind: string; } | { oneofKind: undefined; } +>oneofKind : string +>k : string +>oneofKind : undefined + +type AnyOneofKind = T extends { oneofKind: keyof T } +>AnyOneofKind : AnyOneofKind +>oneofKind : keyof T + + ? T['oneofKind'] + : never; + diff --git a/tests/cases/compiler/reducibleIndexedAccessTypes.ts b/tests/cases/compiler/reducibleIndexedAccessTypes.ts new file mode 100644 index 0000000000000..0bf3702f57344 --- /dev/null +++ b/tests/cases/compiler/reducibleIndexedAccessTypes.ts @@ -0,0 +1,50 @@ +// @strict: true +// @noEmit: true + +// Repro from 53030 + +enum Type { A, B, C } + +interface PayloadStructure { + dataType: Type; + data: unknown; +} + +interface PayloadA extends PayloadStructure { + dataType: Type.A; + data: string; +} + +interface PayloadB extends PayloadStructure { + dataType: Type.B; + data: number; +} + +interface PayloadC extends PayloadStructure { + dataType: Type.C; + data: { + x: number; + y: number; + }; +} + +type Payload = PayloadA | PayloadB | PayloadC + +type MappedPayload2 = { + [K in Type]?: (data: (Payload & { dataType: K })["data"]) => void +} + +const payloads2: MappedPayload2 = { + [Type.A]: data => { console.log(data) } +} + +// Additional repro from 53030 + +type GetPayload

= P extends { dataType: K } ? P["data"] : never; + +// Repro from #51161 + +type AnyOneof = { oneofKind: string; [k: string]: unknown } | { oneofKind: undefined }; +type AnyOneofKind = T extends { oneofKind: keyof T } + ? T['oneofKind'] + : never;