2

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 because Tf is "constrained to a dependent type <expr#1>::T [...] referring to new Base()".
  • $v->foo(new Derived()); fails because poof! Tf is suddenly a Base! "[this::T] is an object of type Base [...] resulting from expanding the type constant ImplBase::T".

Am I asking the impossible, or is this perhaps not implemented fully yet?

concat
  • 3,107
  • 16
  • 30

1 Answers1

2

Funnily enough you are the second person to mention this to me today; the first was one of the Hack team developers who is looking into improving the interaction between type constants and constraints.

So yes, we're looking into it. No promises on any timeline for sorting it out yet.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067