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

tuple narrowing is lost when wrapped in Promise #27363

Closed
billba opened this issue Sep 26, 2018 · 5 comments
Closed

tuple narrowing is lost when wrapped in Promise #27363

billba opened this issue Sep 26, 2018 · 5 comments
Labels
Duplicate An existing issue was already created

Comments

@billba
Copy link
Member

billba commented Sep 26, 2018

TypeScript Version:

3.2.0-dev.20180926

Search Terms

tuple array narrow

Code

const gallant = <ARGS extends unknown[] | [unknown]> (
    a: () => ARGS,
    b: (...args: ARGS) => boolean,
 ) => b(...a());


gallant(
    () => [2, "dog"],
    (x, y) => y.repeat(x) === "dogdog"
);


const goofus = async <ARGS extends unknown[] | [unknown]> (
    a: () => Promise<ARGS>,
    b: (...args: ARGS) => Promise<boolean>,
 ) => b(... await a());

goofus(
    () => Promise.resolve([2, "dog"]),
    (x, y) => Promise.resolve(y.repeat(x) === "dogdog")
);

Expected behavior:

The call to goofus to behave like the call to gallant -- ARGS should narrow to the tuple [number, string] and thus x is a number and y is a string

Actual behavior:

In the call to goofus, ARGS === (number | string)[], and so both x and y are number | string, which results in the following error:

[ts]
Property 'repeat' does not exist on type 'string | number'.
  Property 'repeat' does not exist on type 'number'.

Playground Link:

link

Related Issues:

The T extends XXX[] | [XXX] syntax for tuple narrowing is per @ahejlsberg in #27179

@j-oliveras
Copy link
Contributor

The Promise.resolve has no overload to resolve tuple types. You can add this and will work:

interface PromiseConstructor {
    resolve<T extends unknown[] | [unknown]>(value: T | PromiseLike<T>): Promise<T>;
}

Be aware that, probably, any array created inside Promise.resolve will be treated as a tuple.

@billba
Copy link
Member Author

billba commented Sep 26, 2018

Declaration merging didn't work because the tuple overload needs to come first. When I manually inserted it into the declaration for PromiseConstructor it did work:

interface PromiseConstructor {
    ....
    resolve<T extends unknown[] | [unknown]>(value: T | PromiseLike<T>): Promise<T>;
    resolve<T>(value: T | PromiseLike<T>): Promise<T>;
    ...
}

Yuck. This demonstrates the problem with having to depend on a construct like T extends XXX[] | [XXX]. What I want is for TypeScript to infer a tuple literal as a tuple.

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Oct 1, 2018
@RyanCavanaugh
Copy link
Member

Also discussed at #27486

We can't just infer tuples everywhere (it breaks everything).

@billba
Copy link
Member Author

billba commented Oct 2, 2018

What is your recommendation on how to code goofus above?

@typescript-bot
Copy link
Collaborator

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

4 participants