In experimenting with constraints on generic functions in Hack, I conjured up the following:
<?hh // strict
class Base {}
class Derived extends Base {}
abstract class ImplBase {
abstract const type T as Base;
public function foo<Tf as this::T>(Tf $v): void {}
}
class ImplDerived extends ImplBase {
const type T = Derived;
}
function foo(ImplDerived $v): void {
bar($v);
}
function bar(ImplBase $v): void {
$v->foo(new Base());
$v->foo(new Derived());
}
The principle is basic enough: upcast from the concrete ImplDerived
, does the typechecker treat ImplBase::T
as an abstract type in bar
that can be cast to a Base
by the constraint, or does it preserve the reference to T = Derived
? Weirdly enough, though, the answer is neither. Each line in bar
fails for a different reason:
$v->foo(new Base());
fails becauseTf
is "constrained to a dependent type<expr#1>::T
[...] referring tonew Base()
".$v->foo(new Derived());
fails because poof!Tf
is suddenly aBase
! "[this::T]
is an object of typeBase
[...] resulting from expanding the type constantImplBase::T
".
Am I asking the impossible, or is this perhaps not implemented fully yet?