Skip to content

Commit bc74ec4

Browse files
authored
When relating a deferred index type over a mapped type on the source side ... (#56742)
1 parent 3b1db10 commit bc74ec4

5 files changed

+128
-10
lines changed

src/compiler/checker.ts

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22228,6 +22228,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2222822228
return result;
2222922229
}
2223022230

22231+
function getApparentMappedTypeKeys(nameType: Type, targetType: MappedType) {
22232+
const modifiersType = getApparentType(getModifiersTypeFromMappedType(targetType));
22233+
const mappedKeys: Type[] = [];
22234+
forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(
22235+
modifiersType,
22236+
TypeFlags.StringOrNumberLiteralOrUnique,
22237+
/*stringsOnly*/ false,
22238+
t => void mappedKeys.push(instantiateType(nameType, appendTypeMapping(targetType.mapper, getTypeParameterFromMappedType(targetType), t))),
22239+
);
22240+
return getUnionType(mappedKeys);
22241+
}
22242+
2223122243
function structuredTypeRelatedToWorker(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState, saveErrorInfo: ReturnType<typeof captureErrorCalculationState>): Ternary {
2223222244
let result: Ternary;
2223322245
let originalErrorInfo: DiagnosticMessageChain | undefined;
@@ -22391,16 +22403,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2239122403
if (nameType && isMappedTypeWithKeyofConstraintDeclaration(targetType)) {
2239222404
// we need to get the apparent mappings and union them with the generic mappings, since some properties may be
2239322405
// missing from the `constraintType` which will otherwise be mapped in the object
22394-
const modifiersType = getApparentType(getModifiersTypeFromMappedType(targetType));
22395-
const mappedKeys: Type[] = [];
22396-
forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(
22397-
modifiersType,
22398-
TypeFlags.StringOrNumberLiteralOrUnique,
22399-
/*stringsOnly*/ false,
22400-
t => void mappedKeys.push(instantiateType(nameType, appendTypeMapping(targetType.mapper, getTypeParameterFromMappedType(targetType), t))),
22401-
);
22406+
const mappedKeys = getApparentMappedTypeKeys(nameType, targetType);
2240222407
// 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)
22403-
targetKeys = getUnionType([...mappedKeys, nameType]);
22408+
targetKeys = getUnionType([mappedKeys, nameType]);
2240422409
}
2240522410
else {
2240622411
targetKeys = nameType || constraintType;
@@ -22593,9 +22598,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2259322598
}
2259422599
}
2259522600
else if (sourceFlags & TypeFlags.Index) {
22596-
if (result = isRelatedTo(keyofConstraintType, target, RecursionFlags.Source, reportErrors)) {
22601+
const isDeferredMappedIndex = shouldDeferIndexType((source as IndexType).type, (source as IndexType).indexFlags) && getObjectFlags((source as IndexType).type) & ObjectFlags.Mapped;
22602+
if (result = isRelatedTo(keyofConstraintType, target, RecursionFlags.Source, reportErrors && !isDeferredMappedIndex)) {
2259722603
return result;
2259822604
}
22605+
if (isDeferredMappedIndex) {
22606+
const mappedType = (source as IndexType).type as MappedType;
22607+
const nameType = getNameTypeFromMappedType(mappedType);
22608+
// Unlike on the target side, on the source side we do *not* include the generic part of the `nameType`, since that comes from a
22609+
// (potentially anonymous) mapped type local type parameter, so that'd never assign outside the mapped type body, but we still want to
22610+
// allow assignments of index types of identical (or similar enough) mapped types.
22611+
// eg, `keyof {[X in keyof A]: Obj[X]}` should be assignable to `keyof {[Y in keyof A]: Tup[Y]}` because both map over the same set of keys (`keyof A`).
22612+
// Without this source-side breakdown, a `keyof {[X in keyof A]: Obj[X]}` style type won't be assignable to anything except itself, which is much too strict.
22613+
const sourceMappedKeys = nameType && isMappedTypeWithKeyofConstraintDeclaration(mappedType) ? getApparentMappedTypeKeys(nameType, mappedType) : (nameType || getConstraintTypeFromMappedType(mappedType));
22614+
if (result = isRelatedTo(sourceMappedKeys, target, RecursionFlags.Source, reportErrors)) {
22615+
return result;
22616+
}
22617+
}
2259922618
}
2260022619
else if (sourceFlags & TypeFlags.TemplateLiteral && !(targetFlags & TypeFlags.Object)) {
2260122620
if (!(targetFlags & TypeFlags.TemplateLiteral)) {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//// [tests/cases/compiler/declarationEmitNestedAnonymousMappedType.ts] ////
2+
3+
//// [declarationEmitNestedAnonymousMappedType.ts]
4+
export function enumFromStrings<const Members extends readonly string[]>() {
5+
type Part1 = {
6+
[key in keyof Members as Members[key] extends string
7+
? Members[key]
8+
: never]: Members[key];
9+
};
10+
type Part2 = { [Property in keyof Part1]: Part1[Property] };
11+
return Object.create(null) as Part2;
12+
}
13+
14+
15+
//// [declarationEmitNestedAnonymousMappedType.js]
16+
"use strict";
17+
Object.defineProperty(exports, "__esModule", { value: true });
18+
exports.enumFromStrings = void 0;
19+
function enumFromStrings() {
20+
return Object.create(null);
21+
}
22+
exports.enumFromStrings = enumFromStrings;
23+
24+
25+
//// [declarationEmitNestedAnonymousMappedType.d.ts]
26+
export declare function enumFromStrings<const Members extends readonly string[]>(): { [Property in keyof { [key in keyof Members as Members[key] extends string ? Members[key] : never]: Members[key]; }]: { [key in keyof Members as Members[key] extends string ? Members[key] : never]: Members[key]; }[Property]; };
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//// [tests/cases/compiler/declarationEmitNestedAnonymousMappedType.ts] ////
2+
3+
=== declarationEmitNestedAnonymousMappedType.ts ===
4+
export function enumFromStrings<const Members extends readonly string[]>() {
5+
>enumFromStrings : Symbol(enumFromStrings, Decl(declarationEmitNestedAnonymousMappedType.ts, 0, 0))
6+
>Members : Symbol(Members, Decl(declarationEmitNestedAnonymousMappedType.ts, 0, 32))
7+
8+
type Part1 = {
9+
>Part1 : Symbol(Part1, Decl(declarationEmitNestedAnonymousMappedType.ts, 0, 76))
10+
11+
[key in keyof Members as Members[key] extends string
12+
>key : Symbol(key, Decl(declarationEmitNestedAnonymousMappedType.ts, 2, 9))
13+
>Members : Symbol(Members, Decl(declarationEmitNestedAnonymousMappedType.ts, 0, 32))
14+
>Members : Symbol(Members, Decl(declarationEmitNestedAnonymousMappedType.ts, 0, 32))
15+
>key : Symbol(key, Decl(declarationEmitNestedAnonymousMappedType.ts, 2, 9))
16+
17+
? Members[key]
18+
>Members : Symbol(Members, Decl(declarationEmitNestedAnonymousMappedType.ts, 0, 32))
19+
>key : Symbol(key, Decl(declarationEmitNestedAnonymousMappedType.ts, 2, 9))
20+
21+
: never]: Members[key];
22+
>Members : Symbol(Members, Decl(declarationEmitNestedAnonymousMappedType.ts, 0, 32))
23+
>key : Symbol(key, Decl(declarationEmitNestedAnonymousMappedType.ts, 2, 9))
24+
25+
};
26+
type Part2 = { [Property in keyof Part1]: Part1[Property] };
27+
>Part2 : Symbol(Part2, Decl(declarationEmitNestedAnonymousMappedType.ts, 5, 6))
28+
>Property : Symbol(Property, Decl(declarationEmitNestedAnonymousMappedType.ts, 6, 20))
29+
>Part1 : Symbol(Part1, Decl(declarationEmitNestedAnonymousMappedType.ts, 0, 76))
30+
>Part1 : Symbol(Part1, Decl(declarationEmitNestedAnonymousMappedType.ts, 0, 76))
31+
>Property : Symbol(Property, Decl(declarationEmitNestedAnonymousMappedType.ts, 6, 20))
32+
33+
return Object.create(null) as Part2;
34+
>Object.create : Symbol(ObjectConstructor.create, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
35+
>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
36+
>create : Symbol(ObjectConstructor.create, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
37+
>Part2 : Symbol(Part2, Decl(declarationEmitNestedAnonymousMappedType.ts, 5, 6))
38+
}
39+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//// [tests/cases/compiler/declarationEmitNestedAnonymousMappedType.ts] ////
2+
3+
=== declarationEmitNestedAnonymousMappedType.ts ===
4+
export function enumFromStrings<const Members extends readonly string[]>() {
5+
>enumFromStrings : <const Members extends readonly string[]>() => { [Property in keyof { [key in keyof Members as Members[key] extends string ? Members[key] : never]: Members[key]; }]: { [key in keyof Members as Members[key] extends string ? Members[key] : never]: Members[key]; }[Property]; }
6+
7+
type Part1 = {
8+
>Part1 : { [key in keyof Members as Members[key] extends string ? Members[key] : never]: Members[key]; }
9+
10+
[key in keyof Members as Members[key] extends string
11+
? Members[key]
12+
: never]: Members[key];
13+
};
14+
type Part2 = { [Property in keyof Part1]: Part1[Property] };
15+
>Part2 : { [Property in keyof { [key in keyof Members as Members[key] extends string ? Members[key] : never]: Members[key]; }]: { [key in keyof Members as Members[key] extends string ? Members[key] : never]: Members[key]; }[Property]; }
16+
17+
return Object.create(null) as Part2;
18+
>Object.create(null) as Part2 : { [Property in keyof { [key in keyof Members as Members[key] extends string ? Members[key] : never]: Members[key]; }]: { [key in keyof Members as Members[key] extends string ? Members[key] : never]: Members[key]; }[Property]; }
19+
>Object.create(null) : any
20+
>Object.create : { (o: object): any; (o: object, properties: PropertyDescriptorMap & ThisType<any>): any; }
21+
>Object : ObjectConstructor
22+
>create : { (o: object): any; (o: object, properties: PropertyDescriptorMap & ThisType<any>): any; }
23+
}
24+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// @declaration: true
2+
export function enumFromStrings<const Members extends readonly string[]>() {
3+
type Part1 = {
4+
[key in keyof Members as Members[key] extends string
5+
? Members[key]
6+
: never]: Members[key];
7+
};
8+
type Part2 = { [Property in keyof Part1]: Part1[Property] };
9+
return Object.create(null) as Part2;
10+
}

0 commit comments

Comments
 (0)