Typescript: Types for async functions do not handle `void` assignments like normal ones

Created on 13 Sep 2019  ·  5Comments  ·  Source: microsoft/TypeScript


TypeScript Version: 3.4.0-dev.201xxxxx


Search Terms:

Code

const w: void = "";

const x: () => void = () => "";

const y: () => Promise<void> = async () => "";

function a(): void {
    return "";
}

async function b(): Promise<void> {
    return "";
}

const y2: () => Promise<() => void> =  async () => () => "";

function a2(): () => void {
    return () => "";
}

function b2(): Promise<() => void> {
    return () => "";
}

Expected behavior:
Generally speaking, all of them should be an error, or all of them shouldn't be.

Actual behavior:
We have a special "function returning void" rule that allows () => "" to be assignable to () => void, but this doesn't carry thru to async scenarios, so a async () => "" is _not_ assignable to a () => Promise<void>.

Playground Link

Bug

All 5 comments

There are two rules we have in place governing the above:

  1. void-returning functions should not return non-undefined values.
  2. A function type whose return value isvoidis treated as though thatvoidwereunknown`, for the purposes of assignability.

This creates this annoying inconsistency where even though function statement/expressions with annotated returns of void are checked for undefined-ness, consts with function type annotations to which function expressions are assigned are _not_ checked in the same way. Separately, the second rule fails to affect async functions in situations where you'd expect it should, since they _don't_ return void verbatim, but instead return Promise<void>.

This has caused me pain already when writing typings for miniSphere. Generally speaking I like the rule where a void return acts as any for the purpose of assignment, as it’s useful in HOF scenarios (callbacks e.g.), and it frustrates me that I can’t get the same behavior with async functions.

This does seem a bit tricky though: if we made the void assignment rule work for Promise<void> would it then end up affecting any generic with a void type argument? I’m not sure whether that’s desirable...

And then even if it is... what about when the type parameter is contravariant? Or invariant? Does variance affect it at all? It’s a pretty big can of worms.

Tbh, I think void just needs to behave like unknown except we also have lint-level checks to attempt to forbid real-valued returns in implementations whose expected return position type is void.

I don’t know, I think it’s kind of elegant right now that void is in a duality with any. :smiley:

  • any: Do whatever you want, I won’t bother you but runtime errors are your problem now
  • void: I’ll put whatever here, don’t try to stop me (by inspecting the value) or else

Where “you” = programmer, “I” = type system.

Was this page helpful?
1 / 5 - 1 ratings

Related issues

fwanicka picture fwanicka  ·  3Comments

siddjain picture siddjain  ·  3Comments

wmaurer picture wmaurer  ·  3Comments

jbondc picture jbondc  ·  3Comments

manekinekko picture manekinekko  ·  3Comments