Hhvm: Hack: Cannot use `void` as parameter type

Created on 4 Nov 2016  路  10Comments  路  Source: facebook/hhvm

HHVM Version

HipHop VM 3.16.0-dev (rel)
Compiler: heads/master-0-g6e071f9bc61705bf78cc03a34bea56187265b3ea
Repo schema: 5f3c657c22fb79db502a22ee010bf3d040eff565

Standalone code, or other way to reproduce the problem

<?hh // strict

interface Runnable<-Tin, +Tout> {
  public function run(Tin $data): Tout;
}

// Outputs nothing, but consumes ints
class VoidOut implements Runnable<int, void> {
  public function run(int $data): void {}
}

// Outputs ints, but consumes nothing
class VoidIn implements Runnable<void, int> {
  public function run(void $data): int {
    return 1;
  }
}

Expected result

Code type checks, the $data variable is of type void and cannot be used for anything, and VoidIn::run() must be called with null as input.

Actual result

Error on the parameter to VoidIn::run():

The void typehint can only be used to describe a function return type (Naming[2063])

Discussion

I am unsure of what to do in this case. If I have a generic interface with a type parameter used as input, but an implementation that wants to ignore its input, what am I supposed to use as a unit type to fill the type parameter?

I've tried void, (), null and noreturn and none of them work. I could use mixed, but that's a top type, not a unit type, and will permit callers to pass anything as input even though it will be ignored.

hack

Most helpful comment

Hack does have the type null now, which I think can function as a unit type? (The important property of a unit type AIUI being that there is exactly one value of that type, which null satisfies.) Kind of weird to read but I think it will work for you?

// Outputs ints, but consumes nothing
class NullIn implements Runnable<null, int> {
  public function run(null $data): int {
    return 1;
  }
}

Edit: yeah, the docs specifically mention this use-case. https://docs.hhvm.com/hack/built-in-types/null

All 10 comments

Here is a workaround:

final class Nothing {
  private function __construct() {
    throw new \Exception('This class must not be constructed');
  }
}

type void_in = ?Nothing;

The only valid value for void_in is null.

This doesn't actually solve your problem, but an important point for why it works this way:

the $data variable is of type void and cannot be used for anything, and VoidIn::run() must be called with null as input.

This is not correct -- null is not of type void. There are no values at all that are of type void, which is the entire point of the type void. This is why a parameter of type void doesn't make sense, since there are no values that could possibly satisfy it.

That was my rough suggestion for how it should work. I know that's not how it works at the moment.

At runtime, function () {}, function () { return; } and function () { return null; } are all identical, so Hack's idea of void and null being distinct only exists artificially at the type level.

php > var_dump((function () {})());
php shell code:1:
NULL
php > var_dump((function () { return; })());
php shell code:1:
NULL
php > var_dump((function () { return null; })());
php shell code:1:
NULL
php > 

Ideally, void would serve as a perfectly good unit type without restriction, null would have type void and void would be assignable to any nullable type. I suspect this would also solve #7450 since null would then have a complete type.

At least TypeScript, Flow, Swift, Rust, and most functional programming languages work this way (either using void or ()). For example this is valid TypeScript/Flow.

function foo1(): void {
}

function foo2(): void {
  return;
}

function foo3(): void {
  return undefined;
}

let x: void = foo3();

function blah(x: void): void {
  return x;
}

blah(x);

I think that Hack needs _both_ a bottom type _and_ a unit type. Rust, in particular, has both: any empty enum is a bottom type (even having a value of an empty enum invokes Undefined Behavior, and there is no way to create one in safe code, so any code that involves such a value can be considered unreachable).

@DemiMarie Hack's bottom type is noreturn, but it can only be used as a function's return type. I can't imagine needing to write the bottom type in any other position, but if you do need to do that I think you should open a separate issue.

You can also do:

<?hh
newtype nada = (int, int); // newtypes are opaque outside the file, so as long as there's no way to create one, the following declaration works ...

function nulls_only(?nada $data): void {} 

Hack has a true bottom type as nothing added in hhvm 4.1.0. We don't have a unit type, which appears to be what you need.

Hack does have the type null now, which I think can function as a unit type? (The important property of a unit type AIUI being that there is exactly one value of that type, which null satisfies.) Kind of weird to read but I think it will work for you?

// Outputs ints, but consumes nothing
class NullIn implements Runnable<null, int> {
  public function run(null $data): int {
    return 1;
  }
}

Edit: yeah, the docs specifically mention this use-case. https://docs.hhvm.com/hack/built-in-types/null

@jwatzman I don't have HHVM anywhere to test it but it sounds good to me!

null is indeed a unit type. In that case, we've got both a unit and bottom type.

Was this page helpful?
0 / 5 - 0 ratings