Dear Internals,

As you may remember, I closed the voting on the return types
[RFC](https://wiki.php.net/rfc/returntypehinting) because of a design
flaw that was found during the voting process. The purpose of this
discussion thread is to explain the various options for checking
return type compatibility with parent methods that I think are viable,
to show some of the benefits and drawbacks of each option, and to
solicit feedback on which options you prefer and why. As such, it's
much longer than a normal message I would send.

The following code demonstrates the design flaw mentioned above:

    class A {
        function foo(): B { return new B; }
    }

    class B extends A {
        function foo(): C { return new C; }
    }

    class C extends B {}

    $b = new B;
    $c = $b->foo();

I've also used it because it can adequately show the differences in
how each of the following options work:

  1. Do covariant return types; check them at definition time
  2. Do covariant return types; check them at runtime
  3. Do invariant return types; check them at definition time

Option 1: Covariant return types with definition time checking
--------------------------------------------------------------
This is the option that is currently implemented in my pull request.
This option catches all return type variance issues whether code is
used or not; if it got included it is checked. This means return type
variance issues can't bite you later.

When class B is defined, the engine needs to check that the method
`B::foo` has a compatible return type with `A::foo`, in this case that
equates to checking that C is compatible with type B. This would
trigger autoloading for C but it will fail in this case because C is
defined in the same file. Note that there are ways we could fix this
issue, but not reliably because of conditionally defined classes
(defining classes in if blocks, for example) and other such dynamic
behavior. Even if we could fix this issue, there is still an issue if
A, B and C were defined in separate files and then included. You
couldn't require them in any order for it to work; you would have to
autoload.

Option 2: Covariant return types with runtime checking
------------------------------------------------------
This option would do the variance check when the method is used (or
potentially when the class is instantiated, or whichever comes first).
Regardless of the exact details of how this method is implemented, the
above code would work because the first time class B is used in any
way occurs after C is defined. This would also work if you separated
them out into different files

This option would be slower than option 1, but cannot quantify it
because it is not yet implemented. I suspect there would be a way to
cache the result of the variance check to not have to do it on every
instantiation or invocation, so this may be negligble.

This option has the drawback that inheritance problems can exist in
the code and won't be discovered until the code is ran. Let me repeat
that to make sure that everyone understand it: if you have return type
variance errors in your code, they would not be detected until you try
to use the class or method.

Option 3: Invariant return types with definition time checking
--------------------------------------------------------------
This means that the declared types must *exactly match* after
resolving aliases such as parent and self. The advantage of this
option is that the inheritance check can be done at definition time in
all cases without triggering an autoload. As such it is a bit simpler
to implement and is slightly faster to do so. The obvious downside is
that it can't support covariant return types.

---

Note that C++ and Java (and most modern languages) support covariant
return types, but C# supports only invariant return types.

Which of these methods do you prefer the most, and which do you prefer
the least, and why?

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

Reply via email to