On Sat, 23 Apr 2016 10:46:38 +0300, Jesse Schalken <m...@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)

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to