Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14916,6 +14916,11 @@ namespace ts {
return getTypeWithFacts(type, facts);
}

function areStringLiteralEnumComparable (source: Type, target: Type) {
const meaning = TypeFlags.StringLiteral | TypeFlags.EnumLiteral;
return !!(source.flags & meaning && target.flags & meaning && (<StringLiteralType>source).value === (<StringLiteralType>target).value);
}

function narrowTypeBySwitchOnDiscriminant(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) {
// We only narrow if all case expressions specify values with unit types
const switchTypes = getSwitchClauseTypes(switchStatement);
Expand All @@ -14927,7 +14932,7 @@ namespace ts {
const discriminantType = getUnionType(clauseTypes);
const caseType =
discriminantType.flags & TypeFlags.Never ? neverType :
replacePrimitivesWithLiterals(filterType(type, t => areTypesComparable(discriminantType, t)), discriminantType);
replacePrimitivesWithLiterals(filterType(type, t => areTypesComparable(discriminantType, t) || areStringLiteralEnumComparable(discriminantType, t)), discriminantType);
if (!hasDefaultClause) {
return caseType;
}
Expand Down
60 changes: 60 additions & 0 deletions tests/baselines/reference/typeGuardSwitchWithSameString.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
tests/cases/conformance/expressions/typeGuards/typeGuardSwitchWithSameString.ts(33,15): error TS2339: Property 'foo' does not exist on type 'T'.
Property 'foo' does not exist on type 'IBar'.
tests/cases/conformance/expressions/typeGuards/typeGuardSwitchWithSameString.ts(36,15): error TS2339: Property 'bar' does not exist on type 'T'.
Property 'bar' does not exist on type 'IFoo'.
tests/cases/conformance/expressions/typeGuards/typeGuardSwitchWithSameString.ts(39,15): error TS2339: Property 'baz' does not exist on type 'T'.
Property 'baz' does not exist on type 'IFoo'.


==== tests/cases/conformance/expressions/typeGuards/typeGuardSwitchWithSameString.ts (3 errors) ====
enum Foo {
baz = "baz"
}

enum Bar {
baz = "baz"
}

enum Baz {
baz = "ba" + "z"
}

interface IFoo {
type: Foo.baz
foo: string
}

interface IBar {
type: Bar.baz
bar: number
}

interface IBaz {
type: Baz.baz
baz: boolean
}

type T = IFoo | IBar | IBaz

function reduce(t: T) {
switch (t.type) {
case Foo.baz:
t.foo
~~~
!!! error TS2339: Property 'foo' does not exist on type 'T'.
!!! error TS2339: Property 'foo' does not exist on type 'IBar'.
break
case Bar.baz:
t.bar
~~~
!!! error TS2339: Property 'bar' does not exist on type 'T'.
!!! error TS2339: Property 'bar' does not exist on type 'IFoo'.
break
case Baz.baz:
t.baz
~~~
!!! error TS2339: Property 'baz' does not exist on type 'T'.
!!! error TS2339: Property 'baz' does not exist on type 'IFoo'.
}
}

69 changes: 69 additions & 0 deletions tests/baselines/reference/typeGuardSwitchWithSameString.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//// [typeGuardSwitchWithSameString.ts]
enum Foo {
baz = "baz"
}

enum Bar {
baz = "baz"
}

enum Baz {
baz = "ba" + "z"
}

interface IFoo {
type: Foo.baz
foo: string
}

interface IBar {
type: Bar.baz
bar: number
}

interface IBaz {
type: Baz.baz
baz: boolean
}

type T = IFoo | IBar | IBaz

function reduce(t: T) {
switch (t.type) {
case Foo.baz:
t.foo
break
case Bar.baz:
t.bar
break
case Baz.baz:
t.baz
}
}


//// [typeGuardSwitchWithSameString.js]
var Foo;
(function (Foo) {
Foo["baz"] = "baz";
})(Foo || (Foo = {}));
var Bar;
(function (Bar) {
Bar["baz"] = "baz";
})(Bar || (Bar = {}));
var Baz;
(function (Baz) {
Baz["baz"] = "baz";
})(Baz || (Baz = {}));
function reduce(t) {
switch (t.type) {
case Foo.baz:
t.foo;
break;
case Bar.baz:
t.bar;
break;
case Baz.baz:
t.baz;
}
}
102 changes: 102 additions & 0 deletions tests/baselines/reference/typeGuardSwitchWithSameString.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
=== tests/cases/conformance/expressions/typeGuards/typeGuardSwitchWithSameString.ts ===
enum Foo {
>Foo : Symbol(Foo, Decl(typeGuardSwitchWithSameString.ts, 0, 0))

baz = "baz"
>baz : Symbol(Foo.baz, Decl(typeGuardSwitchWithSameString.ts, 0, 10))
}

enum Bar {
>Bar : Symbol(Bar, Decl(typeGuardSwitchWithSameString.ts, 2, 1))

baz = "baz"
>baz : Symbol(Bar.baz, Decl(typeGuardSwitchWithSameString.ts, 4, 10))
}

enum Baz {
>Baz : Symbol(Baz, Decl(typeGuardSwitchWithSameString.ts, 6, 1))

baz = "ba" + "z"
>baz : Symbol(Baz.baz, Decl(typeGuardSwitchWithSameString.ts, 8, 10))
}

interface IFoo {
>IFoo : Symbol(IFoo, Decl(typeGuardSwitchWithSameString.ts, 10, 1))

type: Foo.baz
>type : Symbol(IFoo.type, Decl(typeGuardSwitchWithSameString.ts, 12, 16))
>Foo : Symbol(Foo, Decl(typeGuardSwitchWithSameString.ts, 0, 0))
>baz : Symbol(Foo.baz, Decl(typeGuardSwitchWithSameString.ts, 0, 10))

foo: string
>foo : Symbol(IFoo.foo, Decl(typeGuardSwitchWithSameString.ts, 13, 17))
}

interface IBar {
>IBar : Symbol(IBar, Decl(typeGuardSwitchWithSameString.ts, 15, 1))

type: Bar.baz
>type : Symbol(IBar.type, Decl(typeGuardSwitchWithSameString.ts, 17, 16))
>Bar : Symbol(Bar, Decl(typeGuardSwitchWithSameString.ts, 2, 1))
>baz : Symbol(Bar.baz, Decl(typeGuardSwitchWithSameString.ts, 4, 10))

bar: number
>bar : Symbol(IBar.bar, Decl(typeGuardSwitchWithSameString.ts, 18, 17))
}

interface IBaz {
>IBaz : Symbol(IBaz, Decl(typeGuardSwitchWithSameString.ts, 20, 1))

type: Baz.baz
>type : Symbol(IBaz.type, Decl(typeGuardSwitchWithSameString.ts, 22, 16))
>Baz : Symbol(Baz, Decl(typeGuardSwitchWithSameString.ts, 6, 1))
>baz : Symbol(Baz.baz, Decl(typeGuardSwitchWithSameString.ts, 8, 10))

baz: boolean
>baz : Symbol(IBaz.baz, Decl(typeGuardSwitchWithSameString.ts, 23, 17))
}

type T = IFoo | IBar | IBaz
>T : Symbol(T, Decl(typeGuardSwitchWithSameString.ts, 25, 1))
>IFoo : Symbol(IFoo, Decl(typeGuardSwitchWithSameString.ts, 10, 1))
>IBar : Symbol(IBar, Decl(typeGuardSwitchWithSameString.ts, 15, 1))
>IBaz : Symbol(IBaz, Decl(typeGuardSwitchWithSameString.ts, 20, 1))

function reduce(t: T) {
>reduce : Symbol(reduce, Decl(typeGuardSwitchWithSameString.ts, 27, 27))
>t : Symbol(t, Decl(typeGuardSwitchWithSameString.ts, 29, 16))
>T : Symbol(T, Decl(typeGuardSwitchWithSameString.ts, 25, 1))

switch (t.type) {
>t.type : Symbol(type, Decl(typeGuardSwitchWithSameString.ts, 12, 16), Decl(typeGuardSwitchWithSameString.ts, 17, 16), Decl(typeGuardSwitchWithSameString.ts, 22, 16))
>t : Symbol(t, Decl(typeGuardSwitchWithSameString.ts, 29, 16))
>type : Symbol(type, Decl(typeGuardSwitchWithSameString.ts, 12, 16), Decl(typeGuardSwitchWithSameString.ts, 17, 16), Decl(typeGuardSwitchWithSameString.ts, 22, 16))

case Foo.baz:
>Foo.baz : Symbol(Foo.baz, Decl(typeGuardSwitchWithSameString.ts, 0, 10))
>Foo : Symbol(Foo, Decl(typeGuardSwitchWithSameString.ts, 0, 0))
>baz : Symbol(Foo.baz, Decl(typeGuardSwitchWithSameString.ts, 0, 10))

t.foo
>t : Symbol(t, Decl(typeGuardSwitchWithSameString.ts, 29, 16))

break
case Bar.baz:
>Bar.baz : Symbol(Bar.baz, Decl(typeGuardSwitchWithSameString.ts, 4, 10))
>Bar : Symbol(Bar, Decl(typeGuardSwitchWithSameString.ts, 2, 1))
>baz : Symbol(Bar.baz, Decl(typeGuardSwitchWithSameString.ts, 4, 10))

t.bar
>t : Symbol(t, Decl(typeGuardSwitchWithSameString.ts, 29, 16))

break
case Baz.baz:
>Baz.baz : Symbol(Baz.baz, Decl(typeGuardSwitchWithSameString.ts, 8, 10))
>Baz : Symbol(Baz, Decl(typeGuardSwitchWithSameString.ts, 6, 1))
>baz : Symbol(Baz.baz, Decl(typeGuardSwitchWithSameString.ts, 8, 10))

t.baz
>t : Symbol(t, Decl(typeGuardSwitchWithSameString.ts, 29, 16))
}
}

100 changes: 100 additions & 0 deletions tests/baselines/reference/typeGuardSwitchWithSameString.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
=== tests/cases/conformance/expressions/typeGuards/typeGuardSwitchWithSameString.ts ===
enum Foo {
>Foo : Foo

baz = "baz"
>baz : Foo
>"baz" : "baz"
}

enum Bar {
>Bar : Bar

baz = "baz"
>baz : Bar
>"baz" : "baz"
}

enum Baz {
>Baz : Baz

baz = "ba" + "z"
>baz : Baz
>"ba" + "z" : string
>"ba" : "ba"
>"z" : "z"
}

interface IFoo {
type: Foo.baz
>type : Foo
>Foo : any

foo: string
>foo : string
}

interface IBar {
type: Bar.baz
>type : Bar
>Bar : any

bar: number
>bar : number
}

interface IBaz {
type: Baz.baz
>type : Baz
>Baz : any

baz: boolean
>baz : boolean
}

type T = IFoo | IBar | IBaz
>T : T

function reduce(t: T) {
>reduce : (t: T) => void
>t : T

switch (t.type) {
>t.type : Foo | Bar | Baz
>t : T
>type : Foo | Bar | Baz

case Foo.baz:
>Foo.baz : Foo
>Foo : typeof Foo
>baz : Foo

t.foo
>t.foo : any
>t : T
>foo : any

break
case Bar.baz:
>Bar.baz : Bar
>Bar : typeof Bar
>baz : Bar

t.bar
>t.bar : any
>t : T
>bar : any

break
case Baz.baz:
>Baz.baz : Baz
>Baz : typeof Baz
>baz : Baz

t.baz
>t.baz : any
>t : T
>baz : any
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
enum Foo {
baz = "baz"
}

enum Bar {
baz = "baz"
}

enum Baz {
baz = "ba" + "z"
}

interface IFoo {
type: Foo.baz
foo: string
}

interface IBar {
type: Bar.baz
bar: number
}

interface IBaz {
type: Baz.baz
baz: boolean
}

type T = IFoo | IBar | IBaz

function reduce(t: T) {
switch (t.type) {
case Foo.baz:
t.foo
break
case Bar.baz:
t.bar
break
case Baz.baz:
t.baz
}
}