diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 5ad15f4d5c461..b3e2efb494009 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -623,7 +623,7 @@ 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) { 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/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 } 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..d8df9a7a0bba1 --- /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._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(FooDiscriminator._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(BarDiscriminator._bar, Decl(typeGuardByEqualityCheck.ts, 8, 50)) +} + +interface BaseNode { +>BaseNode : Symbol(BaseNode, Decl(typeGuardByEqualityCheck.ts, 10, 1)) + + kind: Discriminator; +>kind : Symbol(BaseNode.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(FooNode.kind, Decl(typeGuardByEqualityCheck.ts, 16, 36)) +>FooDiscriminator : Symbol(FooDiscriminator, Decl(typeGuardByEqualityCheck.ts, 2, 1)) + + foo: string; +>foo : Symbol(FooNode.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(BarNode.kind, Decl(typeGuardByEqualityCheck.ts, 21, 36)) +>BarDiscriminator : Symbol(BarDiscriminator, Decl(typeGuardByEqualityCheck.ts, 6, 1)) + + bar: string; +>bar : Symbol(BarNode.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(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(FooThing.kind, Decl(typeGuardByEqualityCheck.ts, 52, 34)) + + foo: string; +>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(BarThing.kind, Decl(typeGuardByEqualityCheck.ts, 56, 34)) + + bar: string; +>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(FooBarThing.kind, Decl(typeGuardByEqualityCheck.ts, 60, 37)) + + foo: string; +>foo : Symbol(FooBarThing.foo, Decl(typeGuardByEqualityCheck.ts, 61, 16)) + + bar: string; +>bar : Symbol(FooBarThing.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 new file mode 100644 index 0000000000000..ac158c3806153 --- /dev/null +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardByEqualityCheck.ts @@ -0,0 +1,77 @@ +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"; +} \ No newline at end of file