Method declarations in Hack

4 minute read

This post will go more in details of my last post about types. After reading that last post you know that a method declaration in Hack will look like this:

//hack
class Foo {
  public function bar (int $var): string {
    return "Number: ".$var;
  }
}

I will dig in to some problems that isn’t obvious at the moment.

Inheritance

When Child extends Base the types will work as expected.

//hack
class Base {}
class Child extends Base {}


class MyClass {
    public function valid1():Base
    {
        return new Child();
    }


    public function valid2():Child
    {
        return new Child();
    }


    public function notValid():Child
    {
        return new Base();
    }
}

When you are working with interfaces you need to be aware how you change the type.

//hack
class Base {}
class Child extends Base {}
class GrandChild extends Child {}


interface FooInterface {
    public function getChild():Child;
}


//Valid: Foo returns GrandChild which is a subtype of Child
class Foo implements FooInterface {
    public function getChild():Child
    {
        return new GrandChild();
    }
}


//Invalid: Bar returns Base which is a supertype Child
class Bar implements FooInterface {
    public function getChild():Child
    {
        return new Base();
    }
}

Inheritance from PHP

When your Child class implements an interface from PHP. You need to be aware of what you are doing. You cant use Child::increase(int):int.  This is because Base::increase says: “I’ll handle inputs of any type” and Child::increase says: “I’ll handle just ints”. Since int is a narrower type this will be throw an error saying incompatible type. You should use mixed.

//php
interface Base
{
    public function increase($var) {
        return $var + 1;
    }
}


//hack
require "Base.php";
class Child extends Base {
     public function increase(mixed $var):int {
        return $var+2;
     }
}
$obj=new Child();
echo $obj->increase(2);

If Base is a PHP interface you will get an error since the interface rely on a wider type:

Fatal error: Declaration of Child::increase() must be compatible with that of Base::increase() in /path/to/Child.hh on line 5

 

Methods with the same name

In Java you may have multiple methods with the same name but with different types of parameters or different parameter length. If the same applied in Hack you could write something like this:

//hack
class Foobar {
     public function increase(int $var):int {
         return $var+2;
     }
     public function increase(string $var):string {
         return "Many ".$var;
     }
}
$obj=new Foobar();
echo $obj->increase(1)."\n"; //should output 3
echo $obj->increase("Apples")."\n"; //should output "Many Apples"

But the type checker will raise an error here:

Foobar.hh:11:21,28: Name already bound

This is a feature not available in Hack. Java’s type checker is always running in “strict mode”. Since Hack may run in partial mode you may exclude the argument type annotation if you want to. That makes it impossible to know what method to invoke. You will find more about overloading methods in Hack this Github issue.

More about subtypes and supertypes

When you are changing the type you could return a subtype and change the arguments into a supertype. The later is not supported in Hack at the time of writing. (HHVM version 3.0.1)

//hack
class Base {}
class Child extends Base {}
class GrandChild extends Child {}


interface FooInterface {
    public function getChild(Child $var): void;
}


//Invalid: Foo::getChild takes a GrandChild as argument. GrandChild is a subtype of Child
class Foo implements FooInterface {
    public function getChild(GrandChild $var): void
    {


    }
}


//Invalid (but should be valid): Bar::getChild takes a Base as argument. Base is a supertype of Child
class Bar implements FooInterface {
    public function getChild(Base $var): void
    {


    }
}

This will generate an error like:

This is an object of type Child, It is incompatible with an object of type Base

Categories:

Updated:

Leave a Comment