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

Array.isArray type guard not working for generic array arguments #23542

Closed
athasach opened this issue Apr 19, 2018 · 2 comments
Closed

Array.isArray type guard not working for generic array arguments #23542

athasach opened this issue Apr 19, 2018 · 2 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@athasach
Copy link

TypeScript Version: 2.8.1, 2.9.0-dev.20180419

Search Terms:

  • type guard
  • type guard generic
  • type guard array
  • type guard arraylike

Code

class X1<T, A extends ArrayLike<T>> {
    reset(a: A): void {
        if (Array.isArray(a)) {
            // Cannot assign to 'length' because it is a constant or a read-only property.
            a.length = 0;
            //~~~~~~
            // When hovering over `a`, it is of type `A & any[]`
        }
    }
}

function fn1<T, A extends ArrayLike<T>>(a: A): void {
    if (Array.isArray(a)) {
        // Cannot assign to 'length' because it is a constant or a read-only property.
        a.length = 0;
        //~~~~~~
        // When hovering over `a`, it is of type `A & any[]`
    }
}

class X2<T> {
    reset(a: ArrayLike<T>): void {
        if (Array.isArray(a)) {
            // Cast is not required below, as expected.
            a.length = 0;
        }
    }
}

function fn2<T>(a: ArrayLike<T>): void {
    if (Array.isArray(a)) {
        // Cast is not required below, as expected.
        a.length = 0;
    }
}

Expected behavior:

The Array.isArray type guard should yield type any[].

Actual behavior:

Where A is generic as defined by the enclosing function or class and the argument passed to Array.isArray is of type A, the type yielded is A & any[]. In this case, it becomes ArrayLike<T> & any[] causing the length property to be read only.

Playground Link:
https://www.typescriptlang.org/play/#src=class%20X1%3CT%2C%20A%20extends%20ArrayLike%3CT%3E%3E%20%7B%0D%0A%20%20%20%20reset(a%3A%20A)%3A%20void%20%7B%0D%0A%20%20%20%20%20%20%20%20if%20(Array.isArray(a))%20%7B%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Cannot%20assign%20to%20'length'%20because%20it%20is%20a%20constant%20or%20a%20read-only%20property.%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20a.length%20%3D%200%3B%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F~~~~~~%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20When%20hovering%20over%20%60a%60%2C%20it%20is%20of%20type%20%60A%20%26%20any%5B%5D%60%0D%0A%20%20%20%20%20%20%20%20%7D%0D%0A%20%20%20%20%7D%0D%0A%7D%0D%0A%0D%0Afunction%20fn1%3CT%2C%20A%20extends%20ArrayLike%3CT%3E%3E(a%3A%20A)%3A%20void%20%7B%0D%0A%20%20%20%20if%20(Array.isArray(a))%20%7B%0D%0A%20%20%20%20%20%20%20%20%2F%2F%20Cannot%20assign%20to%20'length'%20because%20it%20is%20a%20constant%20or%20a%20read-only%20property.%0D%0A%20%20%20%20%20%20%20%20a.length%20%3D%200%3B%0D%0A%20%20%20%20%20%20%20%20%2F%2F~~~~~~%0D%0A%20%20%20%20%20%20%20%20%2F%2F%20When%20hovering%20over%20%60a%60%2C%20it%20is%20of%20type%20%60A%20%26%20any%5B%5D%60%0D%0A%20%20%20%20%7D%0D%0A%7D%0D%0A%0D%0Aclass%20X2%3CT%3E%20%7B%0D%0A%20%20%20%20reset(a%3A%20ArrayLike%3CT%3E)%3A%20void%20%7B%0D%0A%20%20%20%20%20%20%20%20if%20(Array.isArray(a))%20%7B%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Cast%20is%20not%20required%20below%2C%20as%20expected.%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20a.length%20%3D%200%3B%0D%0A%20%20%20%20%20%20%20%20%7D%0D%0A%20%20%20%20%7D%0D%0A%7D%0D%0A%0D%0Afunction%20fn2%3CT%3E(a%3A%20ArrayLike%3CT%3E)%3A%20void%20%7B%0D%0A%20%20%20%20if%20(Array.isArray(a))%20%7B%0D%0A%20%20%20%20%20%20%20%20%2F%2F%20Cast%20is%20not%20required%20below%2C%20as%20expected.%0D%0A%20%20%20%20%20%20%20%20a.length%20%3D%200%3B%0D%0A%20%20%20%20%7D%0D%0A%7D%0D%0A%0D%0A

Related Issues:

Possibly related to #22214

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Apr 19, 2018
@RyanCavanaugh
Copy link
Member

This happens because all of the following things are true:

  • any[] is a subtype of ArrayLike<T> for any T
  • The intersection type T & U reduces to U if U is a subtype of T
  • any[] is not a subtype of T if T extends ArrayLike<U>
  • An operation (in this case, setting .length) is allowed on the intersection type T & U if it would be legal for T and legal for U

The result of these individually-good rules is an inconsistency.

@athasach
Copy link
Author

Thanks, Ryan. Closing this issue since it's working as intended.

@microsoft microsoft locked and limited conversation to collaborators Jul 31, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

2 participants