Readonly: Subtyping

From a typing perspective, one can think of a readonly object as a supertype of its mutable counterpart. For example, readonly values cannot be passed to a function that takes mutable values.

class Foo {
  public int $prop = 0;
}

function takes_mutable(Foo $x): void {
  $x->prop = 4;
}

function test(): void {
    $z = readonly new Foo();
    takes_mutable($z); // error, takes_mutable's first parameter
                       // is mutable, but $z is readonly
}
Typechecker output
File "/home/example/readonly_takes_mutable.hack", line 16, characters 19-20:
Invalid argument (Typing[4411])
  File "/home/example/readonly_takes_mutable.hack", line 16, characters 19-20:
  This expression is readonly
  File "/home/example/readonly_takes_mutable.hack", line 10, characters 28-29:
  It is incompatible with this parameter, which is mutable

Similarly, functions cannot return readonly values unless they are annotated to return readonly.

class Foo {}
function returns_mutable(readonly Foo $x): Foo {
  return $x; // error, $x is readonly
}
function returns_readonly(readonly Foo $x): readonly Foo {
  return $x; // correct
}
Typechecker output
File "/home/example/readonly_return.hack", line 8, characters 10-11:
This expression is readonly, but we expected a mutable value because this function does not return readonly. Please mark it to return readonly if needed. (Parsing[1002])

Note that non-readonly (i.e. mutable) values can be passed to a function that takes a readonly parameter:

class Foo {}
// promises not to modify $x
function takes_readonly(readonly Foo $x): void {
}

function test(): void {
    $z = new Foo();
    takes_readonly($z); // ok
}

Similarly, class properties cannot be set to readonly values unless they are declared as readonly properties.

class Bar {}
class Foo {
  public function __construct(
    public readonly Bar $ro_prop,
    public Bar $mut_prop
  ){}
}

function test(
  Foo $x,
  readonly Bar $bar,
) : void {
  $x->mut_prop = $bar; // error, $bar is readonly but the prop is mutable
  $x->ro_prop = $bar; // ok
}
Typechecker output
File "/home/example/readonly_prop_assign.hack", line 18, characters 3-14:
Invalid property assignment (Typing[4411])
  File "/home/example/readonly_prop_assign.hack", line 18, characters 18-21:
  This expression is readonly
  File "/home/example/readonly_prop_assign.hack", line 10, characters 12-14:
  But it's being assigned to a mutable property