Repository: TypeScript
Issue: #AvoidTheVoid Allow users to avoid the `void` type.
Labels: Suggestion,In Discussion

# Suggestion
Allow developers to avoid the `void` type.

![AVOIDTHEVOID](https://user-images.githubusercontent.com/2996754/107414463-0724c180-6ae0-11eb-98a9-e1c9f26f1e34.png)

I’d like to propose a couple changes to TypeScript to make `void` an optional type, and infer `undefined` in more places where `void` is currently used. I’ve argued in multiple places that TypeScript’s `void` type is confusing and detrimental to development, and I was asked by a maintainer to create the following document to outline the problems with `void` and propose potential solutions.

## The Problem

First, what is `void`? According to [the TypeScript handbook](https://www.typescriptlang.org/docs/handbook/basic-types.html#void):

> void is a little like the opposite of any: the absence of having any type at all. You may commonly see this as the return type of functions that do not return a value.

In practice, the `void` type is more or less a black hole kind of type. Nothing is assignable to `void` except itself, `undefined` and `any`, and `void` is not assignable to anything except itself, `unknown` and `any`. Additionally, typical type narrowings like `typeof x === "string"` do not narrow `void`, and `void` infects type unions like `void | string` by making the resulting type behave like `void`.

The handbook hints at the original use-case for void, which is that it was used to model functions which don’t return, i.e. functions without a `return` statement. Although the runtime behavior of these functions is to return `undefined`, maintainers argued for modeling these functions as returning `void` instead at the type level because implicit return values are seldom used, and if they are used this usually indicates some kind of programmer error. The reasoning was that if you actually wanted a function to have a return type of `undefined`, you could do so by explicitly returning `undefined` from the function.

This section of the TypeScript handbook is a good start, but it doesn’t fully address the intricacies of the `void` type. While `void` behaves mostly like a special type alias for `undefined` when used in typical assignments, it behaves more like `unknown` when used as the return type of a function type (`() => void`).

```ts
 // these functions should return implicitly or will error
function fn1(): void {}
const fn2 = (): void => {};

// only undefined or void can be assigned to void variables
const v: void = undefined;

// void in function types behaves like unknown insofar as it allows the assigned function to return anything
const callback: () => void = () => 1337;
```

Again, this makes sense according to the original motivations for void: if you’re marking a callback’s return type as void, then you don’t really care what the callback returns, because you’ve essentially promised that you’ll never use it. It’s important to note that `void` came very early in TypeScript’s development, and neither the `unknown` type nor even strict null checks existed at its point of creation.

Unfortunately, this dual nature of `void` as being either `undefined` or `unknown` based on its position has an undesirable consequence, which is that while `undefined` is always assignable to `void`, `void` is not assignable to `undefined`. Personally, I noticed this issue with increasing frequency because of my work with async and generator functions, to the point where I felt that the `void` type was getting in my way. These function syntaxes also did not exist when the `void` type was created, and their very existence violates the key assumption which motivated the creation of the `void` type in the first place, that we don’t or shouldn’t use the return values of functions with implicit returns.

This assumption collapses when we consider async and generator functions because we more or less *must* use the return values of async and generator functions even if they return implicitly.  Many async functions have implicit returns, where the function body is used to await other promises, and the vast majority of generator functions do not contain return statements at all. Despite this, it would be a basic programmer error not to use the returned promises or generator objects in some way, so much so that there are even lint rules like `no-floating-promise` to prevent you from ignoring the return values of these types of functions.

Because we must use the return values of async and generator functions, and because they are inferred to return compositions of `void` like `Promise<void>` or `Generator<any, void>`, `void` types inevitably leak into assignments. I have frequently been frustrated by attempts to assign `Promise<void>` to `Promise<undefined>`, or `Generator<any, void>` to `Generator<any, undefined>`; invariably, the error pyramids which TypeScript emits all end with `void` not being assignable to `undefined`.

```ts
class Example {
  _initPromise: Promise<undefined>;

  async initialize() {
    // some async setup code
    // no explicit return
  }

  constructor() {
    // A compiler error because initialize is inferred to return Promise<void>.
    this._initPromise = this.initialize();
  }
}

function *integers() {
  let i = 0;
  while (true) {
    yield i++;
  }
}

// A compiler error because value is inferred as number | void.
const value: number | undefined = integers().next().value;
```

The TypeScript lib definitions also leak void into assignments, with `Promise.resolve()` called with no arguments being inferred as `Promise<void>`, and the promise constructor explicitly requiring `undefined` to be passed to the `resolve()` function if the promise’s type parameter is `undefined` and not `void`.

All these details make trying to avoid `void` painful, almost impossible. We could explicitly `return undefined` from all non-returning functions and call `Promise.resolve(undefined)` to create a non-void Promise, but this violates one of the main goals of TypeScript, which is to produce idiomatic JavaScript. Moreover, it is not a workable solution for library authors who wish to avoid the `void` type, insofar as it would require libraries to impose the same requirements of explicit usages of `undefined` on their users.

Here’s the thing. If we were to do a survey of all the places where the `void` type is currently inferred, I would estimate that in >95% of these positions, the actual runtime value is always `undefined`. However, because we can’t eliminate the possibility that `void` is being used like `unknown`, we’re stuck leaving the voids in place. In essence, TypeScript is not providing the best possible inference for code wherever `void` is inferred. It’s almost as if TypeScript is forcing a nominal type alias for `undefined` on developers, while most TypeScript typing is structural.

Beyond producing unnecessary semantic errors, the `void` type is very confusing for JavaScript developers. Not many developers distinguish the return type of function declarations from the return type of function types as two separate type positions, and `void` is (probably?) the only type whose behavior differs based on its usage in these two places. Furthermore, we see evidence of this confusion whenever people create unions with `void` like `Promise<void> | void` or `void | undefined`. These union types are mostly useless, and hint that developers are just trying to get errors to go away without understanding the vagueries of void.

## The Solution

To allow the void type to be optional, I propose we make the following changes to TypeScript.

### Allow functions which have a return type of undefined or unknown to return implicitly.

```ts
// The following function definitions should not cause compiler errors
function foo(): undefined {
}

function bar(): unknown {
}

async function fooAsync(): Promise<undefined> {
}

function *barIterator(): Generator<any, unknown> {
}
```

Currently, attempting to type the return types of functions with implicit returns as `undefined` or `unknown` will cause the error `A function whose declared type is neither 'void' nor 'any' must return a value.` The `any` type is unacceptable because it makes functions with implicit returns type-unsafe, so we’re left with `void`. By allowing `undefined` or `unknown` returning functions to return implicitly, we can avoid `void` when typing implicitly returning functions.

This behavior should also extend to async and generator functions.

Related proposal https://github.com/microsoft/TypeScript/issues/36288

### Infer functions with implicit returns and no return type as returning undefined.

There are many situations where we would rather not have `void` inferred. For instance, when using anonymous callbacks with promises, we can unwittingly transform `Promise<T>` into `Promise<T | void>` rather than `Promise<T | undefined>`.

```ts
declare let p: Promise<number>;
// inferred as Promise<number | void> while Promise<number | undefined> would be SO MUCH NICER here.
const q = p.catch((err) => console.log(err));
```

I propose that all functions with implicit returns should be inferred as returning `undefined` when no return type is provided.

This behavior should also extend to async and generator functions.

Related proposal: https://github.com/microsoft/TypeScript/issues/36239

### Infer Promise.resolve() as returning Promise\<undefined\> and remove any other pressing usages of void in lib.d.ts.

I’m not sure if there are other problematic instances of `void` in TypeScript’s lib typings, but it would be nice if the idiomatic `Promise.resolve()` could just be inferred as `Promise<undefined>`. We could also change callback types to use `unknown` instead of `void` but that change feels much less necessary to avoid `void`.

### Allow trailing parameters which extend undefined to be optional.

One problem with the semantic changes above which I haven’t mentioned relates to yet another unusual `void` behavior. When trailing parameters in function types extend `void`, they become optional. This kind of makes sense because, for instance, it allows people to omit the argument to the `resolve()` function of a void promise, or the argument to the `return()` method of a void returning generator. Because of this, inferring `undefined` where we used to infer `void` might inadvertently change the required number of arguments to functions, causing type errors where previously there were none.

Therefore, I think if we were to consider the above proposals, we should also strongly consider changing TypeScript to infer trailing parameters which extend `undefined` as being optional.

```ts
function optionalAdd(a: number, b: number | undefined) {
  return b ? a + b : a;
}

// this should not error
optionalAdd(1);
```

This might be a more controversial change which bogs down the larger project of avoiding void, but I think we could reasonably implement the earlier proposals without making this change. Furthermore, I also think this change would be beneficial to TypeScript in a similar way to making `void` optional. Ultimately, I think the larger theme at play is that TypeScript has too many special ways to indicate undefined-ness which don’t affect the runtime and don’t provide any additional type safety.

___

These changes might seem sweeping, but the inflexible non-assignability of `void` should make the changes not as hard to upgrade into as they might seem. Moreover, the benefit to the TypeScript ecosystem will be that we can start refactoring `void` out of our type definitions. By replacing `void` with `undefined` and `unknown`, we can start being more explicit about what exactly our functions return and what we want from our callback types without losing type safety.

Furthermore, I think these changes are in line with TypeScript’s design goals, specifically that TypeScript should “3. Impose no runtime overhead on emitted programs” and “4. Emit clean, idiomatic, recognizable JavaScript code.” By allowing `undefined` to model implicit returns and optional trailing parameters, we’re merely describing JavaScript as it is, and the use of `void` to model these values can be surprising.

## ☝️ Potential Objections
Lots of people will object and say that `void` is actually a useful type, that the idea of an opposite “dual” of `any` is interesting or whatever. I don’t care for this kind of astronaut analysis and I don’t think the `void` type is useful, but any such arguments are immaterial to this proposal. My main problem isn’t that the `void` type exists, but that it is forced upon me. To be clear, none of my proposals change the behavior of the `void` type, nor do they seek the removal of the `void` type from TypeScript. I just want `void` to stop being inferred or required when I write idiomatic JavaScript. I believe that even if my proposals were implemented, people who actually cared about `void` could continue to explore the unusual features of this gross type and not know anything had changed.

People may also object that they still want to use the `void` type, because its inflexibility is useful for typing callbacks, as a guarantee to callback providers that the return value of the callback is never used. I would argue that for the callback case, `void` is completely superseded by `unknown`, and that you can use `unknown` in the place of `void` when typing first-class functions today without much friction. Furthermore, as the callback provider, if you really wanted to make sure that a callback’s return value is never used, the most idiomatic and foolproof way to do this in JavaScript would be to use the `void` operator in the runtime (`() => void hideMyReturn()`). Ironically, TypeScript infers this function as returning `undefined` and not `void`.

## 🔍 Search Terms
void, undefined, implicit return, non-returning function, noImplicitReturns

## 💣 Relevant Errors
- TS1345: An expression of type 'void' cannot be tested for truthiness.
- TS2322: Type 'void' is not assignable to type 'undefined'.
- TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
- TS7030: Not all code paths return a value.

## ✅ Viability Checklist
My suggestion meets these guidelines:

* [ ] This wouldn't be a breaking change in existing TypeScript/JavaScript code
* [x] This wouldn't change the runtime behavior of existing JavaScript code
* [x] This could be implemented without emitting different JS based on the types of the expressions
* [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
* [x] This feature would agree with the rest of [TypeScript's Design Goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals).

## 🔗 Selected Relevant Issues
- https://github.com/microsoft/TypeScript/issues/62
  Earliest reference to the `void` type.
- https://github.com/microsoft/TypeScript/issues/20006
  Some informative discussions on what maintainers think `void` should do.
- https://github.com/microsoft/TypeScript/issues/33357
  Void in generator types as it relates to the `return()` method.
- https://github.com/microsoft/TypeScript/issues/33420
  Documentation of some inconsistencies of function types which return `Promise<void>`, indicating that void isn’t as well-baked as some people seem to think it is.
- https://github.com/microsoft/TypeScript/issues/35850
  Issue which highlights the general confusion surrounding `void`.
- https://github.com/microsoft/TypeScript/issues/36239
  Proposal to infer non-returning functions as returning `undefined`.
- https://github.com/microsoft/TypeScript/issues/36288
  Proposal to allow functions whose return type is `undefined` to return nothing.
- https://github.com/microsoft/TypeScript/pull/40823
  Proposal to make `void` properties optional, which would further entrench `void`.