Hi internals,

It's been a few years since I originally brought up the named parameters
RFC: https://wiki.php.net/rfc/named_params

This topic has recently come up as part of
https://externals.io/message/109220 again, in particular with the
observation that the combination of constructor parameter promotion and
named parameters will give us, as a side-effect, an ergonomic object
initialization syntax that is well-integrated with the remainder of the
language.

I've reimplemented basic named param support for master in
https://github.com/php/php-src/pull/5357, but before going any further
here, I want to restart discussion on the various open issues that the
named parameter concept in PHP has. I think there's two primary issues:

## LSP checks for parameter names

Parameter names currently have no particular significance in PHP, only
their position in the signature is important. If named parameters are
introduced (in a way that does not require opt-in at the declaration-site),
then parameter names become significant. This means that changing a
parameter name during inheritance constitutes an LSP violation (for
non-constructor methods). There are a number of ways in which this issue
may be approached:

1. Silently ignore the problem. No diagnostic is issued when a parameter
name is changed during inheritance. An error exception will be thrown when
trying to use the (now) unknown parameter name.

2. Throw a notice if a parameter name is changed. While LSP violations are
normally fatal errors (in PHP 8), we could use a lower-severity diagnostic
for this case, that allows code to still run, but makes developers aware of
the problem. (It should be noted that automatic fixup of parameter names
using tooling should be fairly easy to implement.)

3. Allow using parameter names from the parent method, even if they have
been renamed. This makes things "just work", but seems fairly magic, and
has edge cases like a signature foo($a, $b) being changed to foo($b, $a),
where it's not possible to implicitly support both at the same time.

4. Make named-parameter opt-in in some fashion, so that parameter names
only need to be preserved for methods that have the opt-in marker. I'm not
a fan of this, as it greatly diminishes the practical usefulness of named
parameters.

## Internal functions

There are two problems when it comes to named parameters and internal
functions. The first is that the internal parameter names do not always
match the parameter names in the documentation. This is nothing we can't
solve (in a hopefully mostly automated way), but is work that needs to be
done.

The larger problem is that internal functions don't have a well-defined
concept of parameter default value. Parameter defaults are implicit in C
code, and sometimes based on argument count checks. When named parameters
are involved, one generally cannot assume that if a later (optional)
parameter is passed, all earlier (optional) parameters are passed as well.

While it is possible (but rather challenging) to make things work mostly
transparently with current ZPP, some functions do need to adjusted to
support named parameters, and may cause misbehavior/crashes if they are
not. As a rule of thumb, functions that make non-trivial use of
ZEND_NUM_ARGS() are affected. This is something we can address for
functions in bundled extensions, but may also affect 3rd-party extensions.

I think that ideally, we'd deal with internal functions the same way we do
for userland functions, by relying on default values that are part of the
function declaration. As part of the PHP stub work, we now have information
on default values in stub files, but this information is currently not
available to the engine.

Máté Kocsis has been working on getting this information exposed in
Reflection, see https://github.com/php/php-src/pull/5353. In the current
form, accessing these defaults is fairly inefficient, but maybe with some
caching, we could base named parameters on this information:

If there are no "gaps" in the passed parameters, then named parameters will
always work. If an optional parameter is not specified, but a later
parameter is, then we will fetch the default value for that parameter, and
pass it instead. If the parameter is optional, but has no well-defined
default (UNKNOWN in stubs), then the call is not permitted (Error: "Cannot
skip parameter $foo with unknown default value", or similar.)

Any thoughts on these issues, or named parameters in general would be
welcome.

Regards,
Nikita

Reply via email to