Skip to content

Commit a91c287

Browse files
weswighamOrta
andauthored
Allow discrimination to identical object types when discriminating contextual types (#40574)
* Merge identical object types when discriminating contextual types Co-authored-by: Orta <[email protected]> * Allow identical discriminants when discriminating, rather than trying to unify identical union members * Fix lint Co-authored-by: Orta <[email protected]>
1 parent ad2a074 commit a91c287

File tree

6 files changed

+159
-2
lines changed

6 files changed

+159
-2
lines changed

src/compiler/checker.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18348,8 +18348,18 @@ namespace ts {
1834818348
}
1834918349
}
1835018350
const match = discriminable.indexOf(/*searchElement*/ true);
18351+
if (match === -1) {
18352+
return defaultValue;
18353+
}
1835118354
// make sure exactly 1 matches before returning it
18352-
return match === -1 || discriminable.indexOf(/*searchElement*/ true, match + 1) !== -1 ? defaultValue : target.types[match];
18355+
let nextMatch = discriminable.indexOf(/*searchElement*/ true, match + 1);
18356+
while (nextMatch !== -1) {
18357+
if (!isTypeIdenticalTo(target.types[match], target.types[nextMatch])) {
18358+
return defaultValue;
18359+
}
18360+
nextMatch = discriminable.indexOf(/*searchElement*/ true, nextMatch + 1);
18361+
}
18362+
return target.types[match];
1835318363
}
1835418364

1835518365
/**

src/compiler/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4170,7 +4170,7 @@ namespace ts {
41704170
export const enum UnionReduction {
41714171
None = 0,
41724172
Literal,
4173-
Subtype
4173+
Subtype,
41744174
}
41754175

41764176
/* @internal */
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//// [inferenceUnionOfObjectsMappedContextualType.ts]
2+
type Entity = {
3+
someDate: Date | null;
4+
} & ({ id: string; } | { id: number; })
5+
6+
type RowRendererMeta<TInput extends {}> = {
7+
[key in keyof TInput]: { key: key; caption: string; formatter?: (value: TInput[key]) => string; };
8+
}
9+
10+
type RowRenderer<TInput extends {}> = RowRendererMeta<TInput>[keyof RowRendererMeta<TInput>];
11+
12+
const test: RowRenderer<Entity> = {
13+
key: 'someDate',
14+
caption: 'My Date',
15+
formatter: (value) => value ? value.toString() : '-' // value: any
16+
}
17+
18+
19+
//// [inferenceUnionOfObjectsMappedContextualType.js]
20+
"use strict";
21+
var test = {
22+
key: 'someDate',
23+
caption: 'My Date',
24+
formatter: function (value) { return value ? value.toString() : '-'; } // value: any
25+
};
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
=== tests/cases/compiler/inferenceUnionOfObjectsMappedContextualType.ts ===
2+
type Entity = {
3+
>Entity : Symbol(Entity, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 0, 0))
4+
5+
someDate: Date | null;
6+
>someDate : Symbol(someDate, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 0, 15))
7+
>Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --))
8+
9+
} & ({ id: string; } | { id: number; })
10+
>id : Symbol(id, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 2, 6))
11+
>id : Symbol(id, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 2, 24))
12+
13+
type RowRendererMeta<TInput extends {}> = {
14+
>RowRendererMeta : Symbol(RowRendererMeta, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 2, 39))
15+
>TInput : Symbol(TInput, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 4, 21))
16+
17+
[key in keyof TInput]: { key: key; caption: string; formatter?: (value: TInput[key]) => string; };
18+
>key : Symbol(key, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 5, 5))
19+
>TInput : Symbol(TInput, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 4, 21))
20+
>key : Symbol(key, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 5, 28))
21+
>key : Symbol(key, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 5, 5))
22+
>caption : Symbol(caption, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 5, 38))
23+
>formatter : Symbol(formatter, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 5, 55))
24+
>value : Symbol(value, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 5, 69))
25+
>TInput : Symbol(TInput, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 4, 21))
26+
>key : Symbol(key, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 5, 5))
27+
}
28+
29+
type RowRenderer<TInput extends {}> = RowRendererMeta<TInput>[keyof RowRendererMeta<TInput>];
30+
>RowRenderer : Symbol(RowRenderer, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 6, 1))
31+
>TInput : Symbol(TInput, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 8, 17))
32+
>RowRendererMeta : Symbol(RowRendererMeta, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 2, 39))
33+
>TInput : Symbol(TInput, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 8, 17))
34+
>RowRendererMeta : Symbol(RowRendererMeta, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 2, 39))
35+
>TInput : Symbol(TInput, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 8, 17))
36+
37+
const test: RowRenderer<Entity> = {
38+
>test : Symbol(test, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 10, 5))
39+
>RowRenderer : Symbol(RowRenderer, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 6, 1))
40+
>Entity : Symbol(Entity, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 0, 0))
41+
42+
key: 'someDate',
43+
>key : Symbol(key, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 10, 35))
44+
45+
caption: 'My Date',
46+
>caption : Symbol(caption, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 11, 20))
47+
48+
formatter: (value) => value ? value.toString() : '-' // value: any
49+
>formatter : Symbol(formatter, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 12, 23))
50+
>value : Symbol(value, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 13, 16))
51+
>value : Symbol(value, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 13, 16))
52+
>value.toString : Symbol(Date.toString, Decl(lib.es5.d.ts, --, --))
53+
>value : Symbol(value, Decl(inferenceUnionOfObjectsMappedContextualType.ts, 13, 16))
54+
>toString : Symbol(Date.toString, Decl(lib.es5.d.ts, --, --))
55+
}
56+
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
=== tests/cases/compiler/inferenceUnionOfObjectsMappedContextualType.ts ===
2+
type Entity = {
3+
>Entity : Entity
4+
5+
someDate: Date | null;
6+
>someDate : Date | null
7+
>null : null
8+
9+
} & ({ id: string; } | { id: number; })
10+
>id : string
11+
>id : number
12+
13+
type RowRendererMeta<TInput extends {}> = {
14+
>RowRendererMeta : RowRendererMeta<TInput>
15+
16+
[key in keyof TInput]: { key: key; caption: string; formatter?: (value: TInput[key]) => string; };
17+
>key : key
18+
>caption : string
19+
>formatter : ((value: TInput[key]) => string) | undefined
20+
>value : TInput[key]
21+
}
22+
23+
type RowRenderer<TInput extends {}> = RowRendererMeta<TInput>[keyof RowRendererMeta<TInput>];
24+
>RowRenderer : RowRenderer<TInput>
25+
26+
const test: RowRenderer<Entity> = {
27+
>test : RowRenderer<Entity>
28+
>{ key: 'someDate', caption: 'My Date', formatter: (value) => value ? value.toString() : '-' // value: any} : { key: "someDate"; caption: string; formatter: (value: Date | null) => string; }
29+
30+
key: 'someDate',
31+
>key : "someDate"
32+
>'someDate' : "someDate"
33+
34+
caption: 'My Date',
35+
>caption : string
36+
>'My Date' : "My Date"
37+
38+
formatter: (value) => value ? value.toString() : '-' // value: any
39+
>formatter : (value: Date | null) => string
40+
>(value) => value ? value.toString() : '-' : (value: Date | null) => string
41+
>value : Date | null
42+
>value ? value.toString() : '-' : string
43+
>value : Date | null
44+
>value.toString() : string
45+
>value.toString : () => string
46+
>value : Date
47+
>toString : () => string
48+
>'-' : "-"
49+
}
50+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// @strict: true
2+
type Entity = {
3+
someDate: Date | null;
4+
} & ({ id: string; } | { id: number; })
5+
6+
type RowRendererMeta<TInput extends {}> = {
7+
[key in keyof TInput]: { key: key; caption: string; formatter?: (value: TInput[key]) => string; };
8+
}
9+
10+
type RowRenderer<TInput extends {}> = RowRendererMeta<TInput>[keyof RowRendererMeta<TInput>];
11+
12+
const test: RowRenderer<Entity> = {
13+
key: 'someDate',
14+
caption: 'My Date',
15+
formatter: (value) => value ? value.toString() : '-' // value: any
16+
}

0 commit comments

Comments
 (0)