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.
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:
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 FooI'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.
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.
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
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...