[Vote] New in initializers

  115224
June 30, 2021 07:55 nikita.ppv@gmail.com (Nikita Popov)
Hi internals,

I've opened voting on https://wiki.php.net/rfc/new_in_initializers. Voting
will close on 2021-07-14.

Note that relative to the original RFC, new support is limited to parameter
default values, attribute arguments, static variable initializers and
global constant initializers, and not supported in property initializers
and class constant initializers. The discussion thread
https://externals.io/message/113347 has some extensive information on how
we got here.

Regards,
Nikita
  115309
July 6, 2021 07:30 nicolas.grekas+php@gmail.com (Nicolas Grekas)
Hi NIkita,

I've opened voting on https://wiki.php.net/rfc/new_in_initializers. Voting
> will close on 2021-07-14. > > Note that relative to the original RFC, new support is limited to parameter > default values, attribute arguments, static variable initializers and > global constant initializers, and not supported in property initializers > and class constant initializers. The discussion thread > https://externals.io/message/113347 has some extensive information on how > we got here. >
I voted yes and I'm happy this will come to PHP. I realized I still have one concern that I want to share here, related to attributes: The RFC breaks the possibility to parse the arguments of an attribute in a generic and safe way. What I mean is that right now, attributes can be inspected while the corresponding classes are not installed, due eg to a missing optional dependency. This behavior is what makes attributes truly declarative: one can ignore what they don't care about. Extra semantics can be carried out by classes without making the related attributes a mandatory dependency. I think there is a way to preserve this behavior and that we should look for it. If I may propose one: we might add a new ReflectionAttribute::getUninitializedArguments() method, that would return the same as ReflectionAttribute::getArguments(), except that it would put a ReflectionAttribute (or similar) instance in place of objects in the data structure. As a corollary, we might also want to enforce that only child classes of the Attribute class can be nested inside another Attribute (at least if we want to reuse ReflectionAttribute as a placeholder.) WDYT? Nicolas
  115314
July 6, 2021 10:31 kontakt@beberlei.de (Benjamin Eberlei)
On Tue, Jul 6, 2021 at 9:31 AM Nicolas Grekas grekas+php@gmail.com>
wrote:

> Hi NIkita, > > I've opened voting on https://wiki.php.net/rfc/new_in_initializers. Voting > > will close on 2021-07-14. > > > > Note that relative to the original RFC, new support is limited to > parameter > > default values, attribute arguments, static variable initializers and > > global constant initializers, and not supported in property initializers > > and class constant initializers. The discussion thread > > https://externals.io/message/113347 has some extensive information on > how > > we got here. > > > > I voted yes and I'm happy this will come to PHP. > > I realized I still have one concern that I want to share here, related to > attributes: > The RFC breaks the possibility to parse the arguments of an attribute in a > generic and safe way. > What I mean is that right now, attributes can be inspected while the > corresponding classes are not installed, due eg to a missing optional > dependency. >
This is not 100% correct, you can have an attribte #[Foo(Foo::class)] and then calling ReflectionAttribute::getArguments would also require to resolve the type Foo. So this is not different than what could happen right now already. What you can do is use `ReflectionAttribute::getName()` to filter the attributes you want to work on and only then call getArguments().
> This behavior is what makes attributes truly declarative: one can ignore > what they don't care about. Extra semantics can be carried out by classes > without making the related attributes a mandatory dependency. > > I think there is a way to preserve this behavior and that we should look > for it. > > If I may propose one: we might add a new > ReflectionAttribute::getUninitializedArguments() method, that would return > the same as ReflectionAttribute::getArguments(), except that it would put a > ReflectionAttribute (or similar) instance in place of objects in the data > structure. As a corollary, we might also want to enforce that only child > classes of the Attribute class can be nested inside another Attribute (at > least if we want to reuse ReflectionAttribute as a placeholder.) >
A function like this could return all arguments that are not AST Nodes but "literals" (instances of scalar / array types). Foo::class or new Foo() are both AST Node types that are resolved the same way in `getArguments`.
> > WDYT? > > Nicolas >
  115316
July 6, 2021 10:44 nicolas.grekas+php@gmail.com (Nicolas Grekas)
> > I've opened voting on https://wiki.php.net/rfc/new_in_initializers. Voting >> > will close on 2021-07-14. >> > >> > Note that relative to the original RFC, new support is limited to >> parameter >> > default values, attribute arguments, static variable initializers and >> > global constant initializers, and not supported in property initializers >> > and class constant initializers. The discussion thread >> > https://externals.io/message/113347 has some extensive information on >> how >> > we got here. >> > >> >> I voted yes and I'm happy this will come to PHP. >> >> I realized I still have one concern that I want to share here, related to >> attributes: >> The RFC breaks the possibility to parse the arguments of an attribute in a >> generic and safe way. >> What I mean is that right now, attributes can be inspected while the >> corresponding classes are not installed, due eg to a missing optional >> dependency. >> > > This is not 100% correct, you can have an attribte #[Foo(Foo::class)] and > then calling ReflectionAttribute::getArguments would also require to > resolve the type Foo. So this is not different than what could happen right > now already. >
Right. Yet this can be easily worked around by using a string literal, so at least this issue can be avoided with a CS fixer when it matters. What you can do is use `ReflectionAttribute::getName()` to filter the
> attributes you want to work on and only then call getArguments(). >
Yes, my comment is about nested instances, not the root ones. #[Any(new Foo, new Bar)] <= Foo and Bar classes have to be defined, but this should be inspectable without instantiation, like root attributes.
> This behavior is what makes attributes truly declarative: one can ignore >> what they don't care about. Extra semantics can be carried out by classes >> without making the related attributes a mandatory dependency. >> >> I think there is a way to preserve this behavior and that we should look >> for it. >> >> If I may propose one: we might add a new >> ReflectionAttribute::getUninitializedArguments() method, that would return >> the same as ReflectionAttribute::getArguments(), except that it would put >> a >> ReflectionAttribute (or similar) instance in place of objects in the data >> structure. As a corollary, we might also want to enforce that only child >> classes of the Attribute class can be nested inside another Attribute (at >> least if we want to reuse ReflectionAttribute as a placeholder.) >> > > A function like this could return all arguments that are not AST Nodes but > "literals" (instances of scalar / array types). > Foo::class or new Foo() are both AST Node types that are resolved the same > way in `getArguments`. >
I guess so, but I'm not sure to get the exact point you want to make here :)
  115317
July 6, 2021 10:58 rowan.collins@gmail.com (Rowan Tommins)
On 06/07/2021 11:31, Benjamin Eberlei wrote:
> This is not 100% correct, you can have an attribte #[Foo(Foo::class)] and > then calling ReflectionAttribute::getArguments would also require to > resolve the type Foo. So this is not different than what could happen right > now already.
Despite its name, "::class" doesn't care about class definitions, it just performs a string substitution based on the "namespace" and "use" statements in the current file. In most cases, that happens entirely at compile time, so the following two source files compile identically: Short form: namespace Somebody\Something; #[ Foo( Foo::class ) ] class Whatever {} Expanded form: namespace Somebody\Something; #[ \Somebody\Something\Foo( '\Somebody\Something\Foo' ) ] class Whatever {} There is no need for the class \Somebody\Something\Foo to actually exist in either case, the argument is just a string: https://3v4l.org/bgNa2#v8.0.8 Regards, -- Rowan Tommins [IMSoP]
  115318
July 6, 2021 11:29 kontakt@beberlei.de (Benjamin Eberlei)
On Tue, Jul 6, 2021 at 12:58 PM Rowan Tommins collins@gmail.com>
wrote:

> On 06/07/2021 11:31, Benjamin Eberlei wrote: > > This is not 100% correct, you can have an attribte #[Foo(Foo::class)] and > > then calling ReflectionAttribute::getArguments would also require to > > resolve the type Foo. So this is not different than what could happen > right > > now already. > > > Despite its name, "::class" doesn't care about class definitions, it > just performs a string substitution based on the "namespace" and "use" > statements in the current file. > > In most cases, that happens entirely at compile time, so the following > two source files compile identically: >
Hah, I realized after sending the example was bad :) I should have used an example using actual constants (vs magic ones): #[Foo(Foo::BAR)] This would trigger autoloading and resolving during getArguments()
> > Short form: > > namespace Somebody\Something; > #[ Foo( Foo::class ) ] > class Whatever {} > > Expanded form: > > namespace Somebody\Something; > #[ \Somebody\Something\Foo( '\Somebody\Something\Foo' ) ] > class Whatever {} > > > There is no need for the class \Somebody\Something\Foo to actually exist > in either case, the argument is just a string: > https://3v4l.org/bgNa2#v8.0.8 > > > Regards, > > -- > Rowan Tommins > [IMSoP] > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: https://www.php.net/unsub.php > >
  115320
July 6, 2021 12:30 nicolas.grekas+php@gmail.com (Nicolas Grekas)
> > > This is not 100% correct, you can have an attribte #[Foo(Foo::class)] > and > > > then calling ReflectionAttribute::getArguments would also require to > > > resolve the type Foo. So this is not different than what could happen > > right > > > now already. > > > > > > Despite its name, "::class" doesn't care about class definitions, it > > just performs a string substitution based on the "namespace" and "use" > > statements in the current file. > > > > In most cases, that happens entirely at compile time, so the following > > two source files compile identically: > > > > Hah, I realized after sending the example was bad :) I should have used an > example using actual constants (vs magic ones): > > #[Foo(Foo::BAR)] > > This would trigger autoloading and resolving during getArguments() >
Right! Extending on my proposal, getUninitializedArguments() could return a ReflectionConstant in place of such values.
  115322
July 6, 2021 13:38 nikita.ppv@gmail.com (Nikita Popov)
On Tue, Jul 6, 2021 at 2:30 PM Nicolas Grekas grekas+php@gmail.com>
wrote:

> > > > This is not 100% correct, you can have an attribte #[Foo(Foo::class)] >> and >> > > then calling ReflectionAttribute::getArguments would also require to >> > > resolve the type Foo. So this is not different than what could happen >> > right >> > > now already. >> > >> > >> > Despite its name, "::class" doesn't care about class definitions, it >> > just performs a string substitution based on the "namespace" and "use" >> > statements in the current file. >> > >> > In most cases, that happens entirely at compile time, so the following >> > two source files compile identically: >> > >> >> Hah, I realized after sending the example was bad :) I should have used an >> example using actual constants (vs magic ones): >> >> #[Foo(Foo::BAR)] >> >> This would trigger autoloading and resolving during getArguments() >> > > > Right! > > Extending on my proposal, getUninitializedArguments() could return a > ReflectionConstant in place of such values. >
This doesn't extend to any more complex scenario: It's not just #[Foo(A::B)], it could also be #[Foo(A::FLAG_1 | A::FLAG_2)] and so on. The only way to do this is to go all the way back to Dmitry's attribute proposal (https://wiki.php.net/rfc/attributes) which allows fetching the AST of attribute arguments. That could represent arbitrary arguments without evaluating them. (In fact, that proposal also allowed attribute arguments that PHP cannot constant-evaluate at all.) I also think that viewing this as "nested attributes" is not quite the right way to think about it. Yes, the Assert\All use case is nested attributes, but that's just a special case. More generally this just allows you to use an object as an attribute argument, and that object does not necessarily have to be an attribute itself. To give a silly example, if we were to write argument and return types as attributes, you could have something like #[ReturnType(new IntersectionType(Foo::class, Bar::class))], where IntersectionType is just the representation of a particular type, but is not (and shouldn't be) an attribute itself. Regards, Nikita
  115327
July 6, 2021 14:40 nicolas.grekas+php@gmail.com (Nicolas Grekas)
Le mar. 6 juil. 2021 à 15:38, Nikita Popov ppv@gmail.com> a écrit :

> On Tue, Jul 6, 2021 at 2:30 PM Nicolas Grekas < > nicolas.grekas+php@gmail.com> wrote: > >> >> > > This is not 100% correct, you can have an attribte #[Foo(Foo::class)] >>> and >>> > > then calling ReflectionAttribute::getArguments would also require to >>> > > resolve the type Foo. So this is not different than what could happen >>> > right >>> > > now already. >>> > >>> > >>> > Despite its name, "::class" doesn't care about class definitions, it >>> > just performs a string substitution based on the "namespace" and "use" >>> > statements in the current file. >>> > >>> > In most cases, that happens entirely at compile time, so the following >>> > two source files compile identically: >>> > >>> >>> Hah, I realized after sending the example was bad :) I should have used >>> an >>> example using actual constants (vs magic ones): >>> >>> #[Foo(Foo::BAR)] >>> >>> This would trigger autoloading and resolving during getArguments() >>> >> >> >> Right! >> >> Extending on my proposal, getUninitializedArguments() could return a >> ReflectionConstant in place of such values. >> > > This doesn't extend to any more complex scenario: It's not just > #[Foo(A::B)], it could also be #[Foo(A::FLAG_1 | A::FLAG_2)] and so on. The > only way to do this is to go all the way back to Dmitry's attribute > proposal (https://wiki.php.net/rfc/attributes) which allows fetching the > AST of attribute arguments. That could represent arbitrary arguments > without evaluating them. (In fact, that proposal also allowed attribute > arguments that PHP cannot constant-evaluate at all.) > > I also think that viewing this as "nested attributes" is not quite the > right way to think about it. Yes, the Assert\All use case is nested > attributes, but that's just a special case. More generally this just allows > you to use an object as an attribute argument, and that object does not > necessarily have to be an attribute itself. To give a silly example, if we > were to write argument and return types as attributes, you could have > something like #[ReturnType(new IntersectionType(Foo::class, Bar::class))], > where IntersectionType is just the representation of a particular type, but > is not (and shouldn't be) an attribute itself. >
Types are a good example of a structure that the language already knows how to parse without actually requiring all symbols to be loaded: Foo|Bar doesn't require having both types loaded to work. I think we don't need to account for constants in my scenario. ReflectionAttribute::getUninitializedArguments() would then only replace objects with uninitialized placeholders that represent their class and arguments. That should cover the need.
  115423
July 14, 2021 07:20 nikita.ppv@gmail.com (Nikita Popov)
On Wed, Jun 30, 2021 at 9:55 AM Nikita Popov ppv@gmail.com> wrote:

> Hi internals, > > I've opened voting on https://wiki.php.net/rfc/new_in_initializers. > Voting will close on 2021-07-14. > > Note that relative to the original RFC, new support is limited to > parameter default values, attribute arguments, static variable initializers > and global constant initializers, and not supported in property > initializers and class constant initializers. The discussion thread > https://externals.io/message/113347 has some extensive information on how > we got here. >
This RFC has been accepted with 43 votes in favor and 2 against. Regards, Nikita