更新
更完整的答案是允许子类继承 this-argumented 方法从根本上是有缺陷的。从基础开始,函数参数是逆变的,所以我们可以将它们向下强制转换,因为函数可以接受它指定类型的任何子类型,但它不能接受超类型。因此,Derived <: Base 的 function(Derived): void 到 function(Base): void 的转换无效。通过扩展,以下层次结构无效,因为Derived <: Base,但Derived 到Base 的转换以相同的方式转换foo(...):
<?hh // strict
interface Base {
public function foo(Base $v): void;
}
interface Derived extends Base {
<<__Override>>
public function foo(Derived $v): void;
}
// Derived -> Base means foo(Derived): void -> foo(Base): void
现在,请注意 this 类型正是这样做的,即使 Derived 没有显式覆盖 foo(...)。换句话说,以下是完全等价的:
<?hh // strict
interface Base {
public function foo(this $v): void;
}
interface Derived extends Base {}
我能想到的最简单的违规:
<?hh
interface Base {
public function foo(this $v): void;
}
final class Derived implements Base {
public function bar(): void {}
public function foo(this $v): void {
$v->bar(); // trying to call `bar()` on OtherDerived fails miserably!
}
}
final class OtherDerived implements Base {
public function foo(this $v): void {}
}
function violate(Base $v, Base $x): void {
$v->foo($x);
}
bar(new Derived(), new OtherDerived());
但是,如果我们可以确定我们有一个 特定 Base 的后代,那么我们就确切地知道它的 foo(...) 想要什么:它自己的类型,并且只有它自己的类型。我们通常可以将此应用于第一个代码块中显示的覆盖。这通常不是很有用,但我认为 this 非常简洁明了,值得允许编写接口,然后检查每个调用。
原创
如果没有错误,可能会出现以下违规:
<?hh // strict
class A {
public function act(this $v): void {
$v->act($this);
}
}
class B extends A {
public ?int $b_prop;
<<__Override>>
public function act(this $v): void {
$v->b_prop;
}
}
function initiate(): void {
violate(new B());
}
function violate(A $v): void {
(new A())->act($v);
}
我不知道这是否是该错误的典型违规行为,但我猜测其基本原理:
-
A::act(this) 是公共的/受保护的,因此,由于它的存在,至少存在一种方法,其具有可用于子类的 this 参数。
-
act 的主体至少可以调用具有this 参数的$v->act。
-
act 可以从 A 上下文中调用,因此参数 this 正好是 A。因此,$v 的上下文中的this也转换为A。
- 因为
A 不是最终的,所以$v 可能是A 的正确子类型。
-
$v 的基础类(在本例中为B)可以覆盖来自A 的任何this-argumented 方法,假设this <: B。在这种情况下,$v->b_prop 依赖于该假设。
-
A 上下文认为它可以通过 (3.) 将 A 类型(例如其自身)传递给 B,但这违反了 (5.) 中的假设