From b3db6ff16fe9e395d4e573f32e88216fc21a27c2 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 10 Dec 2015 19:15:48 -0800 Subject: [PATCH 1/7] initial pass at equality as member type guards --- .../typeGuards/typeGuardByEqualityCheck.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tests/cases/conformance/expressions/typeGuards/typeGuardByEqualityCheck.ts diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardByEqualityCheck.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardByEqualityCheck.ts new file mode 100644 index 0000000000000..dc96878de37f9 --- /dev/null +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardByEqualityCheck.ts @@ -0,0 +1,44 @@ +interface Discriminator { + _discriminator: void; +} + +interface FooDiscriminator extends Discriminator { + _foo: void; +} + +interface BarDiscriminator extends Discriminator { + _bar: void; +} + +interface BaseNode { + kind: Discriminator; +} + +interface FooNode extends BaseNode { + kind: FooDiscriminator; + foo: string; +} + +interface BarNode extends BaseNode { + kind: BarDiscriminator; + bar: string; +} + +let a: FooDiscriminator; +let x: FooNode | BarNode; + +if (x.kind === a) { + x.foo = "yay!"; +} +else { + x; // Not narrowed at present +} + +let z: { + value: string; + item: FooNode | BarNode; +} +if (z.item.kind === a) { + z.item.foo = "cool!"; + z.value = "yes"; +} \ No newline at end of file From 6e29b517c1915580728739864d5d7f6743d7ae14 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 10 Dec 2015 19:27:37 -0800 Subject: [PATCH 2/7] accept present baselines so output for new tests is captured --- .../reference/typeGuardByEqualityCheck.js | 107 +++++++++ .../typeGuardByEqualityCheck.symbols | 207 ++++++++++++++++ .../reference/typeGuardByEqualityCheck.types | 224 ++++++++++++++++++ .../typeGuards/typeGuardByEqualityCheck.ts | 33 +++ 4 files changed, 571 insertions(+) create mode 100644 tests/baselines/reference/typeGuardByEqualityCheck.js create mode 100644 tests/baselines/reference/typeGuardByEqualityCheck.symbols create mode 100644 tests/baselines/reference/typeGuardByEqualityCheck.types diff --git a/tests/baselines/reference/typeGuardByEqualityCheck.js b/tests/baselines/reference/typeGuardByEqualityCheck.js new file mode 100644 index 0000000000000..f3125c779e53e --- /dev/null +++ b/tests/baselines/reference/typeGuardByEqualityCheck.js @@ -0,0 +1,107 @@ +//// [typeGuardByEqualityCheck.ts] +interface Discriminator { + _discriminator: void; +} + +interface FooDiscriminator extends Discriminator { + _foo: void; +} + +interface BarDiscriminator extends Discriminator { + _bar: void; +} + +interface BaseNode { + kind: Discriminator; +} + +interface FooNode extends BaseNode { + kind: FooDiscriminator; + foo: string; +} + +interface BarNode extends BaseNode { + kind: BarDiscriminator; + bar: string; +} + +let a: FooDiscriminator; +let x: FooNode | BarNode; + +if (x.kind === a) { + x.foo = "yay!"; +} +else { + x; // Not narrowed at present +} + +let z: { + value: string; + item: FooNode | BarNode; +} +if (z.item.kind === a) { + z.item.foo = "cool!"; + z.value = "yes"; +} + +let foo: "foo"; +let bar: "bar"; +let foobar: "foobar"; + +interface Thing { + kind: string; +} +interface FooThing extends Thing { + kind: "foo"; + foo: string; +} +interface BarThing extends Thing { + kind: "bar"; + bar: string; +} +interface FooBarThing extends Thing { + kind: "foobar"; + foo: string; + bar: string; +} + +let gg: FooThing | BarThing | FooBarThing; +if (gg.kind === foobar) { + gg.bar = "bar"; + gg.foo = "foo"; +} +let holder = { + value: gg +}; +if (holder.value.kind === foo) { + holder.value.foo = "foo"; +} + +//// [typeGuardByEqualityCheck.js] +var a; +var x; +if (x.kind === a) { + x.foo = "yay!"; +} +else { + x; // Not narrowed at present +} +var z; +if (z.item.kind === a) { + z.item.foo = "cool!"; + z.value = "yes"; +} +var foo; +var bar; +var foobar; +var gg; +if (gg.kind === foobar) { + gg.bar = "bar"; + gg.foo = "foo"; +} +var holder = { + value: gg +}; +if (holder.value.kind === foo) { + holder.value.foo = "foo"; +} diff --git a/tests/baselines/reference/typeGuardByEqualityCheck.symbols b/tests/baselines/reference/typeGuardByEqualityCheck.symbols new file mode 100644 index 0000000000000..7cac9da3f9a3a --- /dev/null +++ b/tests/baselines/reference/typeGuardByEqualityCheck.symbols @@ -0,0 +1,207 @@ +=== tests/cases/conformance/expressions/typeGuards/typeGuardByEqualityCheck.ts === +interface Discriminator { +>Discriminator : Symbol(Discriminator, Decl(typeGuardByEqualityCheck.ts, 0, 0)) + + _discriminator: void; +>_discriminator : Symbol(_discriminator, Decl(typeGuardByEqualityCheck.ts, 0, 25)) +} + +interface FooDiscriminator extends Discriminator { +>FooDiscriminator : Symbol(FooDiscriminator, Decl(typeGuardByEqualityCheck.ts, 2, 1)) +>Discriminator : Symbol(Discriminator, Decl(typeGuardByEqualityCheck.ts, 0, 0)) + + _foo: void; +>_foo : Symbol(_foo, Decl(typeGuardByEqualityCheck.ts, 4, 50)) +} + +interface BarDiscriminator extends Discriminator { +>BarDiscriminator : Symbol(BarDiscriminator, Decl(typeGuardByEqualityCheck.ts, 6, 1)) +>Discriminator : Symbol(Discriminator, Decl(typeGuardByEqualityCheck.ts, 0, 0)) + + _bar: void; +>_bar : Symbol(_bar, Decl(typeGuardByEqualityCheck.ts, 8, 50)) +} + +interface BaseNode { +>BaseNode : Symbol(BaseNode, Decl(typeGuardByEqualityCheck.ts, 10, 1)) + + kind: Discriminator; +>kind : Symbol(kind, Decl(typeGuardByEqualityCheck.ts, 12, 20)) +>Discriminator : Symbol(Discriminator, Decl(typeGuardByEqualityCheck.ts, 0, 0)) +} + +interface FooNode extends BaseNode { +>FooNode : Symbol(FooNode, Decl(typeGuardByEqualityCheck.ts, 14, 1)) +>BaseNode : Symbol(BaseNode, Decl(typeGuardByEqualityCheck.ts, 10, 1)) + + kind: FooDiscriminator; +>kind : Symbol(kind, Decl(typeGuardByEqualityCheck.ts, 16, 36)) +>FooDiscriminator : Symbol(FooDiscriminator, Decl(typeGuardByEqualityCheck.ts, 2, 1)) + + foo: string; +>foo : Symbol(foo, Decl(typeGuardByEqualityCheck.ts, 17, 24)) +} + +interface BarNode extends BaseNode { +>BarNode : Symbol(BarNode, Decl(typeGuardByEqualityCheck.ts, 19, 1)) +>BaseNode : Symbol(BaseNode, Decl(typeGuardByEqualityCheck.ts, 10, 1)) + + kind: BarDiscriminator; +>kind : Symbol(kind, Decl(typeGuardByEqualityCheck.ts, 21, 36)) +>BarDiscriminator : Symbol(BarDiscriminator, Decl(typeGuardByEqualityCheck.ts, 6, 1)) + + bar: string; +>bar : Symbol(bar, Decl(typeGuardByEqualityCheck.ts, 22, 24)) +} + +let a: FooDiscriminator; +>a : Symbol(a, Decl(typeGuardByEqualityCheck.ts, 26, 3)) +>FooDiscriminator : Symbol(FooDiscriminator, Decl(typeGuardByEqualityCheck.ts, 2, 1)) + +let x: FooNode | BarNode; +>x : Symbol(x, Decl(typeGuardByEqualityCheck.ts, 27, 3)) +>FooNode : Symbol(FooNode, Decl(typeGuardByEqualityCheck.ts, 14, 1)) +>BarNode : Symbol(BarNode, Decl(typeGuardByEqualityCheck.ts, 19, 1)) + +if (x.kind === a) { +>x.kind : Symbol(kind, Decl(typeGuardByEqualityCheck.ts, 16, 36), Decl(typeGuardByEqualityCheck.ts, 21, 36)) +>x : Symbol(x, Decl(typeGuardByEqualityCheck.ts, 27, 3)) +>kind : Symbol(kind, Decl(typeGuardByEqualityCheck.ts, 16, 36), Decl(typeGuardByEqualityCheck.ts, 21, 36)) +>a : Symbol(a, Decl(typeGuardByEqualityCheck.ts, 26, 3)) + + x.foo = "yay!"; +>x.foo : Symbol(FooNode.foo, Decl(typeGuardByEqualityCheck.ts, 17, 24)) +>x : Symbol(x, Decl(typeGuardByEqualityCheck.ts, 27, 3)) +>foo : Symbol(FooNode.foo, Decl(typeGuardByEqualityCheck.ts, 17, 24)) +} +else { + x; // Not narrowed at present +>x : Symbol(x, Decl(typeGuardByEqualityCheck.ts, 27, 3)) +} + +let z: { +>z : Symbol(z, Decl(typeGuardByEqualityCheck.ts, 36, 3)) + + value: string; +>value : Symbol(value, Decl(typeGuardByEqualityCheck.ts, 36, 8)) + + item: FooNode | BarNode; +>item : Symbol(item, Decl(typeGuardByEqualityCheck.ts, 37, 15)) +>FooNode : Symbol(FooNode, Decl(typeGuardByEqualityCheck.ts, 14, 1)) +>BarNode : Symbol(BarNode, Decl(typeGuardByEqualityCheck.ts, 19, 1)) +} +if (z.item.kind === a) { +>z.item.kind : Symbol(kind, Decl(typeGuardByEqualityCheck.ts, 16, 36), Decl(typeGuardByEqualityCheck.ts, 21, 36)) +>z.item : Symbol(item, Decl(typeGuardByEqualityCheck.ts, 37, 15)) +>z : Symbol(z, Decl(typeGuardByEqualityCheck.ts, 36, 3)) +>item : Symbol(item, Decl(typeGuardByEqualityCheck.ts, 37, 15)) +>kind : Symbol(kind, Decl(typeGuardByEqualityCheck.ts, 16, 36), Decl(typeGuardByEqualityCheck.ts, 21, 36)) +>a : Symbol(a, Decl(typeGuardByEqualityCheck.ts, 26, 3)) + + z.item.foo = "cool!"; +>z.item.foo : Symbol(FooNode.foo, Decl(typeGuardByEqualityCheck.ts, 17, 24)) +>z.item : Symbol(item) +>z : Symbol(z, Decl(typeGuardByEqualityCheck.ts, 36, 3)) +>item : Symbol(item) +>foo : Symbol(FooNode.foo, Decl(typeGuardByEqualityCheck.ts, 17, 24)) + + z.value = "yes"; +>z.value : Symbol(value, Decl(typeGuardByEqualityCheck.ts, 36, 8)) +>z : Symbol(z, Decl(typeGuardByEqualityCheck.ts, 36, 3)) +>value : Symbol(value, Decl(typeGuardByEqualityCheck.ts, 36, 8)) +} + +let foo: "foo"; +>foo : Symbol(foo, Decl(typeGuardByEqualityCheck.ts, 45, 3)) + +let bar: "bar"; +>bar : Symbol(bar, Decl(typeGuardByEqualityCheck.ts, 46, 3)) + +let foobar: "foobar"; +>foobar : Symbol(foobar, Decl(typeGuardByEqualityCheck.ts, 47, 3)) + +interface Thing { +>Thing : Symbol(Thing, Decl(typeGuardByEqualityCheck.ts, 47, 21)) + + kind: string; +>kind : Symbol(kind, Decl(typeGuardByEqualityCheck.ts, 49, 17)) +} +interface FooThing extends Thing { +>FooThing : Symbol(FooThing, Decl(typeGuardByEqualityCheck.ts, 51, 1)) +>Thing : Symbol(Thing, Decl(typeGuardByEqualityCheck.ts, 47, 21)) + + kind: "foo"; +>kind : Symbol(kind, Decl(typeGuardByEqualityCheck.ts, 52, 34)) + + foo: string; +>foo : Symbol(foo, Decl(typeGuardByEqualityCheck.ts, 53, 13)) +} +interface BarThing extends Thing { +>BarThing : Symbol(BarThing, Decl(typeGuardByEqualityCheck.ts, 55, 1)) +>Thing : Symbol(Thing, Decl(typeGuardByEqualityCheck.ts, 47, 21)) + + kind: "bar"; +>kind : Symbol(kind, Decl(typeGuardByEqualityCheck.ts, 56, 34)) + + bar: string; +>bar : Symbol(bar, Decl(typeGuardByEqualityCheck.ts, 57, 13)) +} +interface FooBarThing extends Thing { +>FooBarThing : Symbol(FooBarThing, Decl(typeGuardByEqualityCheck.ts, 59, 1)) +>Thing : Symbol(Thing, Decl(typeGuardByEqualityCheck.ts, 47, 21)) + + kind: "foobar"; +>kind : Symbol(kind, Decl(typeGuardByEqualityCheck.ts, 60, 37)) + + foo: string; +>foo : Symbol(foo, Decl(typeGuardByEqualityCheck.ts, 61, 16)) + + bar: string; +>bar : Symbol(bar, Decl(typeGuardByEqualityCheck.ts, 62, 13)) +} + +let gg: FooThing | BarThing | FooBarThing; +>gg : Symbol(gg, Decl(typeGuardByEqualityCheck.ts, 66, 3)) +>FooThing : Symbol(FooThing, Decl(typeGuardByEqualityCheck.ts, 51, 1)) +>BarThing : Symbol(BarThing, Decl(typeGuardByEqualityCheck.ts, 55, 1)) +>FooBarThing : Symbol(FooBarThing, Decl(typeGuardByEqualityCheck.ts, 59, 1)) + +if (gg.kind === foobar) { +>gg.kind : Symbol(kind, Decl(typeGuardByEqualityCheck.ts, 52, 34), Decl(typeGuardByEqualityCheck.ts, 56, 34), Decl(typeGuardByEqualityCheck.ts, 60, 37)) +>gg : Symbol(gg, Decl(typeGuardByEqualityCheck.ts, 66, 3)) +>kind : Symbol(kind, Decl(typeGuardByEqualityCheck.ts, 52, 34), Decl(typeGuardByEqualityCheck.ts, 56, 34), Decl(typeGuardByEqualityCheck.ts, 60, 37)) +>foobar : Symbol(foobar, Decl(typeGuardByEqualityCheck.ts, 47, 3)) + + gg.bar = "bar"; +>gg.bar : Symbol(FooBarThing.bar, Decl(typeGuardByEqualityCheck.ts, 62, 13)) +>gg : Symbol(gg, Decl(typeGuardByEqualityCheck.ts, 66, 3)) +>bar : Symbol(FooBarThing.bar, Decl(typeGuardByEqualityCheck.ts, 62, 13)) + + gg.foo = "foo"; +>gg.foo : Symbol(FooBarThing.foo, Decl(typeGuardByEqualityCheck.ts, 61, 16)) +>gg : Symbol(gg, Decl(typeGuardByEqualityCheck.ts, 66, 3)) +>foo : Symbol(FooBarThing.foo, Decl(typeGuardByEqualityCheck.ts, 61, 16)) +} +let holder = { +>holder : Symbol(holder, Decl(typeGuardByEqualityCheck.ts, 71, 3)) + + value: gg +>value : Symbol(value, Decl(typeGuardByEqualityCheck.ts, 71, 14)) +>gg : Symbol(gg, Decl(typeGuardByEqualityCheck.ts, 66, 3)) + +}; +if (holder.value.kind === foo) { +>holder.value.kind : Symbol(kind, Decl(typeGuardByEqualityCheck.ts, 52, 34), Decl(typeGuardByEqualityCheck.ts, 56, 34), Decl(typeGuardByEqualityCheck.ts, 60, 37)) +>holder.value : Symbol(value, Decl(typeGuardByEqualityCheck.ts, 71, 14)) +>holder : Symbol(holder, Decl(typeGuardByEqualityCheck.ts, 71, 3)) +>value : Symbol(value, Decl(typeGuardByEqualityCheck.ts, 71, 14)) +>kind : Symbol(kind, Decl(typeGuardByEqualityCheck.ts, 52, 34), Decl(typeGuardByEqualityCheck.ts, 56, 34), Decl(typeGuardByEqualityCheck.ts, 60, 37)) +>foo : Symbol(foo, Decl(typeGuardByEqualityCheck.ts, 45, 3)) + + holder.value.foo = "foo"; +>holder.value.foo : Symbol(FooThing.foo, Decl(typeGuardByEqualityCheck.ts, 53, 13)) +>holder.value : Symbol(value) +>holder : Symbol(holder, Decl(typeGuardByEqualityCheck.ts, 71, 3)) +>value : Symbol(value) +>foo : Symbol(FooThing.foo, Decl(typeGuardByEqualityCheck.ts, 53, 13)) +} diff --git a/tests/baselines/reference/typeGuardByEqualityCheck.types b/tests/baselines/reference/typeGuardByEqualityCheck.types new file mode 100644 index 0000000000000..0fa6bf0efcc42 --- /dev/null +++ b/tests/baselines/reference/typeGuardByEqualityCheck.types @@ -0,0 +1,224 @@ +=== tests/cases/conformance/expressions/typeGuards/typeGuardByEqualityCheck.ts === +interface Discriminator { +>Discriminator : Discriminator + + _discriminator: void; +>_discriminator : void +} + +interface FooDiscriminator extends Discriminator { +>FooDiscriminator : FooDiscriminator +>Discriminator : Discriminator + + _foo: void; +>_foo : void +} + +interface BarDiscriminator extends Discriminator { +>BarDiscriminator : BarDiscriminator +>Discriminator : Discriminator + + _bar: void; +>_bar : void +} + +interface BaseNode { +>BaseNode : BaseNode + + kind: Discriminator; +>kind : Discriminator +>Discriminator : Discriminator +} + +interface FooNode extends BaseNode { +>FooNode : FooNode +>BaseNode : BaseNode + + kind: FooDiscriminator; +>kind : FooDiscriminator +>FooDiscriminator : FooDiscriminator + + foo: string; +>foo : string +} + +interface BarNode extends BaseNode { +>BarNode : BarNode +>BaseNode : BaseNode + + kind: BarDiscriminator; +>kind : BarDiscriminator +>BarDiscriminator : BarDiscriminator + + bar: string; +>bar : string +} + +let a: FooDiscriminator; +>a : FooDiscriminator +>FooDiscriminator : FooDiscriminator + +let x: FooNode | BarNode; +>x : FooNode | BarNode +>FooNode : FooNode +>BarNode : BarNode + +if (x.kind === a) { +>x.kind === a : boolean +>x.kind : FooDiscriminator | BarDiscriminator +>x : FooNode | BarNode +>kind : FooDiscriminator | BarDiscriminator +>a : FooDiscriminator + + x.foo = "yay!"; +>x.foo = "yay!" : string +>x.foo : string +>x : FooNode +>foo : string +>"yay!" : string +} +else { + x; // Not narrowed at present +>x : FooNode | BarNode +} + +let z: { +>z : { value: string; item: FooNode | BarNode; } + + value: string; +>value : string + + item: FooNode | BarNode; +>item : FooNode | BarNode +>FooNode : FooNode +>BarNode : BarNode +} +if (z.item.kind === a) { +>z.item.kind === a : boolean +>z.item.kind : FooDiscriminator | BarDiscriminator +>z.item : FooNode | BarNode +>z : { value: string; item: FooNode | BarNode; } +>item : FooNode | BarNode +>kind : FooDiscriminator | BarDiscriminator +>a : FooDiscriminator + + z.item.foo = "cool!"; +>z.item.foo = "cool!" : string +>z.item.foo : string +>z.item : FooNode +>z : { value: string; item: FooNode; } +>item : FooNode +>foo : string +>"cool!" : string + + z.value = "yes"; +>z.value = "yes" : string +>z.value : string +>z : { value: string; item: FooNode; } +>value : string +>"yes" : string +} + +let foo: "foo"; +>foo : "foo" + +let bar: "bar"; +>bar : "bar" + +let foobar: "foobar"; +>foobar : "foobar" + +interface Thing { +>Thing : Thing + + kind: string; +>kind : string +} +interface FooThing extends Thing { +>FooThing : FooThing +>Thing : Thing + + kind: "foo"; +>kind : "foo" + + foo: string; +>foo : string +} +interface BarThing extends Thing { +>BarThing : BarThing +>Thing : Thing + + kind: "bar"; +>kind : "bar" + + bar: string; +>bar : string +} +interface FooBarThing extends Thing { +>FooBarThing : FooBarThing +>Thing : Thing + + kind: "foobar"; +>kind : "foobar" + + foo: string; +>foo : string + + bar: string; +>bar : string +} + +let gg: FooThing | BarThing | FooBarThing; +>gg : FooThing | BarThing | FooBarThing +>FooThing : FooThing +>BarThing : BarThing +>FooBarThing : FooBarThing + +if (gg.kind === foobar) { +>gg.kind === foobar : boolean +>gg.kind : "foo" | "bar" | "foobar" +>gg : FooThing | BarThing | FooBarThing +>kind : "foo" | "bar" | "foobar" +>foobar : "foobar" + + gg.bar = "bar"; +>gg.bar = "bar" : string +>gg.bar : string +>gg : FooBarThing +>bar : string +>"bar" : string + + gg.foo = "foo"; +>gg.foo = "foo" : string +>gg.foo : string +>gg : FooBarThing +>foo : string +>"foo" : string +} +let holder = { +>holder : { value: FooThing | BarThing | FooBarThing; } +>{ value: gg} : { value: FooThing | BarThing | FooBarThing; } + + value: gg +>value : FooThing | BarThing | FooBarThing +>gg : FooThing | BarThing | FooBarThing + +}; +if (holder.value.kind === foo) { +>holder.value.kind === foo : boolean +>holder.value.kind : "foo" | "bar" | "foobar" +>holder.value : FooThing | BarThing | FooBarThing +>holder : { value: FooThing | BarThing | FooBarThing; } +>value : FooThing | BarThing | FooBarThing +>kind : "foo" | "bar" | "foobar" +>foo : "foo" + + holder.value.foo = "foo"; +>holder.value.foo = "foo" : string +>holder.value.foo : string +>holder.value : FooThing +>holder : { value: FooThing; } +>value : FooThing +>foo : string +>"foo" : string +} diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardByEqualityCheck.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardByEqualityCheck.ts index dc96878de37f9..ac158c3806153 100644 --- a/tests/cases/conformance/expressions/typeGuards/typeGuardByEqualityCheck.ts +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardByEqualityCheck.ts @@ -41,4 +41,37 @@ let z: { if (z.item.kind === a) { z.item.foo = "cool!"; z.value = "yes"; +} + +let foo: "foo"; +let bar: "bar"; +let foobar: "foobar"; + +interface Thing { + kind: string; +} +interface FooThing extends Thing { + kind: "foo"; + foo: string; +} +interface BarThing extends Thing { + kind: "bar"; + bar: string; +} +interface FooBarThing extends Thing { + kind: "foobar"; + foo: string; + bar: string; +} + +let gg: FooThing | BarThing | FooBarThing; +if (gg.kind === foobar) { + gg.bar = "bar"; + gg.foo = "foo"; +} +let holder = { + value: gg +}; +if (holder.value.kind === foo) { + holder.value.foo = "foo"; } \ No newline at end of file From f88606adc9852d4e8d1fb921f020f35bbc5b4af8 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 11 Dec 2015 16:27:12 -0800 Subject: [PATCH 3/7] improve style/names --- src/compiler/checker.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6a42dc43d5159..b3d2455ab065e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7644,6 +7644,7 @@ namespace ts { return type && narrowType(type, flow.expression, flow.assumeTrue); } +<<<<<<< 6e29b517c1915580728739864d5d7f6743d7ae14 function getTypeAtFlowNodeCached(flow: FlowNode) { const cache = getFlowTypeCache(flow); if (!key) { @@ -7658,6 +7659,19 @@ namespace ts { if (flowStackNodes[i] === flow && flowStackCacheKeys[i] === key) { return undefined; } +======= + return narrowBasedOnMatchingProperty(type, propName); + } + + function isMemberSubtype(type: Type, check: Type, selectors: string[]): boolean { + if (!selectors.length) { + return isTypeSubtypeOf(type, check); + } + const name = selectors.pop(); + const childProp = getPropertyOfType(type, name); + const propType = childProp && getTypeOfSymbol(childProp); + return propType && isMemberSubtype(propType, check, selectors); +>>>>>>> improve style/names } // Record node and key on stack of nodes being processed. flowStackNodes[flowStackCount] = flow; @@ -7670,6 +7684,7 @@ namespace ts { return cache[key] || type && (cache[key] = type); } +<<<<<<< 6e29b517c1915580728739864d5d7f6743d7ae14 function getTypeAtFlowLabel(flow: FlowLabel) { const antecedentTypes: Type[] = []; for (const antecedent of flow.antecedents) { @@ -7687,6 +7702,20 @@ namespace ts { if (!contains(antecedentTypes, type)) { antecedentTypes.push(type); } +======= + function narrowBasedOnMatchingProperty(type: Type, name: string): Type { + const childProp = getPropertyOfType(type, name); + const propType = childProp && getTypeOfSymbol(childProp); + const narrowedType = propType && narrowIntrospectively(propType); + + if (narrowedType && !isTypeIdenticalTo(propType, narrowedType)) { + const symbols = cloneSymbolTable(resolveStructuredTypeMembers(type as ObjectType).members); + const temp = createSymbol(childProp.flags, name); + getSymbolLinks(temp).type = narrowedType; + symbols[name] = temp; + return createAnonymousType(createSymbol(type.symbol.flags, type.symbol.name), symbols, getSignaturesOfType(type, SignatureKind.Call), + getSignaturesOfType(type, SignatureKind.Construct), getIndexTypeOfType(type, IndexKind.String), getIndexTypeOfType(type, IndexKind.Number)); +>>>>>>> improve style/names } } return antecedentTypes.length === 0 ? undefined : From 74f3bd0f94516d9dd64dee6123f75c02b1896627 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 26 Apr 2016 04:54:18 -0400 Subject: [PATCH 4/7] Remove awful merge conflicts --- src/compiler/checker.ts | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b3d2455ab065e..6a42dc43d5159 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7644,7 +7644,6 @@ namespace ts { return type && narrowType(type, flow.expression, flow.assumeTrue); } -<<<<<<< 6e29b517c1915580728739864d5d7f6743d7ae14 function getTypeAtFlowNodeCached(flow: FlowNode) { const cache = getFlowTypeCache(flow); if (!key) { @@ -7659,19 +7658,6 @@ namespace ts { if (flowStackNodes[i] === flow && flowStackCacheKeys[i] === key) { return undefined; } -======= - return narrowBasedOnMatchingProperty(type, propName); - } - - function isMemberSubtype(type: Type, check: Type, selectors: string[]): boolean { - if (!selectors.length) { - return isTypeSubtypeOf(type, check); - } - const name = selectors.pop(); - const childProp = getPropertyOfType(type, name); - const propType = childProp && getTypeOfSymbol(childProp); - return propType && isMemberSubtype(propType, check, selectors); ->>>>>>> improve style/names } // Record node and key on stack of nodes being processed. flowStackNodes[flowStackCount] = flow; @@ -7684,7 +7670,6 @@ namespace ts { return cache[key] || type && (cache[key] = type); } -<<<<<<< 6e29b517c1915580728739864d5d7f6743d7ae14 function getTypeAtFlowLabel(flow: FlowLabel) { const antecedentTypes: Type[] = []; for (const antecedent of flow.antecedents) { @@ -7702,20 +7687,6 @@ namespace ts { if (!contains(antecedentTypes, type)) { antecedentTypes.push(type); } -======= - function narrowBasedOnMatchingProperty(type: Type, name: string): Type { - const childProp = getPropertyOfType(type, name); - const propType = childProp && getTypeOfSymbol(childProp); - const narrowedType = propType && narrowIntrospectively(propType); - - if (narrowedType && !isTypeIdenticalTo(propType, narrowedType)) { - const symbols = cloneSymbolTable(resolveStructuredTypeMembers(type as ObjectType).members); - const temp = createSymbol(childProp.flags, name); - getSymbolLinks(temp).type = narrowedType; - symbols[name] = temp; - return createAnonymousType(createSymbol(type.symbol.flags, type.symbol.name), symbols, getSignaturesOfType(type, SignatureKind.Call), - getSignaturesOfType(type, SignatureKind.Construct), getIndexTypeOfType(type, IndexKind.String), getIndexTypeOfType(type, IndexKind.Number)); ->>>>>>> improve style/names } } return antecedentTypes.length === 0 ? undefined : From 6636b7b003e56488f7d9ce811c0aa759666b0fa7 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 26 Apr 2016 07:24:08 -0400 Subject: [PATCH 5/7] Reconcile member-guards with CFA work --- src/compiler/binder.ts | 4 +- src/compiler/checker.ts | 99 ++++++++++++++++++- .../typeGuardByEqualityCheck.symbols | 32 +++--- 3 files changed, 116 insertions(+), 19 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 5ad15f4d5c461..7be40d299b25c 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -623,13 +623,13 @@ namespace ts { case SyntaxKind.ExclamationEqualsToken: case SyntaxKind.EqualsEqualsEqualsToken: case SyntaxKind.ExclamationEqualsEqualsToken: - if (isNarrowingExpression(expr.left) && (expr.right.kind === SyntaxKind.NullKeyword || expr.right.kind === SyntaxKind.Identifier)) { + if (isNarrowingExpression(expr.left)) { return true; } if (expr.left.kind === SyntaxKind.TypeOfExpression && isNarrowingExpression((expr.left).expression) && expr.right.kind === SyntaxKind.StringLiteral) { return true; } - return false; + return true; case SyntaxKind.InstanceOfKeyword: return isNarrowingExpression(expr.left); case SyntaxKind.CommaToken: diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6a42dc43d5159..f81b7e1d3e719 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7698,6 +7698,103 @@ namespace ts { return isMatchingReference(expr, reference) ? getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy) : type; } + function narrowTypeByValueExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { + assumeTrue = (expr.operatorToken.kind === SyntaxKind.EqualsEqualsEqualsToken || expr.operatorToken.kind === SyntaxKind.EqualsEqualsToken) ? assumeTrue : !assumeTrue; + let lhs = expr.left; + // selectors is the stack of property names used to select down into a type to get the member being narrowed + const selectors: string[] = []; + while (lhs.kind !== SyntaxKind.Identifier) { + switch (lhs.kind) { + case SyntaxKind.ParenthesizedExpression: + lhs = (lhs as ParenthesizedExpression).expression; + break; + case SyntaxKind.PropertyAccessExpression: + const name = (lhs as PropertyAccessExpression).name.text; + // If a name doesn't resolve, bail + if (name === undefined) { + return type; + } + selectors.push(name); + lhs = (lhs as PropertyAccessExpression).expression; + break; + case SyntaxKind.Identifier: + break; + default: + // Unhandled control flow construct, don't narrow + return type; + } + } + + if (!isMatchingReference(lhs, reference)) { + return type; + } + const rhsType = checkExpressionCached(expr.right); + + if (assumeTrue) { + return narrowIntrospectively(type); + } + return type; + + /** + * Descend into the type using the selectors we accumulated above and narrow any unions along the way + * If assumeTrue, we narrow by removing all types not compatible with the rhs type + * If not, we narrow only if the rhsType is a Value type (ie, StringLiteral) by removing all types compatible with that type (TODO) + */ + function narrowIntrospectively(type: Type) { + const propName = selectors.pop(); + if (propName === undefined) { + // Selected all the way into the object, return the type for the property to be narrowed + if (isTypeSubtypeOf(rhsType, type)) { + return rhsType; + } + else { + return type; + } + } + if (type.flags & TypeFlags.Union) { + const reducedUnion = getUnionType( + filter((type as UnionType).types, t => isMemberSubtype(t, rhsType, [...selectors, propName])), + /*noSubtypeReduction*/ true + ); + + if (reducedUnion !== emptyUnionType) { + return narrowBasedOnMatchingProperty(reducedUnion, propName); + } + else { + return type; + } + } + + return narrowBasedOnMatchingProperty(type, propName); + } + + function isMemberSubtype(type: Type, check: Type, selectors: string[]): boolean { + if (!selectors.length) { + return isTypeSubtypeOf(type, check); + } + const name = selectors.pop(); + const childProp = getPropertyOfType(type, name); + const propType = childProp && getTypeOfSymbol(childProp); + return propType && isMemberSubtype(propType, check, selectors); + } + + function narrowBasedOnMatchingProperty(type: Type, name: string): Type { + const childProp = getPropertyOfType(type, name); + const propType = childProp && getTypeOfSymbol(childProp); + const narrowedType = propType && narrowIntrospectively(propType); + + if (narrowedType && !isTypeIdenticalTo(propType, narrowedType)) { + const symbols = cloneSymbolTable(resolveStructuredTypeMembers(type as ObjectType).members); + const temp = createSymbol(childProp.flags, name); + getSymbolLinks(temp).type = narrowedType; + symbols[name] = temp; + return createAnonymousType(createSymbol(type.symbol.flags, type.symbol.name), symbols, getSignaturesOfType(type, SignatureKind.Call), + getSignaturesOfType(type, SignatureKind.Construct), getIndexInfoOfType(type, IndexKind.String), getIndexInfoOfType(type, IndexKind.Number)); + } + return type; + } + } + function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { switch (expr.operatorToken.kind) { case SyntaxKind.EqualsToken: @@ -7712,7 +7809,7 @@ namespace ts { if (expr.left.kind === SyntaxKind.TypeOfExpression && expr.right.kind === SyntaxKind.StringLiteral) { return narrowTypeByTypeof(type, expr, assumeTrue); } - break; + return narrowTypeByValueExpression(type, expr, assumeTrue); case SyntaxKind.InstanceOfKeyword: return narrowTypeByInstanceof(type, expr, assumeTrue); case SyntaxKind.CommaToken: diff --git a/tests/baselines/reference/typeGuardByEqualityCheck.symbols b/tests/baselines/reference/typeGuardByEqualityCheck.symbols index 7cac9da3f9a3a..d8df9a7a0bba1 100644 --- a/tests/baselines/reference/typeGuardByEqualityCheck.symbols +++ b/tests/baselines/reference/typeGuardByEqualityCheck.symbols @@ -3,7 +3,7 @@ interface Discriminator { >Discriminator : Symbol(Discriminator, Decl(typeGuardByEqualityCheck.ts, 0, 0)) _discriminator: void; ->_discriminator : Symbol(_discriminator, Decl(typeGuardByEqualityCheck.ts, 0, 25)) +>_discriminator : Symbol(Discriminator._discriminator, Decl(typeGuardByEqualityCheck.ts, 0, 25)) } interface FooDiscriminator extends Discriminator { @@ -11,7 +11,7 @@ interface FooDiscriminator extends Discriminator { >Discriminator : Symbol(Discriminator, Decl(typeGuardByEqualityCheck.ts, 0, 0)) _foo: void; ->_foo : Symbol(_foo, Decl(typeGuardByEqualityCheck.ts, 4, 50)) +>_foo : Symbol(FooDiscriminator._foo, Decl(typeGuardByEqualityCheck.ts, 4, 50)) } interface BarDiscriminator extends Discriminator { @@ -19,14 +19,14 @@ interface BarDiscriminator extends Discriminator { >Discriminator : Symbol(Discriminator, Decl(typeGuardByEqualityCheck.ts, 0, 0)) _bar: void; ->_bar : Symbol(_bar, Decl(typeGuardByEqualityCheck.ts, 8, 50)) +>_bar : Symbol(BarDiscriminator._bar, Decl(typeGuardByEqualityCheck.ts, 8, 50)) } interface BaseNode { >BaseNode : Symbol(BaseNode, Decl(typeGuardByEqualityCheck.ts, 10, 1)) kind: Discriminator; ->kind : Symbol(kind, Decl(typeGuardByEqualityCheck.ts, 12, 20)) +>kind : Symbol(BaseNode.kind, Decl(typeGuardByEqualityCheck.ts, 12, 20)) >Discriminator : Symbol(Discriminator, Decl(typeGuardByEqualityCheck.ts, 0, 0)) } @@ -35,11 +35,11 @@ interface FooNode extends BaseNode { >BaseNode : Symbol(BaseNode, Decl(typeGuardByEqualityCheck.ts, 10, 1)) kind: FooDiscriminator; ->kind : Symbol(kind, Decl(typeGuardByEqualityCheck.ts, 16, 36)) +>kind : Symbol(FooNode.kind, Decl(typeGuardByEqualityCheck.ts, 16, 36)) >FooDiscriminator : Symbol(FooDiscriminator, Decl(typeGuardByEqualityCheck.ts, 2, 1)) foo: string; ->foo : Symbol(foo, Decl(typeGuardByEqualityCheck.ts, 17, 24)) +>foo : Symbol(FooNode.foo, Decl(typeGuardByEqualityCheck.ts, 17, 24)) } interface BarNode extends BaseNode { @@ -47,11 +47,11 @@ interface BarNode extends BaseNode { >BaseNode : Symbol(BaseNode, Decl(typeGuardByEqualityCheck.ts, 10, 1)) kind: BarDiscriminator; ->kind : Symbol(kind, Decl(typeGuardByEqualityCheck.ts, 21, 36)) +>kind : Symbol(BarNode.kind, Decl(typeGuardByEqualityCheck.ts, 21, 36)) >BarDiscriminator : Symbol(BarDiscriminator, Decl(typeGuardByEqualityCheck.ts, 6, 1)) bar: string; ->bar : Symbol(bar, Decl(typeGuardByEqualityCheck.ts, 22, 24)) +>bar : Symbol(BarNode.bar, Decl(typeGuardByEqualityCheck.ts, 22, 24)) } let a: FooDiscriminator; @@ -124,40 +124,40 @@ interface Thing { >Thing : Symbol(Thing, Decl(typeGuardByEqualityCheck.ts, 47, 21)) kind: string; ->kind : Symbol(kind, Decl(typeGuardByEqualityCheck.ts, 49, 17)) +>kind : Symbol(Thing.kind, Decl(typeGuardByEqualityCheck.ts, 49, 17)) } interface FooThing extends Thing { >FooThing : Symbol(FooThing, Decl(typeGuardByEqualityCheck.ts, 51, 1)) >Thing : Symbol(Thing, Decl(typeGuardByEqualityCheck.ts, 47, 21)) kind: "foo"; ->kind : Symbol(kind, Decl(typeGuardByEqualityCheck.ts, 52, 34)) +>kind : Symbol(FooThing.kind, Decl(typeGuardByEqualityCheck.ts, 52, 34)) foo: string; ->foo : Symbol(foo, Decl(typeGuardByEqualityCheck.ts, 53, 13)) +>foo : Symbol(FooThing.foo, Decl(typeGuardByEqualityCheck.ts, 53, 13)) } interface BarThing extends Thing { >BarThing : Symbol(BarThing, Decl(typeGuardByEqualityCheck.ts, 55, 1)) >Thing : Symbol(Thing, Decl(typeGuardByEqualityCheck.ts, 47, 21)) kind: "bar"; ->kind : Symbol(kind, Decl(typeGuardByEqualityCheck.ts, 56, 34)) +>kind : Symbol(BarThing.kind, Decl(typeGuardByEqualityCheck.ts, 56, 34)) bar: string; ->bar : Symbol(bar, Decl(typeGuardByEqualityCheck.ts, 57, 13)) +>bar : Symbol(BarThing.bar, Decl(typeGuardByEqualityCheck.ts, 57, 13)) } interface FooBarThing extends Thing { >FooBarThing : Symbol(FooBarThing, Decl(typeGuardByEqualityCheck.ts, 59, 1)) >Thing : Symbol(Thing, Decl(typeGuardByEqualityCheck.ts, 47, 21)) kind: "foobar"; ->kind : Symbol(kind, Decl(typeGuardByEqualityCheck.ts, 60, 37)) +>kind : Symbol(FooBarThing.kind, Decl(typeGuardByEqualityCheck.ts, 60, 37)) foo: string; ->foo : Symbol(foo, Decl(typeGuardByEqualityCheck.ts, 61, 16)) +>foo : Symbol(FooBarThing.foo, Decl(typeGuardByEqualityCheck.ts, 61, 16)) bar: string; ->bar : Symbol(bar, Decl(typeGuardByEqualityCheck.ts, 62, 13)) +>bar : Symbol(FooBarThing.bar, Decl(typeGuardByEqualityCheck.ts, 62, 13)) } let gg: FooThing | BarThing | FooBarThing; From dbe2a5513901fa9c6218445bdd05aa1b8d85fb50 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 26 Apr 2016 07:30:45 -0400 Subject: [PATCH 6/7] This shouldnt have changed --- src/compiler/binder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 7be40d299b25c..b3e2efb494009 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -629,7 +629,7 @@ namespace ts { if (expr.left.kind === SyntaxKind.TypeOfExpression && isNarrowingExpression((expr.left).expression) && expr.right.kind === SyntaxKind.StringLiteral) { return true; } - return true; + return false; case SyntaxKind.InstanceOfKeyword: return isNarrowingExpression(expr.left); case SyntaxKind.CommaToken: From bca9c33045450bb9b9df72cdd686cf2780226d10 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 26 Apr 2016 07:41:58 -0400 Subject: [PATCH 7/7] Tentatively accept test baseline --- tests/baselines/reference/equalityWithUnionTypes01.types | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/baselines/reference/equalityWithUnionTypes01.types b/tests/baselines/reference/equalityWithUnionTypes01.types index 61e3803094903..f61dca64eea29 100644 --- a/tests/baselines/reference/equalityWithUnionTypes01.types +++ b/tests/baselines/reference/equalityWithUnionTypes01.types @@ -54,17 +54,17 @@ else if (y == z || z == y) { >y == z || z == y : boolean >y == z : boolean >y : I2 ->z : I1 +>z : I2 >z == y : boolean ->z : I1 +>z : I2 >y : I2 } else if (y != z || z != y) { >y != z || z != y : boolean >y != z : boolean >y : I2 ->z : I1 +>z : I2 >z != y : boolean ->z : I1 +>z : I2 >y : I2 }