On Thu, Mar 20, 2025, 18:58 Rob Landers <rob@bottled.codes> wrote: > > > On Thu, Mar 20, 2025, at 16:41, Bob Weinand wrote: > > > Am 20.03.2025 um 16:28 schrieb Rob Landers <rob@bottled.codes>: > > > > > On Thu, Mar 20, 2025, at 16:12, Bob Weinand wrote: > > > Am 20.03.2025 um 15:51 schrieb Rob Landers <rob@bottled.codes>: > > > > > On Wed, Mar 19, 2025, at 21:09, Bob Weinand wrote: > > > On 19.3.2025 16:04:06, Rob Landers wrote: > > On Tue, Mar 18, 2025, at 03:37, Bob Weinand wrote: > > Okay, I see the point with LSP. I'm not sure whether we need to preserve > LSP for that specific scenario, but neither can I say that we should ignore > it. > > (Effectively implementing LSP would mean that there's an implicit > interface matching all public method signatures of the parent class, for > child classes - which is doable, but possibly too much for the initial RFC.) > I would however ask, should we not implement LSP compatible inner classes, > to enforce that no child class may name a class the same than any > non-private inner class declared by any of its parents, until we resolve > this question (in possibly a future version of PHP). > I do not think we should bar ourselves from allowing this in the future. > > > I'm not sure I understand what you are asking. But I think you are saying > the following should be illegal? > > class ParentOuter { > class ParentInner {} > } > > class ChildOuter extends ParentOuter { > class ParentInner {} // not allowed > } > > Precisely. > > And not pretending starts with using a different symbol than a backslash. > > > I have been thinking about this for a couple of days now... When thinking > through the ramifications of my decision to use :> over ::, this will also > affect generics, most likely -- whenever that happens. This is because if > this RFC passes, generics will want to be consistent with whatever exists > currently. > > If we were to use :>, then generics would probably look something like > this to be consistent: > > class Box<T> { > public function add(self:>T $item) {} > } > > The same thing would also probably apply to :: > > class Box<T> { > public function add(self::T $item) {} > } > > With `\`, it nearly follows exactly what you would expect-ish: > > use \Box\T as T; > > class Box<T> { > public function add(T $item) {} > > // or without use > public function add(Box\T $item) {} > } > > With `\`, we can also just automatically check the current class as part > of namespace resolution when compiling: > > class Box<T> { > public function add(T $item) {} > } > > This would also make it easier to user inner classes: > > class Outer { > public class Inner {} > public function foo(Inner $bar) {} > } > > The other syntax options do not allow this; at least, I don't think so. > I'm very heavily leaning towards `\` as the separator. > > — Rob > > I'm failing to understand why you'd think this would be related at all? > > If we get generics, > class Box<T> { > public function add(T $item) {} > } > > would just work, without any use or such. There will not be a symbol > Box::T or Box\T, just all mentions of T within the Box class will be taken > as "this is the generic placeholder" and the compiler takes care. > It's not like that T will be directly accessible from outside of the class > or actually a proper type, unlike inner classes. > > A generic is not an inner class nor will it look like it. Also, there's no > accessing of a parents generic - you write class Child<T> extends > ParentClass<T> - or something along these lines, getting the T available > for your class. > > Bob > > > Yes, that is the question right? It might not affect anything there, but > there would probably be an argument to keep it consistent with inner > classes. In PHP, a class is a type; thus an inner class is an inner type, > and generic types are also an inner type that only exist in the scope of > their enclosing class, just like private inner classes. > > If my logic is incorrect, let me know. > > — Rob > > > The difference is that inner classes are a backed type; there's actually a > class/interface/enum/... behind it. > > Generic placeholders are placeholders for another type, which is strictly > local to the compilation of the class. > > Bob > > > If that were the case, then this would be an error: > > function add(T $item) { > if ($item instanceof T) {} > } > > because T isn't a backed type. I don't think they are the same thing, but > during runtime, T is most definitely a backed-type; not an empty box. You > still have to refer to T in some way, and I'm of the opinion that self:>T > is not ideal (I think we can probably all agree on that?). Even in inner > classes, it seems to make more sense to just treat it like you would any > other type in the current namespace. > > > It's a *placeholder* for a backed type. > At runtime (or during monomorphization), the T will be replaced by the > actual type. > > Whatever name T refers to is the actual backed type. > > Bob > > > Right, my point is that you have to refer to T in the first place. If we > use `\`, then T as-is is fine, because there is a clear resolution to what > T you are referring to. If we use :> or ::, then when you write Box<T>, are > you referring to T, as in the generic T; or T as in the class T in the same > namespace. This is clear when we use "T" literally, but quickly becomes not > so clear when using something else: > > class Item {} > > class Box<Item> { > public function add(Item $item) {} > } > > This is exactly why I said this matters! In the case above with the > current :> syntax, we should probably use: > > class Box<Item> { > public function add(self:>Item $item) { > } > > to explicitly define that we want the inner type "Item." To use the first > example, you have to define inner types to allow that, but we are currently > defining inner types in this RFC. I'd personally rather use the first > example when it comes to generics, which means we need to define that now. > In other words, I think you are on team "\" without realizing you are on > team "\". > > — Rob >
Hello all, amazing effort as always. My 2¢: RL usecase I immediately would adopt this for - is building IDE-supported complex DTOs. This means autocompletion, refactor rename, inspections for undefined keys, etc for the price of using no language ”hacks”. A real life example - json response from UPS shipping API - it has 200++ keys in up to 10+ levels! The structure keeps changing too, albeit in glacial speeds, but having it in a structured definition allows one to not loose ones head when providing continuous support. However: there's a *bunch* of multilevel ”leaf nodes” which are reused dozends of times, for example MonetaryAmount - it has the numeric value, but also a Currency as a property. If extending a class does not inherit the public/protected nested classes, we are forced to define the structure of MonetaryAmount in the ”regular” fashion as separate classes. Not to mention this non-inheritance is counter-inuitive IMHO. Also, IMHO ”nested classes” is a better name. Good luck! >