Extend Shapes:: functions override to dynamic
commit0a44da767b57d2a747c71c5d9e41fc041212cce9
authorVassil Mladenov <vmladenov@fb.com>
Tue, 5 Nov 2019 01:10:56 +0000 (4 17:10 -0800)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Tue, 5 Nov 2019 01:12:59 +0000 (4 17:12 -0800)
treee2197e1ee77786df751f58249b6264e83282d653
parent68d20c63d83321d9a0585b09ea18adee18e8161e
Extend Shapes:: functions override to dynamic

Summary:
`Shapes::toDict` and `toArray` are magic. The HHI claims to return `dict<arraykey, mixed>` (or `darray`) but instead the argument itself is analyzed to return a dict of union of the keys of the shape and the union of the values of the shape. So, `Shapes::toDict($s)` where `$s: shape('a' => int)` will return `dict<string, int>`. If the type visitor does not determine a suitable result, the original result `dict<arraykey, mixed>` is returned.

For pessimization, I've made toDict always result in a like type even though for `dict<arraykey, mixed>` it's technically unnecessary. So, the default return type is `~dict<arraykey, mixed>`. The mapper also handles unions shape inputs and performs a simple transformation and union of the result.

Without pessimization, this is fine because the only legal inputs will be unions of shapes. However, with pessimization, we want to allow Shapes::toDict to take like shapes. Unfortunately, this results in the following flow:

- Given input type `~shape('a' => int)`
- Compute default return type: `~dict<arraykey, mixed>`.
- Decompose union of input:
  - `dynamic` => no rule => return default type: `~dict<arraykey, mixed>`
  - `shape('a' => int)` => follow shapes rule => return `~dict<string, int>`
- Union results: `(~dict<arraykey, mixed> | ~dict<string, int>)`
- Simplify unions, covariance yields: `~dict<arraykey, mixed>` as the final result.

Instead, I add a rule for dynamic, saying that `Shapes::toDict($d)` where `$d: dynamic` returns dynamic. Then, the final result in the above example is `~dict<string, int>`. **Note that `dynamic` is only a valid argument to `Shapes::toDict` when pessimization is enabled.**

# Alternative

This is a simpler alternative to another approach which is fetching the type variable that is substituted for T in
```
  public static function toDict<T as shape(...)>(
    T $shape
  ): dict<arraykey, mixed>;
```
after the coercion from `~shape('a' => int) ~> #1` and then doing a computation based off the type variable. This involves a heavier refactor of the shapes code.

Reviewed By: dlreeves

Differential Revision: D18279781

fbshipit-source-id: 70cf66b81118b16e0c3e4e8f25b03df3120876b7
hphp/hack/src/typing/typing_shapes.ml
hphp/hack/test/typecheck/like_types/simple_pessimization/shapes_hhi.php
hphp/hack/test/typecheck/like_types/simple_pessimization/shapes_hhi.php.exp