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

Awaitable type is not additive #51111

Closed
Loskir opened this issue Oct 8, 2022 · 5 comments
Closed

Awaitable type is not additive #51111

Loskir opened this issue Oct 8, 2022 · 5 comments
Labels
Duplicate An existing issue was already created Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@Loskir
Copy link

Loskir commented Oct 8, 2022

Bug Report

🔎 Search Terms

Awaitable, MaybePromise

🕗 Version & Regression Information

  • This is a crash (?)
  • This changed between versions 4.9 and at least 4.4 (didn't check earlier versions)
  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about (?)

⏯ Playground Link

Playground link with relevant code

💻 Code

type A = {a: string}
type B = {b: string}

type Awaitable<T> = T | PromiseLike<T>

type C = Awaitable<A> & Awaitable<B>

async function f(c: C) {
  (await c).a
}

🙁 Actual behavior

The types are not additive (Awaited<Awaitable<A> & Awaitable<B>> != A & B)

Property 'a' does not exist on type 'A | B | (A & B) | (B & A)'.
  Property 'a' does not exist on type 'B'.

🙂 Expected behavior

The types should be additive (Awaited<Awaitable<A> & Awaitable<B>> = A & B)

Related: #31394

@KnorpelSenf
Copy link

I, too, observe this issue. Interestingly enough, this even fails for intersecting a normal value with an awaitable one:

type A = {a: string}
type B = {b: string}

type Awaitable<T> = T | PromiseLike<T>

type C = A & Awaitable<B> // just this breaks it

async function f(c: C) {
  (await c).a
}

This means that in this code snippet:

type Q0 = C extends A ? true : false
type Q1 = Awaited<C> extends A ? true : false

we can see Q0 being inferred as true but Q1 as false.

@Andarist
Copy link
Contributor

Andarist commented Oct 8, 2022

What runtime value Awaitable<A> & Awaitable<B> or A & Awaitable<B> represent? How do you create such a thing in practice? Shouldn't you always await each member of that and only then intersect them?

@jcalz
Copy link
Contributor

jcalz commented Oct 8, 2022

I wonder if this is ultimately due to something like #14107. For any promise-ish thing PromiseIsh<T> which ultimately represents a value of type T but hides it inside a function that accepts a function that accepts a value of type T, you should conceptually be able to treat a PromiseIsh<A> & PromiseIsh<B> as a PromiseIsh<A & B>. But the type manipulation needed to do that doesn't currently work in TS, because you end up getting caught on intersections of functions:

 ((f: (v: A) => R) => R) & ((f: (v: A) => R) => R) 
                👎 #14107
 (f: ((v: A) => R) | ((v: B) => R)) => R 
                👍 #29011
 (f: (v: A & B) => R) => R

Without a fix for #14107 I'm not sure how we could fix this (well, I suppose it could be special-cased, like anything)

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Oct 10, 2022
@RyanCavanaugh
Copy link
Member

The core thing here is that Promises are understood primarily in terms of their .then, and intersection creates overloads. It's entirely legal, therefore, to make a C which doesn't give back an a on await.

let myC: C = {
    then: (cb: ((a: A) => PromiseLike<any>) & ((b: B) => PromiseLike<any>)) => {
        return cb({ b: "" });
    }
}

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Oct 10, 2022
@typescript-bot
Copy link
Collaborator

This issue has been marked 'Working as Intended' 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 Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

6 participants