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

3.7.2 Promise.all infers base type of interfaces rather than actual interface #34937

Closed
thaoula opened this issue Nov 6, 2019 · 14 comments · Fixed by #34501 or #45350
Closed

3.7.2 Promise.all infers base type of interfaces rather than actual interface #34937

thaoula opened this issue Nov 6, 2019 · 14 comments · Fixed by #34501 or #45350
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue

Comments

@thaoula
Copy link

thaoula commented Nov 6, 2019

TypeScript 3.7.2
Playground link

Compiler Options:

{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "strictBindCallApply": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "useDefineForClassFields": false,
    "alwaysStrict": true,
    "allowUnreachableCode": false,
    "allowUnusedLabels": false,
    "downlevelIteration": false,
    "noEmitHelpers": false,
    "noLib": false,
    "noStrictGenericChecks": false,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "esModuleInterop": true,
    "preserveConstEnums": false,
    "removeComments": false,
    "skipLibCheck": false,
    "checkJs": false,
    "allowJs": false,
    "experimentalDecorators": false,
    "emitDecoratorMetadata": false,
    "target": "ES2017",
    "module": "ESNext"
  }
}

Input:

interface QuoteFilter {
    _id: string;
}

interface QuoteFilterAdvanced extends QuoteFilter {
    name: string;
}

function getQuoteFilters(): Promise<QuoteFilter[]> {
    return new Promise<QuoteFilter[]>(
        (resolve, reject) => {
            resolve([
               { _id: 'Test' }
            ]);
        });
}

function getQuoteFiltersAdvanced(): Promise<QuoteFilterAdvanced[]> {
    return new Promise<QuoteFilterAdvanced[]>(
        (resolve, reject) => {
            resolve([
               { _id: 'Test', name: 'Test' }
            ]);
        });
}

async() => {
    const [filters, advanced] = await Promise.all([
        getQuoteFilters(),
        getQuoteFiltersAdvanced()
    ]);
}

Output:

"use strict";
function getQuoteFilters() {
    return new Promise((resolve, reject) => {
        resolve([
            { _id: 'Test' }
        ]);
    });
}
function getQuoteFiltersAdvanced() {
    return new Promise((resolve, reject) => {
        resolve([
            { _id: 'Test', name: 'Test' }
        ]);
    });
}
async () => {
    const [filters, advanced] = await Promise.all([
        getQuoteFilters(),
        getQuoteFiltersAdvanced()
    ]);
};

Expected behavior:
I am expecting the advanced variable to be an array of QuoteFilterAdvanced rather than QuoteFilter.

This was correctly inferred in Typescript 3.6.3

@AnyhowStep
Copy link
Contributor

type Awaited<T> =
    T extends undefined ?
    T :
    T extends PromiseLike<infer U> ?
    U :
    T
;
declare function all<T extends readonly any[]>(
    values: T
): Promise<{ -readonly [P in keyof T]: Awaited<T[P]> }>;

//for empty tuple
declare function betterAll (arr : []) : Promise<[]>;
//for non-empty tuple
declare function betterAll<ArrT extends readonly [any, ...any[]]>(
    arr: ArrT
): Promise<{ -readonly [i in keyof ArrT]: Awaited<ArrT[i]> }>;
//for non-tuple array
declare function betterAll<ArrT extends readonly any[]>(
    arr: ArrT
): Promise<{ -readonly [i in keyof ArrT]: Awaited<ArrT[i]> }>;

//https://github.com/microsoft/TypeScript/issues/34937
interface QuoteFilter {
    _id: string;
}

interface QuoteFilterAdvanced extends QuoteFilter {
    name: string;
}

function getQuoteFilters(): Promise<QuoteFilter[]> {
    return new Promise<QuoteFilter[]>(
        (resolve, reject) => {
            resolve([
               { _id: 'Test' }
            ]);
        });
}

function getQuoteFiltersAdvanced(): Promise<QuoteFilterAdvanced[]> {
    return new Promise<QuoteFilterAdvanced[]>(
        (resolve, reject) => {
            resolve([
               { _id: 'Test', name: 'Test' }
            ]);
        });
}

async () => {
    //const filters: QuoteFilter[]
    //const advanced: QuoteFilter[]
    const [filters, advanced] = await Promise.all([
        getQuoteFilters(),
        getQuoteFiltersAdvanced()
    ]);
}

async () => {
    //const filters: QuoteFilter[]
    //const advanced: QuoteFilterAdvanced[]
    const [filters, advanced] = await all([
        getQuoteFilters(),
        getQuoteFiltersAdvanced()
    ]);
}

async () => {
    //const filters: QuoteFilter[]
    //const advanced: QuoteFilterAdvanced[]
    const [filters, advanced] = await betterAll([
        getQuoteFilters(),
        getQuoteFiltersAdvanced()
    ]);
}

Playground

See #33707

@axross
Copy link

axross commented Nov 6, 2019

Same here. This could look a bug

@BooleT37
Copy link

BooleT37 commented Nov 6, 2019

The same issue occurs even if you don't explicitly inherit types: playground, which looks very odd.
Try to rename foo: string; to anything else (i.e.foz: string;) and the code compiles.

@XGHeaven
Copy link

XGHeaven commented Nov 7, 2019

This looks like a bug. I found Promise.all use wrong overload on the above example.
It use definition in lib.es2015.iterable.d.ts not lib.es2015.promise.d.ts.
But in old version, for example 3.6.2, It use correct definition in lib.es2015.promise.d.ts

@aleksandar-kandzhichki
Copy link

Same here, it does not infer the type properly
image

@jablko
Copy link
Contributor

jablko commented Nov 7, 2019

@XGHeaven I believe the incorrect results you are getting are indeed from the overload in es2015.promise.d.ts (not the one in es2015.iterable.d.ts). In my tests, if I drop all but the overload from es2015.promise.d.ts, I continue to get the same results. FYI.

@XGHeaven
Copy link

XGHeaven commented Nov 8, 2019

@jablko
I can give you an example. In 3.7.2, It use es2015.iterable.d.ts. This is wrong. Because The type of first and second is number | string.
image
Same code in 3.6.4. It use es2015.promise.d.ts. This is correct. The first type is number and the second type is string.
image
So the correct results should be come from es2015.promise.d.ts.

@chadbr
Copy link

chadbr commented Nov 8, 2019

seeing same issue here... undefined is added to everything I call in a Promise.all...

@thaoula thaoula changed the title 3.7.2 Promise.all does infers base type of interfaces rather than actual interface 3.7.2 Promise.all infers base type of interfaces rather than actual interface Nov 10, 2019
@jablko
Copy link
Contributor

jablko commented Nov 11, 2019

@XGHeaven I see what you mean, however I think you've highlighted a separate issue with the playground: That example doesn't work in the playground regardless of version (3.7.2 or 3.6.3). https://www.typescriptlang.org/play/index.html?ts=3.6.3#code/IYZwngdgxgBAZgV2gFwJYHsIwOYFNkCCANkQJLK4C2IAFAJQwDeAUDGzFJiMjANpyoATtwA0MELk4QAJgF0AXHwgJKAI1yCx3QagjZZMALwxgAd2CoeABUHpKqCQDpgJGrwCMYgORfZdANzMAL5AA

I think it's a symptom of microsoft/TypeScript-Website#82 (the playground doesn't use the lib d.ts files that get shipped with a build).

In my tests (locally) it doesn't use es2015.iterable.d.ts, in 3.6 or 3.7. (I do get the same incorrect results as you in 3.7, but they're not coming from es2015.iterable.d.ts.)

@Manc
Copy link

Manc commented Nov 12, 2019

I found something! Is it possible that the added readonly in version 3.7.2 messes things up? I did an experiment…

In my case, all types from all Promises are mashed together in TypeScript 3.7.2, not in 3.6.3. As far as I can tell by looking at Promise.all in Visual Code Studio, both versions point to lib.es2015.promise.d.ts. In my example with three Promises, that's…

In 3.7.2:

all<T1, T2, T3>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>]): Promise<[T1, T2, T3]>;

In 3.6.3:

all<T1, T2, T3>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>]): Promise<[T1, T2, T3]>;

The only difference there is the added readonly in 3.7.2. So I commented out the above line from version 3.7.2 and replaced it with the line from 3.6.3 and the error went away!

This was changed here: 29becf0#diff-ca94294a2cb9f29f16a30f5d32e56c2e

@jablko
Copy link
Contributor

jablko commented Nov 12, 2019

@Manc Yes, see #34501

@shayded-exe
Copy link

A workaround for this is to use as const when passing the array to Promise.all.

@deftomat
Copy link

Similar issue.

This works in 3.6.3 and fails in 3.7 and 3.8.0-dev.20191210

async function foo(): Promise<number> {
  const [first, second] = await Promise.all([generateNumber(), generateStringOrNull()]);
  return first; // ERROR: Type 'number | null' is not assignable to type 'number'.
}

async function generateNumber(): Promise<number> {
  return 10;
}

async function generateStringOrNull(): Promise<string| null> {
  return '10';
}

first should be number not a number | null.

I can confirm that workaround works for me.

@swftvsn
Copy link

swftvsn commented Dec 13, 2019

We've also hit with this when using 3.7.3. I can too confirm that the as const workaround works for me too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue
Projects
None yet