diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9327a7c6f3b..f2b9d50db91 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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, 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, (expr).expression) && - isDiscriminantProperty(declaredType, (expr).name.escapedText); + isDiscriminantProperty(computedType, (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, 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, left, t => narrowTypeByEquality(t, operator, right, assumeTrue)); } - if (isMatchingReferenceDiscriminant(right)) { + if (isMatchingReferenceDiscriminant(right, declaredType)) { return narrowTypeByDiscriminant(type, right, t => narrowTypeByEquality(t, operator, left, assumeTrue)); } if (containsMatchingReferenceDiscriminant(reference, left) || containsMatchingReferenceDiscriminant(reference, right)) { diff --git a/tests/baselines/reference/flowControlTypeGuardThenSwitch.js b/tests/baselines/reference/flowControlTypeGuardThenSwitch.js new file mode 100644 index 00000000000..381cdad58e1 --- /dev/null +++ b/tests/baselines/reference/flowControlTypeGuardThenSwitch.js @@ -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; + } +} diff --git a/tests/baselines/reference/flowControlTypeGuardThenSwitch.symbols b/tests/baselines/reference/flowControlTypeGuardThenSwitch.symbols new file mode 100644 index 00000000000..98d876402d4 --- /dev/null +++ b/tests/baselines/reference/flowControlTypeGuardThenSwitch.symbols @@ -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; + } +} + diff --git a/tests/baselines/reference/flowControlTypeGuardThenSwitch.types b/tests/baselines/reference/flowControlTypeGuardThenSwitch.types new file mode 100644 index 00000000000..faa5dd64252 --- /dev/null +++ b/tests/baselines/reference/flowControlTypeGuardThenSwitch.types @@ -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; + } +} + diff --git a/tests/cases/compiler/flowControlTypeGuardThenSwitch.ts b/tests/cases/compiler/flowControlTypeGuardThenSwitch.ts new file mode 100644 index 00000000000..537e3ca66ff --- /dev/null +++ b/tests/cases/compiler/flowControlTypeGuardThenSwitch.ts @@ -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; + } +}