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

isArray() preserve mutability and element type #48228

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/lib/es2015.iterable.d.ts
Expand Up @@ -56,6 +56,8 @@ interface Array<T> {
}

interface ArrayConstructor {
isArray<T>(arg: Iterable<T>): arg is readonly T[];

/**
* Creates an array from an iterable object.
* @param iterable An iterable object to convert to an array.
Expand Down
3 changes: 2 additions & 1 deletion src/lib/es5.d.ts
Expand Up @@ -1443,7 +1443,8 @@ interface ArrayConstructor {
(arrayLength?: number): any[];
<T>(arrayLength: number): T[];
<T>(...items: T[]): T[];
isArray(arg: any): arg is any[];
isArray<T>(arg: ArrayLike<T>): arg is readonly T[];
isArray(arg: unknown): arg is any[];
Copy link

Choose a reason for hiding this comment

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

Shall it be unknown instead, since arg is unknown?

Suggested change
isArray(arg: unknown): arg is any[];
isArray(arg: unknown): arg is unknown[];

readonly prototype: any[];
}

Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/arrayDestructuringInSwitch1.symbols
Expand Up @@ -14,9 +14,9 @@ export function evaluate(expression: Expression): boolean {
>Expression : Symbol(Expression, Decl(arrayDestructuringInSwitch1.ts, 0, 0))

if (Array.isArray(expression)) {
>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>expression : Symbol(expression, Decl(arrayDestructuringInSwitch1.ts, 3, 25))

const [operator, ...operands] = expression;
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/arrayDestructuringInSwitch1.types
Expand Up @@ -11,9 +11,9 @@ export function evaluate(expression: Expression): boolean {

if (Array.isArray(expression)) {
>Array.isArray(expression) : boolean
>Array.isArray : (arg: any) => arg is any[]
>Array.isArray : { <T>(arg: ArrayLike<T>): arg is readonly T[]; (arg: unknown): arg is any[]; }
>Array : ArrayConstructor
>isArray : (arg: any) => arg is any[]
>isArray : { <T>(arg: ArrayLike<T>): arg is readonly T[]; (arg: unknown): arg is any[]; }
>expression : Expression

const [operator, ...operands] = expression;
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/arrayTypeOfTypeOf.types
Expand Up @@ -14,11 +14,11 @@ var xs2: typeof Array;
>Array : ArrayConstructor

var xs3: typeof Array<number>;
>xs3 : { (arrayLength: number): number[]; (...items: number[]): number[]; new (arrayLength: number): number[]; new (...items: number[]): number[]; isArray(arg: any): arg is any[]; readonly prototype: any[]; }
>xs3 : { (arrayLength: number): number[]; (...items: number[]): number[]; new (arrayLength: number): number[]; new (...items: number[]): number[]; isArray<T>(arg: ArrayLike<T>): arg is readonly T[]; isArray(arg: unknown): arg is any[]; readonly prototype: any[]; }
>Array : ArrayConstructor

var xs4: typeof Array<typeof x>;
>xs4 : { (arrayLength: number): number[]; (...items: number[]): number[]; new (arrayLength: number): number[]; new (...items: number[]): number[]; isArray(arg: any): arg is any[]; readonly prototype: any[]; }
>xs4 : { (arrayLength: number): number[]; (...items: number[]): number[]; new (arrayLength: number): number[]; new (...items: number[]): number[]; isArray<T>(arg: ArrayLike<T>): arg is readonly T[]; isArray(arg: unknown): arg is any[]; readonly prototype: any[]; }
>Array : ArrayConstructor
>x : number

Expand Up @@ -79,9 +79,9 @@ export const updateIfChanged = <T>(t: T) => {
>Object.assign : Symbol(ObjectConstructor.assign, Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>assign : Symbol(ObjectConstructor.assign, Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --))
>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.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, --, --))
>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --))
>u : Symbol(u, Decl(declarationsWithRecursiveInternalTypesProduceUniqueTypeParams.ts, 7, 23))
>u : Symbol(u, Decl(declarationsWithRecursiveInternalTypesProduceUniqueTypeParams.ts, 7, 23))
>[key] : Symbol([key], Decl(declarationsWithRecursiveInternalTypesProduceUniqueTypeParams.ts, 12, 80))
Expand Down
Expand Up @@ -67,9 +67,9 @@ export const updateIfChanged = <T>(t: T) => {
>assign : { <T extends {}, U>(target: T, source: U): T & U; <T extends {}, U, V>(target: T, source1: U, source2: V): T & U & V; <T extends {}, 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: ArrayLike<T>): arg is readonly T[]; (arg: unknown): arg is any[]; <T>(arg: Iterable<T>): arg is readonly T[]; }
>Array : ArrayConstructor
>isArray : (arg: any) => arg is any[]
>isArray : { <T>(arg: ArrayLike<T>): arg is readonly T[]; (arg: unknown): arg is any[]; <T>(arg: Iterable<T>): arg is readonly T[]; }
>u : U
>[] : undefined[]
>{} : {}
Expand Down
Expand Up @@ -41,7 +41,7 @@ tests/cases/conformance/es6/destructuring/destructuringParameterDeclaration4.ts(
a1(...array2); // Error parameter type is (number|string)[]
~~~~~~
!!! error TS2552: Cannot find name 'array2'. Did you mean 'Array'?
!!! related TS2728 /.ts/lib.es5.d.ts:1470:13: 'Array' is declared here.
!!! related TS2728 /.ts/lib.es5.d.ts:1471:13: 'Array' is declared here.
Copy link

Choose a reason for hiding this comment

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

Could you please clarify why it has been changed?

a5([1, 2, "string", false, true]); // Error, parameter type is [any, any, [[any]]]
~~~~~~~~
!!! error TS2322: Type 'string' is not assignable to type '[[any]]'.
Expand Down
8 changes: 4 additions & 4 deletions tests/baselines/reference/fixSignatureCaching.symbols
Expand Up @@ -796,9 +796,9 @@ define(function () {
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))

Array.isArray : function (value) { return Object.prototype.toString.call(value) === '[object Array]'; };
>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>value : Symbol(value, Decl(fixSignatureCaching.ts, 297, 34))
>Object.prototype.toString.call : Symbol(Function.call, Decl(lib.es5.d.ts, --, --))
>Object.prototype.toString : Symbol(Object.toString, Decl(lib.es5.d.ts, --, --))
Expand All @@ -825,9 +825,9 @@ define(function () {
>value : Symbol(value, Decl(fixSignatureCaching.ts, 299, 20))

: Array.isArray;
>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))

function equalIC(a, b) {
>equalIC : Symbol(equalIC, Decl(fixSignatureCaching.ts, 300, 24))
Expand Down
8 changes: 4 additions & 4 deletions tests/baselines/reference/fixSignatureCaching.types
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: ArrayLike<T>): arg is readonly T[]; (arg: unknown): arg is any[]; }
>Array : ArrayConstructor
>isArray : (arg: any) => arg is any[]
>isArray : { <T>(arg: ArrayLike<T>): arg is readonly T[]; (arg: unknown): arg is any[]; }
>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 Expand Up @@ -1150,9 +1150,9 @@ define(function () {
>'[object Array]' : "[object Array]"

: Array.isArray;
>Array.isArray : (arg: any) => arg is any[]
>Array.isArray : { <T>(arg: ArrayLike<T>): arg is readonly T[]; (arg: unknown): arg is any[]; }
>Array : ArrayConstructor
>isArray : (arg: any) => arg is any[]
>isArray : { <T>(arg: ArrayLike<T>): arg is readonly T[]; (arg: unknown): arg is any[]; }

function equalIC(a, b) {
>equalIC : (a: any, b: any) => boolean
Expand Down
8 changes: 4 additions & 4 deletions tests/baselines/reference/instantiationExpressions.types
Expand Up @@ -56,11 +56,11 @@ function f2() {
>Array : ArrayConstructor

const A1 = Array<string>; // new (...) => string[]
>A1 : { (arrayLength: number): string[]; (...items: string[]): string[]; new (arrayLength: number): string[]; new (...items: string[]): string[]; isArray(arg: any): arg is any[]; readonly prototype: any[]; }
>A1 : { (arrayLength: number): string[]; (...items: string[]): string[]; new (arrayLength: number): string[]; new (...items: string[]): string[]; isArray<T>(arg: ArrayLike<T>): arg is readonly T[]; isArray(arg: unknown): arg is any[]; readonly prototype: any[]; }
>Array : ArrayConstructor

const A2 = Array<string, number>; // Error
>A2 : { isArray(arg: any): arg is any[]; readonly prototype: any[]; }
>A2 : { isArray<T>(arg: ArrayLike<T>): arg is readonly T[]; isArray(arg: unknown): arg is any[]; readonly prototype: any[]; }
>Array : ArrayConstructor
}

Expand All @@ -69,11 +69,11 @@ type T20 = typeof Array<>; // Error
>Array : ArrayConstructor

type T21 = typeof Array<string>; // new (...) => string[]
>T21 : { (arrayLength: number): string[]; (...items: string[]): string[]; new (arrayLength: number): string[]; new (...items: string[]): string[]; isArray(arg: any): arg is any[]; readonly prototype: any[]; }
>T21 : { (arrayLength: number): string[]; (...items: string[]): string[]; new (arrayLength: number): string[]; new (...items: string[]): string[]; isArray<T>(arg: ArrayLike<T>): arg is readonly T[]; isArray(arg: unknown): arg is any[]; readonly prototype: any[]; }
>Array : ArrayConstructor

type T22 = typeof Array<string, number>; // Error
>T22 : { isArray(arg: any): arg is any[]; readonly prototype: any[]; }
>T22 : { isArray<T>(arg: ArrayLike<T>): arg is readonly T[]; isArray(arg: unknown): arg is any[]; readonly prototype: any[]; }
>Array : ArrayConstructor

declare class C<T> {
Expand Down
69 changes: 69 additions & 0 deletions tests/baselines/reference/isArray.errors.txt
@@ -0,0 +1,69 @@
tests/cases/compiler/isArray.ts(15,9): error TS4104: The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.
tests/cases/compiler/isArray.ts(26,9): error TS2322: Type 'readonly string[]' is not assignable to type 'readonly void[]'.
Type 'string' is not assignable to type 'void'.
tests/cases/compiler/isArray.ts(32,9): error TS2322: Type 'readonly string[]' is not assignable to type 'readonly void[]'.
tests/cases/compiler/isArray.ts(41,9): error TS2322: Type 'any[]' is not assignable to type 'void'.
tests/cases/compiler/isArray.ts(47,9): error TS2322: Type 'any[]' is not assignable to type 'void'.


==== tests/cases/compiler/isArray.ts (5 errors) ====
/// @errors: 2322 4104

// https://github.com/microsoft/TypeScript/issues/17002
// Preserves mutability, false branch removes arrays, mutable or not

declare const mutable: string | string[];
if (Array.isArray(mutable)) {
const stillMutable: string[] = mutable;
} else {
const narrowed: string = mutable;
}

declare const immutable: string | readonly string[];
if (Array.isArray(immutable)) {
const notMutable: string[] = immutable; // Should fail: readonly string[] isn't assignable to string[]
~~~~~~~~~~
!!! error TS4104: The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.
} else {
const narrowed: string = immutable;
}

// https://github.com/microsoft/TypeScript/issues/33700
// Preserves element or iterated type of wider types

declare const arrayLike: string | ArrayLike<string>;
if (Array.isArray(arrayLike)) {
const arrayOfElementType: readonly string[] = arrayLike;
const notArrayOfAny: readonly void[] = arrayLike; // Should fail: string isn't assignable to void
~~~~~~~~~~~~~
!!! error TS2322: Type 'readonly string[]' is not assignable to type 'readonly void[]'.
!!! error TS2322: Type 'string' is not assignable to type 'void'.
}

declare const iterable: string | Iterable<string>;
if (Array.isArray(iterable)) {
const arrayOfIteratedType: readonly string[] = iterable;
const notArrayOfAny: readonly void[] = iterable; // Should fail: string isn't assignable to void
~~~~~~~~~~~~~
!!! error TS2322: Type 'readonly string[]' is not assignable to type 'readonly void[]'.
}

// https://github.com/microsoft/TypeScript/pull/42316#discussion_r823218462
// any and unknown backward compatibility

declare const any: any;
if (Array.isArray(any)) {
const mutableArrayOfAny: void[] = any;
const notAny: void = any; // Should fail: any[] isn't assignable to void
~~~~~~
!!! error TS2322: Type 'any[]' is not assignable to type 'void'.
}

declare const unknown: unknown;
if (Array.isArray(unknown)) {
const mutableArrayOfAny: void[] = unknown;
const notAny: void = unknown; // Should fail: any[] isn't assignable to void
~~~~~~
!!! error TS2322: Type 'any[]' is not assignable to type 'void'.
}

82 changes: 72 additions & 10 deletions tests/baselines/reference/isArray.js
@@ -1,19 +1,81 @@
//// [isArray.ts]
var maybeArray: number | number[];
/// @errors: 2322 4104

// https://github.com/microsoft/TypeScript/issues/17002
// Preserves mutability, false branch removes arrays, mutable or not

if (Array.isArray(maybeArray)) {
maybeArray.length; // OK
declare const mutable: string | string[];
if (Array.isArray(mutable)) {
const stillMutable: string[] = mutable;
} else {
const narrowed: string = mutable;
}
else {
maybeArray.toFixed(); // OK
}

declare const immutable: string | readonly string[];
if (Array.isArray(immutable)) {
const notMutable: string[] = immutable; // Should fail: readonly string[] isn't assignable to string[]
} else {
const narrowed: string = immutable;
}

// https://github.com/microsoft/TypeScript/issues/33700
// Preserves element or iterated type of wider types

declare const arrayLike: string | ArrayLike<string>;
if (Array.isArray(arrayLike)) {
const arrayOfElementType: readonly string[] = arrayLike;
const notArrayOfAny: readonly void[] = arrayLike; // Should fail: string isn't assignable to void
}

declare const iterable: string | Iterable<string>;
if (Array.isArray(iterable)) {
const arrayOfIteratedType: readonly string[] = iterable;
const notArrayOfAny: readonly void[] = iterable; // Should fail: string isn't assignable to void
}

// https://github.com/microsoft/TypeScript/pull/42316#discussion_r823218462
// any and unknown backward compatibility

declare const any: any;
if (Array.isArray(any)) {
const mutableArrayOfAny: void[] = any;
const notAny: void = any; // Should fail: any[] isn't assignable to void
}

declare const unknown: unknown;
if (Array.isArray(unknown)) {
const mutableArrayOfAny: void[] = unknown;
const notAny: void = unknown; // Should fail: any[] isn't assignable to void
}


//// [isArray.js]
var maybeArray;
if (Array.isArray(maybeArray)) {
maybeArray.length; // OK
/// @errors: 2322 4104
if (Array.isArray(mutable)) {
const stillMutable = mutable;
}
else {
const narrowed = mutable;
}
if (Array.isArray(immutable)) {
const notMutable = immutable; // Should fail: readonly string[] isn't assignable to string[]
}
else {
maybeArray.toFixed(); // OK
const narrowed = immutable;
}
if (Array.isArray(arrayLike)) {
const arrayOfElementType = arrayLike;
const notArrayOfAny = arrayLike; // Should fail: string isn't assignable to void
}
if (Array.isArray(iterable)) {
const arrayOfIteratedType = iterable;
const notArrayOfAny = iterable; // Should fail: string isn't assignable to void
}
if (Array.isArray(any)) {
const mutableArrayOfAny = any;
const notAny = any; // Should fail: any[] isn't assignable to void
}
if (Array.isArray(unknown)) {
const mutableArrayOfAny = unknown;
const notAny = unknown; // Should fail: any[] isn't assignable to void
}