Skip to main content

Contexts & Subtyping

Capabilities are contravariant: a closure that requires fewer capabilities may be passed where a closure requiring more capabilities is expected.

function requires_globals_arg((function()[globals]: void) $f): void {
$f();
}

function caller(): void {
// ok: passing a function that requires fewer capabilities
requires_globals_arg(()[read_globals] ==> {});
// ok: passing a function that requires no capabilities
requires_globals_arg(()[] ==> {});
}

This has the standard implication on inheritance hierarchies. A child class may override a method with a context that requires the same or fewer capabilities than the parent.

class Parent_ {
public function maybeGlobals()[globals]: void {} // {AccessGlobals}
public function maybePure(): void {} // defaults
}

class Mid extends Parent_ {
// ok: {ReadGlobals} ⊆ {AccessGlobals}
public function maybeGlobals()[read_globals]: void {}
// ok: {WriteProperty} ⊆ defaults
public function maybePure()[write_props]: void {}
}

class Child extends Mid {
// ok: {} ⊆ {ReadGlobals}
public function maybeGlobals()[]: void {}
// ok: {} ⊆ {WriteProperty}
public function maybePure()[]: void {}
}

Capability subtyping

There may also exist a subtyping relationship between capabilities themselves. When a capability B is a subtype of capability A, any function or operation that requires A may be called from a context providing B.

For example, AccessGlobals includes ReadGlobals, so a function with the globals context can call functions requiring read_globals:

function requires_read_globals()[read_globals]: void {}

function has_globals()[globals]: void {
// ok: AccessGlobals includes ReadGlobals
requires_read_globals();
}