Skip to content

TS ^4 does not recognize array methods on (T[] | T[][]) as callable #40157

Description

TypeScript Version: 4.0.2, 4.1.0-dev.20200820

Search Terms: union, array methods

Code

The following example uses every method, but the bug affects all other methods as well (map, reduce, etc.).

const a: string[] | string[][] = [];

a.every(b => console.log(b)); // TS-2349: This expression is not callable.

// Attempt №1: suppress TS-7006
a.every((b: string | string[]) => console.log(b)); // TS-2349: This expression is not callable.

// Attempt №2: rewrite as typeguard (as in https://github.com/microsoft/TypeScript/blob/v4.0-beta/lib/lib.es5.d.ts#L1319)
a.every((b: string | string[]): b is string => typeof b === 'string'); // TS-2349: This expression is not callable.

Expected behavior: No errors, code should compile.

Actual behavior: Error TS-2349: This expression is not callable.

Playground Link: https://www.typescriptlang.org/play?ts=4.1.0-dev.20200819#code/MYewdgzgLgBAhgLhtATgSzAcwNoF0YA+yU6WeeMAvDHgNwBQ9cAdAKYBurKAngBQBGVAHwxQkEABtWzCSEwCAlAoZM2nHrwFJUGTIWKkcuBcNHgIk6bPn8lKlhy58tB3fp1ljSQWgiusplDcAA6sIABmMIKUMTAA5B6Yccr0QA

Related Issues: I didn't find anything similar.

Activity

  1. dkamyshov commented on Aug 20, 2020

    @dkamyshov
    Author

    The error disappears if the array is initialized with something inside.

    const a: string[] | string[][] = [];
    
    a.every(() => {}); // TS-2349: This expression is not callable.
    
    const b: string[] | string[][] = [[]];
    
    b.every(() => {}); // ok
    
    const c: string[] | string[][] = ['c'];
    
    c.every(() => {}); // ok
    
    const f = (d: string[] | string[][]) => {
        d.every(() => {}); // TS-2349: This expression is not callable.
    }

    https://www.typescriptlang.org/play?ts=4.1.0-dev.20200819#code/MYewdgzgLgBAhgLhtATgSzAcwNoF0YA+yU6WeeMAvDHgNwBQ9cAdAKYBurKAngBS8BKKgD4YAbwC+AhvVCRYAIySoMOfERVlcFatjy4ZCtpx78hlUZOmM50GMGUlVFDU606aAcmCeDN41x8giLiUjK2sABmVDC8ACaOpGqExEnkuOaW9DA5MHEBpsEWodYS9EA

  2. dkamyshov commented on Aug 20, 2020

    @dkamyshov
    Author

    Methods, that do not have generic overloads (some, forEach) are not affected.

  3. weswigham commented on Aug 20, 2020

    @weswigham
    Member

    This is working as intended (or at least a current design limitation), if a breaking change in 4.0 due to a lib update - #38200 added a second, generic overload to .every, making it so we couldn't resolve a single combined signature for the union (and so it now behaves as .map and friends).

    Changing the types you annotate on the argument to .every aren't going to make .every itself callable. The issue is that the union of the two array types contains irreconcilable overload lists for .every (we'd have to create a power set of signatures and resolve them as a new overload list, while also unifying generics in similar positions, and even then it's not necessarily correct) - you need to cast the .every call itself (which is going to break any type guards you try to apply, mind you).

  4. added
    Breaking ChangeWould introduce errors in existing code
    Working as IntendedThe behavior described is the intended behavior; this is not a bug
    and removed
    Needs InvestigationThis issue needs a team member to investigate its status.
    on Aug 20, 2020
  5. weswigham commented on Aug 20, 2020

    @weswigham
    Member

    Ryan Cavanaugh (@RyanCavanaugh) up to you - this break should possibly be noted in the release breaking changes, if we want to keep it (otherwise we'd revert #38200 and unfix its associated issue).

  6. pleunv commented on Aug 21, 2020

    @pleunv

    So is there currently any proper workaround? I can't seem to get around the error.

  7. dkamyshov commented on Aug 21, 2020

    @dkamyshov
    Author

    Pleun Vanderbauwhede (@pleunv)

    If introducing runtime typeguard is acceptable (in my case - it is), you could go with something along the lines of:

    const isArrayOfArrays = (candidate: unknown[]): candidate is unknown[][] => {
        // you might want to add special case for an empty candidate - this implementation returns `true`
        return candidate.every(Array.isArray);
    }
    
    const f = (a: string[] | string[][]) => {
        const g = (b: string | string[]) => {
            // ...
        }
    
        if(isArrayOfArrays(a)) {
            a.every(g);
        } else {
            a.every(g);
        }
    }
  8. typescript-bot commented on Aug 23, 2020

    @typescript-bot
    Contributor

    This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

  9. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Breaking ChangeWould introduce errors in existing codeWorking as IntendedThe behavior described is the intended behavior; this is not a bug

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions