I figured the following examples would be typesafe: in all three cases, I am trying to instantiate an A
that expects this
, but without much luck:
<?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);
}
}
Because in all cases the typechecker complains that:
The late-bound type of [the
A
from which the constructor argument originates] needs to be exactlyA
.Since
A
is not final this might be an instance of a child class.
The only time it doesn't is if an A
is instantiated within the same scope:
class B {
public function foo(): void {
new A(new A());
}
}
I'm guessing that the underlying reason is that the underlying object in all of the failed cases might be a child instance upcast to an A
? My main confusion is why this renders the instantiation (or, in general, any method call) unsound.
Quoting from the documentation:
In other words, the use of
this
as a type hint is entirely unsupported and may break in future.As for a general explanation,
this
always means the current instance of a class. It does not mean the current type of class. In a static context this is relaxed a bit to mean the current type, but not all subtypes.Which brings us to the point, your static code is broken:
A
which is not nullable and is also not initialized. (Defined in Attempt 3.) Also, you cannot access a non-static context ($this
) from a static context.A
. You can't do that in Hack.If you want your code to work correctly, you will need to get your OO straightened out.