Types: Type System

Describing types in Hack is largely done through explicit annotations in your Hack source code. Hack has a large list of possible annotation types. You can check out a summary of each of these types in our table.

Common Primitives

The main primitive types available in PHP are available as explicit type annotations in Hack. These include:

  • bool
  • int
  • float
  • string
  • array
  • resource
<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\Primitive;

class A {
  protected float $x;
  public string $y;

  public function __construct() {
    $this->x = 4.0;
    $this->y = "Day";
  }
  public function foo(bool $b): float {
    return $b ? 2.3 * $this->x : 1.1 * $this->x;
  }
}

function bar(): string {
  // local variables are inferred, not explicitly typed
  $a = new A();
  if ($a->foo(true) > 8.0) {
    return "Good " . $a->y;
  }
  return "Bad " . $a->y;
}

var_dump(bar());
Output
string(8) "Good Day"

Alias Primitives

Alias primitives are not supported in Hack. So the following are not valid types to be used in type annotations:

  • boolean
  • integer
  • real
  • double

void

void is a special primitive type that means that a function or method returns no observable value. You can use return; in a void function.

NOTE: void can only be used in method or function returns. It is not applicable to properties or parameters.

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\Void;

class A {
  protected float $x;
  public string $y;

  public function __construct() {
    $this->x = 4.0;
    $this->y = "Day";
  }
  public function foo(bool $b): float {
    return $b ? 2.3 * $this->x : 1.1 * $this->x;
  }
}

// void can only be used as a return types
function bar(): void {
  // local variables are inferred, not explicitly typed
  $a = new A();
  if ($a->foo(true) > 8.0) {
    echo "Good " . $a->y;
  } else {
    echo "Bad " . $a->y;
  }
}

bar();
Output
Good Day

In Async

It is relatively common for async functions to return Awaitable<void>. This means that while the function itself is returning an awaitable, the result of that awaitable will have no value. This, in essence, means the async function did some operation asynchronously that did not require a return value to the caller.

noreturn

noreturn is a special primitive type that means that a function or static method never returns a value. Similar to void, but you cannot even use return; in a function that has a return type of noreturn.

noreturn is used to indicate that a given function or static method always throws an exception or somehow terminates the program within the function itself.

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\NoReturn;

class A {
  protected float $x;
  public string $y;

  public function __construct() {
    $this->x = 4.0;
    $this->y = "Day";
  }
  public function foo(bool $b): float {
    return $b ? 2.3 * $this->x : 1.1 * $this->x;
  }

  // no return cannot be on an instance method
  // only functions and static class methods
  public static function baz(bool $b): noreturn {
    if ($b) {
      throw new \Exception("No Return");
    } else {
      exit(1);
    }
    return; // Even this will cause type-errors
  }
}

// void can only be used as a return types
function bar(): void {
  // local variables are inferred, not explicitly typed
  $a = new A();
  if ($a->foo(true) > 8.0) {
    echo "Good " . $a->y;
  } else {
    echo "Bad " . $a->y;
  }
  A::baz(false);
}

bar();
Output
Good Day

NOTE:

Only Static Methods and Functions

noreturn can only be used in function or static method returns.

Instance methods cannot be noreturn. This is due to the order in which the typechecker's analysis phases happen. The return type of an instance method call cannot be determined during control flow analysis because it needs to know the type on the left-hand side of the ->, and the results of type inference aren't available yet. This isn't an issue for calls to static methods, since those can be resolved before types have been inferred.

noreturn is not applicable to properties or parameters.

Objects

You can use the name of any built-in or custom class or interface.

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\Obj;

class Z {
  public function create_A(): A {
    return new A();
  }
}

class A {
  protected float $x;
  public string $y;

  public function __construct() {
    $this->x = 4.0;
    $this->y = "Day";
  }
  public function foo(bool $b): float {
    return $b ? 2.3 * $this->x : 1.1 * $this->x;
  }
}

// We are taking a Z and returning an object of type A
function baz(Z $z): A {
  return $z->create_A();
}

function bar(): string {
  // local variables are inferred, not explicitly typed
  $z = new Z();
  $a = baz($z);
  if ($a->foo(true) > 8.0) {
    return "Good " . $a->y;
  }
  return "Bad " . $a->y;
}

var_dump(bar());
Output
string(8) "Good Day"

mixed

mixed is essentially a catch-all type that represents any possible Hack value (including null and void).

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\Mixed;

class A {
  public float $x;
  protected string $y;

  public function __construct() {
    $this->x = 4.0;
    $this->y = "Day";
  }
  // mixed is the most lax type. Use it only when necessary
  public function foo(bool $b): mixed {
    return $b ? 2.3 * $this->x : $this->y;
  }
}

function bar(): string {
  // local variables are inferred, not explicitly typed
  $a = new A();
  $v = $a->foo(false);
  // Since A::foo() returns a mixed, we need to do various checks to make sure
  // that we let the typechecker know understand what is coming back.
  if (is_float($v)) {
    return "No String";
  }
  invariant(is_string($v), "Something went wrong if this isn't true");
  return "Good " . $v;
}

var_dump(bar());
Output
string(8) "Good Day"

Use sparsely

There are valid uses for mixed, but generally you want to be as specific as possible with your typing since the typechecker can only do so much with mixed given its constraints are so lax.

this

this can only be used as a return type annotation on a method of a class. this signifies that the method returns an object of the same class on which the method is defined.

The primary purpose of return this is to allow chaining of method calls on the instance of the class itself or its subclasses.

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\ThisChaining;

class Vehicle {
  private ?int $numWheels;
  private ?string $make;

  public function setNumWheels(int $num): this {
    $this->numWheels = $num;
    return $this;
  }

  public function setMake(string $make): this {
    $this->make = $make;
    return $this;
  }
}

class Car extends Vehicle {
  private ?bool $autoTransmission;

  public function setAutomaticTransmission(bool $automatic): this {
    $this->autoTransmission = $automatic;
    return $this;
  }
}

class Hybrid extends Car {
  private ?bool $pluggable;

  public function setPluggable(bool $pluggable): this {
    $this->pluggable = $pluggable;
    return $this;
  }

  public function drive(): void {}
}


function run(): void {
  $h = new Hybrid();
  // $h->NumWheels(4) returns the instance so you can immediately call
  // setMake('Tesla') in a chain format, and so on. Finally culminating in an
  // actionable method call, drive().
  $h->setNumWheels(4)
    ->setMake('Tesla')
    ->setAutomaticTransmission(true)
    ->setPluggable(true)
    ->drive();
  var_dump($h);
}

run();
Output
object(Hack\UserDocumentation\Types\TypeSystem\Examples\ThisChaining\Hybrid)#1 (4) {
  ["pluggable":"Hack\UserDocumentation\Types\TypeSystem\Examples\ThisChaining\Hybrid":private]=>
  bool(true)
  ["autoTransmission":"Hack\UserDocumentation\Types\TypeSystem\Examples\ThisChaining\Car":private]=>
  bool(true)
  ["numWheels":"Hack\UserDocumentation\Types\TypeSystem\Examples\ThisChaining\Vehicle":private]=>
  int(4)
  ["make":"Hack\UserDocumentation\Types\TypeSystem\Examples\ThisChaining\Vehicle":private]=>
  string(5) "Tesla"
}

this on a static method means that a class method returns an object of the same class as the calling method. You can use it to return an instance of an object from a static class method where you are returning something like new static().

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\ThisStatic;

class A {
  protected float $x;
  public string $y;

  // typechecker error if constructor isn't final because new static() cannot
  // be called to return an instance of a subclass
  final protected function __construct() {
    $this->x = 4.0;
    $this->y = "Day";
  }

  public function foo(bool $b): float {
    return $b ? 2.3 * $this->x : 1.1 * $this->x;
  }

  // The this type annotation allows you to return an instance of a type
  public static function create(int $x): this {
    $instance = new static();
    if ($x < 4) {
      $instance->x = floatval($x);
    }
    return $instance;
  }
}

function bar(): string {
  // local variables are inferred, not explicitly typed
  // There is no public constructor, so call A's create() method
  $a = A::create(2);
  if ($a->foo(true) > 8.0) {
    return "Good " . $a->y;
  }
  return "Bad " . $a->y;
}

var_dump(bar());
Output
string(7) "Bad Day"

num

num is special union type of int and float. Normally, in Hack, ints and floats are incompatible types. However it was realized that many numerical operating functions work similarly whether you pass an integer or a float. num is used for those cases.

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\Num;

class A {
  protected num $x;
  public string $y;

  public function __construct(num $x) {
    $this->x = $x;
    $this->y = "Day";
  }
  public function foo(bool $b): num {
    return $b ? 2.3 * $this->x : 1.1 * $this->x;
  }
  // The $x property can be either a float or int
  public function setNum(num $x): void {
    $this->x = $x;
  }
}

function check(A $a): string {
  if ($a->foo(true) > 8.0) {
    return "Good " . $a->y;
  }
  return "Bad " . $a->y;
}

function bar(): string {
  // local variables are inferred, not explicitly typed
  // Setting the $x property in A to an int
  $a = new A(4);
  $ret = check($a);
  // Now setting to a float
  $a->setNum(0.4);
  $ret .= "##" . check($a);
  return $ret;
}

var_dump(bar());
Output
string(17) "Good Day##Bad Day"

arraykey

arraykey is special union type of int and string. Arrays and collection types can be keyed by int or string. Suppose, for example, an operation was performed on an array to extract the keys, but you didn't know the type of the key. You were left with using mixed or doing some sort of duplicative code. arraykey resolves that issue.

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\ArrayKey;

class A {
  protected float $x;
  public string $y;

  public function __construct(float $x) {
    $this->x = $x;
    $this->y = "Day";
  }
  public function foo(bool $b): float {
    return $b ? 2.3 * $this->x : 1.1 * $this->x;
  }
}

// This function can return either a string or an int since it is typed to
// return an arraykey
function bar(): arraykey {
  // local variables are inferred, not explicitly typed
  $a = new A(0.9);
  if ($a->foo(true) > 8.0) {
    return "Good " . $a->y;
  }
  return 5;
}

var_dump(bar());
Output
int(5)

XHP

There are two XHP interfaces that are used when typing XHP objects: XHPChild and XHPRoot.

XHPRoot is any object that is an instance of an XHP class.

XHPChild is the set of valid types for echoing within an XHP context (e.g., echo <div>{$xhpobj}</div>;). This includes the primitive types of string, int and float, along with arrays of those types plus any XHP object.

<?hh

// Namespaces and XHP have issues right now

// A custom class extends :x:element and has a render method that returns
// XHPRoot so that you can do something like echo "<custom-class />;" This
// automatically calls the render method
class :ts-simple-xhp extends :x:element {
  public function render(): XHPRoot {
    return <b>Simple</b>;
  }
}

class TSPage {
  protected string $link;
  protected string $title;

  public function __construct(string $title, string $link) {
    $this->link = $link;
    $this->title = $title;
  }

  // return XHPChild when rendering a UI element and the elements
  // of that render are valid for XHP (e.g., strings, arrays of ints, etc.)
  public function render_page(): XHPChild {
    return <div>{$this->title}...{$this->link}</div>;
  }

  public function get_simple(): XHPRoot {
    return <ts-simple-xhp />;
  }
}

function ts_xhp_sample(): void {
  $p = new TSPage("Test XHP", "http://internet.org");
  echo $p->render_page();
  echo PHP_EOL;
  echo $p->get_simple();
}

ts_xhp_sample();
Output
<div>Test XHP...http://internet.org</div>
<b>Simple</b>

Nullable

A nullable type is represented by a ? placed as a prefix to the type itself (e.g., ?int). It merely means that the value can be of that type or null.

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\Nullable;

class A {
  protected float $x;
  public string $y;

  public function __construct() {
    $this->x = 4.0;
    $this->y = "Day";
  }

  // We can pass a nullable as a parameter as well as being nullable on the
  // return type. Properties can also be nullable
  public function foo(?bool $b): ?float {
    return ($b || $b === null) ? 2.3 * $this->x : null;
  }
}

// The ? means that the function can return null in addition to the string
function bar(): ?string {
  // local variables are inferred, not explicitly typed
  $a = new A();
  if ($a->foo(null) === null) {
    return null;
  }
  return "Good " . $a->y;
}

var_dump(bar());
Output
string(8) "Good Day"

What can't be nullable?

void, noreturn cannot be nullable because null is a valid and observable return value.

As mixed already allows for the value null, you cannot write ?mixed.

Generics

Generics allows a specific piece code to work against multiple types in a type-safe way. Depending on the type parameter specified, a generic type can work against one type or many. Box<T> for example is the most permissive for types that can be passed to it. array<int> is the least permissive as ints are only allowed to be placed in the array.

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\Generics;

// This is a generic class that is parameterized by T. T can be bound to any
// type, but once it is bound to that type, it must stay that type. It
// can be bound to mixed.
class Box<T> {

  private array<T> $contents;

  public function __construct() {
    $this->contents = array();
  }

  public function put(T $x): void {
    $this->contents[] = $x;
  }

  public function get(): array<T> {
    return $this->contents;
  }
}

// This is a generic function. You parameterize it by putting the type
// parameters after the function name
function gift<T>(Box<T> $box, T $item): void {
  $box->put($item);
}

function ts_generics_1(): array<string> {
  $box = new Box();
  gift($box, "Hello");
  gift($box, "Goodbye");
  // can't do this because the typechecker knows by our return statement and
  // our return type that we are binding the Box to a string type. If we did
  // something like ": array<arraykey>", then it would work.
  // This will work when running in HHVM though.
  gift($box, 3);
  return $box->get();
}

function ts_generics_2(): array<arraykey> {
  $box = new Box();
  gift($box, "Hello");
  gift($box, "Goodbye");
  gift($box, 3);
  return $box->get();
}

function run(): void {
  var_dump(ts_generics_1());
  var_dump(ts_generics_2());
}

run();
Output
array(3) {
  [0]=>
  string(5) "Hello"
  [1]=>
  string(7) "Goodbye"
  [2]=>
  int(3)
}
array(3) {
  [0]=>
  string(5) "Hello"
  [1]=>
  string(7) "Goodbye"
  [2]=>
  int(3)
}

Enums

An enum is a type made up of constants, usually related to each other. Unlike class constants, etc., enums are first-class types in the Hack type system. As such, they can be used as type annotations anywhere a primitive or object type can.

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\Enum;

enum Color: string {
  BLUE = "blue";
  RED = "red";
  GREEN = "green";
}

// Enums can be used as type annotations just like any other type.
function render_color(Color $c): void {
  echo $c;
}

render_color(Color::BLUE); // "blue"
render_color(Color::RED); // "red"
Output
bluered

Callables

There is a callable typehint, but Hack does not allow it (HHVM accepts it, however if you don't care about the type checker errors).

Instead, Hack provides a more expressive callable type of the form:

function(0..n parameter types): return type
<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\Call;

function use_callable(
  Vector<int> $vec,
  (function(int) : ?int) $callback,
): Vector<?int> {
  $ret = Vector {};
  foreach ($vec as $item) {
    $ret[] = $callback($item);
  }
  return $ret;
}

function ts_callable(): void {
  $callable = function(int $i): ?int {
    return $i % 2 === 0 ? $i + 1 : null;
  };
  var_dump(use_callable(Vector {1, 2, 3}, $callable));
}

ts_callable();

// Returns
/*
object(HH\Vector)#3 (3) {
  [0]=>
  NULL
  [1]=>
  int(3)
  [2]=>
  NULL
}
*/
Output
object(HH\Vector)#3 (3) {
  [0]=>
  NULL
  [1]=>
  int(3)
  [2]=>
  NULL
}

Tuples

Tuples provide a type specifying a fixed number of values of possibly different types. The most common use of a tuple is to return more than one value from a function.

(type1, ... ,type n)

Tuples are like fixed arrays. You cannot remove or change any of the types from a tuple, but you can change the values of each type. To create a tuple, you use the same syntax as an array, but s/array/tuple.

tuple(value1, ..., value n);
<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\Tup;

// You don't use the keyword tuple when annotating with one
// You do use the keyword tuple when forming one.
function q_and_r(int $x, int $y): (int, int, bool) {
  return tuple(round($x / $y), $x % $y, $x % $y === 0);
}

function ts_tuple(): void {
  // Tuples lend themselves very well to list()
  list($q, $r, $has_remainder) = q_and_r(5, 2);
  var_dump($q);
  var_dump($r);
  var_dump($has_remainder);
}

ts_tuple();
Output
float(3)
int(1)
bool(false)

Arrays under the covers

In HHVM, tuples are implemented as arrays, and you can call is_array() on them and get a true returned.

Type Aliases

Type aliases allow you to give a new name to an existing type. They can be used just like those existing types in annotations.

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\TypeAlias;

type ID = int;
type Name = string;

class Customers {
  private array<ID, Name> $c;
  public function __construct() {
    $this->c = array();
    $this->c[0] = "Joel";
    $this->c[1] = "Fred";
    $this->c[2] = "Jez";
    $this->c[3] = "Tim";
    $this->c[4] = "Matthew";
  }

  public function get_name(ID $id): ?Name {
    if (!array_key_exists($id, $this->c)) {
      return null;
    }
    return $this->c[$id];
  }

  public function get_id(Name $name): ?ID {
    $key = array_search($name, $this->c);
    return $key ? $key : null;
  }
}

function ts_type_alias(): void {
  $c = new Customers();
  var_dump($c->get_name(0));
  var_dump($c->get_id("Fred"));
  var_dump($c->get_id("NoName"));
}

ts_type_alias();
Output
string(4) "Joel"
int(1)
NULL

Classname

Foo::class in PHP refers to the string constant containing the fully qualified name of Foo.

Hack introduces a special type aliases called classname<T>. So, now when someone writes Foo::class, not only does the Hack typechecker recognize the string representation of the class, but also this new type that provides semantics of the class itself.

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\CN;

<<__ConsistentConstruct>>
interface I {
  abstract const int A_CONST;
  public static function staticMeth(): void;
  public function meth(): void;
}

class C implements I {
  const int A_CONST = 10;
  public static function staticMeth(): void { echo "staticMeth\n"; }
  public function meth(): void { echo "meth\n"; }
  public function methOnlyInC(): void { echo "methOnlyInC\n"; }
}
class D {}

// With the classname<T> built-in type alias, the typechecker can now
// understand all these constructs!
function check_classname(classname<I> $cls, mixed $value): void {
  $const = $cls::A_CONST; // typechecked!
  $cls::staticMeth(); // typechecked!
  invariant($value instanceof $cls, "Bad if not");
  $value->meth(); // typechecked!
}

function ts_classname(): void {
  $c = new C();
  $d = new D();
  check_classname(C::class, $c);
  check_classname('C', $c); // error! only C::class is a classname
  check_classname(D::class, $d); // error! a D is not an I
}

ts_classname();
Output
staticMeth
meth

Fatal error: Class undefined: C in /data/users/joelm/user-documentation/guides/hack/20-types/02-type-system-examples/classname.php.type-errors on line 23

Shapes

Shapes are a specific type alias representing a structured array, with a deterministic name and type of keys. They can be used as type annotations as well.

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\Shp;

type customer = shape('id' => int, 'name' => string);

function create_user(int $id, string $name): customer {
  return shape('id' => $id, 'name' => $name);
}

function ts_shape(): void {
  $c = create_user(0, "James");
  var_dump($c['id']);
  var_dump($c['name']);
}

ts_shape();
Output
int(0)
string(5) "James"