mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
feat(42639): allow narrowing type in 'in' operator with the identifier on the left side (#44893)
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user