将 darray 存储到 Shape 中时出现不一致错误

Inconsistent error while storing a darray into a Shape

我有这样的身材

const type TFileInfo = shape(
        'displayName' => string,
        'givenName' => string,
        'jobTitle' => string,
        'businessPhones' => vec<string>
    );
    private Person::TFileInfo $person;

现在我的 class 构造函数看起来像这样

public function __construct(string $apiresponse) { // instance method
    $json = \json_decode($response, /* associative = */ true);
    TypeAssert\matches<self::TFileInfo>($json);
    $this->person = $json; //OFFENDING LINE
    $this->person['businessPhones1'] = "";
}

奇怪的是上面的代码没有抛出任何错误。

如果我删除有问题的行,那么最后一行会引发编译时错误 没有预期,因为字段 'businessPhones1' 未在此形状类型中定义,并且此形状类型没有允许未知字段

我在这里错过了什么?有没有更好的方法将 API 响应分配给类型变量?

TypeAssert\matches 不能证明它的参数是你指定的类型,这与其他一些 built-ins 的行为形成对比,比如 is_null 是 special-cased类型检查器。相反,它强制参数和 returns 它,所以你需要将你的独立调用移动到赋值,即 $this->person = TypeAssert\matches<self::TFileInfo>($json);.

你可能预料到 $this->person = $json 赋值会出现类型错误,但实际上 json_decode 和其他一些不安全的 built-in PHP 函数是 special-cased 由类型检查器确定为底层类型(可转换为任何类型),因此它们可以在 type-assert 之前完全可用。今天仍然如此:请参阅 its type definition in the HHVM source,可能是为了兼容性。


关于此案例的另一个有趣的地方是 $this->person = $json$this->person 强制转换为底部类型以及绑定的下游。据我了解,这是 Hack 类型检查器的一种特定行为,它针对单层 属性 嵌套执行此操作,但它保留了属性属性的类型(第二个示例 has_error):

<?hh // strict
class Box<T> { public function __construct(public T $v) {} }
function no_error<T>(Box<int> $arg): T {
    $arg->v = json_decode('');
    return $arg->v;
}
function has_error<T>(Box<Box<int>> $arg): T {
    $arg->v->v = json_decode('');
    return $arg->v->v;
}