Re: [RFC] Callable Types

From: Date: Sat, 23 Apr 2016 10:37:55 +0000
Subject: Re: [RFC] Callable Types
References: 1 2  Groups: php.internals 
Request: Send a blank email to internals+get-92652@lists.php.net to get a copy of this message
On Sat, 23 Apr 2016 10:46:38 +0300, Jesse Schalken <me@jesseschalken.com> wrote:
This is great. The gaps in PHP's type annotations are slowly being filled. :) Are the rules for compatibility between callables the same rules for compatibility between methods in classes/interfaces? Because presently, this is allowed: interface Test {
    public function testMethod();
} function foo1(Test $x) { } foo1(new class implements Test {
    public function testMethod(int $extraParam = 0) { }
}); but, according to the RFC, this would not be: function foo2(callable():void $x) { } foo2(function (int $extraParam = 0) { }); I don't see why they should be different. I also didn't see anything mentioning reference parameters/returns. I assume it just follows the rules for method compatibility?
Hey Jesse, Thanks for reading the RFC. First and foremost - no, the rules for compatibility between callables and method implementations are different at least because our inheritance model currently doesn't support variance for every case: if you have
    class B extends A {}
    interface Foo {
        function foo(B $b);
    }
then implementation of foo() can never have foo(A $a) signature, which is crucial for callables for some cases. Note that callables have runtime variance, where classes would have compile-time variance). So, in this way, callables have it right (it is desirable for classes to have variance as well, but currently it's not possible as there is a need for another compilation phase for verifying variance). What about your case of variance with extra optional parameter, it is actually not type safe with the current way of how PHP works (it's covered in the end of "Variance and Signature Validation" part). The problem lies in the notion that passing extra arguments to the function (the ones it doesn't even expect nor use) is considered valid and doesn't throw an error. So if you have a function that accepts optional argument of type B and you pass this function as a parameter of type callable() $cb, then it would be perfectly valid to call it as $cb(new A); because you assume that $cb is of type callable() and that means if you pass anything extra - it shouldn't Fatal. But in this case it would. While I agree that your use case is legit and ideally should be supported, I don't think it is safe to support it until PHP deprecates passing extra args to functions that don't expect them (which is very troublesome to implement due to BC). Anyway, changing this behavior of callable type later on wouldn't break BC so the gates will still be open. I've just added a part about references to the RFC (and found a bug in the implementation, thanks for that =)), I'll just copy and paste it here: Reference parameters are supported: no variance is applied to the fact whether parameter is referential or not. Example:
    function foo(callable(&$byref) $cb) { }
    foo(function (&$bar) { }); // valid
    foo(function ($bar) { }); // TypeError: Argument 1 passed to foo() must be callable of compliant signature: callable(&$byref), callable($bar) given
    function bar(callable($byval) $cb) { }
    bar(function (&$bar) { }); // TypeError: Argument 1 passed to bar() must be callable of compliant signature: callable($byval), callable(&$bar) given
Functions returning a reference are not different from functions returning a value in PHP, for the caller, hence both are interchangeable:
    function foo(callable(): A $cb) { }
    foo(function (): A { return new A; });
    foo(function &(): A { static $a; $a = $a ?: new A; return $a; }); // both would pass the boundaries of a type check
(the last one has error in the current implementation, I'll fix it later)

« previous php.internals (#92652) next »