Hi internals,
On experimenting with return type-hints I noted an inconsistency on
overwriting an existing hint to a more specific one. On adding a non
existing return type-hint on overwrite it's fine but it's logically the
same (this previously was an E_STRICT).
http://3v4l.org/YdB8s - works fine (E_STRICT lower then php7@20150401)
http://3v4l.org/UDk0n - Fatal error: Declaration of Bar::setTest() must
be compatible with Foo::setTest($test): Foo
http://3v4l.org/AuOsr - HHVM works fine
Marc
Marc,
Hi internals,
On experimenting with return type-hints I noted an inconsistency on
overwriting an existing hint to a more specific one. On adding a non
existing return type-hint on overwrite it's fine but it's logically the same
(this previously was an E_STRICT).http://3v4l.org/YdB8s - works fine (E_STRICT lower then php7@20150401)
http://3v4l.org/UDk0n - Fatal error: Declaration of Bar::setTest() must be
compatible with Foo::setTest($test): Foo
http://3v4l.org/AuOsr - HHVM works fine
That's the concept of Variance:
https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)
Specifically, that return types can be covariant (subtypes can
"narrow" return types) and parameters can be contravariant (subtypes
can "widen" paramters). So this is valid:
class A extends B {}
class C {
public function foo(A $b): B {}
}
class D extends C {
public function foo(B $b): A {}
}
In that everything that C::foo accepts, D::foo accepts, but D::foo can
accept more.
And D produces more strictly (in that anything that can work with a
product of C::foo can work with what D::foo produces).
We looked into doing this for return types (and all other
declarations). However, we ran into some non-trivial problems around
compilation.
Currently, types are never loaded from a declaration. They are only
loaded when instantiated. Therefore, it's completely possible to have
a declared class with undeclared type declarations. If the type
declaration remains undeclared when you call the method, it's by
definition a failed type (since $obj instanceof UndefinedClass
is
always false).
If we want to check variance, we need to know about the type at
compile time. This brings up an issue that basically makes it
impossible to separate code into multiple files without an autoloader.
For example:
<?php // a.php
class A {
public function foo(B $bar) {}
}
<?php // b.php
class B {
public function bar(A $foo) {}
}
You couldn't have that be possible without an autoloader, since
there's a circular dependency. Sure, we could (and should) special
case the "doesn't inherit" situation, but it's pretty trivial to make
a new example which requires an autoloader.
So that leaves us with a weird situation: to support the feature (in
general), you'd have to use an autoloader or put everything into a
single file. That may be OK, but it's also added complexity.
To the best of my knowledge, Levi removed variance from the proposal
to keep it simple (just like nullables weren't in the proposal):
https://wiki.php.net/rfc/return_types#variance_and_signature_validation
If you'd like to create a proposal for 7.1 to add variance support to
type declarations, I think that would be awesome. It's not terribly
difficult, but there are some gotchas (especially around opcache
support) and non-trivial tradeoffs involved.
Anthony
Hi Anthony,
Am 01.04.2015 um 21:25 schrieb Anthony Ferrara:
Marc,
Hi internals,
On experimenting with return type-hints I noted an inconsistency on
overwriting an existing hint to a more specific one. On adding a non
existing return type-hint on overwrite it's fine but it's logically the same
(this previously was an E_STRICT).http://3v4l.org/YdB8s - works fine (E_STRICT lower then php7@20150401)
http://3v4l.org/UDk0n - Fatal error: Declaration of Bar::setTest() must be
compatible with Foo::setTest($test): Foo
http://3v4l.org/AuOsr - HHVM works fine
That's the concept of Variance:
https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)Specifically, that return types can be covariant (subtypes can
"narrow" return types) and parameters can be contravariant (subtypes
can "widen" paramters). So this is valid:class A extends B {}
class C {
public function foo(A $b): B {}
}class D extends C {
public function foo(B $b): A {}
}In that everything that C::foo accepts, D::foo accepts, but D::foo can
accept more.And D produces more strictly (in that anything that can work with a
product of C::foo can work with what D::foo produces).We looked into doing this for return types (and all other
declarations). However, we ran into some non-trivial problems around
compilation.Currently, types are never loaded from a declaration. They are only
loaded when instantiated. Therefore, it's completely possible to have
a declared class with undeclared type declarations. If the type
declaration remains undeclared when you call the method, it's by
definition a failed type (since$obj instanceof UndefinedClass
is
always false).
Currently in the case of "class A extends B {}" and B wasn't defined it
will be already loaded by autoloader.
In the case for "$obj instanceof UndefinedClass" it can't be possible as
$obj was already instantiates and can therefor never be an instance of
an currently undefined class. -> no autloading needed.
If we want to check variance, we need to know about the type at
compile time. This brings up an issue that basically makes it
impossible to separate code into multiple files without an autoloader.
For example:<?php // a.php
class A {
public function foo(B $bar) {}
}<?php // b.php
class B {
public function bar(A $foo) {}
}You couldn't have that be possible without an autoloader, since
there's a circular dependency. Sure, we could (and should) special
case the "doesn't inherit" situation, but it's pretty trivial to make
a new example which requires an autoloader.So that leaves us with a weird situation: to support the feature (in
general), you'd have to use an autoloader or put everything into a
single file. That may be OK, but it's also added complexity.To the best of my knowledge, Levi removed variance from the proposal
to keep it simple (just like nullables weren't in the proposal):
https://wiki.php.net/rfc/return_types#variance_and_signature_validationIf you'd like to create a proposal for 7.1 to add variance support to
type declarations, I think that would be awesome. It's not terribly
difficult, but there are some gotchas (especially around opcache
support) and non-trivial tradeoffs involved.
Thank you for the really helpful explanation!
Now I understand the reasons but I don't have enough experience in C or
the engine to make that possible.
Would it be possible to support it in the first place only if the return
type hints are already known by the engine?
Anthony
Marc
Thank you for the really helpful explanation!
Now I understand the reasons but I don't have enough experience in C or the
engine to make that possible.
Would it be possible to support it in the first place only if the return
type hints are already known by the engine?
If my memory is working correctly, opcache will prevent this from
working as you expect when it is enabled.
I think we want covariant return types eventually, but for now getting
invariant return types into the engine is a nice stepping stone.