Skip to content

Commit

Permalink
feat(42639): allow narrowing type in 'in' operator with the identifie…
Browse files Browse the repository at this point in the history
…r on the left side (#44893)
  • Loading branch information
a-tarasyuk authored Jul 21, 2021
1 parent 1e2c77e commit e064817
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 7 deletions.
2 changes: 1 addition & 1 deletion src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -917,7 +917,7 @@ namespace ts {
}

function isNarrowableInOperands(left: Expression, right: Expression) {
return isStringLiteralLike(left) && isNarrowingExpression(right);
return isNarrowingExpression(right) && (isIdentifier(left) || isStringLiteralLike(left));
}

function isNarrowingBinaryExpression(expr: BinaryExpression) {
Expand Down
13 changes: 7 additions & 6 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23570,13 +23570,12 @@ namespace ts {
return getApplicableIndexInfoForName(type, propName) ? true : !assumeTrue;
}

function narrowByInKeyword(type: Type, literal: LiteralExpression, assumeTrue: boolean) {
function narrowByInKeyword(type: Type, name: __String, assumeTrue: boolean) {
if (type.flags & TypeFlags.Union
|| type.flags & TypeFlags.Object && declaredType !== type
|| isThisTypeParameter(type)
|| type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, t => t.symbol !== globalThisSymbol)) {
const propName = escapeLeadingUnderscores(literal.text);
return filterType(type, t => isTypePresencePossible(t, propName, assumeTrue));
return filterType(type, t => isTypePresencePossible(t, name, assumeTrue));
}
return type;
}
Expand Down Expand Up @@ -23634,13 +23633,15 @@ namespace ts {
return narrowTypeByInstanceof(type, expr, assumeTrue);
case SyntaxKind.InKeyword:
const target = getReferenceCandidate(expr.right);
if (isStringLiteralLike(expr.left)) {
const leftType = getTypeOfNode(expr.left);
if (leftType.flags & TypeFlags.StringLiteral) {
const name = escapeLeadingUnderscores((leftType as StringLiteralType).value);
if (containsMissingType(type) && isAccessExpression(reference) && isMatchingReference(reference.expression, target) &&
getAccessedPropertyName(reference) === escapeLeadingUnderscores(expr.left.text)) {
getAccessedPropertyName(reference) === name) {
return getTypeWithFacts(type, assumeTrue ? TypeFacts.NEUndefined : TypeFacts.EQUndefined);
}
if (isMatchingReference(reference, target)) {
return narrowByInKeyword(type, expr.left, assumeTrue);
return narrowByInKeyword(type, name, assumeTrue);
}
}
break;
Expand Down
47 changes: 47 additions & 0 deletions tests/baselines/reference/controlFlowInOperator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//// [controlFlowInOperator.ts]
const a = 'a';
const b = 'b';
const d = 'd';

type A = { [a]: number; };
type B = { [b]: string; };

declare const c: A | B;

if ('a' in c) {
c; // A
c['a']; // number;
}

if ('d' in c) {
c; // never
}

if (a in c) {
c; // A
c[a]; // number;
}

if (d in c) {
c; // never
}


//// [controlFlowInOperator.js]
var a = 'a';
var b = 'b';
var d = 'd';
if ('a' in c) {
c; // A
c['a']; // number;
}
if ('d' in c) {
c; // never
}
if (a in c) {
c; // A
c[a]; // number;
}
if (d in c) {
c; // never
}
63 changes: 63 additions & 0 deletions tests/baselines/reference/controlFlowInOperator.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
=== tests/cases/conformance/controlFlow/controlFlowInOperator.ts ===
const a = 'a';
>a : Symbol(a, Decl(controlFlowInOperator.ts, 0, 5))

const b = 'b';
>b : Symbol(b, Decl(controlFlowInOperator.ts, 1, 5))

const d = 'd';
>d : Symbol(d, Decl(controlFlowInOperator.ts, 2, 5))

type A = { [a]: number; };
>A : Symbol(A, Decl(controlFlowInOperator.ts, 2, 14))
>[a] : Symbol([a], Decl(controlFlowInOperator.ts, 4, 10))
>a : Symbol(a, Decl(controlFlowInOperator.ts, 0, 5))

type B = { [b]: string; };
>B : Symbol(B, Decl(controlFlowInOperator.ts, 4, 26))
>[b] : Symbol([b], Decl(controlFlowInOperator.ts, 5, 10))
>b : Symbol(b, Decl(controlFlowInOperator.ts, 1, 5))

declare const c: A | B;
>c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13))
>A : Symbol(A, Decl(controlFlowInOperator.ts, 2, 14))
>B : Symbol(B, Decl(controlFlowInOperator.ts, 4, 26))

if ('a' in c) {
>c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13))

c; // A
>c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13))

c['a']; // number;
>c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13))
>'a' : Symbol([a], Decl(controlFlowInOperator.ts, 4, 10))
}

if ('d' in c) {
>c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13))

c; // never
>c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13))
}

if (a in c) {
>a : Symbol(a, Decl(controlFlowInOperator.ts, 0, 5))
>c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13))

c; // A
>c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13))

c[a]; // number;
>c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13))
>a : Symbol(a, Decl(controlFlowInOperator.ts, 0, 5))
}

if (d in c) {
>d : Symbol(d, Decl(controlFlowInOperator.ts, 2, 5))
>c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13))

c; // never
>c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13))
}

72 changes: 72 additions & 0 deletions tests/baselines/reference/controlFlowInOperator.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
=== tests/cases/conformance/controlFlow/controlFlowInOperator.ts ===
const a = 'a';
>a : "a"
>'a' : "a"

const b = 'b';
>b : "b"
>'b' : "b"

const d = 'd';
>d : "d"
>'d' : "d"

type A = { [a]: number; };
>A : A
>[a] : number
>a : "a"

type B = { [b]: string; };
>B : B
>[b] : string
>b : "b"

declare const c: A | B;
>c : A | B

if ('a' in c) {
>'a' in c : boolean
>'a' : "a"
>c : A | B

c; // A
>c : A

c['a']; // number;
>c['a'] : number
>c : A
>'a' : "a"
}

if ('d' in c) {
>'d' in c : boolean
>'d' : "d"
>c : A | B

c; // never
>c : never
}

if (a in c) {
>a in c : boolean
>a : "a"
>c : A | B

c; // A
>c : A

c[a]; // number;
>c[a] : number
>c : A
>a : "a"
}

if (d in c) {
>d in c : boolean
>d : "d"
>c : A | B

c; // never
>c : never
}

26 changes: 26 additions & 0 deletions tests/cases/conformance/controlFlow/controlFlowInOperator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const a = 'a';
const b = 'b';
const d = 'd';

type A = { [a]: number; };
type B = { [b]: string; };

declare const c: A | B;

if ('a' in c) {
c; // A
c['a']; // number;
}

if ('d' in c) {
c; // never
}

if (a in c) {
c; // A
c[a]; // number;
}

if (d in c) {
c; // never
}

0 comments on commit e064817

Please sign in to comment.