Types: Advanced Rules

The type checking rules are generally straightforward (e.g. can't pass a string to something that expects an int). There are, however, some rules that have a bit more advanced semantics.

Soft Type Hints

Take a look at this example.

<?hh

namespace Hack\UserDocumentation\Types\AdvancedRules\Examples\SoftHint;

// HHVM will throw a warning instead of fatal if, for example, a bool is passed
// in
function foo(@int $x): bool {
  return $x < 5 ? true : false;
}

function call_foo(): void {
  foo(5);
  foo(false);
}

call_foo();
Output
Warning: Argument 1 to Hack\UserDocumentation\Types\AdvancedRules\Examples\SoftHint\foo() must be of type @int, bool given in /data/users/joelm/user-documentation/guides/hack/20-types/08-advanced-rules-examples/softhint.php.type-errors on line 9

What does the "@" in front of the type mean? This causes HHVM to trigger a warning (thus always continuing execution) instead of a catchable fatal error when the passed parameter does not match. It is used to allow you to slowly add types to your code.

Soft type hints have no effect on the typechecker behavior. The typechecker will still throw an error if a type is mismatched.

Superglobals

Superglobals are available no matter what scope you are currently in.

<?hh

namespace Hack\UserDocumentation\Types\AdvancedRules\Examples\SuperGlobals;

function get_superglobal(string $s): array<string> {
  // If you try to use a variable that doesn't exist (e.g., $_NOEXIST), the
  // typechecker will thrown an undefined variable error.
  switch ($s) {
    case '_GET':
      return $_GET;
      break;
    case '_ENV':
      return $_ENV;
      break;
    case '_SERVER':
      return $_SERVER;
      break;
    default: // not supporting anything else
      return array();
  }
}

function sg(string $s): array<string> {
  return get_superglobal($s);
}

var_dump(is_array(sg('_SERVER')));
Output
bool(true)

The typechecker knows about the built-in superglobals.

In Hack's strict mode, superglobals are not supported. So you will have to create functions in something like partial mode to call from a strict mode file.

A not perfect, but possibly viable, alternative to using superglobals can be accomplished using a repository that exposes PSR-7 to Hack. The HHI files in this repo give the Hack typechecker information about interfaces.

Variadic Arguments

Hack supports variadic arguments:

function foo(<any explicit arguments>, int ...$args) // $args is a list of int arguments

The typechecker will enforce the variadic types, however for performance, the runtime will not.

<?hh

namespace Hack\UserDocumentation\Types\AdvancedRules\Examples\Variadic;

function foo(int ...$args): vec<int> {
  $ret = vec[];
  foreach ($args as $arg) {
    $ret[] = $arg;
  }
  return $ret;
}

function bar(): void {
  var_dump(foo(1, 2, 3, 4));
}

bar();
Output
vec(4) {
  int(1)
  int(2)
  int(3)
  int(4)
}

Fallthrough

Unintentional fallthrough in switch statements are a common mistake. Hack provides a way to catch fallthrough, adding a way to tell it that it was intentional as well.

<?hh

namespace Hack\UserDocumentation\Types\AdvancedRules\Examples\Fallthrough;

function fallthrough(int $x): void {
  switch ($x) {
    case 1: echo "1"; break; // without the break, typechecker throws an error
    case 2: echo "2"; break;
    case 3: echo "3"; break;
    case 4: echo "4";        // but we can tell the typechecker we want it
    // FALLTHROUGH
    default: echo "5";
  }
}

fallthrough(4);
Output
45

Use // FALLTHROUGH to tell the typechecker that our falling through is intentional.

Class Property Initialization

Hack requires class properties to be initialized to a value of its proper annotated type before it is used.

  • All non-nullable static class properties must be initialized with a value.
  • Nullable static class properties that don't have an initial value has a null value by default.
  • All non-nullable non-static class properties must be initialized in a constructor.
  • Nullable non-static class properties that are not initialized in a constructor will have a null value by default.
  • You cannot call public or private instance methods on a class before its properties are initialized in the constructor.
  • You can call private instance methods before properties are initialized in the constructor, as long as the properties are initialized somewhere along that private call chain before a property is accessed.

Method Inheritance

A parent class defines a method and its children override it. That's very common. Hack has some rules regarding the way overridden methods are typed that must be followed.

  • The parameters of any overridden method must match in both argument count and the type on each argument, exactly.
  • A return type of an overridden method may have a more specific type than its parent; they must be compatible, of course (e.g., arraykey in the parent; string in the child).