# RFC idea: using the void type to control maximum arity of user-defined functions

- Thread: https://externals.io/message/122928
- Original archive: https://news-web.php.net/php.internals/122928
- Started by Pablo Rauzy on 2024-04-04T12:41:52+00:00
- Messages: 21
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #122928 — Pablo Rauzy — 2024-04-04T12:41:52+00:00

URL: https://externals.io/message/122928#122928

Hello all,

First, I'm new here, so let me start by warmly thanking everyone 
involved in PHP development. I've started programming 22 years using it 
as my first programming language, and still enjoy using it a lot :).

I've read the rfc:howto and the first step is to get feedback from this 
mailing list about the intended proposal, so here is a short description 
of what I have in mind:

Currently PHP does not warn when user-defined functions are called with 
too many arguments. Changing this could break a lot of existing code, 
but it would also be a very good verification to catch potential bugs.

I have an idea allowing this feature without breaking any existing code: 
using the void keyword in a function arguments list to strictly limit 
the number of arguments a user-defined function can receive, stopping it 
on the void, which could only be used once and in the last position.

Currently, void is a return-only type so it isn't used in arguments list 
in existing code, so I believe the new behavior would only concern newly 
written code that explicitly wants to take advantage of this feature.

A few examples using functions (the same would apply to class methods, 
callbacks, closures, etc.) :

     function foo (void) {}
     foo(42); // warning: foo() expects exactly 0 arguments, 1 given

     function bar ($a, $b, void) {}
     bar(1, 2, 3); // warning: bar() expects exactly 2 arguments, 3 given

     function baz ($a, $b=null, void) {}
     baz(1, 2, 3); // warning: baz() expects at most 2 arguments, 3 given

I have no knowledge of the PHP internals: would that be feasible without 
breaking things? And, as importantly, would it be a welcome change?

Cheers,

-- 
Pablo

---

## #122929 — Mark Trapp — 2024-04-04T13:03:17+00:00

URL: https://externals.io/message/122928#122929 · In reply to: #122928

>
> Hello all,
>
> First, I'm new here, so let me start by warmly thanking everyone
> involved in PHP development. I've started programming 22 years using it
> as my first programming language, and still enjoy using it a lot :).
>
> I've read the rfc:howto and the first step is to get feedback from this
> mailing list about the intended proposal, so here is a short description
> of what I have in mind:
>
> Currently PHP does not warn when user-defined functions are called with
> too many arguments. Changing this could break a lot of existing code,
> but it would also be a very good verification to catch potential bugs.
>
> I have an idea allowing this feature without breaking any existing code:
> using the void keyword in a function arguments list to strictly limit
> the number of arguments a user-defined function can receive, stopping it
> on the void, which could only be used once and in the last position.
>
> Currently, void is a return-only type so it isn't used in arguments list
> in existing code, so I believe the new behavior would only concern newly
> written code that explicitly wants to take advantage of this feature.
>
> A few examples using functions (the same would apply to class methods,
> callbacks, closures, etc.) :
>
>      function foo (void) {}
>      foo(42); // warning: foo() expects exactly 0 arguments, 1 given
>
>      function bar ($a, $b, void) {}
>      bar(1, 2, 3); // warning: bar() expects exactly 2 arguments, 3 given
>
>      function baz ($a, $b=null, void) {}
>      baz(1, 2, 3); // warning: baz() expects at most 2 arguments, 3 given
>
> I have no knowledge of the PHP internals: would that be feasible without
> breaking things? And, as importantly, would it be a welcome change?
>
> Cheers,
>
> --
> Pablo

Hi Pablo,

I like this concept, but instead of introducing a new syntax, have you
considered leveraging attributes in the same way that PHP 8.3
introduced #[Override]?

#[Nonvariadic]
function foo () {}
foo(42); // warning: foo() expects exactly 0 arguments, 1 given

I think the intent would be clearer and it would avoid introducing a new syntax.

- Mark

---

## #122930 — Kamil Tekiela — 2024-04-04T13:10:48+00:00

URL: https://externals.io/message/122928#122930 · In reply to: #122928

Hi Pablo,

Interesting proposal, but immediately I have two questions:
1. Why is passing extra arguments a problem at all? Is generating a
warning a good idea? If you would like to introduce a new syntax to
enforce non-variadic functions, shouldn't it be a runtime error then?
Generating a warning makes it look like the new syntax still allows
for variadic arguments, but passing the extra arguments now is somehow
a bad thing to do.
2. How does this affect `func_get_args()`? Will the function stop
working or continue working as before?

Regards,
Kamil

---

## #122931 — Pablo Rauzy — 2024-04-04T13:19:51+00:00

URL: https://externals.io/message/122928#122931 · In reply to: #122930

Hello Kamil,

Le 04/04/2024 à 15:10, Kamil Tekiela a écrit :
> Interesting proposal, but immediately I have two questions:
> 1. Why is passing extra arguments a problem at all? Is generating a
> warning a good idea? If you would like to introduce a new syntax to
> enforce non-variadic functions, shouldn't it be a runtime error then?
> Generating a warning makes it look like the new syntax still allows
> for variadic arguments, but passing the extra arguments now is somehow
> a bad thing to do.
> 2. How does this affect `func_get_args()`? Will the function stop
> working or continue working as before?

1. Indeed, if implemented it should be an error rather than a warning, 
you're right.

2. I don't see why `func_get_args()` should work differently, but that 
maybe my lack of knowledge of the internals of PHP. In any case I think 
it would be better if it worked the same.

Regards,

-- 
Pablo

---

## #122932 — Pablo Rauzy — 2024-04-04T13:26:50+00:00

URL: https://externals.io/message/122928#122932 · In reply to: #122929

Le 04/04/2024 à 15:03, Mark Trapp a écrit :
> 
> > […]
> Hi Pablo,
>
> I like this concept, but instead of introducing a new syntax, have you
> considered leveraging attributes in the same way that PHP 8.3
> introduced #[Override]?
>
> #[Nonvariadic]
> function foo () {}
> foo(42); // warning: foo() expects exactly 0 arguments, 1 given
>
> I think the intent would be clearer and it would avoid introducing a new syntax.

Hello Mark,

I never used attributes so I didn't even think of it, but that would 
work too, yes :).

I would personally prefer the void syntax, but that's really because 
attributes look odd to me due to lack of habits (and also because it 
avoids an additional line of code… which is probably not a solid enough 
reason when it comes to language design decisions).

Regards,

-- 
Pablo

---

## #122933 — Claude Pache — 2024-04-04T14:29:18+00:00

URL: https://externals.io/message/122928#122933 · In reply to: #122929

> Le 4 avr. 2024 à 15:03, Mark Trapp <mark@itafroma.com> a écrit :
> 
> 
> 
> I like this concept, but instead of introducing a new syntax, have you
> considered leveraging attributes in the same way that PHP 8.3
> introduced #[Override]?
> 
> #[Nonvariadic]
> function foo () {}
> foo(42); // warning: foo() expects exactly 0 arguments, 1 given
> 
> I think the intent would be clearer and it would avoid introducing a new syntax.
> 

Indeed, except that I think it should be better to have *by default* the same behaviour between built-in functions and user-defined functions:

```php
function foo() { }

foo(42);
// in PHP 8.x: Deprecated: foo() expects expects exactly 0 arguments, 1 given
// in PHP 9: Fatal error: foo() expects expects exactly 0 arguments, 1 given
```

and, in case you want a variadic function:

```php
#[Variadic]
function foo() { }
```

Alternatively, we don’t really need to introduce a new attribute, because one can just write:

```php
function foo(...$args) { }
```


—Claude

---

## #122934 — Pablo Rauzy — 2024-04-04T14:36:39+00:00

URL: https://externals.io/message/122928#122934 · In reply to: #122933

Le 04/04/2024 à 16:29, Claude Pache a écrit :
> Le 4 avr. 2024 à 15:03, Mark Trapp <mark@itafroma.com> a écrit :
> > […]
> Indeed, except that I think it should be better to have *by default* the same behaviour between built-in functions and user-defined functions:
>
> ```php
> function foo() { }
>
> foo(42);
> // in PHP 8.x: Deprecated: foo() expects expects exactly 0 arguments, 1 given
> // in PHP 9: Fatal error: foo() expects expects exactly 0 arguments, 1 given
> ```
>
> and, in case you want a variadic function:
>
> ```php
> #[Variadic]
> function foo() { }
> ```
>
> Alternatively, we don’t really need to introduce a new attribute, because one can just write:
>
> ```php
> function foo(...$args) { }
> ```
>
>
> —Claude

Hello Claude,

I strongly agree in theory, but this could break existing code, and 
moreover such a proposal was already rejected:
https://wiki.php.net/rfc/strict_argcount

Regards,

-- 
Pablo

---

## #122935 — Claude Pache — 2024-04-04T14:39:30+00:00

URL: https://externals.io/message/122928#122935 · In reply to: #122930

> Le 4 avr. 2024 à 15:10, Kamil Tekiela <tekiela246@gmail.com> a écrit :
> 
> Why is passing extra arguments a problem at all?

In a parallel perfect and boring world, it is not a problem. In our world, disallowing extra arguments have two advantages:

* catching bugs;

* allowing to add new optional parameters to an existing function without fearing to break existing code.

—Claude

---

## #122936 — Vinicius Dias — 2024-04-04T15:40:59+00:00

URL: https://externals.io/message/122928#122936 · In reply to: #122932

> I like this concept, but instead of introducing a new syntax, have you
> considered leveraging attributes in the same way that PHP 8.3
> introduced #[Override]?
>
> #[Nonvariadic]
> function foo () {}
> foo(42); // warning: foo() expects exactly 0 arguments, 1 given
>
> I think the intent would be clearer and it would avoid introducing a new syntax.
>

I agree that using an attribute would be better for this case.


> I would personally prefer the void syntax, but that's really because attributes look odd to me due to lack of habits (and also because it avoids an additional line of code… which is probably not a solid enough reason when it comes to language design decisions).


As a reader of the code, having the attribute is much more clear to me
than having something that looks like an argument.
Also, with attributes, the code is backwards-compatible, i.e., I can
add the attribute to my code without any errors or warnings even if I
don't use the latest version of PHP, and even in those cases, IDEs
could still help me.

---

## #122938 — tim@bastelstu.be — 2024-04-04T15:49:03+00:00

URL: https://externals.io/message/122928#122938 · In reply to: #122936

Hi


> > […]
> 
> I agree that using an attribute would be better for this case.

I disagree with an attribute being the right choice here. In contrast to 
#[\Override], this proposal changes the behavior for the *caller* of the 
function and likely also requires adjustments in child classes when 
newly introduced in a parent class or interface.

Thus it is part of the function's API and thus should be part of the 
regular signature and not the attribute-based metadata.

Best regards
Tim Düsterhus

---

## #122939 — tim@bastelstu.be — 2024-04-04T15:57:01+00:00

URL: https://externals.io/message/122928#122939 · In reply to: #122934

Hi


> I strongly agree in theory, but this could break existing code, and
> moreover such a proposal was already rejected:
> https://wiki.php.net/rfc/strict_argcount

The RFC is 9 years old by now. My gut feeling is be that using an actual 
variadic parameter for functions that are variadic is what people do, 
because it makes the function signature much clearer. Actually variadic 
parameters are available since PHP 5.6, which at the time of the 
previous RFC was the newest version. Since then we had two major 
releases, one of which (7.x) is already out of support.

I think it would be reasonable to consider deprecating passing extra 
arguments to a non-variadic function.

Best regards
Tim Düsterhus

---

## #122940 — Larry Garfield — 2024-04-04T16:19:40+00:00

URL: https://externals.io/message/122928#122940 · In reply to: #122938

> Hi
>
> 
> > […]
>
> I disagree with an attribute being the right choice here. In contrast to 
> #[\Override], this proposal changes the behavior for the *caller* of the 
> function and likely also requires adjustments in child classes when 
> newly introduced in a parent class or interface.
>
> Thus it is part of the function's API and thus should be part of the 
> regular signature and not the attribute-based metadata.
>
> Best regards
> Tim Düsterhus

I would think following the pattern of dynamic properties would make the most sense.  Require an opt-in via attribute, with deprecation period.  And possibly just remove the functionality at some point in the future.

The core issue here IMO isn't the behavior itself, but the inconsistency.  C-implemented functions reject extra args.  PHP-implemented functions ignore them (modulo variadics).  This is... a total mess that has bitten me more than I want to admit.  Normalizing it one way or the other would be a win.

--Larry Garfield

---

## #122942 — Pablo Rauzy — 2024-04-04T21:00:16+00:00

URL: https://externals.io/message/122928#122942 · In reply to: #122939

Le 04/04/2024 à 17:57, Tim Düsterhus a écrit :
> Hi
>
> 
> > […]
>
> The RFC is 9 years old by now. My gut feeling is be that using an 
> actual variadic parameter for functions that are variadic is what 
> people do, because it makes the function signature much clearer. 
> Actually variadic parameters are available since PHP 5.6, which at the 
> time of the previous RFC was the newest version. Since then we had two 
> major releases, one of which (7.x) is already out of support.
>
> I think it would be reasonable to consider deprecating passing extra 
> arguments to a non-variadic function.

Well, if there is a consensus to agree on that, it would be even better!

Cheers,

-- 
Pablo

---

## #122944 — Bilge — 2024-04-04T21:12:27+00:00

URL: https://externals.io/message/122928#122944 · In reply to: #122939

> I think it would be reasonable to consider deprecating passing extra 
> arguments to a non-variadic function.

This seems like the only reasonable thing to do, to me. If this were the 
case, there should be no need for any new syntax, since we could simply 
deny passing extra arguments in the next major, no?

Bilge

---

## #122948 — Ilija Tovilo — 2024-04-04T21:27:45+00:00

URL: https://externals.io/message/122928#122948 · In reply to: #122939

>
> 
> > […]
>
> The RFC is 9 years old by now. My gut feeling is be that using an actual
> variadic parameter for functions that are variadic is what people do,
> because it makes the function signature much clearer. Actually variadic
> parameters are available since PHP 5.6, which at the time of the
> previous RFC was the newest version. Since then we had two major
> releases, one of which (7.x) is already out of support.
>
> I think it would be reasonable to consider deprecating passing extra
> arguments to a non-variadic function.

IIRC one of the bigger downsides of this change are closure calls that
may provide arguments that the callee does not care about.

https://3v4l.org/0QdoS

```
function filter($array, callable $c) {
    $result = [];
    foreach ($array as $key => $value) {
        if ($c($value, $key)) {
            $result[$key] = $value;
        }
    }
    return $result;
}

var_dump(filter(['foo', '', 'bar'], function ($value) {
    return strlen($value);
}));

// Internal functions already throw on superfluous args
var_dump(filter(['foo', '', 'bar'], 'strlen'));
```

The user may currently choose to omit the $key parameter of the
closure, as it is never used. In the future, this would throw. We may
decide to create an exemption for such calls, but I'm not sure
replacing one inconsistency with another is a good choice.

Ilija

---

## #122949 — Mark Trapp — 2024-04-04T21:38:08+00:00

URL: https://externals.io/message/122928#122949 · In reply to: #122944

> 
> 
> > […]
> 
> This seems like the only reasonable thing to do, to me. If this were the
> case, there should be no need for any new syntax, since we could simply
> deny passing extra arguments in the next major, no?
> 
> Bilge

I'd caution about being too hasty to deprecate and remove support for
this feature without understanding the underlying reasons why it has
been used historically, and without providing a better (not just
different) alternative for those use cases, lest an impossible upgrade
path is created.

While the variadic operator should be a 1:1 replacement for using
 `func_get_args()`, is it enough of a benefit to force potentially tens
of thousands of codebases to change when they both accomplish the same
thing? What bad thing happens if PHP continues to support both
methods?

There is already enough friction to upgrade between major versions.
Introducing an optional attribute or syntax allows codebases that care
about signature strictness get that safety, while avoiding a
potentially costly and onerous upgrade for the community.

- Mark

---

## #122956 — Pierre — 2024-04-05T07:11:54+00:00

URL: https://externals.io/message/122928#122956 · In reply to: #122949

Le 04/04/2024 à 23:38, Mark Trapp a écrit :
> 
> > […]
> I'd caution about being too hasty to deprecate and remove support for
> this feature without understanding the underlying reasons why it has
> been used historically, and without providing a better (not just
> different) alternative for those use cases, lest an impossible upgrade
> path is created.
>
> While the variadic operator should be a 1:1 replacement for using
> `func_get_args()`, is it enough of a benefit to force potentially tens
> of thousands of codebases to change when they both accomplish the same
> thing? What bad thing happens if PHP continues to support both
> methods?
>
> There is already enough friction to upgrade between major versions.
> Introducing an optional attribute or syntax allows codebases that care
> about signature strictness get that safety, while avoiding a
> potentially costly and onerous upgrade for the community.
>
> - Mark

I agree with both, ie. I think it's reasonable to deprecate extra 
variadic arguments, but also do it with caution: some old code, some 
templating systems for example may use implicit variadic arguments to 
pass values to templates.

There are tons of use case like this in old yet still running code.

But I would love it to be deprecated, raise warnings, and give some time 
for people to fix (adding an explicit `mixed ... $values` is enough to 
fix broken code).

Cheers,

--

Pierre

---

## #122962 — tim@bastelstu.be — 2024-04-05T10:24:30+00:00

URL: https://externals.io/message/122928#122962 · In reply to: #122948

Hi


> > […]
> 
> IIRC one of the bigger downsides of this change are closure calls that
> may provide arguments that the callee does not care about.
> 
> […]
> 
> The user may currently choose to omit the $key parameter of the
> closure, as it is never used. In the future, this would throw. We may
> decide to create an exemption for such calls, but I'm not sure
> replacing one inconsistency with another is a good choice.

I must admit, I find the

> This RFC considers that anonymous functions are not intended to have a formal signature and should skip the exceeding argument count check.

bit of the previous RFC pretty reasonable to reduce the impact of the 
deprecation: I expect this type of callback to primarily be implemented 
as a single-use closure, which aren't really part of a package's API and 
thus relaxed checks are fine for those.

I also don't primarily see deprecating passing of superfluous parameters 
as removing an inconsistency. The more important bit for me is the 
improved error checking, i.e. I'd prefer to resolve the inconsistency in 
favor of the stricter checks, instead of relaxing the checks for 
internal functions. The previous RFC showcases multiple examples where 
it found bugs that likely were introduced during refactoring.

Best regards
Tim Düsterhus

---

## #122969 — Larry Garfield — 2024-04-05T13:53:56+00:00

URL: https://externals.io/message/122928#122969 · In reply to: #122948

> 
> > […]
>
> IIRC one of the bigger downsides of this change are closure calls that
> may provide arguments that the callee does not care about.
>
> https://3v4l.org/0QdoS
>
> ```
> function filter($array, callable $c) {
>     $result = [];
>     foreach ($array as $key => $value) {
>         if ($c($value, $key)) {
>             $result[$key] = $value;
>         }
>     }
>     return $result;
> }
>
> var_dump(filter(['foo', '', 'bar'], function ($value) {
>     return strlen($value);
> }));
>
> // Internal functions already throw on superfluous args
> var_dump(filter(['foo', '', 'bar'], 'strlen'));
> ```
>
> The user may currently choose to omit the $key parameter of the
> closure, as it is never used. In the future, this would throw. We may
> decide to create an exemption for such calls, but I'm not sure
> replacing one inconsistency with another is a good choice.
>
> Ilija

This is unfortunately not reliable today, because of the difference between how internal functions and user-defined ones are handled.  The code above will fatal if you use a callable that is defined in stdlib rather than in user-space.  I have been bitten by this many times, and is why I ended up with double the functions in my FP library:

cf: https://github.com/Crell/fp/blob/master/src/array.php#L34

It's also been argued to me rather effectively that ignoring trailing values and optional arguments creates a whole bunch of exciting landmines, as you may pass an "extra" parameter to a function expecting it to be ignored, but it's actually part of the optional arguments so gets used.  The standard example here is intval($value, $base=10).  Basically no one uses the $base parameter, and most people forget it exists, but if you allow an optional value to get passed to that, especially if in weak typing mode, you could get hilariously wrong results.  (For sufficiently buggy definitions of hilarious.)

The behavior difference between internal and user-defined functions is the root issue.  One way or another it should be addressed, because the current behavior is a landmine I have stepped on many times.

--Larry Garfield

---

## #123041 — Pablo Rauzy — 2024-04-08T12:00:57+00:00

URL: https://externals.io/message/122928#123041 · In reply to: #122969

Hello all,

Le 05/04/2024 à 15:53, Larry Garfield a écrit :
> 
> > […]
> This is unfortunately not reliable today, because of the difference between how internal functions and user-defined ones are handled.  The code above will fatal if you use a callable that is defined in stdlib rather than in user-space.  I have been bitten by this many times, and is why I ended up with double the functions in my FP library:
>
> cf:https://github.com/Crell/fp/blob/master/src/array.php#L34
>
> It's also been argued to me rather effectively that ignoring trailing values and optional arguments creates a whole bunch of exciting landmines, as you may pass an "extra" parameter to a function expecting it to be ignored, but it's actually part of the optional arguments so gets used.  The standard example here is intval($value, $base=10).  Basically no one uses the $base parameter, and most people forget it exists, but if you allow an optional value to get passed to that, especially if in weak typing mode, you could get hilariously wrong results.  (For sufficiently buggy definitions of hilarious.)
>
> The behavior difference between internal and user-defined functions is the root issue.  One way or another it should be addressed, because the current behavior is a landmine I have stepped on many times.
>
> --Larry Garfield

So what should be done to move forward with this?

Should the old RFC on strict argument count be revived?

Or should a new RFC proposal be written? If so, should it contain an 
approval voting (where voters can select any number of candidates), 
prior to the RFC proposal vote itself, to decide if the change should 
be: strict argument count, using the void keyword to explicitely stop 
the argument list, or using a #[Nonvariadic] attribute?

Best regards,

-- 
Pablo

---

## #123066 — tim@bastelstu.be — 2024-04-09T18:49:15+00:00

URL: https://externals.io/message/122928#123066 · In reply to: #123041

Hi


> So what should be done to move forward with this?
> 
> Should the old RFC on strict argument count be revived?
> 
> Or should a new RFC proposal be written? If so, should it contain an

It should be a new "v2" RFC. The old RFC already contains a vote, thus 
editing it would change historic votes.

> approval voting (where voters can select any number of candidates),
> prior to the RFC proposal vote itself, to decide if the change should
> be: strict argument count, using the void keyword to explicitely stop
> the argument list, or using a #[Nonvariadic] attribute?
> 

And RFC needs a clear primary vote. I believe the options you mentioned 
are too different to usefully be voted on with a single primary vote + a 
"tie breaker". I believe it should be a simple opinionated RFC. Should 
it not pass, follow-up RFCs for the alternative options can be proposed 
if it is desired.

Best regards
Tim Düsterhus
