Skip to content

Commit e064817

Browse files
authored
feat(42639): allow narrowing type in 'in' operator with the identifier on the left side (microsoft#44893)
1 parent 1e2c77e commit e064817

File tree

6 files changed

+216
-7
lines changed

6 files changed

+216
-7
lines changed

Diff for: src/compiler/binder.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -917,7 +917,7 @@ namespace ts {
917917
}
918918

919919
function isNarrowableInOperands(left: Expression, right: Expression) {
920-
return isStringLiteralLike(left) && isNarrowingExpression(right);
920+
return isNarrowingExpression(right) && (isIdentifier(left) || isStringLiteralLike(left));
921921
}
922922

923923
function isNarrowingBinaryExpression(expr: BinaryExpression) {

Diff for: src/compiler/checker.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -23570,13 +23570,12 @@ namespace ts {
2357023570
return getApplicableIndexInfoForName(type, propName) ? true : !assumeTrue;
2357123571
}
2357223572

23573-
function narrowByInKeyword(type: Type, literal: LiteralExpression, assumeTrue: boolean) {
23573+
function narrowByInKeyword(type: Type, name: __String, assumeTrue: boolean) {
2357423574
if (type.flags & TypeFlags.Union
2357523575
|| type.flags & TypeFlags.Object && declaredType !== type
2357623576
|| isThisTypeParameter(type)
2357723577
|| type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, t => t.symbol !== globalThisSymbol)) {
23578-
const propName = escapeLeadingUnderscores(literal.text);
23579-
return filterType(type, t => isTypePresencePossible(t, propName, assumeTrue));
23578+
return filterType(type, t => isTypePresencePossible(t, name, assumeTrue));
2358023579
}
2358123580
return type;
2358223581
}
@@ -23634,13 +23633,15 @@ namespace ts {
2363423633
return narrowTypeByInstanceof(type, expr, assumeTrue);
2363523634
case SyntaxKind.InKeyword:
2363623635
const target = getReferenceCandidate(expr.right);
23637-
if (isStringLiteralLike(expr.left)) {
23636+
const leftType = getTypeOfNode(expr.left);
23637+
if (leftType.flags & TypeFlags.StringLiteral) {
23638+
const name = escapeLeadingUnderscores((leftType as StringLiteralType).value);
2363823639
if (containsMissingType(type) && isAccessExpression(reference) && isMatchingReference(reference.expression, target) &&
23639-
getAccessedPropertyName(reference) === escapeLeadingUnderscores(expr.left.text)) {
23640+
getAccessedPropertyName(reference) === name) {
2364023641
return getTypeWithFacts(type, assumeTrue ? TypeFacts.NEUndefined : TypeFacts.EQUndefined);
2364123642
}
2364223643
if (isMatchingReference(reference, target)) {
23643-
return narrowByInKeyword(type, expr.left, assumeTrue);
23644+
return narrowByInKeyword(type, name, assumeTrue);
2364423645
}
2364523646
}
2364623647
break;

Diff for: tests/baselines/reference/controlFlowInOperator.js

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//// [controlFlowInOperator.ts]
2+
const a = 'a';
3+
const b = 'b';
4+
const d = 'd';
5+
6+
type A = { [a]: number; };
7+
type B = { [b]: string; };
8+
9+
declare const c: A | B;
10+
11+
if ('a' in c) {
12+
c; // A
13+
c['a']; // number;
14+
}
15+
16+
if ('d' in c) {
17+
c; // never
18+
}
19+
20+
if (a in c) {
21+
c; // A
22+
c[a]; // number;
23+
}
24+
25+
if (d in c) {
26+
c; // never
27+
}
28+
29+
30+
//// [controlFlowInOperator.js]
31+
var a = 'a';
32+
var b = 'b';
33+
var d = 'd';
34+
if ('a' in c) {
35+
c; // A
36+
c['a']; // number;
37+
}
38+
if ('d' in c) {
39+
c; // never
40+
}
41+
if (a in c) {
42+
c; // A
43+
c[a]; // number;
44+
}
45+
if (d in c) {
46+
c; // never
47+
}
+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
=== tests/cases/conformance/controlFlow/controlFlowInOperator.ts ===
2+
const a = 'a';
3+
>a : Symbol(a, Decl(controlFlowInOperator.ts, 0, 5))
4+
5+
const b = 'b';
6+
>b : Symbol(b, Decl(controlFlowInOperator.ts, 1, 5))
7+
8+
const d = 'd';
9+
>d : Symbol(d, Decl(controlFlowInOperator.ts, 2, 5))
10+
11+
type A = { [a]: number; };
12+
>A : Symbol(A, Decl(controlFlowInOperator.ts, 2, 14))
13+
>[a] : Symbol([a], Decl(controlFlowInOperator.ts, 4, 10))
14+
>a : Symbol(a, Decl(controlFlowInOperator.ts, 0, 5))
15+
16+
type B = { [b]: string; };
17+
>B : Symbol(B, Decl(controlFlowInOperator.ts, 4, 26))
18+
>[b] : Symbol([b], Decl(controlFlowInOperator.ts, 5, 10))
19+
>b : Symbol(b, Decl(controlFlowInOperator.ts, 1, 5))
20+
21+
declare const c: A | B;
22+
>c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13))
23+
>A : Symbol(A, Decl(controlFlowInOperator.ts, 2, 14))
24+
>B : Symbol(B, Decl(controlFlowInOperator.ts, 4, 26))
25+
26+
if ('a' in c) {
27+
>c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13))
28+
29+
c; // A
30+
>c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13))
31+
32+
c['a']; // number;
33+
>c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13))
34+
>'a' : Symbol([a], Decl(controlFlowInOperator.ts, 4, 10))
35+
}
36+
37+
if ('d' in c) {
38+
>c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13))
39+
40+
c; // never
41+
>c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13))
42+
}
43+
44+
if (a in c) {
45+
>a : Symbol(a, Decl(controlFlowInOperator.ts, 0, 5))
46+
>c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13))
47+
48+
c; // A
49+
>c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13))
50+
51+
c[a]; // number;
52+
>c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13))
53+
>a : Symbol(a, Decl(controlFlowInOperator.ts, 0, 5))
54+
}
55+
56+
if (d in c) {
57+
>d : Symbol(d, Decl(controlFlowInOperator.ts, 2, 5))
58+
>c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13))
59+
60+
c; // never
61+
>c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13))
62+
}
63+
+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
=== tests/cases/conformance/controlFlow/controlFlowInOperator.ts ===
2+
const a = 'a';
3+
>a : "a"
4+
>'a' : "a"
5+
6+
const b = 'b';
7+
>b : "b"
8+
>'b' : "b"
9+
10+
const d = 'd';
11+
>d : "d"
12+
>'d' : "d"
13+
14+
type A = { [a]: number; };
15+
>A : A
16+
>[a] : number
17+
>a : "a"
18+
19+
type B = { [b]: string; };
20+
>B : B
21+
>[b] : string
22+
>b : "b"
23+
24+
declare const c: A | B;
25+
>c : A | B
26+
27+
if ('a' in c) {
28+
>'a' in c : boolean
29+
>'a' : "a"
30+
>c : A | B
31+
32+
c; // A
33+
>c : A
34+
35+
c['a']; // number;
36+
>c['a'] : number
37+
>c : A
38+
>'a' : "a"
39+
}
40+
41+
if ('d' in c) {
42+
>'d' in c : boolean
43+
>'d' : "d"
44+
>c : A | B
45+
46+
c; // never
47+
>c : never
48+
}
49+
50+
if (a in c) {
51+
>a in c : boolean
52+
>a : "a"
53+
>c : A | B
54+
55+
c; // A
56+
>c : A
57+
58+
c[a]; // number;
59+
>c[a] : number
60+
>c : A
61+
>a : "a"
62+
}
63+
64+
if (d in c) {
65+
>d in c : boolean
66+
>d : "d"
67+
>c : A | B
68+
69+
c; // never
70+
>c : never
71+
}
72+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const a = 'a';
2+
const b = 'b';
3+
const d = 'd';
4+
5+
type A = { [a]: number; };
6+
type B = { [b]: string; };
7+
8+
declare const c: A | B;
9+
10+
if ('a' in c) {
11+
c; // A
12+
c['a']; // number;
13+
}
14+
15+
if ('d' in c) {
16+
c; // never
17+
}
18+
19+
if (a in c) {
20+
c; // A
21+
c[a]; // number;
22+
}
23+
24+
if (d in c) {
25+
c; // never
26+
}

0 commit comments

Comments
 (0)