我认为以下示例是类型安全的: 在所有三种情况下,我都在尝试实例化一个期望this
的A
,但没有太多运气:
<?hh // strict
class A {
public function __construct(?this $next = null) {}
}
// Attempt 1: infer from return type
function foo(): A {
return new A(foo());
}
// Attempt 2: infer from argument type
function bar(A $A): void {
new A($A);
}
class B {
// Attempt 3: infer from property type
public ?A $A;
public function baz(): void {
new A($this->A);
}
}
因为在所有情况下,打字器都抱怨:
[构造函数参数源自的
A
] 的后期绑定类型需要完全A
。由于
A
不是最终的,因此这可能是子类的实例。
唯一不这样做的情况是在同一范围内实例化A
:
class B {
public function foo(): void {
new A(new A());
}
}
我猜根本原因是所有失败案例中的基础对象可能是向上转换为A
的子实例?我的主要困惑是为什么这会使实例化(或者,一般来说,任何方法调用)不健全。
引用文档:
this
只能用作类方法的返回类型注释。this
表示该方法返回定义该方法的同一类的对象。返回
this
的主要目的是允许在类本身或其子类的实例上链接方法调用。
换句话说,完全不支持使用this
作为类型提示,将来可能会中断。
至于一般解释,this
总是意味着类的当前实例。它并不意味着当前的类类型。在静态上下文中,这稍微放宽了一点,表示当前类型,但不是所有子类型。
这就引出了一点,你的静态代码被破坏了:
- 尝试 1:foo() 在无限循环中递归调用自己。
- 尝试 2:bar() 尝试访问一个名为
A
的成员变量,该变量不可为空且未初始化。(在尝试 3 中定义。此外,您不能从静态上下文访问非静态上下文 ($this
)。
尝试 - 3:同样,您正在尝试使用名为
A
的不可为空的未初始化成员变量。你不能在Hack中做到这一点。
如果您希望代码正常工作,则需要理顺OO流程。
已更新
更完整的答案是允许子类继承this
参数方法从根本上是有缺陷的。从基础开始,函数参数是逆变的,因此我们可以向下转换它们,因为函数可以接受它指定的类型的任何子类型,但它不能接受超类型。因此,Derived <: Base
的function(Base): void
的演员function(Derived): 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
的主体至少可以调用$v->act
具有this
参数。- 可以从
A
上下文调用act
,因此参数this
正好A
。由此可见,$v
语境中的this
也被投射到A
。 - 由于
A
不是最终的,因此$v
可能是A
的适当亚型。 $v
的基础类(在本例中为B
)可以在假设this <: B
的情况下覆盖A
中的任何this
参数方法。在这种情况下,$v->b_prop
依赖于该假设。A
上下文认为它可以通过 (3.) 将A
类型(例如自身)传递给B
,但这违反了 (5.) 中的假设。