@@ -11265,6 +11265,22 @@ namespace ts {
1126511265 return getCheckFlags(s) & CheckFlags.Late;
1126611266 }
1126711267
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+
1126811284 /** Resolve the members of a mapped type { [P in K]: T } */
1126911285 function resolveMappedTypeMembers(type: MappedType) {
1127011286 const members: SymbolTable = createSymbolTable();
@@ -11282,19 +11298,7 @@ namespace ts {
1128211298 const include = keyofStringsOnly ? TypeFlags.StringLiteral : TypeFlags.StringOrNumberLiteralOrUnique;
1128311299 if (isMappedTypeWithKeyofConstraintDeclaration(type)) {
1128411300 // 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);
1129811302 }
1129911303 else {
1130011304 forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType);
@@ -14653,19 +14657,58 @@ namespace ts {
1465314657 type.resolvedIndexType || (type.resolvedIndexType = createIndexType(type, /*stringsOnly*/ false));
1465414658 }
1465514659
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;
1465914705
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+ }
1466914712 }
1467014713
1467114714 // 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 {
1472814771 return type.flags & TypeFlags.Union ? getIntersectionType(map((type as UnionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
1472914772 type.flags & TypeFlags.Intersection ? getUnionType(map((type as IntersectionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
1473014773 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) :
1473214775 type === wildcardType ? wildcardType :
1473314776 type.flags & TypeFlags.Unknown ? neverType :
1473414777 type.flags & (TypeFlags.Any | TypeFlags.Never) ? keyofConstraintType :
@@ -18793,6 +18836,35 @@ namespace ts {
1879318836 return Ternary.True;
1879418837 }
1879518838 }
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+ }
1879618868 }
1879718869 }
1879818870 else if (target.flags & TypeFlags.IndexedAccess) {
0 commit comments