Skip to content

Commit 3c1661f

Browse files
committed
Fixed an issue with a concrete object not being assignable to a generic mapped type with a partially concrete constraint
1 parent 6ee5490 commit 3c1661f

4 files changed

+310
-2
lines changed

src/compiler/checker.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19519,8 +19519,17 @@ namespace ts {
1951919519
? getIntersectionType([filteredByApplicability, typeParameter])
1952019520
: typeParameter;
1952119521
const indexedAccessType = getIndexedAccessType(source, indexingType);
19522-
// Compare `S[indexingType]` to `T`, where `T` is the type of a property of the target type.
19523-
if (result = isRelatedTo(indexedAccessType, templateType, RecursionFlags.Both, reportErrors)) {
19522+
19523+
if (filteredByApplicability && isTypeAssignableTo(filteredByApplicability, target.constraintType!)) {
19524+
const mapper = appendTypeMapping(target.mapper, getTypeParameterFromMappedType(target), filteredByApplicability)
19525+
const propType = instantiateType(templateType, mapper);
19526+
if (result = isRelatedTo(indexedAccessType, propType)) {
19527+
return result;
19528+
}
19529+
} else if (
19530+
// Compare `S[indexingType]` to `T`, where `T` is the type of a property of the target type.
19531+
result = isRelatedTo(indexedAccessType, templateType, RecursionFlags.Both, reportErrors)
19532+
) {
1952419533
return result;
1952519534
}
1952619535
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
=== tests/cases/compiler/mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts ===
2+
type ExtractEvent<
3+
>ExtractEvent : Symbol(ExtractEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 0, 0))
4+
5+
TEvent extends { type: string },
6+
>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 0, 18))
7+
>type : Symbol(type, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 1, 18))
8+
9+
TEventType extends TEvent["type"]
10+
>TEventType : Symbol(TEventType, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 1, 34))
11+
>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 0, 18))
12+
13+
> = TEvent extends {
14+
>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 0, 18))
15+
16+
type: TEventType;
17+
>type : Symbol(type, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 3, 20))
18+
>TEventType : Symbol(TEventType, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 1, 34))
19+
}
20+
? TEvent
21+
>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 0, 18))
22+
23+
: never;
24+
25+
type TransitionConfig<TContext, TEvent extends { type: string }> = {
26+
>TransitionConfig : Symbol(TransitionConfig, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 7, 10))
27+
>TContext : Symbol(TContext, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 9, 22))
28+
>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 9, 31))
29+
>type : Symbol(type, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 9, 48))
30+
31+
actions?: {
32+
>actions : Symbol(actions, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 9, 68))
33+
34+
type: string;
35+
>type : Symbol(type, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 10, 13))
36+
37+
};
38+
};
39+
40+
type IntersectedTransitionConfigMap<TContext, TEvent extends { type: string }> = {
41+
>IntersectedTransitionConfigMap : Symbol(IntersectedTransitionConfigMap, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 13, 2))
42+
>TContext : Symbol(TContext, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 15, 36))
43+
>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 15, 45))
44+
>type : Symbol(type, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 15, 62))
45+
46+
[K in TEvent["type"]]?: TransitionConfig<TContext, ExtractEvent<TEvent, K>>;
47+
>K : Symbol(K, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 16, 3))
48+
>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 15, 45))
49+
>TransitionConfig : Symbol(TransitionConfig, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 7, 10))
50+
>TContext : Symbol(TContext, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 15, 36))
51+
>ExtractEvent : Symbol(ExtractEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 0, 0))
52+
>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 15, 45))
53+
>K : Symbol(K, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 16, 3))
54+
55+
} & {
56+
"*": TransitionConfig<TContext, TEvent>;
57+
>"*" : Symbol("*", Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 17, 5))
58+
>TransitionConfig : Symbol(TransitionConfig, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 7, 10))
59+
>TContext : Symbol(TContext, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 15, 36))
60+
>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 15, 45))
61+
62+
};
63+
64+
type TransitionConfigMap<TContext, TEvent extends { type: string }> = {
65+
>TransitionConfigMap : Symbol(TransitionConfigMap, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 19, 2))
66+
>TContext : Symbol(TContext, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 21, 25))
67+
>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 21, 34))
68+
>type : Symbol(type, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 21, 51))
69+
70+
[K in TEvent["type"] | "*"]?: K extends "*"
71+
>K : Symbol(K, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 22, 3))
72+
>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 21, 34))
73+
>K : Symbol(K, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 22, 3))
74+
75+
? TransitionConfig<TContext, TEvent>
76+
>TransitionConfig : Symbol(TransitionConfig, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 7, 10))
77+
>TContext : Symbol(TContext, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 21, 25))
78+
>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 21, 34))
79+
80+
: TransitionConfig<TContext, ExtractEvent<TEvent, K>>;
81+
>TransitionConfig : Symbol(TransitionConfig, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 7, 10))
82+
>TContext : Symbol(TContext, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 21, 25))
83+
>ExtractEvent : Symbol(ExtractEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 0, 0))
84+
>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 21, 34))
85+
>K : Symbol(K, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 22, 3))
86+
87+
};
88+
89+
export function genericFn2<TEvent extends { type: string }>() {
90+
>genericFn2 : Symbol(genericFn2, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 25, 2))
91+
>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 27, 27))
92+
>type : Symbol(type, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 27, 43))
93+
94+
const wildcardTransitionConfig = {
95+
>wildcardTransitionConfig : Symbol(wildcardTransitionConfig, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 28, 7))
96+
97+
"*": { actions: { type: "someAction" } },
98+
>"*" : Symbol("*", Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 28, 36))
99+
>actions : Symbol(actions, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 29, 10))
100+
>type : Symbol(type, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 29, 21))
101+
102+
} as const;
103+
>const : Symbol(const)
104+
105+
// this should be assignable, in the same way as the following assignment is OK
106+
let test: TransitionConfigMap<
107+
>test : Symbol(test, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 33, 5))
108+
>TransitionConfigMap : Symbol(TransitionConfigMap, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 19, 2))
109+
110+
{ counter: number },
111+
>counter : Symbol(counter, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 34, 5))
112+
113+
{ type: TEvent["type"] }
114+
>type : Symbol(type, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 35, 5))
115+
>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 27, 27))
116+
117+
> = {} as typeof wildcardTransitionConfig;
118+
>wildcardTransitionConfig : Symbol(wildcardTransitionConfig, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 28, 7))
119+
120+
// concrete prop is assignable to the concrete prop of this mapped type
121+
test["*"] = {} as typeof wildcardTransitionConfig["*"];
122+
>test : Symbol(test, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 33, 5))
123+
>"*" : Symbol("*")
124+
>wildcardTransitionConfig : Symbol(wildcardTransitionConfig, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 28, 7))
125+
126+
// similar intersected type accepts this concrete object
127+
let test2: IntersectedTransitionConfigMap<
128+
>test2 : Symbol(test2, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 42, 5))
129+
>IntersectedTransitionConfigMap : Symbol(IntersectedTransitionConfigMap, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 13, 2))
130+
131+
{ counter: number },
132+
>counter : Symbol(counter, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 43, 5))
133+
134+
{ type: TEvent["type"] }
135+
>type : Symbol(type, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 44, 5))
136+
>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 27, 27))
137+
138+
> = {} as typeof wildcardTransitionConfig;
139+
>wildcardTransitionConfig : Symbol(wildcardTransitionConfig, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 28, 7))
140+
}
141+
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
=== tests/cases/compiler/mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts ===
2+
type ExtractEvent<
3+
>ExtractEvent : ExtractEvent<TEvent, TEventType>
4+
5+
TEvent extends { type: string },
6+
>type : string
7+
8+
TEventType extends TEvent["type"]
9+
> = TEvent extends {
10+
type: TEventType;
11+
>type : TEventType
12+
}
13+
? TEvent
14+
: never;
15+
16+
type TransitionConfig<TContext, TEvent extends { type: string }> = {
17+
>TransitionConfig : TransitionConfig<TContext, TEvent>
18+
>type : string
19+
20+
actions?: {
21+
>actions : { type: string; } | undefined
22+
23+
type: string;
24+
>type : string
25+
26+
};
27+
};
28+
29+
type IntersectedTransitionConfigMap<TContext, TEvent extends { type: string }> = {
30+
>IntersectedTransitionConfigMap : IntersectedTransitionConfigMap<TContext, TEvent>
31+
>type : string
32+
33+
[K in TEvent["type"]]?: TransitionConfig<TContext, ExtractEvent<TEvent, K>>;
34+
} & {
35+
"*": TransitionConfig<TContext, TEvent>;
36+
>"*" : TransitionConfig<TContext, TEvent>
37+
38+
};
39+
40+
type TransitionConfigMap<TContext, TEvent extends { type: string }> = {
41+
>TransitionConfigMap : TransitionConfigMap<TContext, TEvent>
42+
>type : string
43+
44+
[K in TEvent["type"] | "*"]?: K extends "*"
45+
? TransitionConfig<TContext, TEvent>
46+
: TransitionConfig<TContext, ExtractEvent<TEvent, K>>;
47+
};
48+
49+
export function genericFn2<TEvent extends { type: string }>() {
50+
>genericFn2 : <TEvent extends { type: string; }>() => void
51+
>type : string
52+
53+
const wildcardTransitionConfig = {
54+
>wildcardTransitionConfig : { readonly "*": { readonly actions: { readonly type: "someAction"; }; }; }
55+
>{ "*": { actions: { type: "someAction" } }, } as const : { readonly "*": { readonly actions: { readonly type: "someAction"; }; }; }
56+
>{ "*": { actions: { type: "someAction" } }, } : { readonly "*": { readonly actions: { readonly type: "someAction"; }; }; }
57+
58+
"*": { actions: { type: "someAction" } },
59+
>"*" : { readonly actions: { readonly type: "someAction"; }; }
60+
>{ actions: { type: "someAction" } } : { readonly actions: { readonly type: "someAction"; }; }
61+
>actions : { readonly type: "someAction"; }
62+
>{ type: "someAction" } : { readonly type: "someAction"; }
63+
>type : "someAction"
64+
>"someAction" : "someAction"
65+
66+
} as const;
67+
68+
// this should be assignable, in the same way as the following assignment is OK
69+
let test: TransitionConfigMap<
70+
>test : TransitionConfigMap<{ counter: number; }, { type: TEvent["type"]; }>
71+
72+
{ counter: number },
73+
>counter : number
74+
75+
{ type: TEvent["type"] }
76+
>type : TEvent["type"]
77+
78+
> = {} as typeof wildcardTransitionConfig;
79+
>{} as typeof wildcardTransitionConfig : { readonly "*": { readonly actions: { readonly type: "someAction"; }; }; }
80+
>{} : {}
81+
>wildcardTransitionConfig : { readonly "*": { readonly actions: { readonly type: "someAction"; }; }; }
82+
83+
// concrete prop is assignable to the concrete prop of this mapped type
84+
test["*"] = {} as typeof wildcardTransitionConfig["*"];
85+
>test["*"] = {} as typeof wildcardTransitionConfig["*"] : { readonly actions: { readonly type: "someAction"; }; }
86+
>test["*"] : TransitionConfig<{ counter: number; }, { type: TEvent["type"]; }> | undefined
87+
>test : TransitionConfigMap<{ counter: number; }, { type: TEvent["type"]; }>
88+
>"*" : "*"
89+
>{} as typeof wildcardTransitionConfig["*"] : { readonly actions: { readonly type: "someAction"; }; }
90+
>{} : {}
91+
>wildcardTransitionConfig : { readonly "*": { readonly actions: { readonly type: "someAction"; }; }; }
92+
93+
// similar intersected type accepts this concrete object
94+
let test2: IntersectedTransitionConfigMap<
95+
>test2 : IntersectedTransitionConfigMap<{ counter: number; }, { type: TEvent["type"]; }>
96+
97+
{ counter: number },
98+
>counter : number
99+
100+
{ type: TEvent["type"] }
101+
>type : TEvent["type"]
102+
103+
> = {} as typeof wildcardTransitionConfig;
104+
>{} as typeof wildcardTransitionConfig : { readonly "*": { readonly actions: { readonly type: "someAction"; }; }; }
105+
>{} : {}
106+
>wildcardTransitionConfig : { readonly "*": { readonly actions: { readonly type: "someAction"; }; }; }
107+
}
108+
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// @strict: true
2+
// @noEmit: true
3+
4+
type ExtractEvent<
5+
TEvent extends { type: string },
6+
TEventType extends TEvent["type"]
7+
> = TEvent extends {
8+
type: TEventType;
9+
}
10+
? TEvent
11+
: never;
12+
13+
type TransitionConfig<TContext, TEvent extends { type: string }> = {
14+
actions?: {
15+
type: string;
16+
};
17+
};
18+
19+
type IntersectedTransitionConfigMap<TContext, TEvent extends { type: string }> = {
20+
[K in TEvent["type"]]?: TransitionConfig<TContext, ExtractEvent<TEvent, K>>;
21+
} & {
22+
"*": TransitionConfig<TContext, TEvent>;
23+
};
24+
25+
type TransitionConfigMap<TContext, TEvent extends { type: string }> = {
26+
[K in TEvent["type"] | "*"]?: K extends "*"
27+
? TransitionConfig<TContext, TEvent>
28+
: TransitionConfig<TContext, ExtractEvent<TEvent, K>>;
29+
};
30+
31+
export function genericFn<TEvent extends { type: string }>() {
32+
const wildcardTransitionConfig = {
33+
"*": { actions: { type: "someAction" } },
34+
} as const;
35+
36+
// this should be assignable, in the same way as the following assignment is OK
37+
let test: TransitionConfigMap<
38+
{ counter: number },
39+
{ type: TEvent["type"] }
40+
> = {} as typeof wildcardTransitionConfig;
41+
42+
// concrete prop is assignable to the concrete prop of this mapped type
43+
test["*"] = {} as typeof wildcardTransitionConfig["*"];
44+
45+
// similar intersected type accepts this concrete object
46+
let test2: IntersectedTransitionConfigMap<
47+
{ counter: number },
48+
{ type: TEvent["type"] }
49+
> = {} as typeof wildcardTransitionConfig;
50+
}

0 commit comments

Comments
 (0)