Typescript: 3.7.2 Promise.all infers base type of interfaces rather than actual interface

Created on 6 Nov 2019  Â·  14Comments  Â·  Source: microsoft/TypeScript

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

Bug Fix Available

Most helpful comment

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

All 14 comments

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 https://github.com/microsoft/TypeScript/pull/33707

Same here. This could look a bug

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.

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

Same here, it does not infer the type properly
image

@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.

@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.

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

@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.)

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: https://github.com/microsoft/TypeScript/commit/29becf05012bfa7ba20d50b0d16813971e46b8a6#diff-ca94294a2cb9f29f16a30f5d32e56c2e

@Manc Yes, see #34501

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

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.

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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jbondc picture jbondc  Â·  3Comments

bgrieder picture bgrieder  Â·  3Comments

kyasbal-1994 picture kyasbal-1994  Â·  3Comments

weswigham picture weswigham  Â·  3Comments

remojansen picture remojansen  Â·  3Comments