Hey Tim, I think you jumped to a conclusion without exploring the space
of possibilities fully:
On 6.2.2026 16:12:05, Tim Düsterhus wrote:
With regard to your comment on the PR and the “Open Issue” listed in
the RFC about the name for the `$this` parameter in the resulting
Closure we have two options to offer:
1. `ClassName::methodName(this: ?)` with the parameter being called
`$__this`.
2. `ClassName::methodName($this: ?)` with the parameter being called
`$this`.
Notably `ClassName::methodName(this: ?)` with the parameter being
called `$this` is not an option, because it would result in
inconsistent behavior:
`ClassName::methodName(this: ?)` semantically relies on `$this` never
being a valid parameter name, such that `this: ?` can unambiguously
refer to the “implicit `$this` value” for a method call. However when
the parameter in the generated Closure would be called `$this`, there
is some ambiguity for cases like:
$c = DateTime::format(this: ?, format: ?);
$c2 = $c(this: ?, format: 'c');
Is `this: ?` in the definition of `$c2` referring to the `$this`
parameter of the generated Closure or is it an attempt to partially
apply the `Closure` object for the `Closure::__invoke()` method that
is referenced by `$c`?
Similarly allowing `this: $object` with a concrete value is explicitly
disallowed, because of possible ambiguity with regard to inheritance:
class P { public function m() { echo "P"; } }
class C extends P { public function m() { echo "C"; } }
// is this calling P::m() or is this calling C::m()?
P::m(this: new C());
This would however prevent calling a partially applied instance method
with named arguments:
$c = DateTime::format(this: ?, format: ?);
// Disallowed, because this: must be partially applied.
$c(this: new DateTime(), format: 'c');
If the syntax to define the PFA was using `C::m($this: ?)`, `$this` in
the resulting Closure would just work like any other parameter.
1. `ClassName::methodName(this: ?)` with the parameter being called
`$__this`.
2. `ClassName::methodName($this: ?)` with the parameter being called
`$this`.
Allowing P::m(this: $object) with a concrete value should behave
identically to P::m(this: ?)($object), which in turn should behave
identically to $object->m() if called outside of C (or its children),
otherwise equivalently to `private function forwardM() { return P::m();
} $object->forwardM();` (i.e. a (grand)parent class method can always be
called).
This approach is consistent with how method calling works in child classes.
More normatively for any class P and method m, P::m(this: ?) needs to
store the class where the PFA closure is created as called_scope on the
Closure when that class is instanceof P unless P::m is abstract, so that
any call to the resulting closure is checked against that called_scope:
If the $this object the Closure is ultimately called with is instanceof
the called_scope of the Closure, the specific given method must be
called (i.e. specifically P::m()), otherwise the object is merely
checked for being instanceof P and the method m on the $this object is
called directly.
Defining it this way preserves LSP guarantees, with maximum flexibility
- making it for example perfectly possible to call
array_map(ParentClass::someValue(this: ?), $objectsArray) without being
surprised that it subtly actually calls the child method someValue on
objects which are instanceof the containing class.
The neat benefit is that any $obj->method() call is now generalized (on
the surface of the language semantics, obviously not implementation
wise) as {get_class($obj)}::method(this: $obj), which makes the this-PFA
a trivial extension of just having ? for the this parameter.
It also obsoletes any concerns about how the this parameter ends up
called in the resulting PFA - because it's just a "normal" parameter
then, from the perspective of the caller.
The only small caveat is Closure::__invoke(this: ?), which literally is
just sort of an identity function and thus useless. To solve that, we
should just decide to have an explicit this parameter on Closures take
precendence. (Ultimately __invoke is more of an implementation detail of
Closure, than anything else).
Also of note is that having a proper implicit $this parameter on methods
must not have a position (as in positional parameters). Otherwise
conflicts arise with e.g. the implicit $this forwarding in parent::m()
syntax in class scope. (obviously, once you create a PFA P::m(this: ?,
?) the first positional parameter becomes the $this parameter and the
original first parameter is now the second one.)
To summarize: Please introduce a first-class implicit $this parameter on
any non-static method call, and have PFA work naturally with it, without
doing contortions in language semantics / introducing a very
PFA-specific syntax.
Thanks,
Bob
P.s.: You should possibly add to future scope allowing
object::someMethod(this: ?) or class::someMethod(this: ?) to allow
proper duck typing without having to know the actual object behind.
That's something I see people asking for, too.