On Thu, Mar 13, 2025, at 21:41, Ilija Tovilo wrote:
> Hi Rob
> 
> On Thu, Mar 13, 2025 at 1:57 PM Rob Landers <rob@bottled.codes> wrote:
> >
> > > the proposal is
> > > currently quite complex.
> >
> > Most of this is just describing how classes work already and going in-depth 
> > on where there may be confusion -- there are no significant changes to how 
> > classes actually work. The actual changes to the engine are basically just 
> > visibility rules, some syntax changes (to allow nesting `class` inside 
> > another class), and handling the new operator. The hard part is explaining 
> > how classes work, 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 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 make 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 more 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 how these 
special scopes interact with the rest of the language. Then I can get around 
defining class-like behaviors, and it may even be easier to reason about.

> > It has a custom operator, it deals with shadowing, LSP, runtime resolution 
> > and more, in addition to visibility which is the actual goal. Those are 
> > unrelated to existing behavior, they are introduced in the proposal.
> 
> * 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 function 
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 change to 
autoloading is probably a non-starter.

That being said, I don't hate it either.

> 
> * 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.
> 
> class Outer {
>     protected class Inner {
>         public static function innerTest() {}
>     }
> 
>     public static function outerTest() {
>         static:>Inner::innerTest();
>     }
> }
> 
> 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 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 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 cannot 
use it as a type declaration on a protected/public method, property, or static 
member. A private class can still be returned and passed around 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 type for 
public methods
  public function makeRect()): ShapeFactory:>Rect { return new 
ShapeFactory:>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.

> 
> > > 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:
> 
> The arguments above are not limited to complex classes. Simple classes
> would apply to.



> 
> * 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.
> 
> Ilija
> 

— Rob

Reply via email to