From ba521ce35b0a0782371ba5581ec1931e9a85d0dc Mon Sep 17 00:00:00 2001 From: kingwl Date: Thu, 9 Apr 2020 16:49:57 +0800 Subject: [PATCH 1/3] wip --- src/compiler/checker.ts | 18 ++++- ...arrowiinigInWithExistedProperty.errors.txt | 37 ++++++++++ .../narrowiinigInWithExistedProperty.js | 47 ++++++++++++ .../narrowiinigInWithExistedProperty.symbols | 70 ++++++++++++++++++ .../narrowiinigInWithExistedProperty.types | 73 +++++++++++++++++++ .../narrowiinigInWithExistedProperty.ts | 30 ++++++++ 6 files changed, 273 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/narrowiinigInWithExistedProperty.errors.txt create mode 100644 tests/baselines/reference/narrowiinigInWithExistedProperty.js create mode 100644 tests/baselines/reference/narrowiinigInWithExistedProperty.symbols create mode 100644 tests/baselines/reference/narrowiinigInWithExistedProperty.types create mode 100644 tests/cases/compiler/narrowiinigInWithExistedProperty.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4ddec362457b4..a5c2446d1b9e0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20308,11 +20308,25 @@ namespace ts { } function narrowByInKeyword(type: Type, literal: LiteralExpression, assumeTrue: boolean) { + const propName = escapeLeadingUnderscores(literal.text); if (type.flags & (TypeFlags.Union | TypeFlags.Object | TypeFlags.Intersection) || isThisTypeParameter(type)) { - const propName = escapeLeadingUnderscores(literal.text); return filterType(type, t => isTypePresencePossible(t, propName, assumeTrue)); } - return type; + + 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 getIntersectionType([type, anonymousType]); } function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { diff --git a/tests/baselines/reference/narrowiinigInWithExistedProperty.errors.txt b/tests/baselines/reference/narrowiinigInWithExistedProperty.errors.txt new file mode 100644 index 0000000000000..1d78903270119 --- /dev/null +++ b/tests/baselines/reference/narrowiinigInWithExistedProperty.errors.txt @@ -0,0 +1,37 @@ +tests/cases/compiler/narrowiinigInWithExistedProperty.ts(23,9): error TS2339: Property 'd' does not exist on type 'never'. +tests/cases/compiler/narrowiinigInWithExistedProperty.ts(26,12): error TS2571: Object is of type 'unknown'. + + +==== tests/cases/compiler/narrowiinigInWithExistedProperty.ts (2 errors) ==== + interface A { + a: number + } + + interface B { + b: number + } + + interface C { + c: number + } + + declare const foo: A | B | C; + declare const bar: unknown; + + 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) { + ~~~ +!!! error TS2571: Object is of type 'unknown'. + 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..05accdbbe5d21 --- /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: unknown; + +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..6cff2ae5f8bb5 --- /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: unknown; +>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..80483cfab24a6 --- /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: unknown; +>bar : unknown + +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 : unknown + + 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..756ffbba12b61 --- /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: unknown; + +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 From 487dff793224deb1c10571d8b86904e2301e23c0 Mon Sep 17 00:00:00 2001 From: kingwl Date: Thu, 9 Apr 2020 18:06:32 +0800 Subject: [PATCH 2/3] fix more case --- src/compiler/checker.ts | 15 +++++++++++---- .../narrowiinigInWithExistedProperty.errors.txt | 7 ++----- .../reference/narrowiinigInWithExistedProperty.js | 2 +- .../narrowiinigInWithExistedProperty.symbols | 2 +- .../narrowiinigInWithExistedProperty.types | 6 +++--- .../compiler/narrowiinigInWithExistedProperty.ts | 2 +- 6 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a5c2446d1b9e0..fe0c303f90bb8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20308,11 +20308,18 @@ namespace ts { } function narrowByInKeyword(type: Type, literal: LiteralExpression, assumeTrue: boolean) { - const propName = escapeLeadingUnderscores(literal.text); if (type.flags & (TypeFlags.Union | TypeFlags.Object | TypeFlags.Intersection) || isThisTypeParameter(type)) { - return filterType(type, t => isTypePresencePossible(t, propName, assumeTrue)); - } + const propName = escapeLeadingUnderscores(literal.text); + 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; @@ -20326,7 +20333,7 @@ namespace ts { undefined ); - return getIntersectionType([type, anonymousType]); + return mapType(type, t => getIntersectionType([t, anonymousType])); } function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { diff --git a/tests/baselines/reference/narrowiinigInWithExistedProperty.errors.txt b/tests/baselines/reference/narrowiinigInWithExistedProperty.errors.txt index 1d78903270119..bb83746ef5156 100644 --- a/tests/baselines/reference/narrowiinigInWithExistedProperty.errors.txt +++ b/tests/baselines/reference/narrowiinigInWithExistedProperty.errors.txt @@ -1,8 +1,7 @@ tests/cases/compiler/narrowiinigInWithExistedProperty.ts(23,9): error TS2339: Property 'd' does not exist on type 'never'. -tests/cases/compiler/narrowiinigInWithExistedProperty.ts(26,12): error TS2571: Object is of type 'unknown'. -==== tests/cases/compiler/narrowiinigInWithExistedProperty.ts (2 errors) ==== +==== tests/cases/compiler/narrowiinigInWithExistedProperty.ts (1 errors) ==== interface A { a: number } @@ -16,7 +15,7 @@ tests/cases/compiler/narrowiinigInWithExistedProperty.ts(26,12): error TS2571: O } declare const foo: A | B | C; - declare const bar: unknown; + declare const bar: {}; if ('a' in foo) { foo.a @@ -31,7 +30,5 @@ tests/cases/compiler/narrowiinigInWithExistedProperty.ts(26,12): error TS2571: O } if ('a' in bar) { - ~~~ -!!! error TS2571: Object is of type 'unknown'. bar.a } \ No newline at end of file diff --git a/tests/baselines/reference/narrowiinigInWithExistedProperty.js b/tests/baselines/reference/narrowiinigInWithExistedProperty.js index 05accdbbe5d21..14c4d7881404e 100644 --- a/tests/baselines/reference/narrowiinigInWithExistedProperty.js +++ b/tests/baselines/reference/narrowiinigInWithExistedProperty.js @@ -12,7 +12,7 @@ interface C { } declare const foo: A | B | C; -declare const bar: unknown; +declare const bar: {}; if ('a' in foo) { foo.a diff --git a/tests/baselines/reference/narrowiinigInWithExistedProperty.symbols b/tests/baselines/reference/narrowiinigInWithExistedProperty.symbols index 6cff2ae5f8bb5..799db745a18f2 100644 --- a/tests/baselines/reference/narrowiinigInWithExistedProperty.symbols +++ b/tests/baselines/reference/narrowiinigInWithExistedProperty.symbols @@ -26,7 +26,7 @@ declare const foo: A | B | C; >B : Symbol(B, Decl(narrowiinigInWithExistedProperty.ts, 2, 1)) >C : Symbol(C, Decl(narrowiinigInWithExistedProperty.ts, 6, 1)) -declare const bar: unknown; +declare const bar: {}; >bar : Symbol(bar, Decl(narrowiinigInWithExistedProperty.ts, 13, 13)) if ('a' in foo) { diff --git a/tests/baselines/reference/narrowiinigInWithExistedProperty.types b/tests/baselines/reference/narrowiinigInWithExistedProperty.types index 80483cfab24a6..5db42f3145d13 100644 --- a/tests/baselines/reference/narrowiinigInWithExistedProperty.types +++ b/tests/baselines/reference/narrowiinigInWithExistedProperty.types @@ -17,8 +17,8 @@ interface C { declare const foo: A | B | C; >foo : A | B | C -declare const bar: unknown; ->bar : unknown +declare const bar: {}; +>bar : {} if ('a' in foo) { >'a' in foo : boolean @@ -64,7 +64,7 @@ if ('a' in foo) { if ('a' in bar) { >'a' in bar : boolean >'a' : "a" ->bar : unknown +>bar : {} bar.a >bar.a : unknown diff --git a/tests/cases/compiler/narrowiinigInWithExistedProperty.ts b/tests/cases/compiler/narrowiinigInWithExistedProperty.ts index 756ffbba12b61..fc3c5f2a493d6 100644 --- a/tests/cases/compiler/narrowiinigInWithExistedProperty.ts +++ b/tests/cases/compiler/narrowiinigInWithExistedProperty.ts @@ -13,7 +13,7 @@ interface C { } declare const foo: A | B | C; -declare const bar: unknown; +declare const bar: {}; if ('a' in foo) { foo.a From 879f69ef6ba693950586598f3d82bcfe3270d26f Mon Sep 17 00:00:00 2001 From: kingwl Date: Thu, 9 Apr 2020 18:18:08 +0800 Subject: [PATCH 3/3] accept baseline --- src/compiler/checker.ts | 2 +- .../reference/inKeywordTypeguard.errors.txt | 12 ++++++++---- tests/baselines/reference/inKeywordTypeguard.types | 4 ++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fe0c303f90bb8..78493426508ad 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20315,7 +20315,7 @@ namespace ts { return resultType; } return narrowTypeByInKeywordWithExistedPropName(type, propName); - } + } return type; } 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 {