From a4356b0fa6308a81d9ebcbbb27e2b3a094295acb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E7=92=90?= Date: Mon, 27 Aug 2018 16:30:25 +0800 Subject: [PATCH] improve switch type narrow with string literal enum --- src/compiler/checker.ts | 7 +- .../typeGuardSwitchWithSameString.errors.txt | 60 +++++++++++ .../typeGuardSwitchWithSameString.js | 69 ++++++++++++ .../typeGuardSwitchWithSameString.symbols | 102 ++++++++++++++++++ .../typeGuardSwitchWithSameString.types | 100 +++++++++++++++++ .../typeGuardSwitchWithSameString.ts | 41 +++++++ 6 files changed, 378 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/typeGuardSwitchWithSameString.errors.txt create mode 100644 tests/baselines/reference/typeGuardSwitchWithSameString.js create mode 100644 tests/baselines/reference/typeGuardSwitchWithSameString.symbols create mode 100644 tests/baselines/reference/typeGuardSwitchWithSameString.types create mode 100644 tests/cases/conformance/expressions/typeGuards/typeGuardSwitchWithSameString.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f070d71bb16de..ae368037e7414 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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 && (source).value === (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); @@ -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; } diff --git a/tests/baselines/reference/typeGuardSwitchWithSameString.errors.txt b/tests/baselines/reference/typeGuardSwitchWithSameString.errors.txt new file mode 100644 index 0000000000000..db269ceb1e54b --- /dev/null +++ b/tests/baselines/reference/typeGuardSwitchWithSameString.errors.txt @@ -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'. + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardSwitchWithSameString.js b/tests/baselines/reference/typeGuardSwitchWithSameString.js new file mode 100644 index 0000000000000..03e6308484512 --- /dev/null +++ b/tests/baselines/reference/typeGuardSwitchWithSameString.js @@ -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; + } +} diff --git a/tests/baselines/reference/typeGuardSwitchWithSameString.symbols b/tests/baselines/reference/typeGuardSwitchWithSameString.symbols new file mode 100644 index 0000000000000..3d7b215bcf049 --- /dev/null +++ b/tests/baselines/reference/typeGuardSwitchWithSameString.symbols @@ -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)) + } +} + diff --git a/tests/baselines/reference/typeGuardSwitchWithSameString.types b/tests/baselines/reference/typeGuardSwitchWithSameString.types new file mode 100644 index 0000000000000..dca22a6dbbf71 --- /dev/null +++ b/tests/baselines/reference/typeGuardSwitchWithSameString.types @@ -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 + } +} + diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardSwitchWithSameString.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardSwitchWithSameString.ts new file mode 100644 index 0000000000000..ddf5d079952e8 --- /dev/null +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardSwitchWithSameString.ts @@ -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 + } +}