Skip to content

Commit 9236e39

Browse files
authored
Fixed an issue with contextual type for intersection properties (microsoft#48668)
* Add a failing test case for contextual type not provided for functions comming from a property with intersection type * Fixed an issue with contextual type for intersection properties * Fixed how type of a property of a contextual type is being computed when intersections with indexers are used
1 parent 17431ea commit 9236e39

5 files changed

+256
-4
lines changed

src/compiler/checker.ts

+30-4
Original file line numberDiff line numberDiff line change
@@ -26819,16 +26819,37 @@ namespace ts {
2681926819
}
2682026820

2682126821
function getTypeOfPropertyOfContextualType(type: Type, name: __String, nameType?: Type) {
26822-
return mapType(type, t => {
26822+
return mapType(type, (t): Type | undefined => {
26823+
if (t.flags & TypeFlags.Intersection) {
26824+
const intersection = t as IntersectionType;
26825+
let newTypes = mapDefined(intersection.types, getTypeOfConcretePropertyOfContextualType);
26826+
if (newTypes.length > 0) {
26827+
return getIntersectionType(newTypes);
26828+
}
26829+
newTypes = mapDefined(intersection.types, getTypeOfApplicableIndexInfoOfContextualType);
26830+
if (newTypes.length > 0) {
26831+
return getIntersectionType(newTypes);
26832+
}
26833+
return undefined;
26834+
}
26835+
const concretePropertyType = getTypeOfConcretePropertyOfContextualType(t);
26836+
if (concretePropertyType) {
26837+
return concretePropertyType;
26838+
}
26839+
return getTypeOfApplicableIndexInfoOfContextualType(t);
26840+
}, /*noReductions*/ true);
26841+
26842+
function getTypeOfConcretePropertyOfContextualType(t: Type) {
2682326843
if (isGenericMappedType(t) && !t.declaration.nameType) {
2682426844
const constraint = getConstraintTypeFromMappedType(t);
2682526845
const constraintOfConstraint = getBaseConstraintOfType(constraint) || constraint;
2682626846
const propertyNameType = nameType || getStringLiteralType(unescapeLeadingUnderscores(name));
2682726847
if (isTypeAssignableTo(propertyNameType, constraintOfConstraint)) {
2682826848
return substituteIndexedMappedType(t, propertyNameType);
2682926849
}
26850+
return undefined;
2683026851
}
26831-
else if (t.flags & TypeFlags.StructuredType) {
26852+
if (t.flags & TypeFlags.StructuredType) {
2683226853
const prop = getPropertyOfType(t, name);
2683326854
if (prop) {
2683426855
return isCircularMappedProperty(prop) ? undefined : getTypeOfSymbol(prop);
@@ -26839,10 +26860,15 @@ namespace ts {
2683926860
return restType;
2684026861
}
2684126862
}
26842-
return findApplicableIndexInfo(getIndexInfosOfStructuredType(t), nameType || getStringLiteralType(unescapeLeadingUnderscores(name)))?.type;
2684326863
}
2684426864
return undefined;
26845-
}, /*noReductions*/ true);
26865+
}
26866+
function getTypeOfApplicableIndexInfoOfContextualType(t: Type) {
26867+
if (!(t.flags & TypeFlags.StructuredType)) {
26868+
return undefined;
26869+
}
26870+
return findApplicableIndexInfo(getIndexInfosOfStructuredType(t), nameType || getStringLiteralType(unescapeLeadingUnderscores(name)))?.type;
26871+
}
2684626872
}
2684726873

2684826874
// In an object literal contextually typed by a type T, the contextual type of a property assignment is the type of
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//// [contextualTypeFunctionObjectPropertyIntersection.ts]
2+
type Action<TEvent extends { type: string }> = (ev: TEvent) => void;
3+
4+
interface MachineConfig<TEvent extends { type: string }> {
5+
schema: {
6+
events: TEvent;
7+
};
8+
on?: {
9+
[K in TEvent["type"]]?: Action<TEvent extends { type: K } ? TEvent : never>;
10+
} & {
11+
"*"?: Action<TEvent>;
12+
};
13+
}
14+
15+
declare function createMachine<TEvent extends { type: string }>(
16+
config: MachineConfig<TEvent>
17+
): void;
18+
19+
createMachine({
20+
schema: {
21+
events: {} as { type: "FOO" } | { type: "BAR" },
22+
},
23+
on: {
24+
FOO: (ev) => {
25+
ev.type; // should be 'FOO'
26+
},
27+
},
28+
});
29+
30+
31+
//// [contextualTypeFunctionObjectPropertyIntersection.js]
32+
"use strict";
33+
createMachine({
34+
schema: {
35+
events: {}
36+
},
37+
on: {
38+
FOO: function (ev) {
39+
ev.type; // should be 'FOO'
40+
}
41+
}
42+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
=== tests/cases/compiler/contextualTypeFunctionObjectPropertyIntersection.ts ===
2+
type Action<TEvent extends { type: string }> = (ev: TEvent) => void;
3+
>Action : Symbol(Action, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 0, 0))
4+
>TEvent : Symbol(TEvent, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 0, 12))
5+
>type : Symbol(type, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 0, 28))
6+
>ev : Symbol(ev, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 0, 48))
7+
>TEvent : Symbol(TEvent, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 0, 12))
8+
9+
interface MachineConfig<TEvent extends { type: string }> {
10+
>MachineConfig : Symbol(MachineConfig, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 0, 68))
11+
>TEvent : Symbol(TEvent, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 2, 24))
12+
>type : Symbol(type, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 2, 40))
13+
14+
schema: {
15+
>schema : Symbol(MachineConfig.schema, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 2, 58))
16+
17+
events: TEvent;
18+
>events : Symbol(events, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 3, 11))
19+
>TEvent : Symbol(TEvent, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 2, 24))
20+
21+
};
22+
on?: {
23+
>on : Symbol(MachineConfig.on, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 5, 4))
24+
25+
[K in TEvent["type"]]?: Action<TEvent extends { type: K } ? TEvent : never>;
26+
>K : Symbol(K, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 7, 5))
27+
>TEvent : Symbol(TEvent, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 2, 24))
28+
>Action : Symbol(Action, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 0, 0))
29+
>TEvent : Symbol(TEvent, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 2, 24))
30+
>type : Symbol(type, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 7, 51))
31+
>K : Symbol(K, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 7, 5))
32+
>TEvent : Symbol(TEvent, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 2, 24))
33+
34+
} & {
35+
"*"?: Action<TEvent>;
36+
>"*" : Symbol("*", Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 8, 7))
37+
>Action : Symbol(Action, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 0, 0))
38+
>TEvent : Symbol(TEvent, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 2, 24))
39+
40+
};
41+
}
42+
43+
declare function createMachine<TEvent extends { type: string }>(
44+
>createMachine : Symbol(createMachine, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 11, 1))
45+
>TEvent : Symbol(TEvent, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 13, 31))
46+
>type : Symbol(type, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 13, 47))
47+
48+
config: MachineConfig<TEvent>
49+
>config : Symbol(config, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 13, 64))
50+
>MachineConfig : Symbol(MachineConfig, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 0, 68))
51+
>TEvent : Symbol(TEvent, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 13, 31))
52+
53+
): void;
54+
55+
createMachine({
56+
>createMachine : Symbol(createMachine, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 11, 1))
57+
58+
schema: {
59+
>schema : Symbol(schema, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 17, 15))
60+
61+
events: {} as { type: "FOO" } | { type: "BAR" },
62+
>events : Symbol(events, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 18, 11))
63+
>type : Symbol(type, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 19, 19))
64+
>type : Symbol(type, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 19, 37))
65+
66+
},
67+
on: {
68+
>on : Symbol(on, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 20, 4))
69+
70+
FOO: (ev) => {
71+
>FOO : Symbol(FOO, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 21, 7))
72+
>ev : Symbol(ev, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 22, 10))
73+
74+
ev.type; // should be 'FOO'
75+
>ev.type : Symbol(type, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 19, 19))
76+
>ev : Symbol(ev, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 22, 10))
77+
>type : Symbol(type, Decl(contextualTypeFunctionObjectPropertyIntersection.ts, 19, 19))
78+
79+
},
80+
},
81+
});
82+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
=== tests/cases/compiler/contextualTypeFunctionObjectPropertyIntersection.ts ===
2+
type Action<TEvent extends { type: string }> = (ev: TEvent) => void;
3+
>Action : Action<TEvent>
4+
>type : string
5+
>ev : TEvent
6+
7+
interface MachineConfig<TEvent extends { type: string }> {
8+
>type : string
9+
10+
schema: {
11+
>schema : { events: TEvent; }
12+
13+
events: TEvent;
14+
>events : TEvent
15+
16+
};
17+
on?: {
18+
>on : ({ [K in TEvent["type"]]?: Action<TEvent extends { type: K; } ? TEvent : never> | undefined; } & { "*"?: Action<TEvent> | undefined; }) | undefined
19+
20+
[K in TEvent["type"]]?: Action<TEvent extends { type: K } ? TEvent : never>;
21+
>type : K
22+
23+
} & {
24+
"*"?: Action<TEvent>;
25+
>"*" : Action<TEvent> | undefined
26+
27+
};
28+
}
29+
30+
declare function createMachine<TEvent extends { type: string }>(
31+
>createMachine : <TEvent extends { type: string; }>(config: MachineConfig<TEvent>) => void
32+
>type : string
33+
34+
config: MachineConfig<TEvent>
35+
>config : MachineConfig<TEvent>
36+
37+
): void;
38+
39+
createMachine({
40+
>createMachine({ schema: { events: {} as { type: "FOO" } | { type: "BAR" }, }, on: { FOO: (ev) => { ev.type; // should be 'FOO' }, },}) : void
41+
>createMachine : <TEvent extends { type: string; }>(config: MachineConfig<TEvent>) => void
42+
>{ schema: { events: {} as { type: "FOO" } | { type: "BAR" }, }, on: { FOO: (ev) => { ev.type; // should be 'FOO' }, },} : { schema: { events: { type: "FOO"; } | { type: "BAR"; }; }; on: { FOO: (ev: { type: "FOO"; }) => void; }; }
43+
44+
schema: {
45+
>schema : { events: { type: "FOO"; } | { type: "BAR"; }; }
46+
>{ events: {} as { type: "FOO" } | { type: "BAR" }, } : { events: { type: "FOO"; } | { type: "BAR"; }; }
47+
48+
events: {} as { type: "FOO" } | { type: "BAR" },
49+
>events : { type: "FOO"; } | { type: "BAR"; }
50+
>{} as { type: "FOO" } | { type: "BAR" } : { type: "FOO"; } | { type: "BAR"; }
51+
>{} : {}
52+
>type : "FOO"
53+
>type : "BAR"
54+
55+
},
56+
on: {
57+
>on : { FOO: (ev: { type: "FOO"; }) => void; }
58+
>{ FOO: (ev) => { ev.type; // should be 'FOO' }, } : { FOO: (ev: { type: "FOO"; }) => void; }
59+
60+
FOO: (ev) => {
61+
>FOO : (ev: { type: "FOO"; }) => void
62+
>(ev) => { ev.type; // should be 'FOO' } : (ev: { type: "FOO"; }) => void
63+
>ev : { type: "FOO"; }
64+
65+
ev.type; // should be 'FOO'
66+
>ev.type : "FOO"
67+
>ev : { type: "FOO"; }
68+
>type : "FOO"
69+
70+
},
71+
},
72+
});
73+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// @strict: true
2+
3+
type Action<TEvent extends { type: string }> = (ev: TEvent) => void;
4+
5+
interface MachineConfig<TEvent extends { type: string }> {
6+
schema: {
7+
events: TEvent;
8+
};
9+
on?: {
10+
[K in TEvent["type"]]?: Action<TEvent extends { type: K } ? TEvent : never>;
11+
} & {
12+
"*"?: Action<TEvent>;
13+
};
14+
}
15+
16+
declare function createMachine<TEvent extends { type: string }>(
17+
config: MachineConfig<TEvent>
18+
): void;
19+
20+
createMachine({
21+
schema: {
22+
events: {} as { type: "FOO" } | { type: "BAR" },
23+
},
24+
on: {
25+
FOO: (ev) => {
26+
ev.type; // should be 'FOO'
27+
},
28+
},
29+
});

0 commit comments

Comments
 (0)