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
+13 -12
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)) {
-7
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);
@@ -160,4 +160,14 @@ tests/cases/compiler/typeGuardConstructorClassAndNumber.ts(115,10): error TS2339
else {
var1; // C1
}
// Repro from #37660
function foo(instance: Function | object) {
if (typeof instance === 'function') {
if (instance.prototype == null || instance.prototype.constructor == null) {
return instance.length;
}
}
}
@@ -118,6 +118,16 @@ if (C1 !== var1["constructor"]) {
else {
var1; // C1
}
// Repro from #37660
function foo(instance: Function | object) {
if (typeof instance === 'function') {
if (instance.prototype == null || instance.prototype.constructor == null) {
return instance.length;
}
}
}
//// [typeGuardConstructorClassAndNumber.js]
@@ -240,3 +250,11 @@ if (C1 !== var1["constructor"]) {
else {
var1; // C1
}
// Repro from #37660
function foo(instance) {
if (typeof instance === 'function') {
if (instance.prototype == null || instance.prototype.constructor == null) {
return instance.length;
}
}
}
@@ -277,3 +277,29 @@ else {
>var1 : Symbol(var1, Decl(typeGuardConstructorClassAndNumber.ts, 5, 3))
}
// Repro from #37660
function foo(instance: Function | object) {
>foo : Symbol(foo, Decl(typeGuardConstructorClassAndNumber.ts, 118, 1))
>instance : Symbol(instance, Decl(typeGuardConstructorClassAndNumber.ts, 122, 13))
>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
if (typeof instance === 'function') {
>instance : Symbol(instance, Decl(typeGuardConstructorClassAndNumber.ts, 122, 13))
if (instance.prototype == null || instance.prototype.constructor == null) {
>instance.prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --))
>instance : Symbol(instance, Decl(typeGuardConstructorClassAndNumber.ts, 122, 13))
>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --))
>instance.prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --))
>instance : Symbol(instance, Decl(typeGuardConstructorClassAndNumber.ts, 122, 13))
>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --))
return instance.length;
>instance.length : Symbol(Function.length, Decl(lib.es5.d.ts, --, --))
>instance : Symbol(instance, Decl(typeGuardConstructorClassAndNumber.ts, 122, 13))
>length : Symbol(Function.length, Decl(lib.es5.d.ts, --, --))
}
}
}
@@ -316,3 +316,38 @@ else {
>var1 : C1
}
// Repro from #37660
function foo(instance: Function | object) {
>foo : (instance: object | Function) => number
>instance : object | Function
if (typeof instance === 'function') {
>typeof instance === 'function' : boolean
>typeof instance : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>instance : object | Function
>'function' : "function"
if (instance.prototype == null || instance.prototype.constructor == null) {
>instance.prototype == null || instance.prototype.constructor == null : boolean
>instance.prototype == null : boolean
>instance.prototype : any
>instance : Function
>prototype : any
>null : null
>instance.prototype.constructor == null : boolean
>instance.prototype.constructor : any
>instance.prototype : any
>instance : Function
>prototype : any
>constructor : any
>null : null
return instance.length;
>instance.length : number
>instance : Function
>length : number
}
}
}
@@ -117,3 +117,13 @@ if (C1 !== var1["constructor"]) {
else {
var1; // C1
}
// Repro from #37660
function foo(instance: Function | object) {
if (typeof instance === 'function') {
if (instance.prototype == null || instance.prototype.constructor == null) {
return instance.length;
}
}
}