通过全权访问,调用范围对他们来说,公共成员是不变的并不奇怪:
<?hh // strict
class Foo<+T> {
public function __construct(
public T $v
) {}
}
class ViolateType {
public static function violate(Foo<int> $foo): void {
self::cast_and_set($foo);
echo $foo->v + 1; // string + integer
}
public static function cast_and_set(Foo<arraykey> $foo): void {
$foo->v = "Poof! The integer `violate()` expects is now a string.";
}
}
// call ViolateType::foo(new Foo(1)); and watch the fireworks
这里的问题是 {/ strong> violate
和cast_and_set
可以读取和修改具有不同类型预期的相同值(Foo->v
)。
但是,对于受保护的成员来说,这个问题似乎并不存在。
由于private
和protected
之间的唯一区别是对后代的可见性,因此我们需要一个类(ImplCov
),在一些受保护的成员之外,否则对类型有效协变,并将其扩展为该类型的类(ImplInv
)不变量。值得注意的是,在T
上保持不变可以让我公开一个公共二传手 - violate(T $v): T
- 我试图打破类型。
<?hh // strict
// helper class hierarchy
class Base {}
class Derived extends Base {}
class ImplCov<+T> {
public function __construct(
protected T $v
) {}
}
class ImplInv<T> extends ImplCov<T> {
public function violate(T $v): T {
// Try to break types here
}
}
使用ImplInv<Derived>
的实例,我被强制转换为ImplCov<Derived>
,然后利用协方差转换为ImplCov<Base>
。这是最危险的事情,所有三种类型都指同一个对象。让我们检查每种类型之间的关系:
ImplInv<Derived>
和ImplCov<Base>
:当属性更改为超类型(int-&gt; arraykey)或具有公共超类型的不相交类型时,发生公共成员案例中的违规(int - &GT;字符串)。但是,由于ImplCov<Base>
在T
上是协变的,因此不存在可以传递Base
实例并使v
成为真Base
的方法。 ImplCov
的方法也无法产生new Base()
并将其分配给v
,因为它不知道T
的最终类型。 1
同时,由于投射ImplCov<Derived> --> ImplCov<Base> --> ...
只会导致其得分较少,因此ImplInv::violate(T)
保证在最差情况下将v
设置为最终ImplCov
&#的子类型39; s T
,保证对最终T
进行有效投射。 ImplInv<Derived>
的派生无法投射,因此一旦参数化,该类型就会被设置。
ImplInv<Derived>
和ImplCov<Derived>
:这些可以共存T
在它们之间是相同的,铸造只是最外层的类型。ImplCov<Derived>
和ImplCov<Base>
:这些可以通过ImplCov
有效协变的假设共存。 protected
的{{1}}可见度与v
无法区分,因为它们属于同一类。所有这些似乎都表明受保护的可见性对协变类型来说是犹太的。我错过了什么吗?
1。我们实际上可以通过引入private
约束new Base()
来生成super
,但这甚至更弱,因为根据定义ImplCov<T super Base>
必须在ImplInv
中参数化ImplCov
带有超类型的{1}}语句,使extends
的操作安全ImplInv
。另外,v
无法假设有关ImplCov
成员的任何内容。
答案 0 :(得分:1)
请记住,子类可以修改该类的任何对象的protected
成员,而不仅仅是$this
。因此,对上面示例的这种轻微修改使用protected
成员来类似地破坏类型系统 - 我们所要做的就是使ViolateType
成为Foo
的子类(并且它不会关于我们设置T
的内容,或者我们将ViolateType
设置为通用或其他内容。
<?hh // strict
class Foo<+T> {
public function __construct(
/* HH_IGNORE_ERROR[4120] */
protected T $v
) {}
}
class ViolateType extends Foo<void> {
public static function violate(Foo<int> $foo): void {
self::cast_and_set($foo);
echo $foo->v + 1; // string + integer
}
public static function cast_and_set(Foo<arraykey> $foo): void {
$foo->v = "Poof! The integer `violate()` expects is now a string.";
}
}
这使得typechecker只对受保护成员进行了一次错误抑制 - 因此允许受保护成员进行协变会破坏类型系统。