From e782cef2219f98737fe9a35b29f40ff99445223b Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 16 Apr 2023 17:24:10 -0700 Subject: [PATCH] Properly handle partial union type properties in `isTypePresencePossible` (#53794) --- src/compiler/checker.ts | 2 +- ...nKeywordTypeguard(strict=false).errors.txt | 29 ++++++++ .../inKeywordTypeguard(strict=false).js | 54 +++++++++++++++ .../inKeywordTypeguard(strict=false).symbols | 66 +++++++++++++++++++ .../inKeywordTypeguard(strict=false).types | 64 ++++++++++++++++++ ...inKeywordTypeguard(strict=true).errors.txt | 29 ++++++++ .../inKeywordTypeguard(strict=true).js | 54 +++++++++++++++ .../inKeywordTypeguard(strict=true).symbols | 66 +++++++++++++++++++ .../inKeywordTypeguard(strict=true).types | 64 ++++++++++++++++++ tests/cases/compiler/inKeywordTypeguard.ts | 29 ++++++++ 10 files changed, 456 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 09a5e8b690d..1ccafca0dcc 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -27006,7 +27006,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function isTypePresencePossible(type: Type, propName: __String, assumeTrue: boolean) { const prop = getPropertyOfType(type, propName); return prop ? - !!(prop.flags & SymbolFlags.Optional) || assumeTrue : + !!(prop.flags & SymbolFlags.Optional || getCheckFlags(prop) & CheckFlags.Partial) || assumeTrue : !!getApplicableIndexInfoForName(type, propName) || !assumeTrue; } diff --git a/tests/baselines/reference/inKeywordTypeguard(strict=false).errors.txt b/tests/baselines/reference/inKeywordTypeguard(strict=false).errors.txt index 7d8bd0bea9a..dd3d705b7fd 100644 --- a/tests/baselines/reference/inKeywordTypeguard(strict=false).errors.txt +++ b/tests/baselines/reference/inKeywordTypeguard(strict=false).errors.txt @@ -432,4 +432,33 @@ tests/cases/compiler/inKeywordTypeguard.ts(186,21): error TS2322: Type 'T' is no const f =

(a: P & {}) => { "foo" in a; }; + + // Repro from #53773 + + function test1>(obj: T) { + if (Array.isArray(obj) || 'length' in obj) { + obj; // T + } + else { + obj; // T + } + } + + function test2>(obj: T) { + if (Array.isArray(obj)) { + obj; // T & any[] + } + else { + obj; // T + } + } + + function test3>(obj: T) { + if ('length' in obj) { + obj; // T + } + else { + obj; // T + } + } \ No newline at end of file diff --git a/tests/baselines/reference/inKeywordTypeguard(strict=false).js b/tests/baselines/reference/inKeywordTypeguard(strict=false).js index ae1ccbb43c5..d42e84293a1 100644 --- a/tests/baselines/reference/inKeywordTypeguard(strict=false).js +++ b/tests/baselines/reference/inKeywordTypeguard(strict=false).js @@ -353,6 +353,35 @@ function isHTMLTable(table: T): boolean { const f =

(a: P & {}) => { "foo" in a; }; + +// Repro from #53773 + +function test1>(obj: T) { + if (Array.isArray(obj) || 'length' in obj) { + obj; // T + } + else { + obj; // T + } +} + +function test2>(obj: T) { + if (Array.isArray(obj)) { + obj; // T & any[] + } + else { + obj; // T + } +} + +function test3>(obj: T) { + if ('length' in obj) { + obj; // T + } + else { + obj; // T + } +} //// [inKeywordTypeguard.js] @@ -675,3 +704,28 @@ function isHTMLTable(table) { const f = (a) => { "foo" in a; }; +// Repro from #53773 +function test1(obj) { + if (Array.isArray(obj) || 'length' in obj) { + obj; // T + } + else { + obj; // T + } +} +function test2(obj) { + if (Array.isArray(obj)) { + obj; // T & any[] + } + else { + obj; // T + } +} +function test3(obj) { + if ('length' in obj) { + obj; // T + } + else { + obj; // T + } +} diff --git a/tests/baselines/reference/inKeywordTypeguard(strict=false).symbols b/tests/baselines/reference/inKeywordTypeguard(strict=false).symbols index 9444bf3fc98..3cbb8f33cc0 100644 --- a/tests/baselines/reference/inKeywordTypeguard(strict=false).symbols +++ b/tests/baselines/reference/inKeywordTypeguard(strict=false).symbols @@ -879,3 +879,69 @@ const f =

(a: P & {}) => { }; +// Repro from #53773 + +function test1>(obj: T) { +>test1 : Symbol(test1, Decl(inKeywordTypeguard.ts, 353, 2)) +>T : Symbol(T, Decl(inKeywordTypeguard.ts, 357, 15)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 357, 54)) +>T : Symbol(T, Decl(inKeywordTypeguard.ts, 357, 15)) + + if (Array.isArray(obj) || 'length' in obj) { +>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 357, 54)) +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 357, 54)) + + obj; // T +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 357, 54)) + } + else { + obj; // T +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 357, 54)) + } +} + +function test2>(obj: T) { +>test2 : Symbol(test2, Decl(inKeywordTypeguard.ts, 364, 1)) +>T : Symbol(T, Decl(inKeywordTypeguard.ts, 366, 15)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 366, 54)) +>T : Symbol(T, Decl(inKeywordTypeguard.ts, 366, 15)) + + if (Array.isArray(obj)) { +>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 366, 54)) + + obj; // T & any[] +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 366, 54)) + } + else { + obj; // T +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 366, 54)) + } +} + +function test3>(obj: T) { +>test3 : Symbol(test3, Decl(inKeywordTypeguard.ts, 373, 1)) +>T : Symbol(T, Decl(inKeywordTypeguard.ts, 375, 15)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 375, 54)) +>T : Symbol(T, Decl(inKeywordTypeguard.ts, 375, 15)) + + if ('length' in obj) { +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 375, 54)) + + obj; // T +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 375, 54)) + } + else { + obj; // T +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 375, 54)) + } +} + diff --git a/tests/baselines/reference/inKeywordTypeguard(strict=false).types b/tests/baselines/reference/inKeywordTypeguard(strict=false).types index 2135c949797..4f69cf393b5 100644 --- a/tests/baselines/reference/inKeywordTypeguard(strict=false).types +++ b/tests/baselines/reference/inKeywordTypeguard(strict=false).types @@ -1085,3 +1085,67 @@ const f =

(a: P & {}) => { }; +// Repro from #53773 + +function test1>(obj: T) { +>test1 : >(obj: T) => void +>obj : T + + if (Array.isArray(obj) || 'length' in obj) { +>Array.isArray(obj) || 'length' in obj : boolean +>Array.isArray(obj) : boolean +>Array.isArray : (arg: any) => arg is any[] +>Array : ArrayConstructor +>isArray : (arg: any) => arg is any[] +>obj : any[] | Record +>'length' in obj : boolean +>'length' : "length" +>obj : T + + obj; // T +>obj : T + } + else { + obj; // T +>obj : T + } +} + +function test2>(obj: T) { +>test2 : >(obj: T) => void +>obj : T + + if (Array.isArray(obj)) { +>Array.isArray(obj) : boolean +>Array.isArray : (arg: any) => arg is any[] +>Array : ArrayConstructor +>isArray : (arg: any) => arg is any[] +>obj : any[] | Record + + obj; // T & any[] +>obj : T & any[] + } + else { + obj; // T +>obj : T + } +} + +function test3>(obj: T) { +>test3 : >(obj: T) => void +>obj : T + + if ('length' in obj) { +>'length' in obj : boolean +>'length' : "length" +>obj : T + + obj; // T +>obj : T + } + else { + obj; // T +>obj : T + } +} + diff --git a/tests/baselines/reference/inKeywordTypeguard(strict=true).errors.txt b/tests/baselines/reference/inKeywordTypeguard(strict=true).errors.txt index d47a0718aac..f0c97560e5c 100644 --- a/tests/baselines/reference/inKeywordTypeguard(strict=true).errors.txt +++ b/tests/baselines/reference/inKeywordTypeguard(strict=true).errors.txt @@ -452,4 +452,33 @@ tests/cases/compiler/inKeywordTypeguard.ts(186,21): error TS2638: Type 'NonNulla const f =

(a: P & {}) => { "foo" in a; }; + + // Repro from #53773 + + function test1>(obj: T) { + if (Array.isArray(obj) || 'length' in obj) { + obj; // T + } + else { + obj; // T + } + } + + function test2>(obj: T) { + if (Array.isArray(obj)) { + obj; // T & any[] + } + else { + obj; // T + } + } + + function test3>(obj: T) { + if ('length' in obj) { + obj; // T + } + else { + obj; // T + } + } \ No newline at end of file diff --git a/tests/baselines/reference/inKeywordTypeguard(strict=true).js b/tests/baselines/reference/inKeywordTypeguard(strict=true).js index 73b2cf77cae..01bd93fe852 100644 --- a/tests/baselines/reference/inKeywordTypeguard(strict=true).js +++ b/tests/baselines/reference/inKeywordTypeguard(strict=true).js @@ -353,6 +353,35 @@ function isHTMLTable(table: T): boolean { const f =

(a: P & {}) => { "foo" in a; }; + +// Repro from #53773 + +function test1>(obj: T) { + if (Array.isArray(obj) || 'length' in obj) { + obj; // T + } + else { + obj; // T + } +} + +function test2>(obj: T) { + if (Array.isArray(obj)) { + obj; // T & any[] + } + else { + obj; // T + } +} + +function test3>(obj: T) { + if ('length' in obj) { + obj; // T + } + else { + obj; // T + } +} //// [inKeywordTypeguard.js] @@ -676,3 +705,28 @@ function isHTMLTable(table) { const f = (a) => { "foo" in a; }; +// Repro from #53773 +function test1(obj) { + if (Array.isArray(obj) || 'length' in obj) { + obj; // T + } + else { + obj; // T + } +} +function test2(obj) { + if (Array.isArray(obj)) { + obj; // T & any[] + } + else { + obj; // T + } +} +function test3(obj) { + if ('length' in obj) { + obj; // T + } + else { + obj; // T + } +} diff --git a/tests/baselines/reference/inKeywordTypeguard(strict=true).symbols b/tests/baselines/reference/inKeywordTypeguard(strict=true).symbols index 9444bf3fc98..3cbb8f33cc0 100644 --- a/tests/baselines/reference/inKeywordTypeguard(strict=true).symbols +++ b/tests/baselines/reference/inKeywordTypeguard(strict=true).symbols @@ -879,3 +879,69 @@ const f =

(a: P & {}) => { }; +// Repro from #53773 + +function test1>(obj: T) { +>test1 : Symbol(test1, Decl(inKeywordTypeguard.ts, 353, 2)) +>T : Symbol(T, Decl(inKeywordTypeguard.ts, 357, 15)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 357, 54)) +>T : Symbol(T, Decl(inKeywordTypeguard.ts, 357, 15)) + + if (Array.isArray(obj) || 'length' in obj) { +>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 357, 54)) +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 357, 54)) + + obj; // T +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 357, 54)) + } + else { + obj; // T +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 357, 54)) + } +} + +function test2>(obj: T) { +>test2 : Symbol(test2, Decl(inKeywordTypeguard.ts, 364, 1)) +>T : Symbol(T, Decl(inKeywordTypeguard.ts, 366, 15)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 366, 54)) +>T : Symbol(T, Decl(inKeywordTypeguard.ts, 366, 15)) + + if (Array.isArray(obj)) { +>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 366, 54)) + + obj; // T & any[] +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 366, 54)) + } + else { + obj; // T +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 366, 54)) + } +} + +function test3>(obj: T) { +>test3 : Symbol(test3, Decl(inKeywordTypeguard.ts, 373, 1)) +>T : Symbol(T, Decl(inKeywordTypeguard.ts, 375, 15)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 375, 54)) +>T : Symbol(T, Decl(inKeywordTypeguard.ts, 375, 15)) + + if ('length' in obj) { +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 375, 54)) + + obj; // T +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 375, 54)) + } + else { + obj; // T +>obj : Symbol(obj, Decl(inKeywordTypeguard.ts, 375, 54)) + } +} + diff --git a/tests/baselines/reference/inKeywordTypeguard(strict=true).types b/tests/baselines/reference/inKeywordTypeguard(strict=true).types index 830b053aab1..d2bb5733703 100644 --- a/tests/baselines/reference/inKeywordTypeguard(strict=true).types +++ b/tests/baselines/reference/inKeywordTypeguard(strict=true).types @@ -1085,3 +1085,67 @@ const f =

(a: P & {}) => { }; +// Repro from #53773 + +function test1>(obj: T) { +>test1 : >(obj: T) => void +>obj : T + + if (Array.isArray(obj) || 'length' in obj) { +>Array.isArray(obj) || 'length' in obj : boolean +>Array.isArray(obj) : boolean +>Array.isArray : (arg: any) => arg is any[] +>Array : ArrayConstructor +>isArray : (arg: any) => arg is any[] +>obj : any[] | Record +>'length' in obj : boolean +>'length' : "length" +>obj : T + + obj; // T +>obj : T + } + else { + obj; // T +>obj : T + } +} + +function test2>(obj: T) { +>test2 : >(obj: T) => void +>obj : T + + if (Array.isArray(obj)) { +>Array.isArray(obj) : boolean +>Array.isArray : (arg: any) => arg is any[] +>Array : ArrayConstructor +>isArray : (arg: any) => arg is any[] +>obj : any[] | Record + + obj; // T & any[] +>obj : T & any[] + } + else { + obj; // T +>obj : T + } +} + +function test3>(obj: T) { +>test3 : >(obj: T) => void +>obj : T + + if ('length' in obj) { +>'length' in obj : boolean +>'length' : "length" +>obj : T + + obj; // T +>obj : T + } + else { + obj; // T +>obj : T + } +} + diff --git a/tests/cases/compiler/inKeywordTypeguard.ts b/tests/cases/compiler/inKeywordTypeguard.ts index 0c74dcded90..26145d0c7d9 100644 --- a/tests/cases/compiler/inKeywordTypeguard.ts +++ b/tests/cases/compiler/inKeywordTypeguard.ts @@ -355,3 +355,32 @@ function isHTMLTable(table: T): boolean { const f =

(a: P & {}) => { "foo" in a; }; + +// Repro from #53773 + +function test1>(obj: T) { + if (Array.isArray(obj) || 'length' in obj) { + obj; // T + } + else { + obj; // T + } +} + +function test2>(obj: T) { + if (Array.isArray(obj)) { + obj; // T & any[] + } + else { + obj; // T + } +} + +function test3>(obj: T) { + if ('length' in obj) { + obj; // T + } + else { + obj; // T + } +}