Skip to content

Commit 1435fb1

Browse files
authored
Merge pull request #10069 from Microsoft/bestChoiceType
Use "best choice type" for || and ?: operators
2 parents 36b6113 + c7d2e59 commit 1435fb1

9 files changed

+223
-10
lines changed

src/compiler/checker.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12903,6 +12903,14 @@ namespace ts {
1290312903
return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target);
1290412904
}
1290512905

12906+
function getBestChoiceType(type1: Type, type2: Type): Type {
12907+
const firstAssignableToSecond = isTypeAssignableTo(type1, type2);
12908+
const secondAssignableToFirst = isTypeAssignableTo(type2, type1);
12909+
return secondAssignableToFirst && !firstAssignableToSecond ? type1 :
12910+
firstAssignableToSecond && !secondAssignableToFirst ? type2 :
12911+
getUnionType([type1, type2], /*subtypeReduction*/ true);
12912+
}
12913+
1290612914
function checkBinaryExpression(node: BinaryExpression, contextualMapper?: TypeMapper) {
1290712915
return checkBinaryLikeExpression(node.left, node.operatorToken, node.right, contextualMapper, node);
1290812916
}
@@ -13046,7 +13054,7 @@ namespace ts {
1304613054
leftType;
1304713055
case SyntaxKind.BarBarToken:
1304813056
return getTypeFacts(leftType) & TypeFacts.Falsy ?
13049-
getUnionType([removeDefinitelyFalsyTypes(leftType), rightType], /*subtypeReduction*/ true) :
13057+
getBestChoiceType(removeDefinitelyFalsyTypes(leftType), rightType) :
1305013058
leftType;
1305113059
case SyntaxKind.EqualsToken:
1305213060
checkAssignmentOperator(rightType);
@@ -13173,7 +13181,7 @@ namespace ts {
1317313181
checkExpression(node.condition);
1317413182
const type1 = checkExpression(node.whenTrue, contextualMapper);
1317513183
const type2 = checkExpression(node.whenFalse, contextualMapper);
13176-
return getUnionType([type1, type2], /*subtypeReduction*/ true);
13184+
return getBestChoiceType(type1, type2);
1317713185
}
1317813186

1317913187
function typeContainsLiteralFromEnum(type: Type, enumType: EnumType) {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//// [bestChoiceType.ts]
2+
3+
// Repro from #10041
4+
5+
(''.match(/ /) || []).map(s => s.toLowerCase());
6+
7+
// Similar cases
8+
9+
function f1() {
10+
let x = ''.match(/ /);
11+
let y = x || [];
12+
let z = y.map(s => s.toLowerCase());
13+
}
14+
15+
function f2() {
16+
let x = ''.match(/ /);
17+
let y = x ? x : [];
18+
let z = y.map(s => s.toLowerCase());
19+
}
20+
21+
22+
//// [bestChoiceType.js]
23+
// Repro from #10041
24+
(''.match(/ /) || []).map(function (s) { return s.toLowerCase(); });
25+
// Similar cases
26+
function f1() {
27+
var x = ''.match(/ /);
28+
var y = x || [];
29+
var z = y.map(function (s) { return s.toLowerCase(); });
30+
}
31+
function f2() {
32+
var x = ''.match(/ /);
33+
var y = x ? x : [];
34+
var z = y.map(function (s) { return s.toLowerCase(); });
35+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
=== tests/cases/compiler/bestChoiceType.ts ===
2+
3+
// Repro from #10041
4+
5+
(''.match(/ /) || []).map(s => s.toLowerCase());
6+
>(''.match(/ /) || []).map : Symbol(Array.map, Decl(lib.d.ts, --, --))
7+
>''.match : Symbol(String.match, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
8+
>match : Symbol(String.match, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
9+
>map : Symbol(Array.map, Decl(lib.d.ts, --, --))
10+
>s : Symbol(s, Decl(bestChoiceType.ts, 3, 26))
11+
>s.toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, --, --))
12+
>s : Symbol(s, Decl(bestChoiceType.ts, 3, 26))
13+
>toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, --, --))
14+
15+
// Similar cases
16+
17+
function f1() {
18+
>f1 : Symbol(f1, Decl(bestChoiceType.ts, 3, 48))
19+
20+
let x = ''.match(/ /);
21+
>x : Symbol(x, Decl(bestChoiceType.ts, 8, 7))
22+
>''.match : Symbol(String.match, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
23+
>match : Symbol(String.match, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
24+
25+
let y = x || [];
26+
>y : Symbol(y, Decl(bestChoiceType.ts, 9, 7))
27+
>x : Symbol(x, Decl(bestChoiceType.ts, 8, 7))
28+
29+
let z = y.map(s => s.toLowerCase());
30+
>z : Symbol(z, Decl(bestChoiceType.ts, 10, 7))
31+
>y.map : Symbol(Array.map, Decl(lib.d.ts, --, --))
32+
>y : Symbol(y, Decl(bestChoiceType.ts, 9, 7))
33+
>map : Symbol(Array.map, Decl(lib.d.ts, --, --))
34+
>s : Symbol(s, Decl(bestChoiceType.ts, 10, 18))
35+
>s.toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, --, --))
36+
>s : Symbol(s, Decl(bestChoiceType.ts, 10, 18))
37+
>toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, --, --))
38+
}
39+
40+
function f2() {
41+
>f2 : Symbol(f2, Decl(bestChoiceType.ts, 11, 1))
42+
43+
let x = ''.match(/ /);
44+
>x : Symbol(x, Decl(bestChoiceType.ts, 14, 7))
45+
>''.match : Symbol(String.match, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
46+
>match : Symbol(String.match, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
47+
48+
let y = x ? x : [];
49+
>y : Symbol(y, Decl(bestChoiceType.ts, 15, 7))
50+
>x : Symbol(x, Decl(bestChoiceType.ts, 14, 7))
51+
>x : Symbol(x, Decl(bestChoiceType.ts, 14, 7))
52+
53+
let z = y.map(s => s.toLowerCase());
54+
>z : Symbol(z, Decl(bestChoiceType.ts, 16, 7))
55+
>y.map : Symbol(Array.map, Decl(lib.d.ts, --, --))
56+
>y : Symbol(y, Decl(bestChoiceType.ts, 15, 7))
57+
>map : Symbol(Array.map, Decl(lib.d.ts, --, --))
58+
>s : Symbol(s, Decl(bestChoiceType.ts, 16, 18))
59+
>s.toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, --, --))
60+
>s : Symbol(s, Decl(bestChoiceType.ts, 16, 18))
61+
>toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, --, --))
62+
}
63+
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
=== tests/cases/compiler/bestChoiceType.ts ===
2+
3+
// Repro from #10041
4+
5+
(''.match(/ /) || []).map(s => s.toLowerCase());
6+
>(''.match(/ /) || []).map(s => s.toLowerCase()) : string[]
7+
>(''.match(/ /) || []).map : <U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]
8+
>(''.match(/ /) || []) : RegExpMatchArray
9+
>''.match(/ /) || [] : RegExpMatchArray
10+
>''.match(/ /) : RegExpMatchArray | null
11+
>''.match : { (regexp: string): RegExpMatchArray | null; (regexp: RegExp): RegExpMatchArray | null; }
12+
>'' : string
13+
>match : { (regexp: string): RegExpMatchArray | null; (regexp: RegExp): RegExpMatchArray | null; }
14+
>/ / : RegExp
15+
>[] : never[]
16+
>map : <U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]
17+
>s => s.toLowerCase() : (s: string) => string
18+
>s : string
19+
>s.toLowerCase() : string
20+
>s.toLowerCase : () => string
21+
>s : string
22+
>toLowerCase : () => string
23+
24+
// Similar cases
25+
26+
function f1() {
27+
>f1 : () => void
28+
29+
let x = ''.match(/ /);
30+
>x : RegExpMatchArray | null
31+
>''.match(/ /) : RegExpMatchArray | null
32+
>''.match : { (regexp: string): RegExpMatchArray | null; (regexp: RegExp): RegExpMatchArray | null; }
33+
>'' : string
34+
>match : { (regexp: string): RegExpMatchArray | null; (regexp: RegExp): RegExpMatchArray | null; }
35+
>/ / : RegExp
36+
37+
let y = x || [];
38+
>y : RegExpMatchArray
39+
>x || [] : RegExpMatchArray
40+
>x : RegExpMatchArray | null
41+
>[] : never[]
42+
43+
let z = y.map(s => s.toLowerCase());
44+
>z : string[]
45+
>y.map(s => s.toLowerCase()) : string[]
46+
>y.map : <U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]
47+
>y : RegExpMatchArray
48+
>map : <U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]
49+
>s => s.toLowerCase() : (s: string) => string
50+
>s : string
51+
>s.toLowerCase() : string
52+
>s.toLowerCase : () => string
53+
>s : string
54+
>toLowerCase : () => string
55+
}
56+
57+
function f2() {
58+
>f2 : () => void
59+
60+
let x = ''.match(/ /);
61+
>x : RegExpMatchArray | null
62+
>''.match(/ /) : RegExpMatchArray | null
63+
>''.match : { (regexp: string): RegExpMatchArray | null; (regexp: RegExp): RegExpMatchArray | null; }
64+
>'' : string
65+
>match : { (regexp: string): RegExpMatchArray | null; (regexp: RegExp): RegExpMatchArray | null; }
66+
>/ / : RegExp
67+
68+
let y = x ? x : [];
69+
>y : RegExpMatchArray
70+
>x ? x : [] : RegExpMatchArray
71+
>x : RegExpMatchArray | null
72+
>x : RegExpMatchArray
73+
>[] : never[]
74+
75+
let z = y.map(s => s.toLowerCase());
76+
>z : string[]
77+
>y.map(s => s.toLowerCase()) : string[]
78+
>y.map : <U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]
79+
>y : RegExpMatchArray
80+
>map : <U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]
81+
>s => s.toLowerCase() : (s: string) => string
82+
>s : string
83+
>s.toLowerCase() : string
84+
>s.toLowerCase : () => string
85+
>s : string
86+
>toLowerCase : () => string
87+
}
88+

tests/baselines/reference/nonContextuallyTypedLogicalOr.symbols

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ var e: Ellement;
2828
>Ellement : Symbol(Ellement, Decl(nonContextuallyTypedLogicalOr.ts, 3, 1))
2929

3030
(c || e).dummy;
31-
>(c || e).dummy : Symbol(dummy, Decl(nonContextuallyTypedLogicalOr.ts, 0, 22), Decl(nonContextuallyTypedLogicalOr.ts, 5, 20))
31+
>(c || e).dummy : Symbol(Contextual.dummy, Decl(nonContextuallyTypedLogicalOr.ts, 0, 22))
3232
>c : Symbol(c, Decl(nonContextuallyTypedLogicalOr.ts, 10, 3))
3333
>e : Symbol(e, Decl(nonContextuallyTypedLogicalOr.ts, 11, 3))
34-
>dummy : Symbol(dummy, Decl(nonContextuallyTypedLogicalOr.ts, 0, 22), Decl(nonContextuallyTypedLogicalOr.ts, 5, 20))
34+
>dummy : Symbol(Contextual.dummy, Decl(nonContextuallyTypedLogicalOr.ts, 0, 22))
3535

tests/baselines/reference/nonContextuallyTypedLogicalOr.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ var e: Ellement;
2929

3030
(c || e).dummy;
3131
>(c || e).dummy : any
32-
>(c || e) : Contextual | Ellement
33-
>c || e : Contextual | Ellement
32+
>(c || e) : Contextual
33+
>c || e : Contextual
3434
>c : Contextual
3535
>e : Ellement
3636
>dummy : any

tests/baselines/reference/subtypingWithObjectMembersOptionality3.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ var b: { Foo2: Derived; }
6969
>Derived : Derived
7070

7171
var r = true ? a : b; // ok
72-
>r : { Foo?: Base; } | { Foo2: Derived; }
73-
>true ? a : b : { Foo?: Base; } | { Foo2: Derived; }
72+
>r : { Foo?: Base; }
73+
>true ? a : b : { Foo?: Base; }
7474
>true : boolean
7575
>a : { Foo?: Base; }
7676
>b : { Foo2: Derived; }

tests/baselines/reference/subtypingWithObjectMembersOptionality4.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ var b: { Foo2?: Derived; }
6969
>Derived : Derived
7070

7171
var r = true ? a : b; // ok
72-
>r : { Foo: Base; } | { Foo2?: Derived; }
73-
>true ? a : b : { Foo: Base; } | { Foo2?: Derived; }
72+
>r : { Foo2?: Derived; }
73+
>true ? a : b : { Foo2?: Derived; }
7474
>true : boolean
7575
>a : { Foo: Base; }
7676
>b : { Foo2?: Derived; }
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// @strictNullChecks: true
2+
3+
// Repro from #10041
4+
5+
(''.match(/ /) || []).map(s => s.toLowerCase());
6+
7+
// Similar cases
8+
9+
function f1() {
10+
let x = ''.match(/ /);
11+
let y = x || [];
12+
let z = y.map(s => s.toLowerCase());
13+
}
14+
15+
function f2() {
16+
let x = ''.match(/ /);
17+
let y = x ? x : [];
18+
let z = y.map(s => s.toLowerCase());
19+
}

0 commit comments

Comments
 (0)