mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Fix narrow-by-constructor logic (#37698)
* Fix narrow-by-constructor logic * Add regression test
This commit is contained in:
+13
-12
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user