Implement conditionally-dynamic classes
Summary:
Writing `<<__SupportDynamicType>>` on a type imposes stringent conditions on its methods: in particular, most generic types cannot be marked this way because the generic parameter is neither an enforced type nor a subtype of `dynamic`. Often we would like the generic type to be support dynamic whenever its generic parameters do, in other words, it is *conditionally-dynamic*. Container classes are a classic example. Here is our favourite `Box<T>` example:
```
<<__SupportDynamicType>>
class Box<T> {
public function __construct(private T $item) { }
public function get(): T {
return $this->item;
}
public function set(T $val): void {
$this->item = $val;
}
}
```
This diff introduces conditional-dynamic as the default on generic parameters, unless the parameter is marked with `__NoRequireDynamic` attribute. There are four major changes:
(1) Subtyping. For the above example, `Box<dynamic> <D: dynamic`, i.e. `Box<dynamic>` is a dynamic-aware subtype of `dynamic`. More generally, an instantiation of a generic type that is marked `__SupportDynamicType` is a subtype of `dynamic` if any of its parameters not marked `__NoRequireDynamic` are instantiated with `dynamic & t1 & ... & tn` where `t1` to `tn` are the upper bounds on the generic parameter.
(2) Checking of members. When checking the *signature* of a member to see it if satisfies constraints for dynamic calling, any parameters not marked `__NoRequireDynamic` are assumed to be equal to `dynamic`. When checking the *body* of a member under dynamic assumptions (if the signature check fails), any generic parameters not marked `__NoRequireDynamic` are assumed to be equal to `dynamic`.
(3) Checking of inheritance. We currently require any class that extends or implements a type that is declared `__SupportDynamicType` to itself also declare `__SupportDynamicType`. In the presence of conditional-dynamic generics, a further check is required. Suppose an class that has an invariant type parameter supports dynamic e.g. `Vector<T>`. Consider what happens when it extends (or implements) a covariant class that also supports dynamic, e.g. `Container<+T>`. We want `Vector<t> <: dynamic` only if `t` is *exactly* `dynamic`. Unfortunately we have `Container<int> <: dynamic` (by covariance), and so `Vector<int> <: dynamic` (by inheritance and transitivity). To prevent this, we require that for *any* instantiation of generic parameters, the parent supports dynamic *implies* the child also supports dynamic.
(4) We add `__SupportDynamicType` to many standard types in `hhi` files, including Hack arrays (`vec`, `dict`) and collection classes. Special casing in subtyping for collections is no longer required.
A subsequent diff will deal with coercion from `dynamic` to instantiations of types whose generic parameters are conditional-dynamic. This will simplify the special casing of collections during coercion checking.
Differential Revision:
D27823442
fbshipit-source-id:
7bdb85251ede544110ba7251414ad4b4b12a693a