Skip to content

Commit

Permalink
Remove assignability cases in getNarrowedType + an isArray improvemen…
Browse files Browse the repository at this point in the history
…t for readonly arrays (#39258)

* Explore using a different isArray declaration

* Add tests and the new isArray definition

* Baseline updates

* Upda the isArray type
  • Loading branch information
Orta Therox committed Sep 8, 2020
1 parent 1d2278b commit fa89ce6
Show file tree
Hide file tree
Showing 23 changed files with 700 additions and 63 deletions.
14 changes: 5 additions & 9 deletions src/compiler/checker.ts
Expand Up @@ -21984,15 +21984,11 @@ namespace ts {
return assignableType;
}
}
// If the candidate type is a subtype of the target type, narrow to the candidate type.
// Otherwise, if the target type is assignable to the candidate type, keep the target type.
// Otherwise, if the candidate type is assignable to the target type, narrow to the candidate
// type. Otherwise, the types are completely unrelated, so narrow to an intersection of the
// two types.
return isTypeSubtypeOf(candidate, type) ? candidate :
isTypeAssignableTo(type, candidate) ? type :
isTypeAssignableTo(candidate, type) ? candidate :
getIntersectionType([type, candidate]);

// If the candidate type is a subtype of the target type, narrow to the candidate type,
// if the target type is a subtype of the candidate type, narrow to the target type,
// otherwise, narrow to an intersection of the two types.
return isTypeSubtypeOf(candidate, type) ? candidate : isTypeSubtypeOf(type, candidate) ? type : getIntersectionType([type, candidate]);
}

function narrowTypeByCallExpression(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/es5.d.ts
Expand Up @@ -1376,7 +1376,7 @@ interface ArrayConstructor {
(arrayLength?: number): any[];
<T>(arrayLength: number): T[];
<T>(...items: T[]): T[];
isArray(arg: any): arg is any[];
isArray<T>(arg: T | {}): arg is T extends readonly any[] ? (unknown extends T ? never : readonly any[]) : any[];
readonly prototype: any[];
}

Expand Down
100 changes: 100 additions & 0 deletions tests/baselines/reference/consistentUnionSubtypeReduction.js
@@ -0,0 +1,100 @@
//// [consistentUnionSubtypeReduction.ts]
// https://github.com/microsoft/TypeScript/issues/31155

declare const MyArray: {
isArray<T>(arg: T | {}): arg is T extends readonly any[] ? (unknown extends T ? never : readonly any[]) : any[];
};

declare const a: readonly string[] | string;
declare const b: string[] | string;
declare const c: unknown;

if (MyArray.isArray(a)) {
a; // readonly string[]
}
else {
a; // string
}
a; // readonly string[] | string;

if (MyArray.isArray(b)) {
b; // string[] | string;
}
else {
b; // string
}
b; // string[] | string;

if (MyArray.isArray(c)) {
c; // any[]
}


function f<T>(x: T) {
const a: readonly T[] | string = null!;
const b: T[] | string = null!;
const c: T = null!;

if (MyArray.isArray(a)) {
a; // readonly T[]
}
else {
a; // string
}
a; // readonly T[] | string;

if (MyArray.isArray(b)) {
b; // T[]
}
else {
b; // string
}
b;

if (MyArray.isArray(c)) {
c; // T & (T extends readonly any[] ? readonly any[] : any[])
}
}


//// [consistentUnionSubtypeReduction.js]
// https://github.com/microsoft/TypeScript/issues/31155
if (MyArray.isArray(a)) {
a; // readonly string[]
}
else {
a; // string
}
a; // readonly string[] | string;
if (MyArray.isArray(b)) {
b; // string[] | string;
}
else {
b; // string
}
b; // string[] | string;
if (MyArray.isArray(c)) {
c; // any[]
}
function f(x) {
var a = null;
var b = null;
var c = null;
if (MyArray.isArray(a)) {
a; // readonly T[]
}
else {
a; // string
}
a; // readonly T[] | string;
if (MyArray.isArray(b)) {
b; // T[]
}
else {
b; // string
}
b;
if (MyArray.isArray(c)) {
c; // T & (T extends readonly any[] ? readonly any[] : any[])
}
}
130 changes: 130 additions & 0 deletions tests/baselines/reference/consistentUnionSubtypeReduction.symbols
@@ -0,0 +1,130 @@
=== tests/cases/compiler/consistentUnionSubtypeReduction.ts ===
// https://github.com/microsoft/TypeScript/issues/31155

declare const MyArray: {
>MyArray : Symbol(MyArray, Decl(consistentUnionSubtypeReduction.ts, 2, 13))

isArray<T>(arg: T | {}): arg is T extends readonly any[] ? (unknown extends T ? never : readonly any[]) : any[];
>isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 3, 12))
>arg : Symbol(arg, Decl(consistentUnionSubtypeReduction.ts, 3, 15))
>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 3, 12))
>arg : Symbol(arg, Decl(consistentUnionSubtypeReduction.ts, 3, 15))
>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 3, 12))
>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 3, 12))

};

declare const a: readonly string[] | string;
>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 6, 13))

declare const b: string[] | string;
>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 7, 13))

declare const c: unknown;
>c : Symbol(c, Decl(consistentUnionSubtypeReduction.ts, 8, 13))

if (MyArray.isArray(a)) {
>MyArray.isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
>MyArray : Symbol(MyArray, Decl(consistentUnionSubtypeReduction.ts, 2, 13))
>isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 6, 13))

a; // readonly string[]
>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 6, 13))
}
else {
a; // string
>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 6, 13))
}
a; // readonly string[] | string;
>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 6, 13))

if (MyArray.isArray(b)) {
>MyArray.isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
>MyArray : Symbol(MyArray, Decl(consistentUnionSubtypeReduction.ts, 2, 13))
>isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 7, 13))

b; // string[] | string;
>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 7, 13))
}
else {
b; // string
>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 7, 13))
}
b; // string[] | string;
>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 7, 13))

if (MyArray.isArray(c)) {
>MyArray.isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
>MyArray : Symbol(MyArray, Decl(consistentUnionSubtypeReduction.ts, 2, 13))
>isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
>c : Symbol(c, Decl(consistentUnionSubtypeReduction.ts, 8, 13))

c; // any[]
>c : Symbol(c, Decl(consistentUnionSubtypeReduction.ts, 8, 13))
}


function f<T>(x: T) {
>f : Symbol(f, Decl(consistentUnionSubtypeReduction.ts, 28, 1))
>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 31, 11))
>x : Symbol(x, Decl(consistentUnionSubtypeReduction.ts, 31, 14))
>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 31, 11))

const a: readonly T[] | string = null!;
>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 32, 9))
>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 31, 11))

const b: T[] | string = null!;
>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 33, 9))
>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 31, 11))

const c: T = null!;
>c : Symbol(c, Decl(consistentUnionSubtypeReduction.ts, 34, 9))
>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 31, 11))

if (MyArray.isArray(a)) {
>MyArray.isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
>MyArray : Symbol(MyArray, Decl(consistentUnionSubtypeReduction.ts, 2, 13))
>isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 32, 9))

a; // readonly T[]
>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 32, 9))
}
else {
a; // string
>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 32, 9))
}
a; // readonly T[] | string;
>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 32, 9))

if (MyArray.isArray(b)) {
>MyArray.isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
>MyArray : Symbol(MyArray, Decl(consistentUnionSubtypeReduction.ts, 2, 13))
>isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 33, 9))

b; // T[]
>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 33, 9))
}
else {
b; // string
>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 33, 9))
}
b;
>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 33, 9))

if (MyArray.isArray(c)) {
>MyArray.isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
>MyArray : Symbol(MyArray, Decl(consistentUnionSubtypeReduction.ts, 2, 13))
>isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
>c : Symbol(c, Decl(consistentUnionSubtypeReduction.ts, 34, 9))

c; // T & (T extends readonly any[] ? readonly any[] : any[])
>c : Symbol(c, Decl(consistentUnionSubtypeReduction.ts, 34, 9))
}
}

132 changes: 132 additions & 0 deletions tests/baselines/reference/consistentUnionSubtypeReduction.types
@@ -0,0 +1,132 @@
=== tests/cases/compiler/consistentUnionSubtypeReduction.ts ===
// https://github.com/microsoft/TypeScript/issues/31155

declare const MyArray: {
>MyArray : { isArray<T>(arg: {} | T): arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]; }

isArray<T>(arg: T | {}): arg is T extends readonly any[] ? (unknown extends T ? never : readonly any[]) : any[];
>isArray : <T>(arg: T | {}) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
>arg : {} | T

};

declare const a: readonly string[] | string;
>a : string | readonly string[]

declare const b: string[] | string;
>b : string | string[]

declare const c: unknown;
>c : unknown

if (MyArray.isArray(a)) {
>MyArray.isArray(a) : boolean
>MyArray.isArray : <T>(arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
>MyArray : { isArray<T>(arg: {} | T): arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]; }
>isArray : <T>(arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
>a : string | readonly string[]

a; // readonly string[]
>a : readonly string[]
}
else {
a; // string
>a : string
}
a; // readonly string[] | string;
>a : string | readonly string[]

if (MyArray.isArray(b)) {
>MyArray.isArray(b) : boolean
>MyArray.isArray : <T>(arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
>MyArray : { isArray<T>(arg: {} | T): arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]; }
>isArray : <T>(arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
>b : string | string[]

b; // string[] | string;
>b : string[]
}
else {
b; // string
>b : string
}
b; // string[] | string;
>b : string | string[]

if (MyArray.isArray(c)) {
>MyArray.isArray(c) : boolean
>MyArray.isArray : <T>(arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
>MyArray : { isArray<T>(arg: {} | T): arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]; }
>isArray : <T>(arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
>c : unknown

c; // any[]
>c : any[]
}


function f<T>(x: T) {
>f : <T>(x: T) => void
>x : T

const a: readonly T[] | string = null!;
>a : string | readonly T[]
>null! : null
>null : null

const b: T[] | string = null!;
>b : string | T[]
>null! : null
>null : null

const c: T = null!;
>c : T
>null! : null
>null : null

if (MyArray.isArray(a)) {
>MyArray.isArray(a) : boolean
>MyArray.isArray : <T>(arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
>MyArray : { isArray<T>(arg: {} | T): arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]; }
>isArray : <T>(arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
>a : string | readonly T[]

a; // readonly T[]
>a : readonly T[]
}
else {
a; // string
>a : string
}
a; // readonly T[] | string;
>a : string | readonly T[]

if (MyArray.isArray(b)) {
>MyArray.isArray(b) : boolean
>MyArray.isArray : <T>(arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
>MyArray : { isArray<T>(arg: {} | T): arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]; }
>isArray : <T>(arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
>b : string | T[]

b; // T[]
>b : T[]
}
else {
b; // string
>b : string
}
b;
>b : string | T[]

if (MyArray.isArray(c)) {
>MyArray.isArray(c) : boolean
>MyArray.isArray : <T>(arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
>MyArray : { isArray<T>(arg: {} | T): arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]; }
>isArray : <T>(arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
>c : T

c; // T & (T extends readonly any[] ? readonly any[] : any[])
>c : T & (T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[])
}
}

0 comments on commit fa89ce6

Please sign in to comment.