Fix narrow-by-constructor logic (#37698)

* Fix narrow-by-constructor logic

* Add regression test
This commit is contained in:
Anders Hejlsberg
2020-03-30 19:31:16 -07:00
committed by GitHub
parent 6ffbffbe62
commit 2b0f351005
7 changed files with 112 additions and 19 deletions

View File

@@ -20265,12 +20265,6 @@ namespace ts {
if (right.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(left)) {
return narrowTypeByTypeof(type, <TypeOfExpression>right, operator, left, assumeTrue);
}
if (isConstructorAccessExpression(left)) {
return narrowTypeByConstructor(type, left, operator, right, assumeTrue);
}
if (isConstructorAccessExpression(right)) {
return narrowTypeByConstructor(type, right, operator, left, assumeTrue);
}
if (isMatchingReference(reference, left)) {
return narrowTypeByEquality(type, operator, right, assumeTrue);
}
@@ -20291,6 +20285,12 @@ namespace ts {
if (isMatchingReferenceDiscriminant(right, declaredType)) {
return narrowTypeByDiscriminant(type, <AccessExpression>right, t => narrowTypeByEquality(t, operator, left, assumeTrue));
}
if (isMatchingConstructorReference(left)) {
return narrowTypeByConstructor(type, operator, right, assumeTrue);
}
if (isMatchingConstructorReference(right)) {
return narrowTypeByConstructor(type, operator, left, assumeTrue);
}
break;
case SyntaxKind.InstanceOfKeyword:
return narrowTypeByInstanceof(type, expr, assumeTrue);
@@ -20564,17 +20564,18 @@ namespace ts {
return getTypeWithFacts(mapType(type, narrowTypeForTypeofSwitch(impliedType)), switchFacts);
}
function narrowTypeByConstructor(type: Type, constructorAccessExpr: AccessExpression, operator: SyntaxKind, identifier: Expression, assumeTrue: boolean): Type {
function isMatchingConstructorReference(expr: Expression) {
return (isPropertyAccessExpression(expr) && idText(expr.name) === "constructor" ||
isElementAccessExpression(expr) && isStringLiteralLike(expr.argumentExpression) && expr.argumentExpression.text === "constructor") &&
isMatchingReference(reference, expr.expression);
}
function narrowTypeByConstructor(type: Type, operator: SyntaxKind, identifier: Expression, assumeTrue: boolean): Type {
// Do not narrow when checking inequality.
if (assumeTrue ? (operator !== SyntaxKind.EqualsEqualsToken && operator !== SyntaxKind.EqualsEqualsEqualsToken) : (operator !== SyntaxKind.ExclamationEqualsToken && operator !== SyntaxKind.ExclamationEqualsEqualsToken)) {
return type;
}
// In the case of `x.y`, a `x.constructor === T` type guard resets the narrowed type of `y` to its declared type.
if (!isMatchingReference(reference, constructorAccessExpr.expression)) {
return declaredType;
}
// Get the type of the constructor identifier expression, if it is not a function then do not narrow.
const identifierType = getTypeOfExpression(identifier);
if (!isFunctionType(identifierType) && !isConstructorType(identifierType)) {

View File

@@ -4432,13 +4432,6 @@ namespace ts {
return isPropertyAccessExpression(node) && isEntityNameExpression(node.expression);
}
export function isConstructorAccessExpression(expr: Expression): expr is AccessExpression {
return (
isPropertyAccessExpression(expr) && idText(expr.name) === "constructor" ||
isElementAccessExpression(expr) && isStringLiteralLike(expr.argumentExpression) && expr.argumentExpression.text === "constructor"
);
}
export function tryGetPropertyAccessOrIdentifierToString(expr: Expression): string | undefined {
if (isPropertyAccessExpression(expr)) {
const baseStr = tryGetPropertyAccessOrIdentifierToString(expr.expression);