diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 71b5e1afbff..4eab97f28bb 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -749,6 +749,10 @@ namespace ts { return expr1.kind === SyntaxKind.TypeOfExpression && isNarrowableOperand((expr1).expression) && expr2.kind === SyntaxKind.StringLiteral; } + function isNarrowableInOperands(left: Expression, right: Expression) { + return left.kind === SyntaxKind.StringLiteral && isNarrowingExpression(right); + } + function isNarrowingBinaryExpression(expr: BinaryExpression) { switch (expr.operatorToken.kind) { case SyntaxKind.EqualsToken: @@ -761,6 +765,8 @@ namespace ts { isNarrowingTypeofOperands(expr.right, expr.left) || isNarrowingTypeofOperands(expr.left, expr.right); case SyntaxKind.InstanceOfKeyword: return isNarrowableOperand(expr.left); + case SyntaxKind.InKeyword: + return isNarrowableInOperands(expr.left, expr.right); case SyntaxKind.CommaToken: return isNarrowingExpression(expr.right); } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 25d967795a7..7f7a75a33f9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12722,6 +12722,14 @@ namespace ts { return type; } + function narrowByInKeyword(type: Type, literal: LiteralExpression, assumeTrue: boolean) { + if ((type.flags & (TypeFlags.Union | TypeFlags.Object)) || (type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType)) { + const propName = literal.text; + return filterType(type, t => !!getPropertyOfType(t, propName) === assumeTrue); + } + return type; + } + function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { switch (expr.operatorToken.kind) { case SyntaxKind.EqualsToken: @@ -12757,6 +12765,12 @@ namespace ts { break; case SyntaxKind.InstanceOfKeyword: return narrowTypeByInstanceof(type, expr, assumeTrue); + case SyntaxKind.InKeyword: + const target = getReferenceCandidate(expr.right); + if (expr.left.kind === SyntaxKind.StringLiteral && isMatchingReference(reference, target)) { + return narrowByInKeyword(type, expr.left, assumeTrue); + } + break; case SyntaxKind.CommaToken: return narrowType(type, expr.right, assumeTrue); } diff --git a/tests/baselines/reference/inKeywordTypeguard.errors.txt b/tests/baselines/reference/inKeywordTypeguard.errors.txt new file mode 100644 index 00000000000..4ee09e71b60 --- /dev/null +++ b/tests/baselines/reference/inKeywordTypeguard.errors.txt @@ -0,0 +1,154 @@ +tests/cases/compiler/inKeywordTypeguard.ts(6,11): error TS2339: Property 'b' does not exist on type 'A'. +tests/cases/compiler/inKeywordTypeguard.ts(8,11): error TS2339: Property 'a' does not exist on type 'B'. +tests/cases/compiler/inKeywordTypeguard.ts(14,11): error TS2339: Property 'b' does not exist on type 'A'. +tests/cases/compiler/inKeywordTypeguard.ts(16,11): error TS2339: Property 'a' does not exist on type 'B'. +tests/cases/compiler/inKeywordTypeguard.ts(42,11): error TS2339: Property 'b' does not exist on type 'AWithMethod'. +tests/cases/compiler/inKeywordTypeguard.ts(49,11): error TS2339: Property 'a' does not exist on type 'never'. +tests/cases/compiler/inKeywordTypeguard.ts(50,11): error TS2339: Property 'b' does not exist on type 'never'. +tests/cases/compiler/inKeywordTypeguard.ts(52,11): error TS2339: Property 'a' does not exist on type 'AWithMethod | BWithMethod'. + Property 'a' does not exist on type 'BWithMethod'. +tests/cases/compiler/inKeywordTypeguard.ts(53,11): error TS2339: Property 'b' does not exist on type 'AWithMethod | BWithMethod'. + Property 'b' does not exist on type 'AWithMethod'. +tests/cases/compiler/inKeywordTypeguard.ts(62,11): error TS2339: Property 'b' does not exist on type 'A | C | D'. + Property 'b' does not exist on type 'A'. +tests/cases/compiler/inKeywordTypeguard.ts(64,11): error TS2339: Property 'a' does not exist on type 'B'. +tests/cases/compiler/inKeywordTypeguard.ts(72,32): error TS2339: Property 'b' does not exist on type 'A'. +tests/cases/compiler/inKeywordTypeguard.ts(74,32): error TS2339: Property 'a' does not exist on type 'B'. +tests/cases/compiler/inKeywordTypeguard.ts(82,39): error TS2339: Property 'b' does not exist on type 'A'. +tests/cases/compiler/inKeywordTypeguard.ts(84,39): error TS2339: Property 'a' does not exist on type 'B'. +tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' does not exist on type 'never'. + + +==== tests/cases/compiler/inKeywordTypeguard.ts (16 errors) ==== + class A { a: string; } + class B { b: string; } + + function negativeClassesTest(x: A | B) { + if ("a" in x) { + x.b = "1"; + ~ +!!! error TS2339: Property 'b' does not exist on type 'A'. + } else { + x.a = "1"; + ~ +!!! error TS2339: Property 'a' does not exist on type 'B'. + } + } + + function positiveClassesTest(x: A | B) { + if ("a" in x) { + x.b = "1"; + ~ +!!! error TS2339: Property 'b' does not exist on type 'A'. + } else { + x.a = "1"; + ~ +!!! error TS2339: Property 'a' does not exist on type 'B'. + } + } + + class AOpt { a?: string } + class BOpn { b?: string } + + function positiveTestClassesWithOptionalProperties(x: AOpt | BOpn) { + if ("a" in x) { + x.a = "1"; + } else { + x.b = "1"; + } + } + + class AWithMethod { + a(): string { return "" } + } + + class BWithMethod { + b(): string { return "" } + } + + function negativeTestClassesWithMembers(x: AWithMethod | BWithMethod) { + if ("a" in x) { + x.a(); + x.b(); + ~ +!!! error TS2339: Property 'b' does not exist on type 'AWithMethod'. + } else { + } + } + + function negativeTestClassesWithMemberMissingInBothClasses(x: AWithMethod | BWithMethod) { + if ("c" in x) { + x.a(); + ~ +!!! error TS2339: Property 'a' does not exist on type 'never'. + x.b(); + ~ +!!! error TS2339: Property 'b' does not exist on type 'never'. + } else { + x.a(); + ~ +!!! error TS2339: Property 'a' does not exist on type 'AWithMethod | BWithMethod'. +!!! error TS2339: Property 'a' does not exist on type 'BWithMethod'. + x.b(); + ~ +!!! error TS2339: Property 'b' does not exist on type 'AWithMethod | BWithMethod'. +!!! error TS2339: Property 'b' does not exist on type 'AWithMethod'. + } + } + + class C { a: string } + class D { a: string } + + function negativeMultipleClassesTest(x: A | B | C | D) { + if ("a" in x) { + x.b = "1"; + ~ +!!! error TS2339: Property 'b' does not exist on type 'A | C | D'. +!!! error TS2339: Property 'b' does not exist on type 'A'. + } else { + x.a = "1"; + ~ +!!! error TS2339: Property 'a' does not exist on type 'B'. + } + } + + class ClassWithProp { prop: A | B } + + function negativePropTest(x: ClassWithProp) { + if ("a" in x.prop) { + let y: string = x.prop.b; + ~ +!!! error TS2339: Property 'b' does not exist on type 'A'. + } else { + let z: string = x.prop.a; + ~ +!!! error TS2339: Property 'a' does not exist on type 'B'. + } + } + + class NegativeClassTest { + protected prop: A | B; + inThis() { + if ('a' in this.prop) { + let z: number = this.prop.b; + ~ +!!! error TS2339: Property 'b' does not exist on type 'A'. + } else { + let y: string = this.prop.a; + ~ +!!! error TS2339: Property 'a' does not exist on type 'B'. + } + } + } + + class UnreachableCodeDetection { + a: string; + inThis() { + if ('a' in this) { + } else { + let y = this.a; + ~ +!!! error TS2339: Property 'a' does not exist on type 'never'. + } + } + } \ No newline at end of file diff --git a/tests/baselines/reference/inKeywordTypeguard.js b/tests/baselines/reference/inKeywordTypeguard.js new file mode 100644 index 00000000000..33e8eefa146 --- /dev/null +++ b/tests/baselines/reference/inKeywordTypeguard.js @@ -0,0 +1,230 @@ +//// [inKeywordTypeguard.ts] +class A { a: string; } +class B { b: string; } + +function negativeClassesTest(x: A | B) { + if ("a" in x) { + x.b = "1"; + } else { + x.a = "1"; + } +} + +function positiveClassesTest(x: A | B) { + if ("a" in x) { + x.b = "1"; + } else { + x.a = "1"; + } +} + +class AOpt { a?: string } +class BOpn { b?: string } + +function positiveTestClassesWithOptionalProperties(x: AOpt | BOpn) { + if ("a" in x) { + x.a = "1"; + } else { + x.b = "1"; + } +} + +class AWithMethod { + a(): string { return "" } +} + +class BWithMethod { + b(): string { return "" } +} + +function negativeTestClassesWithMembers(x: AWithMethod | BWithMethod) { + if ("a" in x) { + x.a(); + x.b(); + } else { + } +} + +function negativeTestClassesWithMemberMissingInBothClasses(x: AWithMethod | BWithMethod) { + if ("c" in x) { + x.a(); + x.b(); + } else { + x.a(); + x.b(); + } +} + +class C { a: string } +class D { a: string } + +function negativeMultipleClassesTest(x: A | B | C | D) { + if ("a" in x) { + x.b = "1"; + } else { + x.a = "1"; + } +} + +class ClassWithProp { prop: A | B } + +function negativePropTest(x: ClassWithProp) { + if ("a" in x.prop) { + let y: string = x.prop.b; + } else { + let z: string = x.prop.a; + } +} + +class NegativeClassTest { + protected prop: A | B; + inThis() { + if ('a' in this.prop) { + let z: number = this.prop.b; + } else { + let y: string = this.prop.a; + } + } +} + +class UnreachableCodeDetection { + a: string; + inThis() { + if ('a' in this) { + } else { + let y = this.a; + } + } +} + +//// [inKeywordTypeguard.js] +var A = (function () { + function A() { + } + return A; +}()); +var B = (function () { + function B() { + } + return B; +}()); +function negativeClassesTest(x) { + if ("a" in x) { + x.b = "1"; + } + else { + x.a = "1"; + } +} +function positiveClassesTest(x) { + if ("a" in x) { + x.b = "1"; + } + else { + x.a = "1"; + } +} +var AOpt = (function () { + function AOpt() { + } + return AOpt; +}()); +var BOpn = (function () { + function BOpn() { + } + return BOpn; +}()); +function positiveTestClassesWithOptionalProperties(x) { + if ("a" in x) { + x.a = "1"; + } + else { + x.b = "1"; + } +} +var AWithMethod = (function () { + function AWithMethod() { + } + AWithMethod.prototype.a = function () { return ""; }; + return AWithMethod; +}()); +var BWithMethod = (function () { + function BWithMethod() { + } + BWithMethod.prototype.b = function () { return ""; }; + return BWithMethod; +}()); +function negativeTestClassesWithMembers(x) { + if ("a" in x) { + x.a(); + x.b(); + } + else { + } +} +function negativeTestClassesWithMemberMissingInBothClasses(x) { + if ("c" in x) { + x.a(); + x.b(); + } + else { + x.a(); + x.b(); + } +} +var C = (function () { + function C() { + } + return C; +}()); +var D = (function () { + function D() { + } + return D; +}()); +function negativeMultipleClassesTest(x) { + if ("a" in x) { + x.b = "1"; + } + else { + x.a = "1"; + } +} +var ClassWithProp = (function () { + function ClassWithProp() { + } + return ClassWithProp; +}()); +function negativePropTest(x) { + if ("a" in x.prop) { + var y = x.prop.b; + } + else { + var z = x.prop.a; + } +} +var NegativeClassTest = (function () { + function NegativeClassTest() { + } + NegativeClassTest.prototype.inThis = function () { + if ('a' in this.prop) { + var z = this.prop.b; + } + else { + var y = this.prop.a; + } + }; + return NegativeClassTest; +}()); +var UnreachableCodeDetection = (function () { + function UnreachableCodeDetection() { + } + UnreachableCodeDetection.prototype.inThis = function () { + if ('a' in this) { + } + else { + var y = this.a; + } + }; + return UnreachableCodeDetection; +}()); diff --git a/tests/baselines/reference/typeGuardOfFromPropNameInUnionType.js b/tests/baselines/reference/typeGuardOfFromPropNameInUnionType.js new file mode 100644 index 00000000000..644abfaf33f --- /dev/null +++ b/tests/baselines/reference/typeGuardOfFromPropNameInUnionType.js @@ -0,0 +1,184 @@ +//// [typeGuardOfFromPropNameInUnionType.ts] +class A { a: string; } +class B { b: number; } +class C { b: Object; } +class D { a: Date; } +class ClassWithProp { prop: A | B } +class NestedClassWithProp { outer: ClassWithProp } + +function namedClasses(x: A | B) { + if ("a" in x) { + x.a = "1"; + } else { + x.b = 1; + } +} + +function multipleClasses(x: A | B | C | D) { + if ("a" in x) { + let y: string | Date = x.a; + } else { + let z: number | Object = x.b; + } +} + +function anonymousClasses(x: { a: string; } | { b: number; }) { + if ("a" in x) { + let y: string = x.a; + } else { + let z: number = x.b; + } +} +function inParenthesizedExpression(x: A | B) { + if ("a" in (x)) { + let y: string = x.a; + } else { + let z: number = x.b; + } +} + + +function inProperty(x: ClassWithProp) { + if ("a" in x.prop) { + let y: string = x.prop.a; + } else { + let z: number = x.prop.b; + } +} + + +function innestedProperty(x: NestedClassWithProp) { + if ("a" in x.outer.prop) { + let y: string = x.outer.prop.a; + } else { + let z: number = x.outer.prop.b; + } +} + +class InMemberOfClass { + protected prop: A | B; + inThis() { + if ('a' in this.prop) { + let y: string = this.prop.a; + } else { + let z: number = this.prop.b; + } + } +} + +//added for completeness +class SelfAssert { + a: string; + inThis() { + if ('a' in this) { + let y: string = this.a; + } else { + } + } +} + +//// [typeGuardOfFromPropNameInUnionType.js] +var A = (function () { + function A() { + } + return A; +}()); +var B = (function () { + function B() { + } + return B; +}()); +var C = (function () { + function C() { + } + return C; +}()); +var D = (function () { + function D() { + } + return D; +}()); +var ClassWithProp = (function () { + function ClassWithProp() { + } + return ClassWithProp; +}()); +var NestedClassWithProp = (function () { + function NestedClassWithProp() { + } + return NestedClassWithProp; +}()); +function namedClasses(x) { + if ("a" in x) { + x.a = "1"; + } + else { + x.b = 1; + } +} +function multipleClasses(x) { + if ("a" in x) { + var y = x.a; + } + else { + var z = x.b; + } +} +function anonymousClasses(x) { + if ("a" in x) { + var y = x.a; + } + else { + var z = x.b; + } +} +function inParenthesizedExpression(x) { + if ("a" in (x)) { + var y = x.a; + } + else { + var z = x.b; + } +} +function inProperty(x) { + if ("a" in x.prop) { + var y = x.prop.a; + } + else { + var z = x.prop.b; + } +} +function innestedProperty(x) { + if ("a" in x.outer.prop) { + var y = x.outer.prop.a; + } + else { + var z = x.outer.prop.b; + } +} +var InMemberOfClass = (function () { + function InMemberOfClass() { + } + InMemberOfClass.prototype.inThis = function () { + if ('a' in this.prop) { + var y = this.prop.a; + } + else { + var z = this.prop.b; + } + }; + return InMemberOfClass; +}()); +//added for completeness +var SelfAssert = (function () { + function SelfAssert() { + } + SelfAssert.prototype.inThis = function () { + if ('a' in this) { + var y = this.a; + } + else { + } + }; + return SelfAssert; +}()); diff --git a/tests/baselines/reference/typeGuardOfFromPropNameInUnionType.symbols b/tests/baselines/reference/typeGuardOfFromPropNameInUnionType.symbols new file mode 100644 index 00000000000..0302635b558 --- /dev/null +++ b/tests/baselines/reference/typeGuardOfFromPropNameInUnionType.symbols @@ -0,0 +1,252 @@ +=== tests/cases/conformance/expressions/typeGuards/typeGuardOfFromPropNameInUnionType.ts === +class A { a: string; } +>A : Symbol(A, Decl(typeGuardOfFromPropNameInUnionType.ts, 0, 0)) +>a : Symbol(A.a, Decl(typeGuardOfFromPropNameInUnionType.ts, 0, 9)) + +class B { b: number; } +>B : Symbol(B, Decl(typeGuardOfFromPropNameInUnionType.ts, 0, 22)) +>b : Symbol(B.b, Decl(typeGuardOfFromPropNameInUnionType.ts, 1, 9)) + +class C { b: Object; } +>C : Symbol(C, Decl(typeGuardOfFromPropNameInUnionType.ts, 1, 22)) +>b : Symbol(C.b, Decl(typeGuardOfFromPropNameInUnionType.ts, 2, 9)) +>Object : Symbol(Object, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + +class D { a: Date; } +>D : Symbol(D, Decl(typeGuardOfFromPropNameInUnionType.ts, 2, 22)) +>a : Symbol(D.a, Decl(typeGuardOfFromPropNameInUnionType.ts, 3, 9)) +>Date : Symbol(Date, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + +class ClassWithProp { prop: A | B } +>ClassWithProp : Symbol(ClassWithProp, Decl(typeGuardOfFromPropNameInUnionType.ts, 3, 20)) +>prop : Symbol(ClassWithProp.prop, Decl(typeGuardOfFromPropNameInUnionType.ts, 4, 21)) +>A : Symbol(A, Decl(typeGuardOfFromPropNameInUnionType.ts, 0, 0)) +>B : Symbol(B, Decl(typeGuardOfFromPropNameInUnionType.ts, 0, 22)) + +class NestedClassWithProp { outer: ClassWithProp } +>NestedClassWithProp : Symbol(NestedClassWithProp, Decl(typeGuardOfFromPropNameInUnionType.ts, 4, 35)) +>outer : Symbol(NestedClassWithProp.outer, Decl(typeGuardOfFromPropNameInUnionType.ts, 5, 27)) +>ClassWithProp : Symbol(ClassWithProp, Decl(typeGuardOfFromPropNameInUnionType.ts, 3, 20)) + +function namedClasses(x: A | B) { +>namedClasses : Symbol(namedClasses, Decl(typeGuardOfFromPropNameInUnionType.ts, 5, 50)) +>x : Symbol(x, Decl(typeGuardOfFromPropNameInUnionType.ts, 7, 22)) +>A : Symbol(A, Decl(typeGuardOfFromPropNameInUnionType.ts, 0, 0)) +>B : Symbol(B, Decl(typeGuardOfFromPropNameInUnionType.ts, 0, 22)) + + if ("a" in x) { +>x : Symbol(x, Decl(typeGuardOfFromPropNameInUnionType.ts, 7, 22)) + + x.a = "1"; +>x.a : Symbol(A.a, Decl(typeGuardOfFromPropNameInUnionType.ts, 0, 9)) +>x : Symbol(x, Decl(typeGuardOfFromPropNameInUnionType.ts, 7, 22)) +>a : Symbol(A.a, Decl(typeGuardOfFromPropNameInUnionType.ts, 0, 9)) + + } else { + x.b = 1; +>x.b : Symbol(B.b, Decl(typeGuardOfFromPropNameInUnionType.ts, 1, 9)) +>x : Symbol(x, Decl(typeGuardOfFromPropNameInUnionType.ts, 7, 22)) +>b : Symbol(B.b, Decl(typeGuardOfFromPropNameInUnionType.ts, 1, 9)) + } +} + +function multipleClasses(x: A | B | C | D) { +>multipleClasses : Symbol(multipleClasses, Decl(typeGuardOfFromPropNameInUnionType.ts, 13, 1)) +>x : Symbol(x, Decl(typeGuardOfFromPropNameInUnionType.ts, 15, 25)) +>A : Symbol(A, Decl(typeGuardOfFromPropNameInUnionType.ts, 0, 0)) +>B : Symbol(B, Decl(typeGuardOfFromPropNameInUnionType.ts, 0, 22)) +>C : Symbol(C, Decl(typeGuardOfFromPropNameInUnionType.ts, 1, 22)) +>D : Symbol(D, Decl(typeGuardOfFromPropNameInUnionType.ts, 2, 22)) + + if ("a" in x) { +>x : Symbol(x, Decl(typeGuardOfFromPropNameInUnionType.ts, 15, 25)) + + let y: string | Date = x.a; +>y : Symbol(y, Decl(typeGuardOfFromPropNameInUnionType.ts, 17, 11)) +>Date : Symbol(Date, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>x.a : Symbol(a, Decl(typeGuardOfFromPropNameInUnionType.ts, 0, 9), Decl(typeGuardOfFromPropNameInUnionType.ts, 3, 9)) +>x : Symbol(x, Decl(typeGuardOfFromPropNameInUnionType.ts, 15, 25)) +>a : Symbol(a, Decl(typeGuardOfFromPropNameInUnionType.ts, 0, 9), Decl(typeGuardOfFromPropNameInUnionType.ts, 3, 9)) + + } else { + let z: number | Object = x.b; +>z : Symbol(z, Decl(typeGuardOfFromPropNameInUnionType.ts, 19, 11)) +>Object : Symbol(Object, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>x.b : Symbol(b, Decl(typeGuardOfFromPropNameInUnionType.ts, 1, 9), Decl(typeGuardOfFromPropNameInUnionType.ts, 2, 9)) +>x : Symbol(x, Decl(typeGuardOfFromPropNameInUnionType.ts, 15, 25)) +>b : Symbol(b, Decl(typeGuardOfFromPropNameInUnionType.ts, 1, 9), Decl(typeGuardOfFromPropNameInUnionType.ts, 2, 9)) + } +} + +function anonymousClasses(x: { a: string; } | { b: number; }) { +>anonymousClasses : Symbol(anonymousClasses, Decl(typeGuardOfFromPropNameInUnionType.ts, 21, 1)) +>x : Symbol(x, Decl(typeGuardOfFromPropNameInUnionType.ts, 23, 26)) +>a : Symbol(a, Decl(typeGuardOfFromPropNameInUnionType.ts, 23, 30)) +>b : Symbol(b, Decl(typeGuardOfFromPropNameInUnionType.ts, 23, 47)) + + if ("a" in x) { +>x : Symbol(x, Decl(typeGuardOfFromPropNameInUnionType.ts, 23, 26)) + + let y: string = x.a; +>y : Symbol(y, Decl(typeGuardOfFromPropNameInUnionType.ts, 25, 11)) +>x.a : Symbol(a, Decl(typeGuardOfFromPropNameInUnionType.ts, 23, 30)) +>x : Symbol(x, Decl(typeGuardOfFromPropNameInUnionType.ts, 23, 26)) +>a : Symbol(a, Decl(typeGuardOfFromPropNameInUnionType.ts, 23, 30)) + + } else { + let z: number = x.b; +>z : Symbol(z, Decl(typeGuardOfFromPropNameInUnionType.ts, 27, 11)) +>x.b : Symbol(b, Decl(typeGuardOfFromPropNameInUnionType.ts, 23, 47)) +>x : Symbol(x, Decl(typeGuardOfFromPropNameInUnionType.ts, 23, 26)) +>b : Symbol(b, Decl(typeGuardOfFromPropNameInUnionType.ts, 23, 47)) + } +} +function inParenthesizedExpression(x: A | B) { +>inParenthesizedExpression : Symbol(inParenthesizedExpression, Decl(typeGuardOfFromPropNameInUnionType.ts, 29, 1)) +>x : Symbol(x, Decl(typeGuardOfFromPropNameInUnionType.ts, 30, 35)) +>A : Symbol(A, Decl(typeGuardOfFromPropNameInUnionType.ts, 0, 0)) +>B : Symbol(B, Decl(typeGuardOfFromPropNameInUnionType.ts, 0, 22)) + + if ("a" in (x)) { +>x : Symbol(x, Decl(typeGuardOfFromPropNameInUnionType.ts, 30, 35)) + + let y: string = x.a; +>y : Symbol(y, Decl(typeGuardOfFromPropNameInUnionType.ts, 32, 11)) +>x.a : Symbol(A.a, Decl(typeGuardOfFromPropNameInUnionType.ts, 0, 9)) +>x : Symbol(x, Decl(typeGuardOfFromPropNameInUnionType.ts, 30, 35)) +>a : Symbol(A.a, Decl(typeGuardOfFromPropNameInUnionType.ts, 0, 9)) + + } else { + let z: number = x.b; +>z : Symbol(z, Decl(typeGuardOfFromPropNameInUnionType.ts, 34, 11)) +>x.b : Symbol(B.b, Decl(typeGuardOfFromPropNameInUnionType.ts, 1, 9)) +>x : Symbol(x, Decl(typeGuardOfFromPropNameInUnionType.ts, 30, 35)) +>b : Symbol(B.b, Decl(typeGuardOfFromPropNameInUnionType.ts, 1, 9)) + } +} + + +function inProperty(x: ClassWithProp) { +>inProperty : Symbol(inProperty, Decl(typeGuardOfFromPropNameInUnionType.ts, 36, 1)) +>x : Symbol(x, Decl(typeGuardOfFromPropNameInUnionType.ts, 39, 20)) +>ClassWithProp : Symbol(ClassWithProp, Decl(typeGuardOfFromPropNameInUnionType.ts, 3, 20)) + + if ("a" in x.prop) { +>x.prop : Symbol(ClassWithProp.prop, Decl(typeGuardOfFromPropNameInUnionType.ts, 4, 21)) +>x : Symbol(x, Decl(typeGuardOfFromPropNameInUnionType.ts, 39, 20)) +>prop : Symbol(ClassWithProp.prop, Decl(typeGuardOfFromPropNameInUnionType.ts, 4, 21)) + + let y: string = x.prop.a; +>y : Symbol(y, Decl(typeGuardOfFromPropNameInUnionType.ts, 41, 11)) +>x.prop.a : Symbol(A.a, Decl(typeGuardOfFromPropNameInUnionType.ts, 0, 9)) +>x.prop : Symbol(ClassWithProp.prop, Decl(typeGuardOfFromPropNameInUnionType.ts, 4, 21)) +>x : Symbol(x, Decl(typeGuardOfFromPropNameInUnionType.ts, 39, 20)) +>prop : Symbol(ClassWithProp.prop, Decl(typeGuardOfFromPropNameInUnionType.ts, 4, 21)) +>a : Symbol(A.a, Decl(typeGuardOfFromPropNameInUnionType.ts, 0, 9)) + + } else { + let z: number = x.prop.b; +>z : Symbol(z, Decl(typeGuardOfFromPropNameInUnionType.ts, 43, 11)) +>x.prop.b : Symbol(B.b, Decl(typeGuardOfFromPropNameInUnionType.ts, 1, 9)) +>x.prop : Symbol(ClassWithProp.prop, Decl(typeGuardOfFromPropNameInUnionType.ts, 4, 21)) +>x : Symbol(x, Decl(typeGuardOfFromPropNameInUnionType.ts, 39, 20)) +>prop : Symbol(ClassWithProp.prop, Decl(typeGuardOfFromPropNameInUnionType.ts, 4, 21)) +>b : Symbol(B.b, Decl(typeGuardOfFromPropNameInUnionType.ts, 1, 9)) + } +} + + +function innestedProperty(x: NestedClassWithProp) { +>innestedProperty : Symbol(innestedProperty, Decl(typeGuardOfFromPropNameInUnionType.ts, 45, 1)) +>x : Symbol(x, Decl(typeGuardOfFromPropNameInUnionType.ts, 48, 26)) +>NestedClassWithProp : Symbol(NestedClassWithProp, Decl(typeGuardOfFromPropNameInUnionType.ts, 4, 35)) + + if ("a" in x.outer.prop) { +>x.outer.prop : Symbol(ClassWithProp.prop, Decl(typeGuardOfFromPropNameInUnionType.ts, 4, 21)) +>x.outer : Symbol(NestedClassWithProp.outer, Decl(typeGuardOfFromPropNameInUnionType.ts, 5, 27)) +>x : Symbol(x, Decl(typeGuardOfFromPropNameInUnionType.ts, 48, 26)) +>outer : Symbol(NestedClassWithProp.outer, Decl(typeGuardOfFromPropNameInUnionType.ts, 5, 27)) +>prop : Symbol(ClassWithProp.prop, Decl(typeGuardOfFromPropNameInUnionType.ts, 4, 21)) + + let y: string = x.outer.prop.a; +>y : Symbol(y, Decl(typeGuardOfFromPropNameInUnionType.ts, 50, 11)) +>x.outer.prop.a : Symbol(A.a, Decl(typeGuardOfFromPropNameInUnionType.ts, 0, 9)) +>x.outer.prop : Symbol(ClassWithProp.prop, Decl(typeGuardOfFromPropNameInUnionType.ts, 4, 21)) +>x.outer : Symbol(NestedClassWithProp.outer, Decl(typeGuardOfFromPropNameInUnionType.ts, 5, 27)) +>x : Symbol(x, Decl(typeGuardOfFromPropNameInUnionType.ts, 48, 26)) +>outer : Symbol(NestedClassWithProp.outer, Decl(typeGuardOfFromPropNameInUnionType.ts, 5, 27)) +>prop : Symbol(ClassWithProp.prop, Decl(typeGuardOfFromPropNameInUnionType.ts, 4, 21)) +>a : Symbol(A.a, Decl(typeGuardOfFromPropNameInUnionType.ts, 0, 9)) + + } else { + let z: number = x.outer.prop.b; +>z : Symbol(z, Decl(typeGuardOfFromPropNameInUnionType.ts, 52, 11)) +>x.outer.prop.b : Symbol(B.b, Decl(typeGuardOfFromPropNameInUnionType.ts, 1, 9)) +>x.outer.prop : Symbol(ClassWithProp.prop, Decl(typeGuardOfFromPropNameInUnionType.ts, 4, 21)) +>x.outer : Symbol(NestedClassWithProp.outer, Decl(typeGuardOfFromPropNameInUnionType.ts, 5, 27)) +>x : Symbol(x, Decl(typeGuardOfFromPropNameInUnionType.ts, 48, 26)) +>outer : Symbol(NestedClassWithProp.outer, Decl(typeGuardOfFromPropNameInUnionType.ts, 5, 27)) +>prop : Symbol(ClassWithProp.prop, Decl(typeGuardOfFromPropNameInUnionType.ts, 4, 21)) +>b : Symbol(B.b, Decl(typeGuardOfFromPropNameInUnionType.ts, 1, 9)) + } +} + +class InMemberOfClass { +>InMemberOfClass : Symbol(InMemberOfClass, Decl(typeGuardOfFromPropNameInUnionType.ts, 54, 1)) + + protected prop: A | B; +>prop : Symbol(InMemberOfClass.prop, Decl(typeGuardOfFromPropNameInUnionType.ts, 56, 23)) +>A : Symbol(A, Decl(typeGuardOfFromPropNameInUnionType.ts, 0, 0)) +>B : Symbol(B, Decl(typeGuardOfFromPropNameInUnionType.ts, 0, 22)) + + inThis() { +>inThis : Symbol(InMemberOfClass.inThis, Decl(typeGuardOfFromPropNameInUnionType.ts, 57, 26)) + + if ('a' in this.prop) { +>this.prop : Symbol(InMemberOfClass.prop, Decl(typeGuardOfFromPropNameInUnionType.ts, 56, 23)) +>this : Symbol(InMemberOfClass, Decl(typeGuardOfFromPropNameInUnionType.ts, 54, 1)) +>prop : Symbol(InMemberOfClass.prop, Decl(typeGuardOfFromPropNameInUnionType.ts, 56, 23)) + + let y: string = this.prop.a; +>y : Symbol(y, Decl(typeGuardOfFromPropNameInUnionType.ts, 60, 15)) +>this.prop.a : Symbol(A.a, Decl(typeGuardOfFromPropNameInUnionType.ts, 0, 9)) +>this.prop : Symbol(InMemberOfClass.prop, Decl(typeGuardOfFromPropNameInUnionType.ts, 56, 23)) +>this : Symbol(InMemberOfClass, Decl(typeGuardOfFromPropNameInUnionType.ts, 54, 1)) +>prop : Symbol(InMemberOfClass.prop, Decl(typeGuardOfFromPropNameInUnionType.ts, 56, 23)) +>a : Symbol(A.a, Decl(typeGuardOfFromPropNameInUnionType.ts, 0, 9)) + + } else { + let z: number = this.prop.b; +>z : Symbol(z, Decl(typeGuardOfFromPropNameInUnionType.ts, 62, 15)) +>this.prop.b : Symbol(B.b, Decl(typeGuardOfFromPropNameInUnionType.ts, 1, 9)) +>this.prop : Symbol(InMemberOfClass.prop, Decl(typeGuardOfFromPropNameInUnionType.ts, 56, 23)) +>this : Symbol(InMemberOfClass, Decl(typeGuardOfFromPropNameInUnionType.ts, 54, 1)) +>prop : Symbol(InMemberOfClass.prop, Decl(typeGuardOfFromPropNameInUnionType.ts, 56, 23)) +>b : Symbol(B.b, Decl(typeGuardOfFromPropNameInUnionType.ts, 1, 9)) + } + } +} + +//added for completeness +class SelfAssert { +>SelfAssert : Symbol(SelfAssert, Decl(typeGuardOfFromPropNameInUnionType.ts, 65, 1)) + + a: string; +>a : Symbol(SelfAssert.a, Decl(typeGuardOfFromPropNameInUnionType.ts, 68, 18)) + + inThis() { +>inThis : Symbol(SelfAssert.inThis, Decl(typeGuardOfFromPropNameInUnionType.ts, 69, 14)) + + if ('a' in this) { +>this : Symbol(SelfAssert, Decl(typeGuardOfFromPropNameInUnionType.ts, 65, 1)) + + let y: string = this.a; +>y : Symbol(y, Decl(typeGuardOfFromPropNameInUnionType.ts, 72, 15)) +>this.a : Symbol(SelfAssert.a, Decl(typeGuardOfFromPropNameInUnionType.ts, 68, 18)) +>this : Symbol(SelfAssert, Decl(typeGuardOfFromPropNameInUnionType.ts, 65, 1)) +>a : Symbol(SelfAssert.a, Decl(typeGuardOfFromPropNameInUnionType.ts, 68, 18)) + + } else { + } + } +} diff --git a/tests/baselines/reference/typeGuardOfFromPropNameInUnionType.types b/tests/baselines/reference/typeGuardOfFromPropNameInUnionType.types new file mode 100644 index 00000000000..d9984840fb3 --- /dev/null +++ b/tests/baselines/reference/typeGuardOfFromPropNameInUnionType.types @@ -0,0 +1,273 @@ +=== tests/cases/conformance/expressions/typeGuards/typeGuardOfFromPropNameInUnionType.ts === +class A { a: string; } +>A : A +>a : string + +class B { b: number; } +>B : B +>b : number + +class C { b: Object; } +>C : C +>b : Object +>Object : Object + +class D { a: Date; } +>D : D +>a : Date +>Date : Date + +class ClassWithProp { prop: A | B } +>ClassWithProp : ClassWithProp +>prop : A | B +>A : A +>B : B + +class NestedClassWithProp { outer: ClassWithProp } +>NestedClassWithProp : NestedClassWithProp +>outer : ClassWithProp +>ClassWithProp : ClassWithProp + +function namedClasses(x: A | B) { +>namedClasses : (x: A | B) => void +>x : A | B +>A : A +>B : B + + if ("a" in x) { +>"a" in x : boolean +>"a" : "a" +>x : A | B + + x.a = "1"; +>x.a = "1" : "1" +>x.a : string +>x : A +>a : string +>"1" : "1" + + } else { + x.b = 1; +>x.b = 1 : 1 +>x.b : number +>x : B +>b : number +>1 : 1 + } +} + +function multipleClasses(x: A | B | C | D) { +>multipleClasses : (x: A | B | C | D) => void +>x : A | B | C | D +>A : A +>B : B +>C : C +>D : D + + if ("a" in x) { +>"a" in x : boolean +>"a" : "a" +>x : A | B | C | D + + let y: string | Date = x.a; +>y : string | Date +>Date : Date +>x.a : string | Date +>x : A | D +>a : string | Date + + } else { + let z: number | Object = x.b; +>z : number | Object +>Object : Object +>x.b : number | Object +>x : B | C +>b : number | Object + } +} + +function anonymousClasses(x: { a: string; } | { b: number; }) { +>anonymousClasses : (x: { a: string; } | { b: number; }) => void +>x : { a: string; } | { b: number; } +>a : string +>b : number + + if ("a" in x) { +>"a" in x : boolean +>"a" : "a" +>x : { a: string; } | { b: number; } + + let y: string = x.a; +>y : string +>x.a : string +>x : { a: string; } +>a : string + + } else { + let z: number = x.b; +>z : number +>x.b : number +>x : { b: number; } +>b : number + } +} +function inParenthesizedExpression(x: A | B) { +>inParenthesizedExpression : (x: A | B) => void +>x : A | B +>A : A +>B : B + + if ("a" in (x)) { +>"a" in (x) : boolean +>"a" : "a" +>(x) : A | B +>x : A | B + + let y: string = x.a; +>y : string +>x.a : string +>x : A +>a : string + + } else { + let z: number = x.b; +>z : number +>x.b : number +>x : B +>b : number + } +} + + +function inProperty(x: ClassWithProp) { +>inProperty : (x: ClassWithProp) => void +>x : ClassWithProp +>ClassWithProp : ClassWithProp + + if ("a" in x.prop) { +>"a" in x.prop : boolean +>"a" : "a" +>x.prop : A | B +>x : ClassWithProp +>prop : A | B + + let y: string = x.prop.a; +>y : string +>x.prop.a : string +>x.prop : A +>x : ClassWithProp +>prop : A +>a : string + + } else { + let z: number = x.prop.b; +>z : number +>x.prop.b : number +>x.prop : B +>x : ClassWithProp +>prop : B +>b : number + } +} + + +function innestedProperty(x: NestedClassWithProp) { +>innestedProperty : (x: NestedClassWithProp) => void +>x : NestedClassWithProp +>NestedClassWithProp : NestedClassWithProp + + if ("a" in x.outer.prop) { +>"a" in x.outer.prop : boolean +>"a" : "a" +>x.outer.prop : A | B +>x.outer : ClassWithProp +>x : NestedClassWithProp +>outer : ClassWithProp +>prop : A | B + + let y: string = x.outer.prop.a; +>y : string +>x.outer.prop.a : string +>x.outer.prop : A +>x.outer : ClassWithProp +>x : NestedClassWithProp +>outer : ClassWithProp +>prop : A +>a : string + + } else { + let z: number = x.outer.prop.b; +>z : number +>x.outer.prop.b : number +>x.outer.prop : B +>x.outer : ClassWithProp +>x : NestedClassWithProp +>outer : ClassWithProp +>prop : B +>b : number + } +} + +class InMemberOfClass { +>InMemberOfClass : InMemberOfClass + + protected prop: A | B; +>prop : A | B +>A : A +>B : B + + inThis() { +>inThis : () => void + + if ('a' in this.prop) { +>'a' in this.prop : boolean +>'a' : "a" +>this.prop : A | B +>this : this +>prop : A | B + + let y: string = this.prop.a; +>y : string +>this.prop.a : string +>this.prop : A +>this : this +>prop : A +>a : string + + } else { + let z: number = this.prop.b; +>z : number +>this.prop.b : number +>this.prop : B +>this : this +>prop : B +>b : number + } + } +} + +//added for completeness +class SelfAssert { +>SelfAssert : SelfAssert + + a: string; +>a : string + + inThis() { +>inThis : () => void + + if ('a' in this) { +>'a' in this : boolean +>'a' : "a" +>this : this + + let y: string = this.a; +>y : string +>this.a : string +>this : this +>a : string + + } else { + } + } +} diff --git a/tests/cases/compiler/inKeywordTypeguard.ts b/tests/cases/compiler/inKeywordTypeguard.ts new file mode 100644 index 00000000000..90d1942e7c5 --- /dev/null +++ b/tests/cases/compiler/inKeywordTypeguard.ts @@ -0,0 +1,97 @@ +class A { a: string; } +class B { b: string; } + +function negativeClassesTest(x: A | B) { + if ("a" in x) { + x.b = "1"; + } else { + x.a = "1"; + } +} + +function positiveClassesTest(x: A | B) { + if ("a" in x) { + x.b = "1"; + } else { + x.a = "1"; + } +} + +class AOpt { a?: string } +class BOpn { b?: string } + +function positiveTestClassesWithOptionalProperties(x: AOpt | BOpn) { + if ("a" in x) { + x.a = "1"; + } else { + x.b = "1"; + } +} + +class AWithMethod { + a(): string { return "" } +} + +class BWithMethod { + b(): string { return "" } +} + +function negativeTestClassesWithMembers(x: AWithMethod | BWithMethod) { + if ("a" in x) { + x.a(); + x.b(); + } else { + } +} + +function negativeTestClassesWithMemberMissingInBothClasses(x: AWithMethod | BWithMethod) { + if ("c" in x) { + x.a(); + x.b(); + } else { + x.a(); + x.b(); + } +} + +class C { a: string } +class D { a: string } + +function negativeMultipleClassesTest(x: A | B | C | D) { + if ("a" in x) { + x.b = "1"; + } else { + x.a = "1"; + } +} + +class ClassWithProp { prop: A | B } + +function negativePropTest(x: ClassWithProp) { + if ("a" in x.prop) { + let y: string = x.prop.b; + } else { + let z: string = x.prop.a; + } +} + +class NegativeClassTest { + protected prop: A | B; + inThis() { + if ('a' in this.prop) { + let z: number = this.prop.b; + } else { + let y: string = this.prop.a; + } + } +} + +class UnreachableCodeDetection { + a: string; + inThis() { + if ('a' in this) { + } else { + let y = this.a; + } + } +} \ No newline at end of file diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardOfFromPropNameInUnionType.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardOfFromPropNameInUnionType.ts new file mode 100644 index 00000000000..2a14fd2aea8 --- /dev/null +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardOfFromPropNameInUnionType.ts @@ -0,0 +1,77 @@ +class A { a: string; } +class B { b: number; } +class C { b: Object; } +class D { a: Date; } +class ClassWithProp { prop: A | B } +class NestedClassWithProp { outer: ClassWithProp } + +function namedClasses(x: A | B) { + if ("a" in x) { + x.a = "1"; + } else { + x.b = 1; + } +} + +function multipleClasses(x: A | B | C | D) { + if ("a" in x) { + let y: string | Date = x.a; + } else { + let z: number | Object = x.b; + } +} + +function anonymousClasses(x: { a: string; } | { b: number; }) { + if ("a" in x) { + let y: string = x.a; + } else { + let z: number = x.b; + } +} +function inParenthesizedExpression(x: A | B) { + if ("a" in (x)) { + let y: string = x.a; + } else { + let z: number = x.b; + } +} + + +function inProperty(x: ClassWithProp) { + if ("a" in x.prop) { + let y: string = x.prop.a; + } else { + let z: number = x.prop.b; + } +} + + +function innestedProperty(x: NestedClassWithProp) { + if ("a" in x.outer.prop) { + let y: string = x.outer.prop.a; + } else { + let z: number = x.outer.prop.b; + } +} + +class InMemberOfClass { + protected prop: A | B; + inThis() { + if ('a' in this.prop) { + let y: string = this.prop.a; + } else { + let z: number = this.prop.b; + } + } +} + +//added for completeness +class SelfAssert { + a: string; + inThis() { + if ('a' in this) { + let y: string = this.a; + } else { + } + } +} \ No newline at end of file