TypeScript: Intersection with void should resolve to never

🔎 Search Terms

intersection, void

🕗 Version & Regression Information

  • This is a crash
  • This changed between versions ______ and _______
  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about void
  • I was unable to test this on prior versions because _______

⏯ Playground Link

https://www.typescriptlang.org/play?#code/C4TwDgpgBAmlC8UDeUCGAuKBnYAnAlgHYDmUAvlAGRQBuA9vgCYBQQA

💻 Code

type Y = { a: string } & void

void is a more restricted type of undefined specifically to describe function that “returns nothing”.

In the JavaScript land, it actually returns an implicit undefined. So for all due and purposes, I believe void should have similar behavior as undefined whenever possible.

For undefined, intersection type resolves to never

type Y = { a: string } & undefined // never

https://www.typescriptlang.org/play?ssl=1&ssc=44&pln=1&pc=1#code/C4TwDgpgBAmlC8UDeUCGAuKBnYAnAlgHYDmUAvlAGRQCuhAJhAGZET1QD0HUhEAbhFwAoIA

🙁 Actual behavior

// Your code here
type Y = { a: string } & void // { a: string } & void

🙂 Expected behavior

// Your code here
type Y = { a: string } & void // never

Additional information about the issue

No response

About this issue

  • Original URL
  • State: closed
  • Created 10 months ago
  • Comments: 16 (12 by maintainers)

Most upvoted comments

The way I think of it is that void is like unknown with some special assignability restrictions when interacting with it directly, but those disappear and it acts more like unknown when it’s being compared across two return types in signatures. void & { x: string } has those same cannot-use-directly restrictions, but it behaves as expected when part of a signature:

[playground]

declare let s1: (() => void) & (() => { x: string })
declare let s2: () => void & { x: string };
declare let t: () => { x: string };

t = s1;
t = s2;
t().x;

Is it useful? Not that I can tell. Is it weird? Yes. But is it about as internally consistent as it can be given the weird rules? I think so.

I don’t know if this has been linked yet but if not: #42709 void is just weird in general, but any changes around it would likely be in the direction of tackling #42709, based on past comments from maintainers.

In particular, this remark from there is in line with what you’re saying:

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.

If const f: () => void = () => ({ x: "hello ") is legal, then I think quite clearly void & { x: string } is not never.

Every time I think about void in TypeScript too much I start feeling this empty despair, like logic has been swallowed up by some great… uh, what’s the word? It’s like “hole” but that’s not it. Gulf? Abyss? Chasm? Ah, never mind. Anyway, void is inconsistent in TypeScript but I don’t know that messing with it would be an improvement.

For function return types it’s essentially unknown, not undefined, and doing as suggested here would almost certainly break a bunch of existing code. What’s the return type of a function which matches both () => void and () => {a: string}? It certainly isn’t never. I’d say it’s {a: string}, bolstering the unknown interpretation. Indeed I’d rather see a suggestion that void should be given consistent unknown semantics instead of having anything to do with undefined, but that would break a lot of code also.

The current behavior has to be intentional though, so whatever it is, it’s not a bug.