Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:127437 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by lists.php.net (Postfix) with ESMTPS id EFD7D1A00BC for ; Fri, 23 May 2025 15:25:35 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1748013807; bh=IUUbmA03pPf7zRd9gTK7AbYyseEg6WS1OB185gUvSQI=; h=Subject:From:In-Reply-To:Date:Cc:References:To:From; b=bYn93dqwIVv7o6U995UZ+wjK5YjHsT4TGXwSbV4XEhjYLxYOYc/kFk+Rs5vW+ACTM vvn7866rDvVJqJk8xpcr0xKEB80Ux6tr0oKYqDjAPoh6GSSYiDsnonrDG1peh2MP/a Uj2wTzaLi6vAMH2LeOeYonacjbSzW6Yi/Khm9GUeJEsczxThB+CdPfWdUU5FQrLIMK x8Esh2bCzrsLJwD1KuzXCpCCyYwo10xdoxvQsO+ApIpe2tTa8PH7A8fuvH6om05Pe5 OFScHKMyVJNm+zTmm/fnYNZJ1JcF65HZBBfusfDQMK1eMfqw3HilvhAUh5iLvbYxSG HsmPxUWf06k5w== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 3802C18006A for ; Fri, 23 May 2025 15:23:26 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on php-smtp4.php.net X-Spam-Level: * X-Spam-Status: No, score=1.9 required=5.0 tests=BAYES_50,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_PASS, RCVD_IN_BL_SPAMCOP_NET,RCVD_IN_MSPIKE_H4,RCVD_IN_MSPIKE_WL, SPF_HELO_NONE,SPF_PASS autolearn=no autolearn_force=no version=4.0.1 X-Spam-Virus: Error (Cannot connect to unix socket '/var/run/clamav/clamd.ctl': connect: Connection refused) X-Envelope-From: Received: from outbound.qs.icloud.com (p-east3-cluster2-host12-snip4-10.eps.apple.com [57.103.87.251]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Fri, 23 May 2025 15:23:25 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=garsi.de; s=sig1; bh=Mry+fQEim1wV9lF0eJHO0I7LRUtoZ9UjP4ti/j1GNpQ=; h=Content-Type:Mime-Version:Subject:From:Date:Message-Id:To:x-icloud-hme; b=qRTzJJpwKKr8BvlQoPB45sDOuwywgIYLOLPqr8gdTgVMtZyc22ZyttZ/9+l0wei6E 0eiF4by2x12ho6AFMWrk8Vp44gf44JQ0pMlOj+Gby5SAXrjxZaVW4EQHGru6YAIDiz NSOcDrex5QzpNs8zpYMVIp19FaGr48H20Or/ExVnqstYZySuqERtArGsNQSbdIuZnC exR+/faUfDXeFzyI5yUd4et/kGzBUBKhfWL0yFRYGE+lPS+3UTw6zCbNwNHPf5T716 r4wx55m53Sw7p8yeLyKZVqEjBucoQthctxD0TFI3pWH2ndkpMLpAer12r5hvzgOAYP EZltWR+5xMS3w== Received: from outbound.qs.icloud.com (localhost [127.0.0.1]) by outbound.qs.icloud.com (Postfix) with ESMTPS id CA7511800667; Fri, 23 May 2025 15:25:31 +0000 (UTC) Received: from smtpclient.apple (qs-asmtp-me-k8s.p00.prod.me.com [17.57.155.37]) by outbound.qs.icloud.com (Postfix) with ESMTPSA id 75B3618006A3; Fri, 23 May 2025 15:19:05 +0000 (UTC) Content-Type: text/plain; charset=utf-8 Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3826.500.181.1.5\)) Subject: Re: [PHP-DEV] Forced named parameters In-Reply-To: Date: Fri, 23 May 2025 17:18:53 +0200 Cc: PHP Internals Content-Transfer-Encoding: quoted-printable Message-ID: <09A30450-9162-4995-B418-86FC36CF61D5@garsi.de> References: To: Anton Smirnov X-Mailer: Apple Mail (2.3826.500.181.1.5) X-Proofpoint-GUID: RKJR7bf7-lGdRPth35mrewl6Ag1wPjvL X-Proofpoint-ORIG-GUID: RKJR7bf7-lGdRPth35mrewl6Ag1wPjvL X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1099,Hydra:6.0.736,FMLib:17.12.80.40 definitions=2025-05-23_05,2025-05-22_01,2025-03-28_01 X-Proofpoint-Spam-Details: rule=notspam policy=default score=0 malwarescore=0 suspectscore=0 mlxscore=0 spamscore=0 mlxlogscore=999 adultscore=0 phishscore=0 clxscore=1030 bulkscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.22.0-2503310001 definitions=main-2505230139 From: alwin@garsi.de (Alwin Garside) Hi Anton, > On 23 May 2025, at 03:24, Anton Smirnov wrote: >=20 >> Moreover, what if I don't need a variadic parameter, but would like = to declare that the function's interface requires named arguments = (because I don't want to be tied to locking parameters to specific = positions). >=20 > If you don't need a variadic, you write an ellipsis without a variable = name, like in my example: >=20 >>> function f(Type $pos1, Type $pos2, ..., Type $named1, Type $named2) Ah, that hadn't really registered in my brain. So, if I want all = parameters to be named, it'd be: ```php function f(..., Type $named1, Typ $named2) { /* ... */ } ``` I still don't think I like this syntax. I don't think one should taint = the declaration of the parameters with how the arguments are expected to = be provided. Forcing named parameters is a way of declaring additional = requirements for the interface of your function, and attributes are the = go-to approach for this =E2=80=93 think `#[\Override]`, = `#[\Deprecated]`, etc. >> So instead I'd propose to implement this using an attribute (e.g. = `#[RequireNamedArgs]`) instead. That way, you could even make it so that = only specific arguments are required to be named. It's also = syntactically backwards compatible, so this feature wouldn't make your = code break compatibility with older versions of PHP, and could be made = available in older PHP versions using static analysis. >=20 > Please explain the syntax you propose about this part: >=20 >> That way, you could even make it so that only specific arguments are = required to be named >=20 > because I see only 2 ways to call it: >=20 > #[AllArgsAreNamed] function f (/* ... */) >=20 > and >=20 > function f($arg1, #[AllArgsAfterThisOneAreNamed] $arg2, $arg3) >=20 > because >=20 > function f($arg1, #[NamedArg] $arg2, /* positional again */ $arg3) >=20 > doesn't make sense, you cannot specify a positional argument after a = forced-named because it would be impossible to fill position 2 on the = function call. >=20 > Both cases are better covered by my syntax. In this case it would imply all params including and after the `$arg2` = have to be named as well. So in a way the attribute would replace your = proposed `...,` notation 1:1. >> It's also syntactically backwards compatible >=20 > Making it syntactically incompatible is what avoids the BC break I think I understand what you mean, but the way you write that phrase = makes it read like an oxymoron. What I'm trying to get at here is libraries could start using the new = attribute to tighten their internal library code without requiring = consumers to bump their required PHP version straight away. > relying on userland static analysis seems to be discouraged in this = community. I don't suggest we _rely_ on static analysis. However, consumers of a = library that starts declaring their params as explicitly named could = start getting hints in their editor or static analysis tool before = bumping their PHP version. > Consider this scenario: >=20 > let's say version x is the first one with the feature >=20 > #[RequireNamedArgs] > function f($a, $b, $c) { /* ... */ } >=20 > f(1, 2, 3); // works in x-1, broken in x >=20 > so this feature should at least go through the deprecation-like phase, = and be fully accepted only in a major version, which just means that it = cannot be fully used until the next major version. That defeats the = purpose for me. The same applies to `#[\Override]` and any other attributes that change = PHP behavior. If someone decides to use them in an older version of PHP = (through a polyfill or what not) it's their responsibility to ensure = that the restrictions that would otherwise be imposed natively by PHP = are sufficiently replaced through static analysis. It is true that this expects some discipline from library developers to = properly apply semantic versioning to their pacakges, but I much prefer = this over being forced to bump my PHP requirements, both in my libraries = and in my applications, before I can start using a feature. Besides, even with the `...,` syntax, a certain amount of discipline is = required from library developers at some point. Otherwise, even if I'm = ont he most recent version of PHP, a package that starts enforcing named = parameters without increasing their major version of their packages will = just break my code once I update it. In the end, the semantic versioning of PHP doesn't have to consider how = a new PHP _feature_ is emulated on older versions of PHP through = polyfills / static analysis. Again my main issue with the `...,` syntax is that the goal of this = feature is to hint that you intend to provide no guarantees about the = positioning of the arguments. It shouldn't require backwards = incompatible syntax, and there is precedent in how similar features have = been implemented using attributes, namely: `#[\AllowDynamicProperties]`, = `#[\Deprecated]`, `#[\Override]`, `#[\ReturnTypeWillChange]` and = `#[\SensitiveParameter]`. Additionally, I don't think a fatal Error should be thrown by this = feature, at least not by default. An `E_USER_NOTICE` or = `E_USER_DEPRECATED` would suffice to inform users that they have to take = into account the guarantees around the signature of the function they're = calling, but there is no reason to outright block otherwise perfectly = fine function or method calls. I would like the ability to ease = consumers of my library into using named arguments rather than forcing = them to do so with a new major version release. Having said that, using an attribute has the added advantage of being = able to provide arguments to adjust its behavior. So for example, a = `throw ` argument could be provided to hint that PHP should throw an = error, if you really feel so strongly about the proper usage of our = functions: ```php #[\Named(throw: true)] function f($a, $b, $c) { /* ... */ } f(1, 2, 3); // Throws a `NamedArgumentException`. ``` > Anyway, the biggest problem for me, why I'm not considering writing an = RFC for now, is that I don't know what to do with func_get_args() and = reflection. Well, that's another benefit of using attributes; since they define the = usage of the function at a different scope, `func_get_args()` and = reflection will just continue to work the same as they already do. One = could consider adding a `isNamed()` method to the `ReflectionParameter` = class, but that's about it, I think. Of course, if you foresee other issues with `func_get_args()` or = reflection, I'd love to hear them. Anyway, I hope I don't come off as too harsh. Fact is I really like the = idea. I just think it makes more sense, and will have a better chance of = succeeding, as an attribute. Oh, come to think of it, it might also make sense to offer the opposite: = a way to force positional arguments. What if I don't want users of my = function to rely on a parameter name, but I will guarantee the = consistent positions of the parameters... Again, this would be much more = straightforward to implement as an attribute (e.g. `#[\Positional]`) = while I'm not sure how that could be implemented using traditional = syntax. Alwin=