[RFC] Static return type

php.internals

Nikita Popov

6 years ago
Hi internals, I would like to propose the following RFC, which allows using "static" as a return type: https://wiki.php.net/rfc/static_return_type While I'm personally not a fan of late static binding, we do support it and people do use it quite heavily, so I think we should also support it in return types. Regards, Nikita

Rowan Collins

6 years ago
On Wed, 8 Jan 2020 at 11:42, Nikita Popov <nikita.ppv@gmail.com> wrote:
> I would like to propose the following RFC, which allows using "static" as a > return type: > > https://wiki.php.net/rfc/static_return_type > > While I'm personally not a fan of late static binding, we do support it and > people do use it quite heavily, so I think we should also support it in > return types. >
A big +1 from me; I'd probably have assumed it would work and be disappointed when it didn't. I can see why late static binding isn't to everyone's taste as a concept, but it is very useful to emulate other features like named constructors, as you show in the RFC. The fluent interface example is interesting, because it doesn't actually involve late static binding at all, we just need some way to say "what comes out is the same type as what goes in". Regards,
-- Rowan Tommins [IMSoP]

Mark Randall

6 years ago
On 08/01/2020 11:42, Nikita Popov wrote:
> Hi internals, > > I would like to propose the following RFC, which allows using "static" as a > return type: > > https://wiki.php.net/rfc/static_return_type
So many "@return $this" docblocks are going to be deleted as a result of this when PHP8 lands. +1 from me.
-- Mark Randall marandall@php.net

Paul M. Jones

6 years ago
> On Jan 8, 2020, at 05:42, Nikita Popov <nikita.ppv@gmail.com> wrote: > > Hi internals, > > I would like to propose the following RFC, which allows using "static" as a > return type: > > https://wiki.php.net/rfc/static_return_type
Very happy to see this.
-- Paul M. Jones pmjones@pmjones.io http://paul-m-jones.com Modernizing Legacy Applications in PHP https://leanpub.com/mlaphp Solving the N+1 Problem in PHP https://leanpub.com/sn1php

Benjamin Morel

6 years ago
> > I would like to propose the following RFC, which allows using "static" as a > return type: > https://wiki.php.net/rfc/static_return_type
Big +1 from me as well. I actually asked why this wasn't supported back in 2015: static return type in PHP 7 interfaces <https://stackoverflow.com/q/30952876/759866> — Benjamin

Ben Ramsey

6 years ago
> On Jan 8, 2020, at 05:42, Nikita Popov <nikita.ppv@gmail.com> wrote: > > Hi internals, > > I would like to propose the following RFC, which allows using "static" as a > return type: > > https://wiki.php.net/rfc/static_return_type > > While I'm personally not a fan of late static binding, we do support it and > people do use it quite heavily, so I think we should also support it in > return types. > > Regards, > Nikita
Yes, please!
> For the fluent method example above, many projects will use a @return $this > annotation, rather than @return static.
In my own code, I usually use `self` to mean “return the same instance.” While I know that, in practice, the use of `self` is identical to using the class name as the return type, it seems natural to designate as practice (among my team) that `self` always means itself and should never refer to an instance with different identity. -Ben

Larry Garfield

6 years ago
On Wed, Jan 8, 2020, at 5:42 AM, Nikita Popov wrote:
> Hi internals, > > I would like to propose the following RFC, which allows using "static" as a > return type: > > https://wiki.php.net/rfc/static_return_type > > While I'm personally not a fan of late static binding, we do support it and > people do use it quite heavily, so I think we should also support it in > return types. > > Regards, > Nikita
Is "squee!" an appropriate response on the list? For interface authors this is a huge deal. --Larry Garfield

Andreas Hennings

6 years ago
> However, $this is not a real type, and it is unclear what the advantage
of specifying $this rather than static would be from a type system level perspective. Perhaps not from a "type system level", but from a more broad "enforced contract" level. E.g. IDEs or code inspection tools can warn if a method will not return $this. This also means recursive calls also need to return $this instead of static. class C { function foo(): static { return clone $this; } function bar(): $this { return $this->foo(); // IDE can complain. } } On Thu, 9 Jan 2020 at 15:07, Larry Garfield <larry@garfieldtech.com> wrote:

Andreas Hennings

6 years ago
On Thu, 9 Jan 2020 at 15:22, Andreas Hennings <andreas@dqxtech.net> wrote:
> > However, $this is not a real type, and it is unclear what the advantage > of specifying $this rather than static would be from a type system level > perspective. > > Perhaps not from a "type system level", but from a more broad "enforced > contract" level. > E.g. IDEs or code inspection tools can warn if a method will not return > $this. > This also means recursive calls also need to return $this instead of > static. >
> class C { > function foo(): static { > return clone $this; > } > function bar(): $this { > return $this->foo(); // IDE can complain. > } > } >
Of course this ": $this" could be added later in the future. But this would have the risk that people start adding ": static" on methods that really return $this. This would cause confusion if later some packages introduce ": $this", but others are still behind. namespace oldpackage; class B { function foo(): static { // Actually returns $this, not just static. return $this; } } namespace newpackage; class C extends B { function foo(): $this { return parent::foo(); // Should the IDE complain? } }

Nicolas Grekas

6 years ago
> Of course this ": $this" could be added later in the future. > But this would have the risk that people start adding ": static" on methods > that really return $this. >
I second $this. I also would like to be able to declare `function foo(): $this` at the same time as `function foo(): static` so that we can use the most accurate description right away. Although I'm not into the C part of the proposal, if I may have a suggestion here, it would be to implement the check *at compile time*. Like nullable return types check at compile time that `return null;` is used instead of just `return;`, I think a `$this` return type could and should enforce `return $this;` on all return point of such methods. That would also make the check free at runtime. Cheers, Nicolas

Rowan Collins

6 years ago
On Thu, 9 Jan 2020 at 14:23, Andreas Hennings <andreas@dqxtech.net> wrote:
> > However, $this is not a real type, and it is unclear what the advantage > of specifying $this rather than static would be from a type system level > perspective. > > Perhaps not from a "type system level", but from a more broad "enforced > contract" level. > E.g. IDEs or code inspection tools can warn if a method will not return > $this. > This also means recursive calls also need to return $this instead of > static. > > class C { > function foo(): static { > return clone $this; > } > function bar(): $this { > return $this->foo(); // IDE can complain. > } > } >
I think there are two different purposes for annotating return types: 1) Contracts that tell the caller what values they should expect when calling the function. 2) Constraints on the implementation which don't affect the caller, but allow the author to catch certain bugs. The primary purpose, in my mind, is (1), with (2) generally coming as a side-effect: if your contract is to return an int, then tools can warn you when you don't. All concrete types can be seen this way: scalars, classes, and pseudo-types like "iterable" are all contracts that calling code can rely on. "self", "parent", and "static" fit into that list fine, because they're ultimately specifying a class name. The special "void" keyword doesn't actually make sense as a contract given PHP's calling convention - as far as the calling code's concerned, it's equivalent to "null". So it exists only for purpose (2), constraining the implementation for the benefit of the function's author. $this would fit into the same category - as far as calling code is concerned it is equivalent to "static", unless they're doing something very specific with object identity. This makes it an odd constraint on interfaces, for example - DateTimeInterface exists specifically to have mutable and immutable implementations, and a return type of $this would explicitly prevent that. If we add ": $this" alongside ": void", I wonder where that takes us next - what other constraints on a function can be expressed using that notation which aren't contracts on the value seen by the caller? If we don't want to explore that question, should we avoid adding ": $this"? Regards,
-- Rowan Tommins [IMSoP]

Nikita Popov

6 years ago
On Thu, Jan 9, 2020 at 4:06 PM Rowan Tommins <rowan.collins@gmail.com> wrote:
> On Thu, 9 Jan 2020 at 14:23, Andreas Hennings <andreas@dqxtech.net> wrote: > > > > However, $this is not a real type, and it is unclear what the advantage > > of specifying $this rather than static would be from a type system level > > perspective. > > > > Perhaps not from a "type system level", but from a more broad "enforced > > contract" level. > > E.g. IDEs or code inspection tools can warn if a method will not return > > $this. > > This also means recursive calls also need to return $this instead of > > static. > > > > class C { > > function foo(): static { > > return clone $this; > > } > > function bar(): $this { > > return $this->foo(); // IDE can complain. > > } > > } > > > > > I think there are two different purposes for annotating return types: > > 1) Contracts that tell the caller what values they should expect when > calling the function. > 2) Constraints on the implementation which don't affect the caller, but > allow the author to catch certain bugs. > > The primary purpose, in my mind, is (1), with (2) generally coming as a > side-effect: if your contract is to return an int, then tools can warn you > when you don't. > > All concrete types can be seen this way: scalars, classes, and pseudo-types > like "iterable" are all contracts that calling code can rely on. "self", > "parent", and "static" fit into that list fine, because they're ultimately > specifying a class name. > > The special "void" keyword doesn't actually make sense as a contract given > PHP's calling convention - as far as the calling code's concerned, it's > equivalent to "null". So it exists only for purpose (2), constraining the > implementation for the benefit of the function's author. > > $this would fit into the same category - as far as calling code is > concerned it is equivalent to "static", unless they're doing something very > specific with object identity. This makes it an odd constraint on > interfaces, for example - DateTimeInterface exists specifically to have > mutable and immutable implementations, and a return type of $this would > explicitly prevent that. > > If we add ": $this" alongside ": void", I wonder where that takes us next - > what other constraints on a function can be expressed using that notation > which aren't contracts on the value seen by the caller? If we don't want to > explore that question, should we avoid adding ": $this"? >
An argument could be made that $this does also specify a certain contract to the caller: That the API may be used fluently or not without change in functionality. That is $foo->setBar(); $foo->setBaz(); must be strictly equivalent to $foo ->setBar() ->setBaz() ; The same is not the case for a plain "static" type. In fact, I think that for all other uses of "static" outside of fluent interfaces, not using the return value would be a programming error. But still, this kind of contract is not a type-system contract, and I'm not sure it's a good idea to mix other types of API contracts into the type system in this fashion. Regards, Nikita

Andreas Hennings

6 years ago
On Thu, 9 Jan 2020 at 16:31, Nikita Popov <nikita.ppv@gmail.com> wrote:
> On Thu, Jan 9, 2020 at 4:06 PM Rowan Tommins <rowan.collins@gmail.com> > wrote: > > > On Thu, 9 Jan 2020 at 14:23, Andreas Hennings <andreas@dqxtech.net> > wrote: > > > > > > However, $this is not a real type, and it is unclear what the > advantage > > > of specifying $this rather than static would be from a type system > level > > > perspective. > > > > > > Perhaps not from a "type system level", but from a more broad "enforced > > > contract" level. > > > E.g. IDEs or code inspection tools can warn if a method will not return > > > $this. > > > This also means recursive calls also need to return $this instead of > > > static. > > > > > > class C { > > > function foo(): static { > > > return clone $this; > > > } > > > function bar(): $this { > > > return $this->foo(); // IDE can complain. > > > } > > > } > > > > > > > > > I think there are two different purposes for annotating return types: > > > > 1) Contracts that tell the caller what values they should expect when > > calling the function. > > 2) Constraints on the implementation which don't affect the caller, but > > allow the author to catch certain bugs. > > > > The primary purpose, in my mind, is (1), with (2) generally coming as a > > side-effect: if your contract is to return an int, then tools can warn > you > > when you don't. > > > > All concrete types can be seen this way: scalars, classes, and > pseudo-types > > like "iterable" are all contracts that calling code can rely on. "self", > > "parent", and "static" fit into that list fine, because they're > ultimately > > specifying a class name. > > > > The special "void" keyword doesn't actually make sense as a contract > given > > PHP's calling convention - as far as the calling code's concerned, it's > > equivalent to "null". So it exists only for purpose (2), constraining the > > implementation for the benefit of the function's author. > > > > $this would fit into the same category - as far as calling code is > > concerned it is equivalent to "static", unless they're doing something > very > > specific with object identity. This makes it an odd constraint on > > interfaces, for example - DateTimeInterface exists specifically to have > > mutable and immutable implementations, and a return type of $this would > > explicitly prevent that. > > > > If we add ": $this" alongside ": void", I wonder where that takes us > next - > > what other constraints on a function can be expressed using that notation > > which aren't contracts on the value seen by the caller? If we don't want > to > > explore that question, should we avoid adding ": $this"? > > > > An argument could be made that $this does also specify a certain contract > to the caller: That the API may be used fluently or not without change in > functionality. That is > > $foo->setBar(); > $foo->setBaz(); > > must be strictly equivalent to > > $foo > ->setBar() > ->setBaz() > ; > > The same is not the case for a plain "static" type. In fact, I think that > for all other uses of "static" outside of fluent interfaces, not using the > return value would be a programming error. > > But still, this kind of contract is not a type-system contract, and I'm not > sure it's a good idea to mix other types of API contracts into the type > system in this fashion. >
We currently don't have another "system" where this kind of info could be specified. And in this case, $this also implies static, so if we would use whichever other system to specify $this, the type hint "static" would become redundant. "@return $this" is common in phpdoc (*) since a while, and so far I have not seen any problems with it. If we ask "where does this lead?", one natural next step might be to specifically say that the return type is the same type, but NOT $this. So, a promise that we really get a new object which we can modify without changing the original object. I don't know how we would express this. Perhaps ": clone", but this would be too specific imo, because whether it is a clone is an implementation detail. In phpdoc (*) there is no common syntax for "static, but not $this", so perhaps it was not needed yet. And in fact, an immutable wither can safely return the original object if no changes are needed, if other methods are also immutable. (*) When I say "phpdoc", I really mean the kinds of docs that people commonly use in the wild, and that are understood by IDEs. Which of that is documented and supported by the phpdoc library is a different question.

Dik Takken

6 years ago
On 09-01-2020 17:52, Andreas Hennings wrote:
> On Thu, 9 Jan 2020 at 16:31, Nikita Popov <nikita.ppv@gmail.com> wrote: > >> On Thu, Jan 9, 2020 at 4:06 PM Rowan Tommins <rowan.collins@gmail.com> >> wrote: >> >> An argument could be made that $this does also specify a certain contract >> to the caller: That the API may be used fluently or not without change in >> functionality. That is >> >> $foo->setBar(); >> $foo->setBaz(); >> >> must be strictly equivalent to >> >> $foo >> ->setBar() >> ->setBaz() >> ; >> >> The same is not the case for a plain "static" type. In fact, I think that >> for all other uses of "static" outside of fluent interfaces, not using the >> return value would be a programming error. >> >> But still, this kind of contract is not a type-system contract, and I'm not >> sure it's a good idea to mix other types of API contracts into the type >> system in this fashion. >> > > We currently don't have another "system" where this kind of info could be > specified.
How about: public fluent function doWhatever(): static Perhaps this could be introduced at some point in the future to enforce returning $this.
> And in this case, $this also implies static, so if we would use whichever > other system to specify $this, the type hint "static" would become > redundant. > > "@return $this" is common in phpdoc (*) since a while, and so far I have > not seen any problems with it. > > If we ask "where does this lead?", one natural next step might be to > specifically say that the return type is the same type, but NOT $this. > So, a promise that we really get a new object which we can modify without > changing the original object. > > I don't know how we would express this. Perhaps ": clone", but this would > be too specific imo, because whether it is a clone is an implementation > detail.
Maybe ": new" ? Regards, Dik Takken

Andreas Hennings

6 years ago
On Thu, 9 Jan 2020 at 20:53, Dik Takken <d.takken@xs4all.nl> wrote:
> On 09-01-2020 17:52, Andreas Hennings wrote: > > On Thu, 9 Jan 2020 at 16:31, Nikita Popov <nikita.ppv@gmail.com> wrote: > > > >> On Thu, Jan 9, 2020 at 4:06 PM Rowan Tommins <rowan.collins@gmail.com> > >> wrote: > >> > >> An argument could be made that $this does also specify a certain > contract > >> to the caller: That the API may be used fluently or not without change > in > >> functionality. That is > >> > >> $foo->setBar(); > >> $foo->setBaz(); > >> > >> must be strictly equivalent to > >> > >> $foo > >> ->setBar() > >> ->setBaz() > >> ; > >> > >> The same is not the case for a plain "static" type. In fact, I think > that > >> for all other uses of "static" outside of fluent interfaces, not using > the > >> return value would be a programming error. > >> > >> But still, this kind of contract is not a type-system contract, and I'm > not > >> sure it's a good idea to mix other types of API contracts into the type > >> system in this fashion. > >> > > > > We currently don't have another "system" where this kind of info could be > > specified. > > How about: > > public fluent function doWhatever(): static > > Perhaps this could be introduced at some point in the future to enforce > returning $this. >
This would work, but: - The ": static" would be redundant if the method is fluid. - I still would find it more natural to look into the return hint and find ": $this". With fluent + static I would have to look in two places. But I was thinking something else related to the ": $this" (or "fluent"). If we already annotate a method as fluent, then the actual "return $this;" statement is redundant as well. We _could_ make this implicit, so you no longer have to write "return $this;". Not sure if that's a good idea, and definitely not in scope of the ": static" proposal. But I think it is useful to have an idea where we want to go with this.
> > > And in this case, $this also implies static, so if we would use whichever > > other system to specify $this, the type hint "static" would become > > redundant. > > > > "@return $this" is common in phpdoc (*) since a while, and so far I have > > not seen any problems with it. > > > > If we ask "where does this lead?", one natural next step might be to > > specifically say that the return type is the same type, but NOT $this. > > So, a promise that we really get a new object which we can modify without > > changing the original object. > > > > I don't know how we would express this. Perhaps ": clone", but this would > > be too specific imo, because whether it is a clone is an implementation > > detail. > > Maybe ": new" ? >
To me this would not imply that this has to be the same type.. Either way, I think there is no strong enough reason to introduce a syntax for this case. It would be mostly for wither methods, and these can safely return the original object if the entire thing is designed as immutable.

Mike Schinkel

6 years ago
> On Jan 10, 2020, at 1:19 PM, Andreas Hennings <andreas@dqxtech.net> wrote: > > This would work, but: > ": $this". With fluent + static I would have to look in two places.
How about :fluent? This would indicate both a type and a further constraint meaning $this. Something to consider...? -Mike

Guilliam Xavier

6 years ago
Hi, After reading all the thread: I'm +1 for `: static` as proposed by this RFC. I'm also +1 for `: $this`, preferably also in PHP 8.0, but that could be a separate RFC: - I like the idea to check it at compile time (like `: void`), i.e. that all the return points of the method be syntactically `return $this;` (maybe also allow `return $this->otherMethod(/*...*/);` where the forwarded method is itself declared `: $this`). - Regarding covariance, `$this` should probably be treated like a "subtype" of `static`. - I don't like the idea of making an implicit `return $this;` in addition to the check. I'm -1 for `: fluent` or `fluent function` as an alternative to `: $this`. I'm -0.5 for `: new` / `: clone`. About "what comes out is the same type as what goes in", I would wait for generics.
-- Guilliam Xavier

Andrew Johnstone

6 years ago
Sent from my iPhone

Nikita Popov

6 years ago
On Wed, Jan 8, 2020 at 12:42 PM Nikita Popov <nikita.ppv@gmail.com> wrote:
> Hi internals, > > I would like to propose the following RFC, which allows using "static" as > a return type: > > https://wiki.php.net/rfc/static_return_type > > While I'm personally not a fan of late static binding, we do support it > and people do use it quite heavily, so I think we should also support it in > return types. > > Regards, > Nikita >
Heads up: I plan to start voting on this RFC tomorrow. I think most of the discussion here has been around the "$this" type mentioned in future scope, but no particular concerns with "static" itself have been raised. (I don't plan to include $this as part of this RFC.) Regards, Nikita