1

What is the purpose of void as a union type compared to a optional parameter, can they be used interchangeably? I was expecting example.func1(); to throw an error in the following example:

interface IExample
{
    num: number;
}

class Example implements IExample{
    num = 1;
    public func1(param: IExample | void){
    }

    public func2(param?: IExample){
    }
}

var example = new Example();
example.func1(); // no error - error expected
example.func2(); // no error
example.func1(example); // no error
example.func2(example); // no error

playground

Zze
  • 18,229
  • 13
  • 85
  • 118
  • 1
    There are a few things going on here. You have an empty `interface`. Most linters will warn you about declaring types with no members because almost everything is a subtype of `{}` including `{}`, `1`, `{p: number}`, `["x", 3]`, `() => {}`, etc. The last 2 calls, are valid because `() => Example` is a subtype of `IExample`. I'm perplexed by the first two calls because `function f(x: {} | undefined)` does not render `x` optional, just allows you to pass `undefined`. I feel like there is something obvious I am missing... – Aluan Haddad Aug 10 '20 at 23:44
  • 1
    @AluanHaddad 100% with you on empty interfaces, I made it empty for this example only. `example.func1();` is the reason that I made this post, because I don't understand why this is not an error. I am in the same boat re missing something super obvious. – Zze Aug 11 '20 at 01:34
  • Interestingly, the following _is_ an error: `type T = {p: string | void}; let x: T = {}`. – Aluan Haddad Aug 11 '20 at 01:51

1 Answers1

4

I'm going to ignore the empty interface issue whereby any non-nullish values are compatible with {}. Add members to your interface and the () => example calls will fail.


I don't think this is really documented anywhere obvious, but in TypeScript trailing function arguments that accept void are considered optional, a feature implemented in microsoft/TypeScript#27522. So your two functions behave similarly.

declare let f1: (x?: string) => string;
declare let f2: (x: string | void) => string;
f1(); // okay
f2(); // okay

There's some weirdness surrounding this feature, such as the fact that it doesn't work in the face of generics (microsoft/TypeScript#29131 and microsoft/TypeScript#39600).

And the compiler doesn't see the types of f1 and f2 above as truly interchangeable, despite being callable in the same ways:

f1 = f2; // okay
f2 = f1; // error!

probably because void is also a type given to return values from functions where you're not supposed to use the return value, leading to this bizarre situation:

f1(console.log()); // error
f2(console.log()); // okay?!

And the feature only applies to function parameters; object properties that accept void are not considered optional (although it's been even suggested and implemented but not (yet?) merged in microsoft/TypeScript#40823).

So I'd say it's kind of a limited-scope not-fully-general not-fully-baked not-well-documented feature you've stumbled into. Oh well, hope that makes some sense. Good luck!

Playground link

jcalz
  • 264,269
  • 27
  • 359
  • 360
  • 1
    Thanks jcalz - this is really good. I have also updated my question now with a better defined interface to mitigate the identified issue. – Zze Aug 11 '20 at 02:23
  • 2
    I appreciate the explanation and the context here. Would be fair to say that one should prefer `x:? T` over `x: T | void` to denote optional parameters wherever possible for the preservation of sanity and love of common decency? – Aluan Haddad Aug 11 '20 at 02:30
  • 1
    Yeah, given the state of the feature, I'd recommend staying clear of it. – jcalz Aug 11 '20 at 02:32