Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:126742 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by qa.php.net (Postfix) with ESMTPS id 3850F1A00BC for ; Thu, 13 Mar 2025 21:24:15 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1741900901; bh=LdLFeeGqHyQ9LDixXRrP9K9q2A59fEGOIEvIcMwEl44=; h=Date:From:To:In-Reply-To:References:Subject:From; b=A0wncbkyc0rpukn/q2evrw/gBbSa2224jzU7YvdJR6BjlXOpur7cXediw8fCl2HSn NnGsyxuO2lm/cuCfYHbkGlSvbJ2PMe67+CFDaW/UEy3VU56C6D3snN5HStO3AHdgim QYPTv1MspEN0tr7OQsNSbvG+C1+CnlyCFy2/iNbnWYw8ksrTRjFFgWVSVm8mNlfjuN IocMkuB3ZpcVefP2FsSFZYPO4XmHgvaFEvtcQGYKUh7/iq3LQoebjHgwANHHLsuC5A MA5RqNwfKftd7pZ4Kw3oe+FTyxoLa7oyPuhsepkg+FnELkLD+z1KXP+eq5kOADdc/I G6BtUMh9+Et9w== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 8493C18004E for ; Thu, 13 Mar 2025 21:21:40 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-2.8 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_MISSING,HTML_MESSAGE, RCVD_IN_DNSWL_LOW,SPF_HELO_PASS,SPF_PASS autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: No X-Envelope-From: Received: from fout-a5-smtp.messagingengine.com (fout-a5-smtp.messagingengine.com [103.168.172.148]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Thu, 13 Mar 2025 21:21:40 +0000 (UTC) Received: from phl-compute-11.internal (phl-compute-11.phl.internal [10.202.2.51]) by mailfout.phl.internal (Postfix) with ESMTP id 21E461382CA1 for ; Thu, 13 Mar 2025 17:24:13 -0400 (EDT) Received: from phl-imap-09 ([10.202.2.99]) by phl-compute-11.internal (MEProxy); Thu, 13 Mar 2025 17:24:13 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bottled.codes; h=cc:content-type:content-type:date:date:from:from:in-reply-to :in-reply-to:message-id:mime-version:references:reply-to:subject :subject:to:to; s=fm2; t=1741901053; x=1741987453; bh=9ClSUyXNHv fBGEjrV1fFrvTDVjziPC6s3kfdt6EahxA=; b=LVfLX32mhMyULf38YQ2tIYycNv Qs1lkRvDSG/cMC7AWJsIC1VNrEwTxaglYD6riC59Abxek51PHbFju3NZkIaAKCRA Hn31++wk9DrcVKYUSmRu/IkiMWeHXY95x5Os+ys+8Uxp0Cc/3NCDCEPCXsD2EYl5 CcUH4hW/NscyIFc8+2QM05GZLqP7/i+7A8JnRBLXWYfOKjNitN9qM13pip6t9LSv zIrLkp/luuyyzMbcQuRG65twyrO128d65JZknE2oBQd2Ncclo/puK1XIJOoWMCW8 1cYylwlx3EFOjpfavM/nVOHCPfKJjKE6CkYaJjDoN7fbwSj0L6c1e9dWZKlg== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-type:content-type:date:date :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:subject:subject:to :to:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm1; t= 1741901053; x=1741987453; bh=9ClSUyXNHvfBGEjrV1fFrvTDVjziPC6s3kf dt6EahxA=; b=lxZKzzUZtrRa2TVzN2j71yL1JH34MkUBsmsSSgB49HhJ+itC5iS h/nx8eTeic9wOpQt5K/fSjwURL9nQ7eywLCy62CgDHkTCCsl7DGi8eIxxpWCTR+Y cxFmAu1h79OavgGXkQfJgLmARGrMtCpIzvSJmsIaQ2q0v5/0D9tojcLcWceVAPgQ jQbRKTBtN4C+7dJFfjchH170fOoWtf8rAVNNMr4edY/E0iexJHP4hGPWf/oBmRrp w1h32OlxfjDmIQx0OY3o5/RUXWeI722l/VYLCArsUFYy9BoafuGcyTTdob8SCv97 vcgwu9B1wf7WhI199dU2ekyMVInAt0BxY6g== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefvddrtddtgdduvdeltddvucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdggtfgfnhhsuhgsshgtrhhisggv pdfurfetoffkrfgpnffqhgenuceurghilhhouhhtmecufedttdenucenucfjughrpefogg ffhffvkfgjfhfutgesrgdtreerredtjeenucfhrhhomhepfdftohgsucfnrghnuggvrhhs fdcuoehrohgssegsohhtthhlvggurdgtohguvghsqeenucggtffrrghtthgvrhhnpedtue ejtdethfeulefhtdelieduteelffdtudelheffgedtieehhfelieejgfevgeenucevlhhu shhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgrihhlfhhrohhmpehrohgssegsohhtth hlvggurdgtohguvghspdhnsggprhgtphhtthhopedupdhmohguvgepshhmthhpohhuthdp rhgtphhtthhopehinhhtvghrnhgrlhhssehlihhsthhsrdhphhhprdhnvght X-ME-Proxy: Feedback-ID: ifab94697:Fastmail Received: by mailuser.phl.internal (Postfix, from userid 501) id BC4B0780068; Thu, 13 Mar 2025 17:24:12 -0400 (EDT) X-Mailer: MessagingEngine.com Webmail Interface Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow MIME-Version: 1.0 Date: Thu, 13 Mar 2025 22:23:39 +0100 To: internals@lists.php.net Message-ID: <0543a041-4d31-415e-be30-b8d34e29ee0f@app.fastmail.com> In-Reply-To: References: <2935d0e2-ddc4-447c-ab37-c9b7337b8b60@app.fastmail.com> <0d94bf183543ee9948fcd31337198438@bastelstu.be> Subject: Re: [PHP-DEV] Re: RFC: short and inner classes Content-Type: multipart/alternative; boundary=920afeb387aa4dfb941e0ae5827c6b8c From: rob@bottled.codes ("Rob Landers") --920afeb387aa4dfb941e0ae5827c6b8c Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable On Thu, Mar 13, 2025, at 21:41, Ilija Tovilo wrote: > Hi Rob >=20 > On Thu, Mar 13, 2025 at 1:57=E2=80=AFPM Rob Landers wrote: > > > > > the proposal is > > > currently quite complex. > > > > Most of this is just describing how classes work already and going i= n-depth on where there may be confusion -- there are no significant chan= ges to how classes actually work. The actual changes to the engine are b= asically just visibility rules, some syntax changes (to allow nesting `c= lass` inside another class), and handling the new operator. The hard par= t is explaining how classes work, because they don't really have a defin= ed behavior. In other words, I cannot just say "the way this works doesn= 't change anything." >=20 > Well, you cut out the examples I gave: Ahh, ok. FWIW, I only cut out your examples to shorten the email. But I = see what you mean here. I think I can word the RFC in such a way as to m= ake it so I don't have to specify how classes work along with how inner = classes work... I'll have to think about it. Maybe I can just explain mo= re about how it actually works? I'm not sure. I'll probably look at the = enums RFC to get a better idea of how to do this. For example, I can explain that an inner class is basically a class with= a special name and special scopes attached. Then I can simply explain h= ow these special scopes interact with the rest of the language. Then I c= an get around defining class-like behaviors, and it may even be easier t= o reason about. > > It has a custom operator, it deals with shadowing, LSP, runtime reso= lution and more, in addition to visibility which is the actual goal. Tho= se are unrelated to existing behavior, they are introduced in the propos= al. >=20 > * The custom operator and runtime resolution is not something that > technically needs to be there. The name of class Foo { class Bar {} } > could simply be Foo\Bar. I've mentioned before that this does not work > with PSR-4, but there's no reason it can't work with autoloading at > all. An extended autoloading spec could recursively search > Foo/Bar/Baz.php, then Foo/Bar.php, and so forth. Given this happens > only when loading the class, and nesting would usually be limited in > quantity and amount, that seems like a reasonable solution, and > Composers optimized autoloader could avoid it entirely. This would > also solve the same issue for sealed classes, assuming they're named > in a similar fashion. I disagree completely. If you recall, last year, I tried to pass functio= n autoloading (which would have helped with records) and people really, = really, really didn't want to have to change how autoloading worked (and= I'm not talking about the list -- I mean projects). So, suggesting a ch= ange to autoloading is probably a non-starter. That being said, I don't hate it either. >=20 > * By shadowing I referred to static:>MyClass. This makes it more > modular, but it's also another layer of complexity and indirection. > You haven't really provided a reasoning and examples of how this could > be used. It's also not clear if this can be prevented. The inner class > can be marked as final, but that wouldn't stop you from shadowing the > inner class without extending it. Similarly, static:>MyClass would > have no type guarantees, given that it can be shadowed by any classes, > not just classes that are compatible. >=20 > class Outer { > protected class Inner { > public static function innerTest() {} > } >=20 > public static function outerTest() { > static:>Inner::innerTest(); > } > } >=20 > class OuterV2 extends Outer { > protected class Inner {} // This breaks outerTest() > } `static` is expressly forbidden with inner classes after Tim pointed out= that it basically breaks LSP. It doesn't, technically, but it makes it = hard to reason about for the exact example you gave. One thing worth pointing out here, though, is that in your example you h= ave Outer:>Inner and OuterV2:>Inner. These are two completely distinct c= lasses. They are not related to each other at all.=20 >=20 > * LSP issues have been mentioned before. You can use self:>Inner in > method signatures even if the inner classes are private. The method > could be called from sub-classes, but they couldn't ever override it, > since there's no way to satisfy the parent signature. This makes it > implicitly final. Not technically a problem, just odd. This is not the case any more. If you have a private inner class, you ca= nnot use it as a type declaration on a protected/public method, property= , or static member. A private class can still be returned and passed aro= und once instantiated, and it can implement an interface/base class (or = not). This is very similar to C# or Java. For example, this is ok: interface Shape {} class ShapeFactory { private class Rect implements Shape {} public function makeRect()): Shape { return new ShapeFactory:>Rect(); } } But this is not: class ShapeFactory { private class Rect {} // Private inner class ShapeFactory::Rect cannot be used as a return t= ype for public methods public function makeRect()): ShapeFactory:>Rect { return new ShapeFact= ory:>Rect(); } } > As mentioned, maybe there are additional use-cases this complexity can > cover, but the RFC doesn't give many examples. If the primary reason > for this complexity is just visibility, then I don't think this is the > simplest and best way in which that goal could be achieved. I'll add some more realistic examples to the RFC. >=20 > > > They might > > > still be ok if they are extremely simple > > > > And now you can understand why they WERE just simple classes (short = classes). So, you can see why I originally bundled them together because= of this EXACT argument. :sigh: >=20 > The arguments above are not limited to complex classes. Simple classes > would apply to. >=20 > * The :> operator is still something new that I don't believe needs to= be there. > * Shadowing simple classes can still cause incompatibilities between > constructors. > * The LSP issue applies. > * As soon as we try to add support for complex classes, we'll run into > the same questions again. Thinking ahead is always worth it, to > prevent us from running into issues later. Doesn't mean everything > needs to land at the same time ofc. >=20 > Ilija >=20 =E2=80=94 Rob --920afeb387aa4dfb941e0ae5827c6b8c Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable
On Thu, Mar 13,= 2025, at 21:41, Ilija Tovilo wrote:
Hi Rob

On Thu, Ma= r 13, 2025 at 1:57=E2=80=AFPM Rob Landers <rob@bottled.codes> wrote:
>
=
> > the proposal is
> > currently quite c= omplex.
>
> Most of this is just descr= ibing how classes work already and going in-depth on where there may be = confusion -- there are no significant changes to how classes actually wo= rk. The actual changes to the engine are basically just visibility rules= , some syntax changes (to allow nesting `class` inside another class), a= nd handling the new operator. The hard part is explaining how classes wo= rk, because they don't really have a defined behavior. In other words, I= cannot just say "the way this works doesn't change anything."
=

Well, you cut out the examples I gave:

Ahh, ok. FWIW, I only cut out your exa= mples to shorten the email. But I see what you mean here. I think I can = word the RFC in such a way as to make it so I don't have to specify how = classes work along with how inner classes work... I'll have to think abo= ut it. Maybe I can just explain more about how it actually works? I'm no= t sure. I'll probably look at the enums RFC to get a better idea of how = to do this.

For example, I can explain that= an inner class is basically a class with a special name and special sco= pes attached. Then I can simply explain how these special scopes interac= t with the rest of the language. Then I can get around defining class-li= ke behaviors, and it may even be easier to reason about.

<= /div>
> It h= as a custom operator, it deals with shadowing, LSP, runtime resolution a= nd more, in addition to visibility which is the actual goal. Those are u= nrelated to existing behavior, they are introduced in the proposal.
<= /div>

* The custom operator and runtime resolution is= not something that
technically needs to be there. The nam= e of class Foo { class Bar {} }
could simply be Foo\Bar. I= 've mentioned before that this does not work
with PSR-4, b= ut there's no reason it can't work with autoloading at
all= . An extended autoloading spec could recursively search
Fo= o/Bar/Baz.php, then Foo/Bar.php, and so forth. Given this happens
only when loading the class, and nesting would usually be limite= d in
quantity and amount, that seems like a reasonable sol= ution, and
Composers optimized autoloader could avoid it e= ntirely. This would
also solve the same issue for sealed c= lasses, assuming they're named
in a similar fashion.

I disagree completely. If you recal= l, last year, I tried to pass function autoloading (which would have hel= ped with records) and people really, really, really didn't want to have = to change how autoloading worked (and I'm not talking about the list -- = I mean projects). So, suggesting a change to autoloading is probably a n= on-starter.

That being said, I don't hate i= t either.


* By shadowing I referred to static:>MyC= lass. This makes it more
modular, but it's also another la= yer of complexity and indirection.
You haven't really prov= ided a reasoning and examples of how this could
be used. I= t's also not clear if this can be prevented. The inner class
can be marked as final, but that wouldn't stop you from shadowing the=
inner class without extending it. Similarly, static:>M= yClass would
have no type guarantees, given that it can be= shadowed by any classes,
not just classes that are compat= ible.

class Outer {
 &nb= sp;  protected class Inner {
    =     public static function innerTest() {}
&= nbsp;   }

    publ= ic static function outerTest() {
    &= nbsp;   static:>Inner::innerTest();
 &nb= sp;  }
}

class OuterV2 e= xtends Outer {
    protected class Inner {}= // This breaks outerTest()
}
<= br>
`static` is expressly forbidden with inner classes after T= im pointed out that it basically breaks LSP. It doesn't, technically, bu= t it makes it hard to reason about for the exact example you gave.

One thing worth pointing out here, though, is th= at in your example you have Outer:>Inner and OuterV2:>Inner. These= are two completely distinct classes. They are not related to each other= at all. 


* LSP issues have been mentioned befor= e. You can use self:>Inner in
method signatures even if= the inner classes are private. The method
could be called= from sub-classes, but they couldn't ever override it,
sin= ce there's no way to satisfy the parent signature. This makes it
implicitly final. Not technically a problem, just odd.
<= /blockquote>

This is not the case any more. If you ha= ve a private inner class, you cannot use it as a type declaration on a p= rotected/public method, property, or static member. A private class can = still be returned and passed around once instantiated, and it can implem= ent an interface/base class (or not). This is very similar to C# or Java= .

For example, this is ok:
interface Shape {}

class Shape= Factory {
  private class Rect implements Shape {}

  public function makeRect()): Shape { r= eturn new ShapeFactory:>Rect(); }
}

<= /div>
But this is not:

class ShapeFacto= ry {
  private class Rect {}

=
  // Private inner class ShapeFactory::Rect cannot be use= d as a return type for public methods
  public functi= on makeRect()): ShapeFactory:>Rect { return new ShapeFactory:>Rect= (); }
}

As mentioned, maybe there are additional use-c= ases this complexity can
cover, but the RFC doesn't give m= any examples. If the primary reason
for this complexity is= just visibility, then I don't think this is the
simplest = and best way in which that goal could be achieved.

I'll add some more realistic examples to the RFC.

<= br>
> > They might
> > still be ok i= f they are extremely simple
>
> And no= w you can understand why they WERE just simple classes (short classes). = So, you can see why I originally bundled them together because of this E= XACT argument. :sigh:

The arguments above a= re not limited to complex classes. Simple classes
would ap= ply to.




* = The :> operator is still something new that I don't believe needs to = be there.
* Shadowing simple classes can still cause incom= patibilities between
constructors.
* The LSP= issue applies.
* As soon as we try to add support for com= plex classes, we'll run into
the same questions again. Thi= nking ahead is always worth it, to
prevent us from running= into issues later. Doesn't mean everything
needs to land = at the same time ofc.

Ilija
<= br>

=E2=80=94 = Rob
--920afeb387aa4dfb941e0ae5827c6b8c--