PHP's support to contravariance and covariance

php.internals

Guilherme Blanco

9 years ago
Hi internals, During my regular open source work, I realized that PHP yet do not support contravariance and covariance for user classes. Some examples that I found that could be really easy to implement and useful to end user are highlighted here: - contravariance.php - https://3v4l.org/I3v0u - covariance.php - https://3v4l.org/i79O5 For all those lazy people that don't want to open 2 new tabs to understand what I mean, here's a more elaborate example covering both scenarios: <?php class A {} class B extends A {} class Foo { public function bar(B $b) : A {} } class Woo extends Foo { public function bar(A $b) : B {} } $w = new Woo(); Basically, here are the highlights: - method arguments are ok to reference wider parameter types (and we just approved the PR for Parameter Type Widening) - this is what we call contravariant, as contra inheritance direction - return types are ok to enforce strictness for subtypes - this is what we call covariant, as Woo is a subtype of Foo I've walked through PHP source and it looks like the only needed place to modify is https://github.com/php/php-src/blob/master/Zend/zend_inheritance.c#L184, which would require to be decoupled in 2 functions to check which direction is should support (covariance or contravariance). Is there anything else that I am missing? I'm happy to write an RFC for that... =) Cheers,
-- Guilherme Blanco Senior Technical Architect at Huge Inc.

Rasmus Schultz

9 years ago
We were talking about this exact issue at work today. Supporting this for interfaces would be particularly useful - if an implementation better than satisfies the requirements defined by an interface, it should be able to implement that interface. I'd be very happy to see an RFC for this :-) On Tue, Jan 31, 2017 at 7:30 PM, guilhermeblanco@gmail.com < guilhermeblanco@gmail.com> wrote:

Levi Morrison

9 years ago
> Is there anything else that I am missing?
Sadly, yes. Consider the following snippet: class A { function method(): B; } class B extends A { function method(): C; } class C extends B {} When checking that B::method satisfies the requirements of A::method it must know if C extends B. At the time we check if B::method satisfies A::method, C will *not* yet be in the symbol table. You need to adjust the passes over the code to register symbols and their declared relationships, and then in a separate pass validate them. After that if the class isn't found then you trigger an autoload. It's doable, it just hasn't been done.

Christoph Becker

9 years ago
On 31.01.2017 at 21:47, Levi Morrison wrote:
>> Is there anything else that I am missing? > > Sadly, yes. Consider the following snippet: > > class A { > function method(): B; > } > > class B extends A { > function method(): C; > } > > class C extends B {} > > When checking that B::method satisfies the requirements of A::method > it must know if C extends B. At the time we check if B::method > satisfies A::method, C will *not* yet be in the symbol table. > > You need to adjust the passes over the code to register symbols and > their declared relationships, and then in a separate pass validate > them. After that if the class isn't found then you trigger an > autoload. > > It's doable, it just hasn't been done.
An alternative *might* be forward class declarations: class B extends A; class C extends B; class A { function method(): B; } class B extends A { function method(): C; } class C extends B {} I haven't really thought about the feasibility – just throwing in a rough idea.
-- Christoph M. Becker

Marco Pivetta

9 years ago
On 1 Feb 2017 12:27 a.m., "Christoph M. Becker" <cmbecker69@gmx.de> wrote: On 31.01.2017 at 21:47, Levi Morrison wrote:
>> Is there anything else that I am missing? > > Sadly, yes. Consider the following snippet: > > class A { > function method(): B; > } > > class B extends A { > function method(): C; > } > > class C extends B {} > > When checking that B::method satisfies the requirements of A::method > it must know if C extends B. At the time we check if B::method > satisfies A::method, C will *not* yet be in the symbol table. > > You need to adjust the passes over the code to register symbols and > their declared relationships, and then in a separate pass validate > them. After that if the class isn't found then you trigger an > autoload. > > It's doable, it just hasn't been done.
An alternative *might* be forward class declarations: class B extends A; class C extends B; class A { function method(): B; } class B extends A { function method(): C; } class C extends B {} I haven't really thought about the feasibility – just throwing in a rough idea. That's simply dumping a language problem onto the user though...