Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:123475 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 qa.php.net (Postfix) with ESMTPS id 5AE2B1A009C for ; Fri, 31 May 2024 16:09:20 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1717171821; bh=tA7KiReKfEtAY6Hyp+Sjou+t2WymSRb2oBDKNdfAO+I=; h=In-Reply-To:References:Date:From:To:Subject:From; b=hjtWfaxnU0lUmZyZ+lgPuWlMos6MfeDPeHmG8QWd3WtXf6aSQnD0ICpIs5IJSvbRu EScbvZuuNKVuUdcxwLsO6zLv1rDtlE2zCTolUngH4KC4bloUCFvgWeI4j/hisI10+W wMD5Ag8zaMynwL3vX2WeRmQSI0NIJwP7G/VASJp3SdNtBGBvEwjI5xE/E80bUwBbTH SGo2NPDvOeZqD7hI7Yi9jacPijn4n5peg+cddtgbACqXQvAu93kFn1omqpD88VCtUX tCjB1ZHB0L5tbqV98GhZgq2MkYMsu5B+Pp9FpwtGxvc0tVoYwjaJvrR+A+KDNQpNAp 2UXrzymF121xw== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 13A1D18056C for ; Fri, 31 May 2024 16:10:18 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-0.1 required=5.0 tests=BAYES_50,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_MISSING,RCVD_IN_DNSWL_LOW, SPF_HELO_PASS,SPF_NONE,T_SCC_BODY_TEXT_LINE autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: Error (Cannot connect to unix socket '/var/run/clamav/clamd.ctl': connect: Connection refused) X-Envelope-From: Received: from fhigh6-smtp.messagingengine.com (fhigh6-smtp.messagingengine.com [103.168.172.157]) (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, 31 May 2024 16:10:17 +0000 (UTC) Received: from compute1.internal (compute1.nyi.internal [10.202.2.41]) by mailfhigh.nyi.internal (Postfix) with ESMTP id 6F90D11400F7 for ; Fri, 31 May 2024 12:09:15 -0400 (EDT) Received: from imap50 ([10.202.2.100]) by compute1.internal (MEProxy); Fri, 31 May 2024 12:09:15 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= garfieldtech.com; h=cc:content-transfer-encoding:content-type :content-type:date:date:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:subject:subject:to :to; s=fm1; t=1717171755; x=1717258155; bh=grBuS4dfLZPwNmNbiusEm Xi0T7cI70rPoRGXainrJaA=; b=C92x6yv8uGjIt9ccIPtCGeA5uF9/Lw6jUekrN jsE3zz8Ax4u9IguWElZIqcV6z54ayaUsEXQ5Z1yUhmCL9od1n0pDX8dCHTKljKjI /QfZl5ssns545oafIPpWQuYX3Eq21sUhuw054MRfE+Ncv5PZZdMO4D4CMIdbiZLE QhA9fks6KWnxLGmF+9HqhtIeOHD+KBbXpwyBv0RbiY64nSLOMT8oINqxLFJCMI5+ fN29ryS8LV6lg82t5yd79CaBM5LR+vkC5QA/XGIuZfW09fpex0EkwaRHCjB/NonY ZCV67L58Ii3VCEEAod3FZyelVTNtNzwcYN3NTRyK+txuM4SGw== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-transfer-encoding:content-type :content-type:date:date:feedback-id:feedback-id:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to:x-me-proxy:x-me-proxy :x-me-sender:x-me-sender:x-sasl-enc; s=fm1; t=1717171755; x= 1717258155; bh=grBuS4dfLZPwNmNbiusEmXi0T7cI70rPoRGXainrJaA=; b=c XAlVF5mBeI6tGMOf6PyK3+/n69ztpwneBzBEpv14nOFTRlcdwBBLU2T1n3CKjHwA uwEmZ9jTXI4PWQw7rtnLvE507Cdu7NHKWVSdjWYwlZZ39RlT67DdUpB9vqGQWY+4 xR5Q6hJGxTs6vJ7eW6vuHAMcgvI5hpMgca/5LP6KWdmBE6VRM/ZgOEqizUuM+sQl dIcNHknOz6oUlmnKYtCnixfnjg+XneWmZuN15p3IDpMgj8h/EmX1WArX+wnlwRbM PHzA0+YuZhdWW5g9fpw2ZGjXH9D7/kn+LqwAvgTdr1aarJdXxJpdoV/9/f5/nD6L 22Ta9IbBScprArVh5fkiw== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedvledrvdekiedgleehucetufdoteggodetrfdotf fvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfqfgfvpdfurfetoffkrfgpnffqhgen uceurghilhhouhhtmecufedttdenucesvcftvggtihhpihgvnhhtshculddquddttddmne goufhushhpvggtthffohhmrghinhculdegledmnecujfgurhepofgfggfkjghffffhvffu tgfgsehtqhertderreejnecuhfhrohhmpedfnfgrrhhrhicuifgrrhhfihgvlhgufdcuoe hlrghrrhihsehgrghrfhhivghlughtvggthhdrtghomheqnecuggftrfgrthhtvghrnhep keeuvdefveelgfelhfekfeefheffvdefjefggffgtddtteejvdeukefhhedvhffhnecuff homhgrihhnpeefvheglhdrohhrghdpkhhothhlihhnlhgrnhhgrdhorhhgpdhmihgtrhho shhofhhtrdgtohhmnecuvehluhhsthgvrhfuihiivgeptdenucfrrghrrghmpehmrghilh hfrhhomheplhgrrhhrhiesghgrrhhfihgvlhguthgvtghhrdgtohhm X-ME-Proxy: Feedback-ID: i8414410d:Fastmail Received: by mailuser.nyi.internal (Postfix, from userid 501) id BE8951700093; Fri, 31 May 2024 12:09:14 -0400 (EDT) X-Mailer: MessagingEngine.com Webmail Interface User-Agent: Cyrus-JMAP/3.11.0-alpha0-491-g033e30d24-fm-20240520.001-g033e30d2 Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net MIME-Version: 1.0 Message-ID: <734bb8e8-2fdf-4e50-9039-e53c99ee4930@app.fastmail.com> In-Reply-To: References: <0a6a61cd-f203-4dea-a7f8-97e6b885c52d@app.fastmail.com> Date: Fri, 31 May 2024 16:08:53 +0000 To: "php internals" Subject: Re: [PHP-DEV] [RFC] Asymmetric Visibility, v2 Content-Type: text/plain;charset=utf-8 Content-Transfer-Encoding: quoted-printable From: larry@garfieldtech.com ("Larry Garfield") On Fri, May 31, 2024, at 12:04 PM, Alexandru P=C4=83tr=C4=83nescu wrote: > On Fri, May 31, 2024 at 10:30=E2=80=AFAM Claude Pache wrote: >>=20 >>=20 >>> Le 30 mai 2024 =C3=A0 17:07, Derick Rethans a =C3=A9= crit : >>>=20 >>>>=20 >>>> Now, if I define the property as public private(set) with similar=20 >>>> intentions, to make sure that there is no way for external scope or=20 >>>> extending classes scope to write to the property, while allowing=20 >>>> reading from external scope (or extending classes scope). >>>>=20 >>>> But the problem is that an extending class can define the property = as=20 >>>> public protected(set), and that will easily allow the property that= I=20 >>>> wanted to make sure it is private for writing to be changed by an=20 >>>> extending class to be protected. >>>=20 >>> public private(set) properties aren't really private, so you don't g= et=20 >>> the shadowing, but you do have a point wrt to the expectation that a= n=20 >>> inherited class can't easily override the private(set) part (with=20 >>> protected(set) or public(set)). >>=20 >>=20 >> Note that the issue already exists today with readonly properties: th= ose are basically private(set); but if you redeclare a non-private reado= nly property in a subclass, you can in fact initialise it from the subcl= ass bypassing the initial private(set) restriction of the superclass: ht= tps://3v4l.org/9AV4r > > Yes, thank you; that's a good point. > Seems like another issue with readonly, but then again, the=20 > private(set) part of readonly is not really explicitly designed, I=20 > guess. >=20 >>=20 >> If you want a property not to be overridable, end of story, you can m= ark it as `final` (the final marker for properties was added as part of = the hooks RFC, but it works also with non-hooked properties). > > Yes, it seems like a good enough option, and use "final public=20 > private(set)" to ensure only the current class will be able to set the=20 > value. > If we all agree, I think this should be documented in the RFC. > > There is another small problem, I think, with "final" modifier not=20 > being allowed for constructor-promoted properties. > And maybe we can have this fixed in the current RFC if this ends up=20 > being "the correct" way to define public-read private-write properties. > > Regards, > Alex Mm, yeah, this is an interesting corner case we'd not thought of. We sp= ent a little time looking at how other languages handle this. Kotlin just disallows using private-set on a non-final property. (Kotli= n properties are final by default, unless otherwise specified.) cf: htt= ps://kotlinlang.org/spec/inheritance.html#overriding C# disallows changing property visibility in child classes entirely. cf= : https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/clas= ses-and-structs/restricting-accessor-accessibility#access-modifiers-on-o= verriding-accessors Swift is... weird. If you have a public private(set) property, you can = override it by turning it into a virtual property with only a get hook. = If you also define a set hook, though, it just never gets called. It d= oesn't appear that you can widen the set visibility, I think. So since we now have final properties (yay, hooks RFC!), we could probab= ly mostly solve this by just only allowing private(set) on a final prope= rty, like Kotlin. That's probably the easiest approach. We can also ma= ke private(set) properties implicitly final to avoid lots of boilerplate= . (Having to always type final with private(set) every time is kinda si= lly.) It would mean that proxy objects implemented using child classes with ho= oks would only work with a public protected(set) class, not with a priva= te(set), but that's probably fine, and since no one is doing that quite = yet, obviously, there's no existing code to be concerned about. However, this also brings up another interesting issue: readonly propert= ies (in 8.3) DO allow redeclaration, essentially adjusting the property = scope (the class that declares it) to make the visibility check pass. Th= at is, the definition of the class it is private to changes, which is di= fferent from how inheritance works elsewhere. When the parent writes to= the same property, a special check is needed to verify the two properti= es are related. All that special casing effectively means that readonly= in 8.4 wouldn't really be "write once + private(set)", but "write once = + private(set) - final", which is... just kinda screwy. That means our = options are: * A BC break on readonly (not allowing it to be overridden) * Make readonly an exception to the implicit final. * Just don't allow readonly with aviz after all. After some consideration, we believe the third option is best (least bad= ). The other options add still-more special cases or break existing cod= e, neither of which are good. If a property is declared private(set), t= hen child classes simply should not be allowed to change that, period. = Even adding a get hook in a child is potentially a problem, as it would = change the "round trip" behavior in ways the parent class doesn't expect= . Disallowing readonly and making private(set) implicitly final avoids = the loopholes in the language that would violate that expectation. With aviz, all the use cases for readonly are essentially covered alread= y. If you have private(set), you can control when it gets written anywa= y, so if you want it to be write-once, it=E2=80=99s write once. If you = have protected(set), you=E2=80=99re allowing a child to set it, so they = can do so whenever they want anyway, and could be doing it out of order = from the parent class to begin with. So if you wanted the equivalent of "public protected(set) readonly strin= g $foo", in practice, the readonly is not giving you much to begin with.= And "public public(set) readonly string $foo"... we just can't really = think of a good use case for. Even if one exists, that could be emulate= d with a set hook now. In terms of code length, thanks to the optional public-get visibility, t= he amount of typing is roughly the same: readonly class ReadonlyPoint { public function __construct( public int $x, public int $y, ) {} } class AvizPoint { public function __construct( private(set) int $x, private(set) int $y, ) {} } (Or protected, if you prefer.) The latter provides a lot more flexibili= ty, at the cost of "enforce write-once yourself in the class if you care= ", which seems an entirely reasonable tradeoff. So we feel the best way forward is to make the following changes: * private(set) implicitly means "final". (You can declare it explicitly= if you want, but it isn't necessary.) * Make readonly incompatible with aviz again. Thoughts? Also, Alexandru noted earlier that final properties don't seem to be sup= ported in constructor promotion. That's an oversight from the hooks imp= lementation, not a design choice, so Ilija will just fix that as part of= the hooks PR before it gets fully merged. --Larry Garfield