diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4ddec362457b4..78493426508ad 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20310,11 +20310,32 @@ namespace ts { function narrowByInKeyword(type: Type, literal: LiteralExpression, assumeTrue: boolean) { if (type.flags & (TypeFlags.Union | TypeFlags.Object | TypeFlags.Intersection) || isThisTypeParameter(type)) { const propName = escapeLeadingUnderscores(literal.text); - return filterType(type, t => isTypePresencePossible(t, propName, assumeTrue)); + const resultType = filterType(type, t => isTypePresencePossible(t, propName, assumeTrue)); + if (!assumeTrue || resultType !== neverType) { + return resultType; + } + return narrowTypeByInKeywordWithExistedPropName(type, propName); } return type; } + function narrowTypeByInKeywordWithExistedPropName(type: Type, propName: __String) { + const symbolTable = createSymbolTable(); + const symbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, propName); + symbol.type = unknownType; + symbolTable.set(symbol.escapedName, symbol); + const anonymousType = createAnonymousType( + undefined, + symbolTable, + emptyArray, + emptyArray, + undefined, + undefined + ); + + return mapType(type, t => getIntersectionType([t, anonymousType])); + } + function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { switch (expr.operatorToken.kind) { case SyntaxKind.EqualsToken: diff --git a/tests/baselines/reference/inKeywordTypeguard.errors.txt b/tests/baselines/reference/inKeywordTypeguard.errors.txt index f5885acfc8835..c93369080ed44 100644 --- a/tests/baselines/reference/inKeywordTypeguard.errors.txt +++ b/tests/baselines/reference/inKeywordTypeguard.errors.txt @@ -5,8 +5,10 @@ tests/cases/compiler/inKeywordTypeguard.ts(16,11): error TS2339: Property 'a' do tests/cases/compiler/inKeywordTypeguard.ts(27,11): error TS2339: Property 'b' does not exist on type 'AWithOptionalProp | BWithOptionalProp'. Property 'b' does not exist on type 'AWithOptionalProp'. 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(49,11): error TS2339: Property 'a' does not exist on type '(AWithMethod & { c: unknown; }) | (BWithMethod & { c: unknown; })'. + Property 'a' does not exist on type 'BWithMethod & { c: unknown; }'. +tests/cases/compiler/inKeywordTypeguard.ts(50,11): error TS2339: Property 'b' does not exist on type '(AWithMethod & { c: unknown; }) | (BWithMethod & { c: unknown; })'. + Property 'b' does not exist on type 'AWithMethod & { c: unknown; }'. 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'. @@ -85,10 +87,12 @@ tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' do if ("c" in x) { x.a(); ~ -!!! error TS2339: Property 'a' does not exist on type 'never'. +!!! error TS2339: Property 'a' does not exist on type '(AWithMethod & { c: unknown; }) | (BWithMethod & { c: unknown; })'. +!!! error TS2339: Property 'a' does not exist on type 'BWithMethod & { c: unknown; }'. x.b(); ~ -!!! error TS2339: Property 'b' does not exist on type 'never'. +!!! error TS2339: Property 'b' does not exist on type '(AWithMethod & { c: unknown; }) | (BWithMethod & { c: unknown; })'. +!!! error TS2339: Property 'b' does not exist on type 'AWithMethod & { c: unknown; }'. } else { x.a(); ~ diff --git a/tests/baselines/reference/inKeywordTypeguard.types b/tests/baselines/reference/inKeywordTypeguard.types index bd77a8da45ede..b11b360dfffc0 100644 --- a/tests/baselines/reference/inKeywordTypeguard.types +++ b/tests/baselines/reference/inKeywordTypeguard.types @@ -146,13 +146,13 @@ function negativeTestClassesWithMemberMissingInBothClasses(x: AWithMethod | BWit x.a(); >x.a() : any >x.a : any ->x : never +>x : (AWithMethod & { c: unknown; }) | (BWithMethod & { c: unknown; }) >a : any x.b(); >x.b() : any >x.b : any ->x : never +>x : (AWithMethod & { c: unknown; }) | (BWithMethod & { c: unknown; }) >b : any } else { diff --git a/tests/baselines/reference/narrowiinigInWithExistedProperty.errors.txt b/tests/baselines/reference/narrowiinigInWithExistedProperty.errors.txt new file mode 100644 index 0000000000000..bb83746ef5156 --- /dev/null +++ b/tests/baselines/reference/narrowiinigInWithExistedProperty.errors.txt @@ -0,0 +1,34 @@ +tests/cases/compiler/narrowiinigInWithExistedProperty.ts(23,9): error TS2339: Property 'd' does not exist on type 'never'. + + +==== tests/cases/compiler/narrowiinigInWithExistedProperty.ts (1 errors) ==== + interface A { + a: number + } + + interface B { + b: number + } + + interface C { + c: number + } + + declare const foo: A | B | C; + declare const bar: {}; + + if ('a' in foo) { + foo.a + } else if ('b' in foo) { + foo.b + } else if ('c' in foo) { + foo.c + } else if ('d' in foo) { + foo.d + ~ +!!! error TS2339: Property 'd' does not exist on type 'never'. + } + + if ('a' in bar) { + bar.a + } \ No newline at end of file diff --git a/tests/baselines/reference/narrowiinigInWithExistedProperty.js b/tests/baselines/reference/narrowiinigInWithExistedProperty.js new file mode 100644 index 0000000000000..14c4d7881404e --- /dev/null +++ b/tests/baselines/reference/narrowiinigInWithExistedProperty.js @@ -0,0 +1,47 @@ +//// [narrowiinigInWithExistedProperty.ts] +interface A { + a: number +} + +interface B { + b: number +} + +interface C { + c: number +} + +declare const foo: A | B | C; +declare const bar: {}; + +if ('a' in foo) { + foo.a +} else if ('b' in foo) { + foo.b +} else if ('c' in foo) { + foo.c +} else if ('d' in foo) { + foo.d +} + +if ('a' in bar) { + bar.a +} + +//// [narrowiinigInWithExistedProperty.js] +"use strict"; +if ('a' in foo) { + foo.a; +} +else if ('b' in foo) { + foo.b; +} +else if ('c' in foo) { + foo.c; +} +else if ('d' in foo) { + foo.d; +} +if ('a' in bar) { + bar.a; +} diff --git a/tests/baselines/reference/narrowiinigInWithExistedProperty.symbols b/tests/baselines/reference/narrowiinigInWithExistedProperty.symbols new file mode 100644 index 0000000000000..799db745a18f2 --- /dev/null +++ b/tests/baselines/reference/narrowiinigInWithExistedProperty.symbols @@ -0,0 +1,70 @@ +=== tests/cases/compiler/narrowiinigInWithExistedProperty.ts === +interface A { +>A : Symbol(A, Decl(narrowiinigInWithExistedProperty.ts, 0, 0)) + + a: number +>a : Symbol(A.a, Decl(narrowiinigInWithExistedProperty.ts, 0, 13)) +} + +interface B { +>B : Symbol(B, Decl(narrowiinigInWithExistedProperty.ts, 2, 1)) + + b: number +>b : Symbol(B.b, Decl(narrowiinigInWithExistedProperty.ts, 4, 13)) +} + +interface C { +>C : Symbol(C, Decl(narrowiinigInWithExistedProperty.ts, 6, 1)) + + c: number +>c : Symbol(C.c, Decl(narrowiinigInWithExistedProperty.ts, 8, 13)) +} + +declare const foo: A | B | C; +>foo : Symbol(foo, Decl(narrowiinigInWithExistedProperty.ts, 12, 13)) +>A : Symbol(A, Decl(narrowiinigInWithExistedProperty.ts, 0, 0)) +>B : Symbol(B, Decl(narrowiinigInWithExistedProperty.ts, 2, 1)) +>C : Symbol(C, Decl(narrowiinigInWithExistedProperty.ts, 6, 1)) + +declare const bar: {}; +>bar : Symbol(bar, Decl(narrowiinigInWithExistedProperty.ts, 13, 13)) + +if ('a' in foo) { +>foo : Symbol(foo, Decl(narrowiinigInWithExistedProperty.ts, 12, 13)) + + foo.a +>foo.a : Symbol(A.a, Decl(narrowiinigInWithExistedProperty.ts, 0, 13)) +>foo : Symbol(foo, Decl(narrowiinigInWithExistedProperty.ts, 12, 13)) +>a : Symbol(A.a, Decl(narrowiinigInWithExistedProperty.ts, 0, 13)) + +} else if ('b' in foo) { +>foo : Symbol(foo, Decl(narrowiinigInWithExistedProperty.ts, 12, 13)) + + foo.b +>foo.b : Symbol(B.b, Decl(narrowiinigInWithExistedProperty.ts, 4, 13)) +>foo : Symbol(foo, Decl(narrowiinigInWithExistedProperty.ts, 12, 13)) +>b : Symbol(B.b, Decl(narrowiinigInWithExistedProperty.ts, 4, 13)) + +} else if ('c' in foo) { +>foo : Symbol(foo, Decl(narrowiinigInWithExistedProperty.ts, 12, 13)) + + foo.c +>foo.c : Symbol(C.c, Decl(narrowiinigInWithExistedProperty.ts, 8, 13)) +>foo : Symbol(foo, Decl(narrowiinigInWithExistedProperty.ts, 12, 13)) +>c : Symbol(C.c, Decl(narrowiinigInWithExistedProperty.ts, 8, 13)) + +} else if ('d' in foo) { +>foo : Symbol(foo, Decl(narrowiinigInWithExistedProperty.ts, 12, 13)) + + foo.d +>foo : Symbol(foo, Decl(narrowiinigInWithExistedProperty.ts, 12, 13)) +} + +if ('a' in bar) { +>bar : Symbol(bar, Decl(narrowiinigInWithExistedProperty.ts, 13, 13)) + + bar.a +>bar.a : Symbol(a) +>bar : Symbol(bar, Decl(narrowiinigInWithExistedProperty.ts, 13, 13)) +>a : Symbol(a) +} diff --git a/tests/baselines/reference/narrowiinigInWithExistedProperty.types b/tests/baselines/reference/narrowiinigInWithExistedProperty.types new file mode 100644 index 0000000000000..5db42f3145d13 --- /dev/null +++ b/tests/baselines/reference/narrowiinigInWithExistedProperty.types @@ -0,0 +1,73 @@ +=== tests/cases/compiler/narrowiinigInWithExistedProperty.ts === +interface A { + a: number +>a : number +} + +interface B { + b: number +>b : number +} + +interface C { + c: number +>c : number +} + +declare const foo: A | B | C; +>foo : A | B | C + +declare const bar: {}; +>bar : {} + +if ('a' in foo) { +>'a' in foo : boolean +>'a' : "a" +>foo : A | B | C + + foo.a +>foo.a : number +>foo : A +>a : number + +} else if ('b' in foo) { +>'b' in foo : boolean +>'b' : "b" +>foo : B | C + + foo.b +>foo.b : number +>foo : B +>b : number + +} else if ('c' in foo) { +>'c' in foo : boolean +>'c' : "c" +>foo : C + + foo.c +>foo.c : number +>foo : C +>c : number + +} else if ('d' in foo) { +>'d' in foo : boolean +>'d' : "d" +>foo : never + + foo.d +>foo.d : any +>foo : never +>d : any +} + +if ('a' in bar) { +>'a' in bar : boolean +>'a' : "a" +>bar : {} + + bar.a +>bar.a : unknown +>bar : { a: unknown; } +>a : unknown +} diff --git a/tests/cases/compiler/narrowiinigInWithExistedProperty.ts b/tests/cases/compiler/narrowiinigInWithExistedProperty.ts new file mode 100644 index 0000000000000..fc3c5f2a493d6 --- /dev/null +++ b/tests/cases/compiler/narrowiinigInWithExistedProperty.ts @@ -0,0 +1,30 @@ +// @strict: true + +interface A { + a: number +} + +interface B { + b: number +} + +interface C { + c: number +} + +declare const foo: A | B | C; +declare const bar: {}; + +if ('a' in foo) { + foo.a +} else if ('b' in foo) { + foo.b +} else if ('c' in foo) { + foo.c +} else if ('d' in foo) { + foo.d +} + +if ('a' in bar) { + bar.a +} \ No newline at end of file