Fix #16778 - use previous type and not declared type (#17381)

* Fix #16778 - use previous type to check discriminable type and not declared type

* Rename prevType -> computedType
This commit is contained in:
Wesley Wigham 2017-07-26 15:27:02 -07:00 committed by GitHub
parent b9fe9964d2
commit b080aa9440
5 changed files with 300 additions and 7 deletions

View File

@ -11500,7 +11500,7 @@ namespace ts {
if (isMatchingReference(reference, expr)) {
type = narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
}
else if (isMatchingReferenceDiscriminant(expr)) {
else if (isMatchingReferenceDiscriminant(expr, type)) {
type = narrowTypeByDiscriminant(type, <PropertyAccessExpression>expr, t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd));
}
return createFlowType(type, isIncomplete(flowType));
@ -11620,11 +11620,11 @@ namespace ts {
return result;
}
function isMatchingReferenceDiscriminant(expr: Expression) {
function isMatchingReferenceDiscriminant(expr: Expression, computedType: Type) {
return expr.kind === SyntaxKind.PropertyAccessExpression &&
declaredType.flags & TypeFlags.Union &&
computedType.flags & TypeFlags.Union &&
isMatchingReference(reference, (<PropertyAccessExpression>expr).expression) &&
isDiscriminantProperty(declaredType, (<PropertyAccessExpression>expr).name.escapedText);
isDiscriminantProperty(computedType, (<PropertyAccessExpression>expr).name.escapedText);
}
function narrowTypeByDiscriminant(type: Type, propAccess: PropertyAccessExpression, narrowType: (t: Type) => Type): Type {
@ -11638,7 +11638,7 @@ namespace ts {
if (isMatchingReference(reference, expr)) {
return getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy);
}
if (isMatchingReferenceDiscriminant(expr)) {
if (isMatchingReferenceDiscriminant(expr, declaredType)) {
return narrowTypeByDiscriminant(type, <PropertyAccessExpression>expr, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy));
}
if (containsMatchingReferenceDiscriminant(reference, expr)) {
@ -11670,10 +11670,10 @@ namespace ts {
if (isMatchingReference(reference, right)) {
return narrowTypeByEquality(type, operator, left, assumeTrue);
}
if (isMatchingReferenceDiscriminant(left)) {
if (isMatchingReferenceDiscriminant(left, declaredType)) {
return narrowTypeByDiscriminant(type, <PropertyAccessExpression>left, t => narrowTypeByEquality(t, operator, right, assumeTrue));
}
if (isMatchingReferenceDiscriminant(right)) {
if (isMatchingReferenceDiscriminant(right, declaredType)) {
return narrowTypeByDiscriminant(type, <PropertyAccessExpression>right, t => narrowTypeByEquality(t, operator, left, assumeTrue));
}
if (containsMatchingReferenceDiscriminant(reference, left) || containsMatchingReferenceDiscriminant(reference, right)) {

View File

@ -0,0 +1,58 @@
//// [flowControlTypeGuardThenSwitch.ts]
enum Kind {
A,
B,
}
interface Base {
kind: Kind;
}
interface A extends Base {
kind: Kind.A;
yar: any;
}
interface B extends Base {
kind: Kind.B;
gar: any;
}
type Both = A | B;
function isBoth(x: Base): x is Both {
return true;
}
let foo: Base = undefined;
if (isBoth(foo)) {
switch (foo.kind) {
case Kind.A:
const myA: A = foo; // Should not be an error
break;
case Kind.B:
const myB: B = foo;
break;
}
}
//// [flowControlTypeGuardThenSwitch.js]
var Kind;
(function (Kind) {
Kind[Kind["A"] = 0] = "A";
Kind[Kind["B"] = 1] = "B";
})(Kind || (Kind = {}));
function isBoth(x) {
return true;
}
var foo = undefined;
if (isBoth(foo)) {
switch (foo.kind) {
case Kind.A:
var myA = foo; // Should not be an error
break;
case Kind.B:
var myB = foo;
break;
}
}

View File

@ -0,0 +1,99 @@
=== tests/cases/compiler/flowControlTypeGuardThenSwitch.ts ===
enum Kind {
>Kind : Symbol(Kind, Decl(flowControlTypeGuardThenSwitch.ts, 0, 0))
A,
>A : Symbol(Kind.A, Decl(flowControlTypeGuardThenSwitch.ts, 0, 11))
B,
>B : Symbol(Kind.B, Decl(flowControlTypeGuardThenSwitch.ts, 1, 6))
}
interface Base {
>Base : Symbol(Base, Decl(flowControlTypeGuardThenSwitch.ts, 3, 1))
kind: Kind;
>kind : Symbol(Base.kind, Decl(flowControlTypeGuardThenSwitch.ts, 5, 16))
>Kind : Symbol(Kind, Decl(flowControlTypeGuardThenSwitch.ts, 0, 0))
}
interface A extends Base {
>A : Symbol(A, Decl(flowControlTypeGuardThenSwitch.ts, 7, 1))
>Base : Symbol(Base, Decl(flowControlTypeGuardThenSwitch.ts, 3, 1))
kind: Kind.A;
>kind : Symbol(A.kind, Decl(flowControlTypeGuardThenSwitch.ts, 9, 26))
>Kind : Symbol(Kind, Decl(flowControlTypeGuardThenSwitch.ts, 0, 0))
>A : Symbol(Kind.A, Decl(flowControlTypeGuardThenSwitch.ts, 0, 11))
yar: any;
>yar : Symbol(A.yar, Decl(flowControlTypeGuardThenSwitch.ts, 10, 17))
}
interface B extends Base {
>B : Symbol(B, Decl(flowControlTypeGuardThenSwitch.ts, 12, 1))
>Base : Symbol(Base, Decl(flowControlTypeGuardThenSwitch.ts, 3, 1))
kind: Kind.B;
>kind : Symbol(B.kind, Decl(flowControlTypeGuardThenSwitch.ts, 14, 26))
>Kind : Symbol(Kind, Decl(flowControlTypeGuardThenSwitch.ts, 0, 0))
>B : Symbol(Kind.B, Decl(flowControlTypeGuardThenSwitch.ts, 1, 6))
gar: any;
>gar : Symbol(B.gar, Decl(flowControlTypeGuardThenSwitch.ts, 15, 17))
}
type Both = A | B;
>Both : Symbol(Both, Decl(flowControlTypeGuardThenSwitch.ts, 17, 1))
>A : Symbol(A, Decl(flowControlTypeGuardThenSwitch.ts, 7, 1))
>B : Symbol(B, Decl(flowControlTypeGuardThenSwitch.ts, 12, 1))
function isBoth(x: Base): x is Both {
>isBoth : Symbol(isBoth, Decl(flowControlTypeGuardThenSwitch.ts, 19, 18))
>x : Symbol(x, Decl(flowControlTypeGuardThenSwitch.ts, 20, 16))
>Base : Symbol(Base, Decl(flowControlTypeGuardThenSwitch.ts, 3, 1))
>x : Symbol(x, Decl(flowControlTypeGuardThenSwitch.ts, 20, 16))
>Both : Symbol(Both, Decl(flowControlTypeGuardThenSwitch.ts, 17, 1))
return true;
}
let foo: Base = undefined;
>foo : Symbol(foo, Decl(flowControlTypeGuardThenSwitch.ts, 24, 3))
>Base : Symbol(Base, Decl(flowControlTypeGuardThenSwitch.ts, 3, 1))
>undefined : Symbol(undefined)
if (isBoth(foo)) {
>isBoth : Symbol(isBoth, Decl(flowControlTypeGuardThenSwitch.ts, 19, 18))
>foo : Symbol(foo, Decl(flowControlTypeGuardThenSwitch.ts, 24, 3))
switch (foo.kind) {
>foo.kind : Symbol(kind, Decl(flowControlTypeGuardThenSwitch.ts, 9, 26), Decl(flowControlTypeGuardThenSwitch.ts, 14, 26))
>foo : Symbol(foo, Decl(flowControlTypeGuardThenSwitch.ts, 24, 3))
>kind : Symbol(kind, Decl(flowControlTypeGuardThenSwitch.ts, 9, 26), Decl(flowControlTypeGuardThenSwitch.ts, 14, 26))
case Kind.A:
>Kind.A : Symbol(Kind.A, Decl(flowControlTypeGuardThenSwitch.ts, 0, 11))
>Kind : Symbol(Kind, Decl(flowControlTypeGuardThenSwitch.ts, 0, 0))
>A : Symbol(Kind.A, Decl(flowControlTypeGuardThenSwitch.ts, 0, 11))
const myA: A = foo; // Should not be an error
>myA : Symbol(myA, Decl(flowControlTypeGuardThenSwitch.ts, 28, 17))
>A : Symbol(A, Decl(flowControlTypeGuardThenSwitch.ts, 7, 1))
>foo : Symbol(foo, Decl(flowControlTypeGuardThenSwitch.ts, 24, 3))
break;
case Kind.B:
>Kind.B : Symbol(Kind.B, Decl(flowControlTypeGuardThenSwitch.ts, 1, 6))
>Kind : Symbol(Kind, Decl(flowControlTypeGuardThenSwitch.ts, 0, 0))
>B : Symbol(Kind.B, Decl(flowControlTypeGuardThenSwitch.ts, 1, 6))
const myB: B = foo;
>myB : Symbol(myB, Decl(flowControlTypeGuardThenSwitch.ts, 31, 17))
>B : Symbol(B, Decl(flowControlTypeGuardThenSwitch.ts, 12, 1))
>foo : Symbol(foo, Decl(flowControlTypeGuardThenSwitch.ts, 24, 3))
break;
}
}

View File

@ -0,0 +1,101 @@
=== tests/cases/compiler/flowControlTypeGuardThenSwitch.ts ===
enum Kind {
>Kind : Kind
A,
>A : Kind.A
B,
>B : Kind.B
}
interface Base {
>Base : Base
kind: Kind;
>kind : Kind
>Kind : Kind
}
interface A extends Base {
>A : A
>Base : Base
kind: Kind.A;
>kind : Kind.A
>Kind : any
>A : Kind.A
yar: any;
>yar : any
}
interface B extends Base {
>B : B
>Base : Base
kind: Kind.B;
>kind : Kind.B
>Kind : any
>B : Kind.B
gar: any;
>gar : any
}
type Both = A | B;
>Both : Both
>A : A
>B : B
function isBoth(x: Base): x is Both {
>isBoth : (x: Base) => x is Both
>x : Base
>Base : Base
>x : any
>Both : Both
return true;
>true : true
}
let foo: Base = undefined;
>foo : Base
>Base : Base
>undefined : undefined
if (isBoth(foo)) {
>isBoth(foo) : boolean
>isBoth : (x: Base) => x is Both
>foo : Base
switch (foo.kind) {
>foo.kind : Kind
>foo : Both
>kind : Kind
case Kind.A:
>Kind.A : Kind.A
>Kind : typeof Kind
>A : Kind.A
const myA: A = foo; // Should not be an error
>myA : A
>A : A
>foo : A
break;
case Kind.B:
>Kind.B : Kind.B
>Kind : typeof Kind
>B : Kind.B
const myB: B = foo;
>myB : B
>B : B
>foo : B
break;
}
}

View File

@ -0,0 +1,35 @@
enum Kind {
A,
B,
}
interface Base {
kind: Kind;
}
interface A extends Base {
kind: Kind.A;
yar: any;
}
interface B extends Base {
kind: Kind.B;
gar: any;
}
type Both = A | B;
function isBoth(x: Base): x is Both {
return true;
}
let foo: Base = undefined;
if (isBoth(foo)) {
switch (foo.kind) {
case Kind.A:
const myA: A = foo; // Should not be an error
break;
case Kind.B:
const myB: B = foo;
break;
}
}