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

Regression: Argument of type 'awaited T' is not assignable to parameter of type 'T' #37534

Closed
Pajn opened this issue Mar 23, 2020 · 9 comments · Fixed by #37610
Closed

Regression: Argument of type 'awaited T' is not assignable to parameter of type 'T' #37534

Pajn opened this issue Mar 23, 2020 · 9 comments · Fixed by #37610
Labels
Discussion Issues which may not have code impact

Comments

@Pajn
Copy link

Pajn commented Mar 23, 2020

TypeScript Version: 3.9.0-dev.20200322

Search Terms:
awaited

Code

async function runSequentially<T>(
  executors: Array<() => Promise<T>>,
): Promise<Array<T>> {
  const result: Array<T> = [];

  for (const executor of executors) {
    result.push(await executor());
  }

  return result;
}

Expected behavior:
The above code should compile. The types area reasonable and it did compile in previous versions without awaited.

Actual behavior:
Type error on await executor():

Argument of type 'awaited T' is not assignable to parameter of type 'T'.
  'T' could be instantiated with an arbitrary type which could be unrelated to 'awaited T'.ts(2345)

Playground Link: Playground Link

Related Issues: #37526

@RyanCavanaugh RyanCavanaugh added the Discussion Issues which may not have code impact label Mar 23, 2020
@RyanCavanaugh
Copy link
Member

This is a correct error. You could have written code like this:

declare const arr: Array<() => Promise<Promise<string>>>;

async function fn() {
  // x: Array<Promise<string>>
  const x = await runSequentially(arr);
  // Crashes
  x[0].then(() => { });
}

There are some workaround available (adding a constraint to T for example) but we're also considering adding a flag to allow the assignment to proceed even though it's unsound. Interested to hear your feedback and from others who encounter this error.

@falsandtru
Copy link
Contributor

strictAwaitedTypes was implemented but removed after in #35998. @rbuckton Why?

@RyanCavanaugh
Copy link
Member

We don't want to add the flag unless we really need it. Assessing what kind of breaks people encounter in real codebases is how we're figuring that out.

@Pajn
Copy link
Author

Pajn commented Mar 23, 2020

Is there any real world example that this is unsound in? Because a Promise<Promise<string>> string can't be constructed.

Personally, I consider that Typescript allows Promise<Promise<string>> to be written a bug (that hopefully can be fixed soon #29317)

@falsandtru
Copy link
Contributor

#37501

Conclusion: ship it without a flag in the beta, re-evaluate for the RC if the breaks are that bad.

Hm...

awaited operator is effective to find a bug but correcting for awaited operator was very bored.

falsandtru/spica@c569afd

#37511

@RyanCavanaugh
Copy link
Member

Is there any real world example that this is unsound in?

Sure, any higher-order type operation involving Promise<T> is potentially a problem. Note that the actual type Promise<Promise<T>> never appears in this example:

// Previously-allowed but unsound definition
async function myAwaitWrong<T>(arg: Promise<T>): Promise<T[]> {
  // Proposed: That this be valid without a type assertion
  return [await arg] as any;
}

// Correct definition of an awaiting function
async function myAwaitRight<T>(arg: Promise<T>): Promise<(awaited T)[]> {
  return [await arg];
}

// Toggle these to observe different behavior
const myAwait = myAwaitRight;
// const myAwait = myAwaitWrong;


class MyThing<T> {
  value!: T;
  // Returns Promise<T>
  async getValue() {
    return this.value;
  }
  // Returns Promise<T[]>
  async doSomething() {
    return myAwait(this.getValue());
  }
}

async function f() {
  const p = new MyThing<Promise<string>>();
  // Under myAwaitWrong - m: Incorrectly Promise<string>[]
  // Under myAwaitRight - m: Correctly string[]
  const m = await p.doSomething();
}

Because a Promise<Promise<string>> can't be constructed.

People keep saying this and I continually question what it means for this to be the case; it's like saying f(x) => abs(abs(x)) isn't a thing because abs is idempotent on itself. It's valid and you can do it. Higher-order constructions exist and you can't just say "abs(abs(x)) should be illegal" because it implies any higher-order type construction should be banned unless it somehow proves that it can never operate in a way that produces this result.

@jeiea
Copy link

jeiea commented Mar 25, 2020

May I ask a question? Can I make the following type check?

async function* flatten<T>(generator: AsyncGenerator<T[]>): AsyncGenerator<T> {
  for await (const array of generator) {
    for (const value of array) {
      yield (value as unknown) as T;
      //    ^^^^^^^^^^^^^^^^^^^^^^^
      // Type 'awaited T' is not assignable to type 'T'.
      //  'T' could be instantiated with an arbitrary type which could be unrelated to 'awaited T'.ts(2322)
    }
  }
}

I'm using typescript 3.9.0-dev.20200324.

@falsandtru
Copy link
Contributor

You have to change the return type to AsyncGenerator<awaited T>.

@jeiea
Copy link

jeiea commented Mar 25, 2020

@falsandtru It causes Parsing error: '>' expected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Discussion Issues which may not have code impact
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants