Skip to content

Commit

Permalink
Properly handle partial union type properties in `isTypePresencePossi…
Browse files Browse the repository at this point in the history
…ble` (#53794)
  • Loading branch information
ahejlsberg authored Apr 17, 2023
1 parent 378ffa4 commit e782cef
Show file tree
Hide file tree
Showing 10 changed files with 456 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -432,4 +432,33 @@ tests/cases/compiler/inKeywordTypeguard.ts(186,21): error TS2322: Type 'T' is no
const f = <P extends object>(a: P & {}) => {
"foo" in a;
};

// Repro from #53773

function test1<T extends any[] | Record<string, any>>(obj: T) {
if (Array.isArray(obj) || 'length' in obj) {
obj; // T
}
else {
obj; // T
}
}

function test2<T extends any[] | Record<string, any>>(obj: T) {
if (Array.isArray(obj)) {
obj; // T & any[]
}
else {
obj; // T
}
}

function test3<T extends any[] | Record<string, any>>(obj: T) {
if ('length' in obj) {
obj; // T
}
else {
obj; // T
}
}

54 changes: 54 additions & 0 deletions tests/baselines/reference/inKeywordTypeguard(strict=false).js
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,35 @@ function isHTMLTable<T extends object | null>(table: T): boolean {
const f = <P extends object>(a: P & {}) => {
"foo" in a;
};

// Repro from #53773

function test1<T extends any[] | Record<string, any>>(obj: T) {
if (Array.isArray(obj) || 'length' in obj) {
obj; // T
}
else {
obj; // T
}
}

function test2<T extends any[] | Record<string, any>>(obj: T) {
if (Array.isArray(obj)) {
obj; // T & any[]
}
else {
obj; // T
}
}

function test3<T extends any[] | Record<string, any>>(obj: T) {
if ('length' in obj) {
obj; // T
}
else {
obj; // T
}
}


//// [inKeywordTypeguard.js]
Expand Down Expand Up @@ -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
}
}
66 changes: 66 additions & 0 deletions tests/baselines/reference/inKeywordTypeguard(strict=false).symbols
Original file line number Diff line number Diff line change
Expand Up @@ -879,3 +879,69 @@ const f = <P extends object>(a: P & {}) => {

};

// Repro from #53773

function test1<T extends any[] | Record<string, any>>(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<T extends any[] | Record<string, any>>(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<T extends any[] | Record<string, any>>(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))
}
}

64 changes: 64 additions & 0 deletions tests/baselines/reference/inKeywordTypeguard(strict=false).types
Original file line number Diff line number Diff line change
Expand Up @@ -1085,3 +1085,67 @@ const f = <P extends object>(a: P & {}) => {

};

// Repro from #53773

function test1<T extends any[] | Record<string, any>>(obj: T) {
>test1 : <T extends any[] | Record<string, any>>(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<string, any>
>'length' in obj : boolean
>'length' : "length"
>obj : T

obj; // T
>obj : T
}
else {
obj; // T
>obj : T
}
}

function test2<T extends any[] | Record<string, any>>(obj: T) {
>test2 : <T extends any[] | Record<string, any>>(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<string, any>

obj; // T & any[]
>obj : T & any[]
}
else {
obj; // T
>obj : T
}
}

function test3<T extends any[] | Record<string, any>>(obj: T) {
>test3 : <T extends any[] | Record<string, any>>(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
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -452,4 +452,33 @@ tests/cases/compiler/inKeywordTypeguard.ts(186,21): error TS2638: Type 'NonNulla
const f = <P extends object>(a: P & {}) => {
"foo" in a;
};

// Repro from #53773

function test1<T extends any[] | Record<string, any>>(obj: T) {
if (Array.isArray(obj) || 'length' in obj) {
obj; // T
}
else {
obj; // T
}
}

function test2<T extends any[] | Record<string, any>>(obj: T) {
if (Array.isArray(obj)) {
obj; // T & any[]
}
else {
obj; // T
}
}

function test3<T extends any[] | Record<string, any>>(obj: T) {
if ('length' in obj) {
obj; // T
}
else {
obj; // T
}
}

54 changes: 54 additions & 0 deletions tests/baselines/reference/inKeywordTypeguard(strict=true).js
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,35 @@ function isHTMLTable<T extends object | null>(table: T): boolean {
const f = <P extends object>(a: P & {}) => {
"foo" in a;
};

// Repro from #53773

function test1<T extends any[] | Record<string, any>>(obj: T) {
if (Array.isArray(obj) || 'length' in obj) {
obj; // T
}
else {
obj; // T
}
}

function test2<T extends any[] | Record<string, any>>(obj: T) {
if (Array.isArray(obj)) {
obj; // T & any[]
}
else {
obj; // T
}
}

function test3<T extends any[] | Record<string, any>>(obj: T) {
if ('length' in obj) {
obj; // T
}
else {
obj; // T
}
}


//// [inKeywordTypeguard.js]
Expand Down Expand Up @@ -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
}
}
Loading

0 comments on commit e782cef

Please sign in to comment.