On Thu, Mar 6, 2025, at 23:31, Tim Düsterhus wrote: > Hi > > On 3/6/25 23:05, Rob Landers wrote: > >> > >> Closure::fromCallable('Outer::Inner::method'); > > > > You end up with: > > > > object(Closure)#1 (1) { > > ["function"]=> > > string(20) "Outer::Inner::method" > > } > > Okay, does calling the closure work and correctly call the `method` on > the inner class?
Yep, it works! > The question was intended to make sure that the > implementation for callables uses the correct `::` to split. This is largely why nesting can only be one level deep. Multiple levels end up with far more intrusive changes to the engine and IMHO, severely impact readability. I'll update the RFC on this. > Here's > another one that might be interesting: > > Closure::fromCallable(["Outer::Inner", "method"]); > Closure::fromCallable(["Outer", "Inner::method"]); object(Closure)#1 (1) { ["function"]=> string(20) "Outer::Inner::method" } and Uncaught TypeError: Failed to create closure from callable: class "Inner" not found I believe this is correct? I haven't seen this form since my 5.3 days, so I am not sure if it is correct or a bug. > > >> constant('Outer::Inner'); > > > > This returns: > > > > string(12) "Outer::Inner" > > Okay, so this behaves as a constant containing the class name. I assume > it's with the full namespace if the outer class is namespaced? I'm not > sure if I want this to work like this (i.e. whether this should be an > error). That's correct, it contains the fully qualified name. I may have to think on this, but I concur that this might be better as an error. > > >> $inner = 'Inner'; > >> Outer::{$inner}; > > > > This does nothing (but resolves to "Outer::Inner") > > It's consistent with `constant()` and that's good. > > >> … and any other meta-programming functionality working on class > >> constants or static methods. > >> > >> Also, what will happen for: > >> > >> class P { > >> class Inner { } > >> } > >> > >> class C extends P { > >> const Inner = 'x'; > >> } > >> > >> (and vice versa) > > > > This is a really good one. If for no other reason than I did a really poor > > job of explaining resolution(?) in the RFC. `P::Inner` belongs to `P`, not > > to `C`, so you can do `new C::Inner()` and it will resolve to `P::Inner()`: > > I don't think the RFC explains “resolution” at all. That's why I'm > asking with those specific “edge-casey” examples, so that the RFC > explicitly spells out the behavior. This is not something that should be > “implementation defined”, but something where an explicit design > decision has been made. 100% agree. I'll update the RFC after thinking through some points in your email. > > I also don't understand why `new C::Inner()` (w|sh)ould resolve to > `P::Inner()`. I think my expectation of the code snippet above would be > that it is an error. 100% agree. It actually should be an error, according to the RFC. To be honest, until I wrote my response to you, I forgot you could redefine constants. So, I'll have to take a look at this. > > Likewise, LSP being ignored for inner classes raises an interesting > question about the behavior of: > > class P { > class Inner { > public function __construct(public string $foo) { } > } > > public static function create() { > return new static::Inner('x'); > } > } > > class C extends P { > class Inner { > public function __construct(public int $bar) { } > } > } > > What happens if I call `C::create()`? This should also be specified in > the RFC (and tested with a .phpt test). I'll add a test for it (and detail in the RFC), but it will instantiate a C::Inner. This doesn't violate LSP, though, because C::Inner is distinct from P::Inner -- they are separate classes and completely unrelated. This is just like using static properties or constants. To explain further, static::Inner is about the same as doing `new $class::Inner;`-ish. You are not referencing the same class depending on where you are calling the function from. The fact that `static` is a shorthand for this is what makes it weird. I wouldn't be opposed to making `new static::Inner` (or even `new self::Inner`) an error, since it is confusing; it would force people to spell out the class name. However, I think it is useful when the different inner classes implement the same interfaces (explicitly or implicitly). I'd be interested to hear thoughts on this, but I'm now wondering if static:: allows casual violations of LSP, in general. 🤔 > > > As with other static things in PHP, you can do some really strange things > > like this. This is similar to how you can redefine static constants in > > subclasses. > > We should remove the number of strange things, not add to them. Truth. Thank you for these questions Tim! — Rob