Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ReadonlyArray overload to Array.isArray #28916

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/lib/es5.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1341,7 +1341,7 @@ interface ArrayConstructor {
(arrayLength?: number): any[];
<T>(arrayLength: number): T[];
<T>(...items: T[]): T[];
isArray(arg: any): arg is Array<any>;
isArray<T>(arg: T): arg is T extends ReadonlyArray<any> ? T : Array<any> extends T ? (T & any[]) : never;
Copy link

@laughinghan laughinghan Jan 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
isArray<T>(arg: T): arg is T extends ReadonlyArray<any> ? T : Array<any> extends T ? (T & any[]) : never;
isArray<T>(arg: T): arg is Extract<any[], T> | Extract<[any], T> | (unknown extends T ? never : Extract<T, readonly any[]>) {
// the first two clauses, `Extract<any[], T>` and `Extract<[any], T>`,
// ensure that the type predicate will extract `B[]` out of `A | B[]`
// and `[B]` out of `A | [B]`, just like a the naive predicate `arg is any[]`
// would. The final clause is a special case if T is known to include
// a readonly array, to extract `readonly B[]` out of `A | readonly B[]`,
// and `readonly [B]` out of `A | readonly [B]`, but as a special exception
// it needs to ignore the case of T = any, because `any` is an ill-behaved
// type. See https://github.com/microsoft/TypeScript/pull/28916#issuecomment-573217751

readonly prototype: Array<any>;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ export const updateIfChanged = <T>(t: T) => {
>assign : { <T, U>(target: T, source: U): T & U; <T, U, V>(target: T, source1: U, source2: V): T & U & V; <T, U, V, W>(target: T, source1: U, source2: V, source3: W): T & U & V & W; (target: object, ...sources: any[]): any; }
>Array.isArray(u) ? [] : {} : undefined[] | {}
>Array.isArray(u) : boolean
>Array.isArray : (arg: any) => arg is any[]
>Array.isArray : <T>(arg: T) => arg is T extends readonly any[] ? T : any[] extends T ? T & any[] : never
>Array : ArrayConstructor
>isArray : (arg: any) => arg is any[]
>isArray : <T>(arg: T) => arg is T extends readonly any[] ? T : any[] extends T ? T & any[] : never
>u : U
>[] : undefined[]
>{} : {}
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/fixSignatureCaching.types
Original file line number Diff line number Diff line change
Expand Up @@ -1109,9 +1109,9 @@ define(function () {
>Array : ArrayConstructor

Array.isArray : function (value) { return Object.prototype.toString.call(value) === '[object Array]'; };
>Array.isArray : (arg: any) => arg is any[]
>Array.isArray : <T>(arg: T) => arg is T extends readonly any[] ? T : any[] extends T ? T & any[] : never
>Array : ArrayConstructor
>isArray : (arg: any) => arg is any[]
>isArray : <T>(arg: T) => arg is T extends readonly any[] ? T : any[] extends T ? T & any[] : never
>function (value) { return Object.prototype.toString.call(value) === '[object Array]'; } : (value: any) => boolean
>value : any
>Object.prototype.toString.call(value) === '[object Array]' : boolean
Expand Down
128 changes: 128 additions & 0 deletions tests/baselines/reference/isArray.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
tests/cases/compiler/isArray.ts(21,33): error TS2339: Property 'push' does not exist on type 'readonly string[]'.
tests/cases/compiler/isArray.ts(22,33): error TS2339: Property 'push' does not exist on type 'readonly string[]'.
tests/cases/compiler/isArray.ts(28,38): error TS2345: Argument of type '10' is not assignable to parameter of type 'string'.
tests/cases/compiler/isArray.ts(36,33): error TS2339: Property 'push' does not exist on type 'readonly string[] | number[]'.
Property 'push' does not exist on type 'readonly string[]'.
tests/cases/compiler/isArray.ts(40,33): error TS2339: Property 'push' does not exist on type 'readonly string[] | number[]'.
Property 'push' does not exist on type 'readonly string[]'.
tests/cases/compiler/isArray.ts(44,38): error TS2345: Argument of type '10' is not assignable to parameter of type 'string'.
tests/cases/compiler/isArray.ts(50,33): error TS2339: Property 'push' does not exist on type 'MyReadOnlyArray<string>'.
tests/cases/compiler/isArray.ts(51,41): error TS2345: Argument of type '10' is not assignable to parameter of type 'string'.
tests/cases/compiler/isArray.ts(61,33): error TS2339: Property 'push' does not exist on type 'readonly T[]'.
tests/cases/compiler/isArray.ts(70,33): error TS2339: Property 'push' does not exist on type 'readonly [T]'.
tests/cases/compiler/isArray.ts(76,38): error TS2345: Argument of type '10' is not assignable to parameter of type 'string'.
tests/cases/compiler/isArray.ts(83,15): error TS2345: Argument of type '"str"' is not assignable to parameter of type 'string[]'.


==== tests/cases/compiler/isArray.ts (12 errors) ====
interface MyArray<T> extends Array<T> { manifest: any; }
interface MyReadOnlyArray<T> extends ReadonlyArray<T> { manifest: any; }

function fn1(arg: string | string[]) {
if (Array.isArray(arg)) arg.push(""); // Should OK
}

function fn2(arg: unknown) {
if (Array.isArray(arg)) arg.push(""); // Should OK
}

function fn3(arg: object) {
if (Array.isArray(arg)) arg.push(""); // Should OK
}

function fn4(arg: {}) {
if (Array.isArray(arg)) arg.push(""); // Should OK
}

function fn5(arg: string | ReadonlyArray<string>) {
if (Array.isArray(arg)) arg.push(10); // Should FAIL
~~~~
!!! error TS2339: Property 'push' does not exist on type 'readonly string[]'.
if (Array.isArray(arg)) arg.push(""); // Should FAIL
~~~~
!!! error TS2339: Property 'push' does not exist on type 'readonly string[]'.
if (Array.isArray(arg)) arg.indexOf(""); // Should OK
if (!Array.isArray(arg)) arg.toUpperCase(); // Should OK
}

function fn6(arg: string | string[]) {
if (Array.isArray(arg)) arg.push(10); // Should FAIL
~~
!!! error TS2345: Argument of type '10' is not assignable to parameter of type 'string'.
}

function fn7(arg: boolean | number[] | string[], stringAndNumber: string & number) {
if (Array.isArray(arg)) arg.push(stringAndNumber); // Should OK
}

function fn8(arg: string | number[] | readonly string[]) {
if (Array.isArray(arg)) arg.push(10); // Should FAIL
~~~~
!!! error TS2339: Property 'push' does not exist on type 'readonly string[] | number[]'.
!!! error TS2339: Property 'push' does not exist on type 'readonly string[]'.
}

function fn9(arg: string | number[] | readonly string[]) {
if (Array.isArray(arg)) arg.push(10); // Should FAIL
~~~~
!!! error TS2339: Property 'push' does not exist on type 'readonly string[] | number[]'.
!!! error TS2339: Property 'push' does not exist on type 'readonly string[]'.
}

function fn10(arg: string | MyArray<string>) {
if (Array.isArray(arg)) arg.push(10); // Should FAIL
~~
!!! error TS2345: Argument of type '10' is not assignable to parameter of type 'string'.
if (Array.isArray(arg)) arg.push(""); // Should OK
if (Array.isArray(arg)) arg.manifest; // Should OK
}

function fn11(arg: string | MyReadOnlyArray<string>) {
if (Array.isArray(arg)) arg.push(""); // Should FAIL
~~~~
!!! error TS2339: Property 'push' does not exist on type 'MyReadOnlyArray<string>'.
if (Array.isArray(arg)) arg.indexOf(10); // Should FAIL
~~
!!! error TS2345: Argument of type '10' is not assignable to parameter of type 'string'.
if (Array.isArray(arg)) arg.indexOf(""); // Should OK
if (Array.isArray(arg)) arg.manifest; // Should OK
}

function fn12<T>(arg: T | T[], t: T) {
if (Array.isArray(arg)) arg.push(t); // Should OK
}

function fn13<T>(arg: T | ReadonlyArray<T>, t: T) {
if (Array.isArray(arg)) arg.push(t); // Should fail
~~~~
!!! error TS2339: Property 'push' does not exist on type 'readonly T[]'.
if (Array.isArray(arg)) arg.indexOf(t); // OK
}

function fn14<T>(arg: T | [T], t: T) {
if (Array.isArray(arg)) arg.push(t); // Should OK
}

function fn15<T>(arg: T | readonly [T], t: T) {
if (Array.isArray(arg)) arg.push(t); // Should fail
~~~~
!!! error TS2339: Property 'push' does not exist on type 'readonly [T]'.
if (Array.isArray(arg)) arg.indexOf(t); // Should OK
}

function fn16<T extends string | string[]>(arg: T) {
if (Array.isArray(arg)) arg.push("10"); // Should OK
if (Array.isArray(arg)) arg.push(10); // Should fail
~~
!!! error TS2345: Argument of type '10' is not assignable to parameter of type 'string'.
}

function fn17() {
const s: Array<string | string[]> = [];
const arrs = s.filter(Array.isArray);
arrs.push(["one"]); // Should OK
arrs.push("str"); // Should fail
~~~~~
!!! error TS2345: Argument of type '"str"' is not assignable to parameter of type 'string[]'.
}

183 changes: 172 additions & 11 deletions tests/baselines/reference/isArray.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,180 @@
//// [isArray.ts]
var maybeArray: number | number[];
interface MyArray<T> extends Array<T> { manifest: any; }
interface MyReadOnlyArray<T> extends ReadonlyArray<T> { manifest: any; }

function fn1(arg: string | string[]) {
if (Array.isArray(arg)) arg.push(""); // Should OK
}

if (Array.isArray(maybeArray)) {
maybeArray.length; // OK
function fn2(arg: unknown) {
if (Array.isArray(arg)) arg.push(""); // Should OK
}
else {
maybeArray.toFixed(); // OK
}

function fn3(arg: object) {
if (Array.isArray(arg)) arg.push(""); // Should OK
}

function fn4(arg: {}) {
if (Array.isArray(arg)) arg.push(""); // Should OK
}

function fn5(arg: string | ReadonlyArray<string>) {
if (Array.isArray(arg)) arg.push(10); // Should FAIL
if (Array.isArray(arg)) arg.push(""); // Should FAIL
if (Array.isArray(arg)) arg.indexOf(""); // Should OK
if (!Array.isArray(arg)) arg.toUpperCase(); // Should OK
}

function fn6(arg: string | string[]) {
if (Array.isArray(arg)) arg.push(10); // Should FAIL
}

function fn7(arg: boolean | number[] | string[], stringAndNumber: string & number) {
if (Array.isArray(arg)) arg.push(stringAndNumber); // Should OK
}

function fn8(arg: string | number[] | readonly string[]) {
if (Array.isArray(arg)) arg.push(10); // Should FAIL
}

function fn9(arg: string | number[] | readonly string[]) {
if (Array.isArray(arg)) arg.push(10); // Should FAIL
}

function fn10(arg: string | MyArray<string>) {
if (Array.isArray(arg)) arg.push(10); // Should FAIL
if (Array.isArray(arg)) arg.push(""); // Should OK
if (Array.isArray(arg)) arg.manifest; // Should OK
}

function fn11(arg: string | MyReadOnlyArray<string>) {
if (Array.isArray(arg)) arg.push(""); // Should FAIL
if (Array.isArray(arg)) arg.indexOf(10); // Should FAIL
if (Array.isArray(arg)) arg.indexOf(""); // Should OK
if (Array.isArray(arg)) arg.manifest; // Should OK
}

function fn12<T>(arg: T | T[], t: T) {
if (Array.isArray(arg)) arg.push(t); // Should OK
}

function fn13<T>(arg: T | ReadonlyArray<T>, t: T) {
if (Array.isArray(arg)) arg.push(t); // Should fail
if (Array.isArray(arg)) arg.indexOf(t); // OK
}

function fn14<T>(arg: T | [T], t: T) {
if (Array.isArray(arg)) arg.push(t); // Should OK
}

function fn15<T>(arg: T | readonly [T], t: T) {
if (Array.isArray(arg)) arg.push(t); // Should fail
if (Array.isArray(arg)) arg.indexOf(t); // Should OK
}

function fn16<T extends string | string[]>(arg: T) {
if (Array.isArray(arg)) arg.push("10"); // Should OK
if (Array.isArray(arg)) arg.push(10); // Should fail
}

function fn17() {
const s: Array<string | string[]> = [];
const arrs = s.filter(Array.isArray);
arrs.push(["one"]); // Should OK
arrs.push("str"); // Should fail
}


//// [isArray.js]
var maybeArray;
if (Array.isArray(maybeArray)) {
maybeArray.length; // OK
function fn1(arg) {
if (Array.isArray(arg))
arg.push(""); // Should OK
}
function fn2(arg) {
if (Array.isArray(arg))
arg.push(""); // Should OK
}
function fn3(arg) {
if (Array.isArray(arg))
arg.push(""); // Should OK
}
function fn4(arg) {
if (Array.isArray(arg))
arg.push(""); // Should OK
}
function fn5(arg) {
if (Array.isArray(arg))
arg.push(10); // Should FAIL
if (Array.isArray(arg))
arg.push(""); // Should FAIL
if (Array.isArray(arg))
arg.indexOf(""); // Should OK
if (!Array.isArray(arg))
arg.toUpperCase(); // Should OK
}
function fn6(arg) {
if (Array.isArray(arg))
arg.push(10); // Should FAIL
}
function fn7(arg, stringAndNumber) {
if (Array.isArray(arg))
arg.push(stringAndNumber); // Should OK
}
function fn8(arg) {
if (Array.isArray(arg))
arg.push(10); // Should FAIL
}
function fn9(arg) {
if (Array.isArray(arg))
arg.push(10); // Should FAIL
}
function fn10(arg) {
if (Array.isArray(arg))
arg.push(10); // Should FAIL
if (Array.isArray(arg))
arg.push(""); // Should OK
if (Array.isArray(arg))
arg.manifest; // Should OK
}
function fn11(arg) {
if (Array.isArray(arg))
arg.push(""); // Should FAIL
if (Array.isArray(arg))
arg.indexOf(10); // Should FAIL
if (Array.isArray(arg))
arg.indexOf(""); // Should OK
if (Array.isArray(arg))
arg.manifest; // Should OK
}
function fn12(arg, t) {
if (Array.isArray(arg))
arg.push(t); // Should OK
}
function fn13(arg, t) {
if (Array.isArray(arg))
arg.push(t); // Should fail
if (Array.isArray(arg))
arg.indexOf(t); // OK
}
function fn14(arg, t) {
if (Array.isArray(arg))
arg.push(t); // Should OK
}
function fn15(arg, t) {
if (Array.isArray(arg))
arg.push(t); // Should fail
if (Array.isArray(arg))
arg.indexOf(t); // Should OK
}
function fn16(arg) {
if (Array.isArray(arg))
arg.push("10"); // Should OK
if (Array.isArray(arg))
arg.push(10); // Should fail
}
else {
maybeArray.toFixed(); // OK
function fn17() {
var s = [];
var arrs = s.filter(Array.isArray);
arrs.push(["one"]); // Should OK
arrs.push("str"); // Should fail
}
Loading