Repository navigation
Revisit void/unknown/undefined interactions #52486
Description
Current situation
void is a special type which behaves like unknown with three exceptions (that I am aware of):
- When used as a function return type, the function must return
undefined. - The compiler assumes that
void === undefinedfor the purposes of narrowing. In other words, this type-checks whereunknownwould not:function bar(x: string) {} function foo(x: void | string): void { if (x !== undefined) { bar(x) } }
- Functions which return
voidmay have non-returning code branches.
Problems
Firstly, I am aware that the unsoundness caused by these special cases is intentional: that in itself is not the issue I am raising. The issue is that with other unsoundness, you can usually opt-in to more sound type-checking. For example, with any, users have the option to not use it - instead using unknown or generics to get a more sound (if more verbose) solution.
With void, we have no choice. There is no way to say that a function returns undefined, because the following does not type-check:
function foo(): undefined {} // A function whose declared type is neither 'void' nor 'any' must return a value.(2355)This stems from the fact that void servese two purposes:
- To relax type-checking
- To annotate that a function intentionally does not explicitly return a value
There are additional problems with void when it comes to async functions. Since Promise<void> is not compatible with Promise<undefined> it works against itself, resulting in spurious type errors where there should be none.
Suggestion
- Allow implicit returns from any function definition whose return-type is compatible with
undefined. - When unspecified, infer
undefinedinstead ofvoidfor functions which do not return a value. - [Optional] Change the
Not all code paths return a value.warning to fire only when some but not all codepaths have an explicit return.
Of these (1) is not a breaking change. (2) may cause some breakage if the return-type of such a function is extracted via ReturnType<F>. (3) would cause warnings for functions which use a mix of implicit/explicit returns.
For this reason, it may be desirable to implement these changes behind a new configuration option.
Overall this would improve typescript because it allows more valid code to compile, while providing more sound type-checking. Common errors like accidentally ommitting a return statement will still be caught. The language will be simpler to learn because void and its special rules will be encountered less often. These changes could be the start of a gradual phase-out of the void type entirely in favour of unknown.
🔍 Search Terms
void undefined unknown explicit implicit return function unsound soundness
✅ Viability Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
Activity
MartinJohns commentedon Jan 29, 2023
Related: #42709
RyanCavanaugh commentedon Jan 30, 2023
I think this is a duplicate of #42709 (if it's not, a compare/contrast is necessary)
Re this error:
I agree it's silly. I think we would take a PR that allows no return statements in a function returning exactly
undefined, even ifnoImplicitReturnsis set. I'll have to run it by folks, but I'll co-opt this issue to track that rather than mark as duplicate.RyanCavanaugh commentedon Jan 30, 2023
Updates from the team chat:
undefinedis clearly the best way to do itMartinJohns commentedon Jan 30, 2023
Allowing implicit returns when implicit returns are explicitly disallowed feels really weird.
Diggsey commentedon Jan 30, 2023
Martin Johns (@MartinJohns) with
noImplicitReturnsdisabled you get no error at all even if the function is supposed to return eg. a number. That's behaviour that we'd really want to continue to detect. Although perhaps that option is poorly named.RyanCavanaugh commentedon Jan 30, 2023
noImplicitReturnsis indeed a bit poorly named. If the setting never let you exit a function through the end of the statement block, that'd be a consistent rule (albeit one you could just lint). The current rule says you can exit that way as long as the return type isvoidorany, and not allowingundefinedin those carve-outs feels like a miss rather than an intentional behavior.It's all pretty ironic since, for all the holes our type system has, knowing that a function with no return statements will return
undefinedis a 100% guarantee!fatcerberus commentedon Jan 30, 2023
I always figured it was intentional: if you annotate as
void, that suggests the caller isn’t meant to use the return value at all and therefore an explicitreturnis redundant. If you annotate asundefinedthen that’s an explicit contract so the return should be explicit as well.whzx5byb commentedon Mar 27, 2023
Fixed by #53092