[RFC] Parameter No Type Variance

php.internals

Niklas Keller

9 years ago
Morning Internals, I'd like to announce a RFC to allow omitting the type declarations for parameters in subclasses: https://wiki.php.net/rfc/parameter-no-type-variance PHP doesn't currently allow variance for parameters as checking these for compatibility isn't possible on compile time. This limitation is caused by autoloading and doesn't allow widening the accepted parameters. This RFC proposes to allow ommiting the type entirely in a subclass, as dropping all parameter constraints is always valid according to the LSP principle. We already allow return types being added in subclasses. Your feedback is welcome. :-) Regards, Niklas

Josh Di Fabio

9 years ago
On Mon, Nov 21, 2016 at 9:40 AM Niklas Keller <me@kelunik.com> wrote:
> Morning Internals, > > I'd like to announce a RFC to allow omitting the type declarations for > parameters in subclasses: > https://wiki.php.net/rfc/parameter-no-type-variance > > PHP doesn't currently allow variance for parameters as checking these for > compatibility isn't possible on compile time. > This limitation is caused by autoloading and doesn't allow widening the > accepted parameters. > > This RFC proposes to allow ommiting the type entirely in a subclass, as > dropping all parameter constraints is > always valid according to the LSP principle. > > We already allow return types being added in subclasses. > > Your feedback is welcome. :-) > > Regards, Niklas >
Sounds good Niklas!

Daniel Morris

9 years ago
What happens if the method signature is defined within an interface? I would consider that method signature a contract and one that should not be able to be overwritten.
-- Daniel Morris daniel@honestempire.com

Niklas Keller

9 years ago
2016-11-21 11:00 GMT+01:00 Daniel Morris <daniel@honestempire.com>:
> What happens if the method signature is defined within an interface?
Just the same as for every class. Having other rules for interfaces doesn't make sense. Imagine the following example: interface I { function foo(array $foo); } class C1 implements I { function foo(array $foo) { /* with your proposal, we have to keep the type here? */ } } class C2 extends C2 { function foo($foo) { /* we can suddenly drop the parameter type now?! */ } }
> I would consider that method signature a contract and one that should not > be able to be overwritten. >
Subtypes can always accept more than a parent by the LSP ( https://en.wikipedia.org/wiki/Liskov_substitution_principle) Regards, Niklas

Christoph Becker

9 years ago
On 21.11.2016 at 10:39, Niklas Keller wrote:
> I'd like to announce a RFC to allow omitting the type declarations for > parameters in subclasses: > https://wiki.php.net/rfc/parameter-no-type-variance > > PHP doesn't currently allow variance for parameters as checking these for > compatibility isn't possible on compile time. > This limitation is caused by autoloading and doesn't allow widening the > accepted parameters. > > This RFC proposes to allow ommiting the type entirely in a subclass, as > dropping all parameter constraints is > always valid according to the LSP principle.
Have you considered that instead of simply omitting the type declaration, one would have to mark the omission explicitly, to still get a compile time warning in case of unintended omission?
-- Christoph M. Becker

Niklas Keller

9 years ago
2016-11-21 12:59 GMT+01:00 Christoph M. Becker <cmbecker69@gmx.de>:
> On 21.11.2016 at 10:39, Niklas Keller wrote: > > > I'd like to announce a RFC to allow omitting the type declarations for > > parameters in subclasses: > > https://wiki.php.net/rfc/parameter-no-type-variance > > > > PHP doesn't currently allow variance for parameters as checking these for > > compatibility isn't possible on compile time. > > This limitation is caused by autoloading and doesn't allow widening the > > accepted parameters. > > > > This RFC proposes to allow ommiting the type entirely in a subclass, as > > dropping all parameter constraints is > > always valid according to the LSP principle. > > Have you considered that instead of simply omitting the type > declaration, one would have to mark the omission explicitly, to still > get a compile time warning in case of unintended omission >
No, not really. While we could use "mixed" there, those signatures _are_ compatible without it and shouldn't throw a warning. Anyone else interested in the usage of "mixed" as an explicit type declaration?

Stas Malyshev

9 years ago
Hi!
> Anyone else interested in the usage of "mixed" as an explicit type > declaration?
Please no. We already have "mixed" - every variable is "mixed". We don't need more cruft in the code that does not add anything that doesn't already exist.
-- Stas Malyshev smalyshev@gmail.com

Jan Altensen

9 years ago
no please no "mixed" keyword my idea to this is having something like this: class Foo { public function test (int $a) : int { return $a; } } class Bar extends Foo { override public function test (string $a) : string { return $a; } } class FooBar extends Foo { override public function test ($a) { return $a; } } or maybe this too class Bar extends Foo { override private function test (string $a) : string { return $a; } } so you have to mark the override explicitly if you leave the override keyword it still throws a fatal error dunno if this is a good idea or not, just my 2 cents Regards, Jan Altensen (Stricted) Am 26.11.2016 um 16:54 schrieb Niklas Keller:

Fleshgrinder

9 years ago
On 11/21/2016 10:39 AM, Niklas Keller wrote:
> Morning Internals, > > I'd like to announce a RFC to allow omitting the type declarations for > parameters in subclasses: > https://wiki.php.net/rfc/parameter-no-type-variance > > PHP doesn't currently allow variance for parameters as checking these for > compatibility isn't possible on compile time. > This limitation is caused by autoloading and doesn't allow widening the > accepted parameters. > > This RFC proposes to allow ommiting the type entirely in a subclass, as > dropping all parameter constraints is > always valid according to the LSP principle. > > We already allow return types being added in subclasses. > > Your feedback is welcome. :-) > > Regards, Niklas >
I think that this is a good proposal. @Daniel Morris: an Interface is just a fully abstract class, no special casing is necessary. Every definition is a contract and every definition adheres to certain rules. In this case LSP applies. I do not think that it is necessary to add any special keyword as proposed by @Christoph Becker since nothing means mixed and mixed means anything, thus, it actually already is a special keyword. However, I do not think that this is the solution for type variance in general since type checking is now up to userland which means lots of repetition. Actual type checks are of arguments are also performed at runtime, same could be done for type variance checks. Pretty much the same as with _type erasure_ vs. _type reification_. Having a runtime check is better than having none at all. We will probably want this anyways in PHP since we want Generics that are type safe at runtime. At least I hope we do. ;) Note that implementing this would also allow your proposal to work. This goes back to what I said before. No restriction is mixed which is the parent of anything, this is true a runtime too.
-- Richard "Fleshgrinder" Fussenegger

Rasmus Schultz

9 years ago
But this leads to code that can't pass static inspections? interface Joiner { public function join(array $array): string; } class WideJoiner implements Joiner { public function join($iterable): string { $array = is_array($iterable) ? $array : iterable_to_array($iterable); return implode(", ", $array); } } function joinWith(Joiner $joiner, $iterable) { return $joiner->join($iterable); // <-- invalid argument ? } According to the Joiner abstraction, only array is accepted, and that's all a static analysis tool can know about the $joiner argument in the joinWith() function. Being unable to pass static inspections is one thing, but this also makes the code generally difficult to explain - there's a contract, Joiner, which states that an array is required - to a person reading the joinWith() function, that's all they can know about a Joiner instance; it isn't safe for anybody to depend on a specific implementation of Joiner with a widened argument-type, unless they read through the entire codebase and happen to know about the WideJoiner implementation, but even then, someone using the joinWith() function would also need to know which implementation of Joiner is being passed, which seems to defeats the purpose of even having an abstraction in the first place? On Mon, Nov 21, 2016 at 10:39 AM, Niklas Keller <me@kelunik.com> wrote:

Niklas Keller

9 years ago
> > But this leads to code that can't pass static inspections? >
No.
> interface Joiner { > public function join(array $array): string; > } > > class WideJoiner implements Joiner { > public function join($iterable): string { > $array = is_array($iterable) ? $array : > iterable_to_array($iterable); > > return implode(", ", $array); > } > } > > function joinWith(Joiner $joiner, $iterable) { > return $joiner->join($iterable); // <-- invalid argument ? > } >
Yes, a static analysis engine should report invalid argument. If you depend on another thing being accepted, you shouldn't declare just Joiner but instead an extending interface or an implementation directly.
> According to the Joiner abstraction, only array is accepted, and that's > all a static analysis tool can know about the $joiner argument in the > joinWith() function. >
Correct, that's why the static analysis engine should throw a warning on that code.
> Being unable to pass static inspections is one thing, but this also makes > the code generally difficult to explain >
How so?
> there's a contract, Joiner, which states that an array is required - to a > person reading the joinWith() function, that's all they can know about a > Joiner instance >
Correct.
> it isn't safe for anybody to depend on a specific implementation of Joiner > with a widened argument-type >
It is safe as long as you depend on the specific implementation or an extended interface with the widened argument type.
> unless they read through the entire codebase and happen to know about the > WideJoiner implementation, but even then, someone using the joinWith() > function would also need to know which implementation of Joiner is being > passed >
You'd just declare that you depend on a Joiner that accepts also iterables.
> which seems to defeats the purpose of even having an abstraction in the > first place? >
So your argument is basically that contra-variance is never needed? Regards, Niklas

Nikita Popov

9 years ago
On Mon, Nov 21, 2016 at 10:39 AM, Niklas Keller <me@kelunik.com> wrote:
> Morning Internals, > > I'd like to announce a RFC to allow omitting the type declarations for > parameters in subclasses: > https://wiki.php.net/rfc/parameter-no-type-variance > > PHP doesn't currently allow variance for parameters as checking these for > compatibility isn't possible on compile time. > This limitation is caused by autoloading and doesn't allow widening the > accepted parameters. > > This RFC proposes to allow ommiting the type entirely in a subclass, as > dropping all parameter constraints is > always valid according to the LSP principle. > > We already allow return types being added in subclasses. > > Your feedback is welcome. :-) > > Regards, Niklas >
The RFC is a bit lacking in motivation ... The main practical use-case I see for this is post-hoc addition of type-hints on an interface. To cite a particular example we ran into for PHP 7.0: Derick added a DateTimeZone type hint for the third arg of the DateTime::createFromFormat() method [1]. The method is already documented to accept only this class in the manual, but the typehint is not actually present in the implementation. However, this change had to be reverted, because all classes extending DateTime currently don't have this typehint (and adding it would be illegal under LSP), so they started throwing a method signature mismatch warning. Allowing extending classes to drop typehints in accordance with variance-rules allow people to add additional parameter typehints on parent classes without breaking BC. (Adding an additional return typehint would break BC, but it is still a cross-version compatible change, as consumers have the possibility of writing code that is valid under both versions -- something that is currently impossible if you want to add parameter typehints.) Thanks, Nikita [1]: https://github.com/php/php-src/commit/8e19705a93d785cd1ff8ba3a69699b00169fea47

Niklas Keller

9 years ago
> > The RFC is a bit lacking in motivation ... > > The main practical use-case I see for this is post-hoc addition of > type-hints on an interface. To cite a particular example we ran into for > PHP 7.0: Derick added a DateTimeZone type hint for the third arg of the > DateTime::createFromFormat() method [1]. The method is already documented > to accept only this class in the manual, but the typehint is not actually > present in the implementation. > > However, this change had to be reverted, because all classes extending > DateTime currently don't have this typehint (and adding it would be illegal > under LSP), so they started throwing a method signature mismatch warning. > > Allowing extending classes to drop typehints in accordance with > variance-rules allow people to add additional parameter typehints on parent > classes without breaking BC. (Adding an additional return typehint would > break BC, but it is still a cross-version compatible change, as consumers > have the possibility of writing code that is valid under both versions -- > something that is currently impossible if you want to add parameter > typehints.) >
You're completely right, I'll add that before I start the vote. I think it makes sense to defer the vote into the next year, we still have plenty of time then. Regards, Niklas