@@ -11265,6 +11265,22 @@ namespace ts {
11265
11265
return getCheckFlags(s) & CheckFlags.Late;
11266
11266
}
11267
11267
11268
+ function forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(type: Type, include: TypeFlags, stringsOnly: boolean, cb: (keyType: Type) => void) {
11269
+ for (const prop of getPropertiesOfType(type)) {
11270
+ cb(getLiteralTypeFromProperty(prop, include));
11271
+ }
11272
+ if (type.flags & TypeFlags.Any) {
11273
+ cb(stringType);
11274
+ }
11275
+ else {
11276
+ for (const info of getIndexInfosOfType(type)) {
11277
+ if (!stringsOnly || info.keyType.flags & (TypeFlags.String | TypeFlags.TemplateLiteral)) {
11278
+ cb(info.keyType);
11279
+ }
11280
+ }
11281
+ }
11282
+ }
11283
+
11268
11284
/** Resolve the members of a mapped type { [P in K]: T } */
11269
11285
function resolveMappedTypeMembers(type: MappedType) {
11270
11286
const members: SymbolTable = createSymbolTable();
@@ -11282,19 +11298,7 @@ namespace ts {
11282
11298
const include = keyofStringsOnly ? TypeFlags.StringLiteral : TypeFlags.StringOrNumberLiteralOrUnique;
11283
11299
if (isMappedTypeWithKeyofConstraintDeclaration(type)) {
11284
11300
// We have a { [P in keyof T]: X }
11285
- for (const prop of getPropertiesOfType(modifiersType)) {
11286
- addMemberForKeyType(getLiteralTypeFromProperty(prop, include));
11287
- }
11288
- if (modifiersType.flags & TypeFlags.Any) {
11289
- addMemberForKeyType(stringType);
11290
- }
11291
- else {
11292
- for (const info of getIndexInfosOfType(modifiersType)) {
11293
- if (!keyofStringsOnly || info.keyType.flags & (TypeFlags.String | TypeFlags.TemplateLiteral)) {
11294
- addMemberForKeyType(info.keyType);
11295
- }
11296
- }
11297
- }
11301
+ forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, include, keyofStringsOnly, addMemberForKeyType);
11298
11302
}
11299
11303
else {
11300
11304
forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType);
@@ -14653,19 +14657,58 @@ namespace ts {
14653
14657
type.resolvedIndexType || (type.resolvedIndexType = createIndexType(type, /*stringsOnly*/ false));
14654
14658
}
14655
14659
14656
- function instantiateTypeAsMappedNameType(nameType: Type, type: MappedType, t: Type) {
14657
- return instantiateType(nameType, appendTypeMapping(type.mapper, getTypeParameterFromMappedType(type), t));
14658
- }
14660
+ /**
14661
+ * This roughly mirrors `resolveMappedTypeMembers` in the nongeneric case, except only reports a union of the keys calculated,
14662
+ * rather than manufacturing the properties. We can't just fetch the `constraintType` since that would ignore mappings
14663
+ * and mapping the `constraintType` directly ignores how mapped types map _properties_ and not keys (thus ignoring subtype
14664
+ * reduction in the constraintType) when possible.
14665
+ * @param noIndexSignatures Indicates if _string_ index signatures should be elided. (other index signatures are always reported)
14666
+ */
14667
+ function getIndexTypeForMappedType(type: MappedType, stringsOnly: boolean, noIndexSignatures: boolean | undefined) {
14668
+ const typeParameter = getTypeParameterFromMappedType(type);
14669
+ const constraintType = getConstraintTypeFromMappedType(type);
14670
+ const nameType = getNameTypeFromMappedType(type.target as MappedType || type);
14671
+ if (!nameType && !noIndexSignatures) {
14672
+ // no mapping and no filtering required, just quickly bail to returning the constraint in the common case
14673
+ return constraintType;
14674
+ }
14675
+ const keyTypes: Type[] = [];
14676
+ if (isMappedTypeWithKeyofConstraintDeclaration(type)) {
14677
+ // We have a { [P in keyof T]: X }
14678
+
14679
+ // `getApparentType` on the T in a generic mapped type can trigger a circularity
14680
+ // (conditionals and `infer` types create a circular dependency in the constraint resolution)
14681
+ // so we only eagerly manifest the keys if the constraint is nongeneric
14682
+ if (!isGenericIndexType(constraintType)) {
14683
+ const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T'
14684
+ forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, TypeFlags.StringOrNumberLiteralOrUnique, stringsOnly, addMemberForKeyType);
14685
+ }
14686
+ else {
14687
+ // we have a generic index and a homomorphic mapping (but a distributive key remapping) - we need to defer the whole `keyof whatever` for later
14688
+ // since it's not safe to resolve the shape of modifier type
14689
+ return getIndexTypeForGenericType(type, stringsOnly);
14690
+ }
14691
+ }
14692
+ else {
14693
+ forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType);
14694
+ }
14695
+ if (isGenericIndexType(constraintType)) { // include the generic component in the resulting type
14696
+ forEachType(constraintType, addMemberForKeyType);
14697
+ }
14698
+ // we had to pick apart the constraintType to potentially map/filter it - compare the final resulting list with the original constraintType,
14699
+ // so we can return the union that preserves aliases/origin data if possible
14700
+ const result = noIndexSignatures ? filterType(getUnionType(keyTypes), t => !(t.flags & (TypeFlags.Any | TypeFlags.String))) : getUnionType(keyTypes);
14701
+ if (result.flags & TypeFlags.Union && constraintType.flags & TypeFlags.Union && getTypeListId((result as UnionType).types) === getTypeListId((constraintType as UnionType).types)){
14702
+ return constraintType;
14703
+ }
14704
+ return result;
14659
14705
14660
- function getIndexTypeForMappedType(type: MappedType, noIndexSignatures: boolean | undefined) {
14661
- const constraint = filterType(getConstraintTypeFromMappedType(type), t => !(noIndexSignatures && t.flags & (TypeFlags.Any | TypeFlags.String)));
14662
- const nameType = type.declaration.nameType && getTypeFromTypeNode(type.declaration.nameType);
14663
- // If the constraint is exclusively string/number/never type(s), we need to pull the property names from the modified type and run them through the `nameType` mapper as well
14664
- // since they won't appear in the constraint, due to subtype reducing with the string/number index types
14665
- const properties = nameType && everyType(constraint, t => !!(t.flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.Never))) && getPropertiesOfType(getApparentType(getModifiersTypeFromMappedType(type)));
14666
- return nameType ?
14667
- getUnionType([mapType(constraint, t => instantiateTypeAsMappedNameType(nameType, type, t)), mapType(getUnionType(map(properties || emptyArray, p => getLiteralTypeFromProperty(p, TypeFlags.StringOrNumberLiteralOrUnique))), t => instantiateTypeAsMappedNameType(nameType, type, t))]):
14668
- constraint;
14706
+ function addMemberForKeyType(keyType: Type) {
14707
+ const propNameType = nameType ? instantiateType(nameType, appendTypeMapping(type.mapper, typeParameter, keyType)) : keyType;
14708
+ // `keyof` currently always returns `string | number` for concrete `string` index signatures - the below ternary keeps that behavior for mapped types
14709
+ // See `getLiteralTypeFromProperties` where there's a similar ternary to cause the same behavior.
14710
+ keyTypes.push(propNameType === stringType ? stringOrNumberType : propNameType);
14711
+ }
14669
14712
}
14670
14713
14671
14714
// Ordinarily we reduce a keyof M, where M is a mapped type { [P in K as N<P>]: X }, to simply N<K>. This however presumes
@@ -14728,7 +14771,7 @@ namespace ts {
14728
14771
return type.flags & TypeFlags.Union ? getIntersectionType(map((type as UnionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
14729
14772
type.flags & TypeFlags.Intersection ? getUnionType(map((type as IntersectionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
14730
14773
type.flags & TypeFlags.InstantiableNonPrimitive || isGenericTupleType(type) || isGenericMappedType(type) && !hasDistributiveNameType(type) ? getIndexTypeForGenericType(type as InstantiableType | UnionOrIntersectionType, stringsOnly) :
14731
- getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(type as MappedType, noIndexSignatures) :
14774
+ getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(type as MappedType, stringsOnly, noIndexSignatures) :
14732
14775
type === wildcardType ? wildcardType :
14733
14776
type.flags & TypeFlags.Unknown ? neverType :
14734
14777
type.flags & (TypeFlags.Any | TypeFlags.Never) ? keyofConstraintType :
@@ -18793,6 +18836,35 @@ namespace ts {
18793
18836
return Ternary.True;
18794
18837
}
18795
18838
}
18839
+ else if (isGenericMappedType(targetType)) {
18840
+ // generic mapped types that don't simplify or have a constraint still have a very simple set of keys we can compare against
18841
+ // - their nameType or constraintType.
18842
+ // In many ways, this comparison is a deferred version of what `getIndexTypeForMappedType` does to actually resolve the keys for _non_-generic types
18843
+
18844
+ const nameType = getNameTypeFromMappedType(targetType);
18845
+ const constraintType = getConstraintTypeFromMappedType(targetType);
18846
+ let targetKeys;
18847
+ if (nameType && isMappedTypeWithKeyofConstraintDeclaration(targetType)) {
18848
+ // we need to get the apparent mappings and union them with the generic mappings, since some properties may be
18849
+ // missing from the `constraintType` which will otherwise be mapped in the object
18850
+ const modifiersType = getApparentType(getModifiersTypeFromMappedType(targetType));
18851
+ const mappedKeys: Type[] = [];
18852
+ forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(
18853
+ modifiersType,
18854
+ TypeFlags.StringOrNumberLiteralOrUnique,
18855
+ /*stringsOnly*/ false,
18856
+ t => void mappedKeys.push(instantiateType(nameType, appendTypeMapping(targetType.mapper, getTypeParameterFromMappedType(targetType), t)))
18857
+ );
18858
+ // We still need to include the non-apparent (and thus still generic) keys in the target side of the comparison (in case they're in the source side)
18859
+ targetKeys = getUnionType([...mappedKeys, nameType]);
18860
+ }
18861
+ else {
18862
+ targetKeys = nameType || constraintType;
18863
+ }
18864
+ if (isRelatedTo(source, targetKeys, reportErrors) === Ternary.True) {
18865
+ return Ternary.True;
18866
+ }
18867
+ }
18796
18868
}
18797
18869
}
18798
18870
else if (target.flags & TypeFlags.IndexedAccess) {
0 commit comments