Re: [PHP-DEV] Proposal: AS assertions

2024-03-21 Thread Robert Landers
On Thu, Mar 21, 2024 at 11:06 PM Rowan Tommins [IMSoP]
 wrote:
>
> On 21/03/2024 19:03, Robert Landers wrote:
>
> I suppose we are taking this from different viewpoints, yours appears
> to be more of a philosophical one, whereas mine is more of a practical
> one.
>
>
> My main concern is consistency; which is partly philosophical, but does have 
> practical impact - the same syntax meaning the same thing in different 
> contexts leads to less user confusion and fewer bugs.
>
> But I also think there are real use cases for "error on anything other than 
> either Foo or null" separate from "give me a null for anything other than 
> Foo".

I can't think of any example of this. In every case I've ever written
a manual typecheck, I've done something differently from null vs. the
actual type I'm checking for. In only a few instances were they ever
the same. I'd be happy to research this by looking at some older code
that has to do manual typechecks; but I have a feeling if you were to
make Foo|null only throw, it would be pointless as most people would
end up writing this anyway, making the |null completely superfluous:

if $x === null {
 /* do something for null */
}
$y = $x as Foo;

> $x = $a as null;
>
> (or any other value, such as true|false) appears to have no practical
> purpose in this particular case.
>
>
> There's plenty of possible pieces of code that have no practical purpose, but 
> that on its own isn't a good reason to make them do something different.
>
> "null" as a standalone type (rather than part of a union) is pretty much 
> always pointless, and was forbidden until PHP 8.2. It's now allowed, partly 
> because there are scenarios involving inheritance where it does actually make 
> sense (e.g. narrowing a return type from Foo|null to null); and probably also 
> because it's easier to allow it than forbid it.
>
>
> That's not really what we're talking about anyway, though; we're talking 
> about nullable types, or null in a union type, which are much more frequently 
> used.

I think that is where we are getting confused: `null` is a value (or
at least, the absence of a value). The fact that the type system
allows it to be used as though its a type (along with true and false)
is interesting, but I think it is confusing the conversation. It might
be worth defining the meaning of "type" and "value" as well defining
what the "as" means in each context. Is it the same? Is it different?
I think this is worth spending some time on, but I have a feeling
there'll be bigger discussion about that when pattern matching shows
up.

> Further, reading "$x =
> $a as null", as a native English speaker, appears to be the same as
> "$x = null".
>
>
> Well, that's a potential problem with the choice of syntax: "$x = $a as int" 
> could easily be mistaken for "cast $a as int", rather than "assert that $a is 
> int".
>
> If you spell out "assert that $a is null", or "assert that $a is int|null", 
> it becomes very surprising for 'hello' to do anything other than fail the 
> assertion.

$a as int is quite different from $a as null. One is a bonafide type,
the other is a value. I don't think you can mistake this and they are
very different semantics.

> As I mentioned in the beginning, I see this mostly being used when
> dealing with mixed types from built-in/library functions, where you
> have no idea what the actual type is, but when you write the code, you
> have a reasonable expectation of a set of types and you want to throw
> if it is unexpected.
>
>
> My argument is that you might have a set of expected types which includes 
> null, *and* want to throw for other, unexpected, values. If "|null" is 
> special-cased to mean "default to null", there's no way to do that.

I'm arguing that that doesn't make any sense; this isn't a
method/function signature. I invite you to try writing some fictional
code using both semantics. I'm being sincere when I say I'd love to
see an example that would use |null on the right hand side of "as" and
want to throw.

> Right now, the best way to do that is to simply
> set a function signature and pass the mixed type to the function to
> have the engine do it for you
>
>
> And if you do that, then a value of 'hello' passed to a parameter of type 
> int|null, will throw a TypeError, not give you a null.
>
> As I illustrated in my last e-mail, you can even (since PHP 8.2) have a 
> parameter of type null, and get a TypeError for any other value. That may not 
> be useful, but it's entirely logical.

Ah, yeah, I guess I could have been more clear. This is what I find
myself writing quite a lot of lately (unfortunately):

foreach ($listOfMyAttributes as $attributeReflection) {
  $instance = $attributeReflection->newInstance();
  assert($instance instanceof MyAttribute);
  $instance->someMethod();
}

as well as:

$value = genericFuncReturnsMixed();
if ($value === null) {
  /* handle null */
}
if($value instanceof MyType) {
  $value->doSomething();
} else if ($value instanceof 

Re: [PHP-DEV] Proposal: AS assertions

2024-03-21 Thread Matthew Brown
>
> What's the advantage of a language construct over the following?
>
> ```php
> /**
>  * @template T of object
>  * @psalm-assert T $value
>  * @param class-string $type
>  */
> function as(mixed $value, string $type): mixed
> {
> if (! $value instanceof $type) { throw
> SomeKindOfException::forMismatchingRequirements($value, $type); }
>
> return $value;
> }
>
> echo as(myExpression(), MyType::class)->methodOfMyType();
> ```
>
>
A static analysis tool supporting something in docblocks should not
preclude those things being added in syntax IMO.

Hack's `as` operator is very neat, and it'd be trivial for existing SA
tools to support the same in PHP.

Here's a demo of `as` in Hack getting flagged by a SA tool:
https://hakana.dev/#XQAAgAA2AAAzHUn_qWH7EwabJzyN0tdfxv3ug6f7oZ-qScnamcl1qjUZCPmuKA3tD-KFr1f0ZPcrAXt_D1L___KsQAA%3D

This would also benefit from a `nonnull` subtype of `mixed` which could be
used as a null refinement.


Re: [PHP-DEV] Proposal: AS assertions

2024-03-21 Thread Rowan Tommins [IMSoP]

On 21/03/2024 19:03, Robert Landers wrote:

I suppose we are taking this from different viewpoints, yours appears
to be more of a philosophical one, whereas mine is more of a practical
one.



My main concern is consistency; which is partly philosophical, but does 
have practical impact - the same syntax meaning the same thing in 
different contexts leads to less user confusion and fewer bugs.


But I also think there are real use cases for "error on anything other 
than either Foo or null" separate from "give me a null for anything 
other than Foo".



$x = $a as null;

(or any other value, such as true|false) appears to have no practical
purpose in this particular case.



There's plenty of possible pieces of code that have no practical 
purpose, but that on its own isn't a good reason to make them do 
something different.


"null" as a standalone type (rather than part of a union) is pretty much 
always pointless, and was forbidden until PHP 8.2. It's now allowed, 
partly because there are scenarios involving inheritance where it does 
actually make sense (e.g. narrowing a return type from Foo|null to 
null); and probably also because it's easier to allow it than forbid it.



That's not really what we're talking about anyway, though; we're talking 
about nullable types, or null in a union type, which are much more 
frequently used.





Further, reading "$x =
$a as null", as a native English speaker, appears to be the same as
"$x = null".



Well, that's a potential problem with the choice of syntax: "$x = $a as 
int" could easily be mistaken for "cast $a as int", rather than "assert 
that $a is int".


If you spell out "assert that $a is null", or "assert that $a is 
int|null", it becomes very surprising for 'hello' to do anything other 
than fail the assertion.




As I mentioned in the beginning, I see this mostly being used when
dealing with mixed types from built-in/library functions, where you
have no idea what the actual type is, but when you write the code, you
have a reasonable expectation of a set of types and you want to throw
if it is unexpected.



My argument is that you might have a set of expected types which 
includes null, *and* want to throw for other, unexpected, values. If 
"|null" is special-cased to mean "default to null", there's no way to do 
that.




Right now, the best way to do that is to simply
set a function signature and pass the mixed type to the function to
have the engine do it for you



And if you do that, then a value of 'hello' passed to a parameter of 
type int|null, will throw a TypeError, not give you a null.


As I illustrated in my last e-mail, you can even (since PHP 8.2) have a 
parameter of type null, and get a TypeError for any other value. That 
may not be useful, but it's entirely logical.




It makes more sense, from a practical programming
point-of-view, to simply return the value given if none of the types
match.


This perhaps is a key part of our difference: when I see 
"int|bool|null", I don't see any "value given", just three built-in 
types: int, which has a range of values from PHP_INT_MIN to PHP_INT_MAX; 
bool, which has two possible values "true" and "false"; and null, which 
has a single possible value "null".


So there are 2**64 + 2 + 1 possible values that meet the constraint, and 
nothing to specify that one of those is my preferred default if given 
something unexpected.



Regards,

--
Rowan Tommins
[IMSoP]


Re: [PHP-DEV] [RFC] Release cycle update, take #2

2024-03-21 Thread Morgan

On 2024-03-22 08:19, Jim Winstead wrote:

On Thu, Mar 21, 2024, at 10:54 AM, Derick Rethans wrote:


The RFC is at https://wiki.php.net/rfc/release_cycle_update


Could this RFC also be a good time to clarify what sort of BC changes are 
actually allowed in major and minor releases, or should we save that for a 
different RFC? (Because it's already been acknowledged that the current written 
policy doesn't align with the practiced policy, and I think it would be nice to 
get those in sync.)

If so, would it also good time/place to clarify how deprecation relates 
to future removal. Say, while deprecations could come in any minor 
release, they would be removed only after a full major version has 
elapsed (something deprecated in 8.x would be removed in 10.0; 
technically that would mean a deprecation in 9.0.0 would also mean 
removal in 10.0). It would allow using the overall release cycle to 
forecast when something you're currently relying on will go away and 
plan accordingly.


Re: [PHP-DEV] [RFC] Release cycle update, take #2

2024-03-21 Thread Jim Winstead
On Thu, Mar 21, 2024, at 10:54 AM, Derick Rethans wrote:
> Hi!
>
> On Fri, 10 Nov 2023, Jakub Zelenka wrote:
>
>> I would like to propose a new process RFC for updates to PHP release 
>> cycle:
>> 
>> https://wiki.php.net/rfc/release_cycle_update
>
> I have just published version 0.2 of this proposal — I am taken this 
> over from Jakub by agreement.
>
> I've slightly reordered it, addressed some comments from this thread, 
> and added one new item: aligning the end of releases until Dec 31st, 
> 20xx.
>
> Now that we have policy documents, my next step is to prepare PRs to 
> https://github.com/php/policies/blob/main/feature-proposals.rst for each 
> item, after a more general reset of the document to reflect current 
> practises.
>
> The RFC is at https://wiki.php.net/rfc/release_cycle_update

Could this RFC also be a good time to clarify what sort of BC changes are 
actually allowed in major and minor releases, or should we save that for a 
different RFC? (Because it's already been acknowledged that the current written 
policy doesn't align with the practiced policy, and I think it would be nice to 
get those in sync.)

Jim


Re: [PHP-DEV] Proposal: AS assertions

2024-03-21 Thread Robert Landers
On Thu, Mar 21, 2024 at 7:01 PM Rowan Tommins [IMSoP]
 wrote:
>
> On 21/03/2024 15:02, Robert Landers wrote:
>
> I don't think you are getting what I am saying.
>
> $a as int|float
>
> would be an int, float, or thrown exception.
>
> $a as int|float|null
>
> would be an int, float, or null.
>
>
> I get what you're saying, but I disagree that it's a good idea.
>
> If $a is 'hello', both of those statements should throw exactly the same 
> error, for exactly the same reason - the input is not compatible with the 
> type you have specified.
>
>
>
> Another way of thinking about is:
>
> $x = $a as null
>
> What do you expect $x to be?
>
>
> The same as $x inside this function:
>
> function foo(null $x) { var_dump($x); }
> foo($a);
>
> Which is null if $a is null, and a TypeError if $a is anything else: 
> https://3v4l.org/5UR5A
>
>
> Regards,
>
> --
> Rowan Tommins
> [IMSoP]

I suppose we are taking this from different viewpoints, yours appears
to be more of a philosophical one, whereas mine is more of a practical
one.

$x = $a as null;

(or any other value, such as true|false) appears to have no practical
purpose in this particular case. This is better checked with `===`, or
even in_array(). Values are easy to check in PHP and there are already
lots of great and simple ways to check a value. Further, reading "$x =
$a as null", as a native English speaker, appears to be the same as
"$x = null".

As I mentioned in the beginning, I see this mostly being used when
dealing with mixed types from built-in/library functions, where you
have no idea what the actual type is, but when you write the code, you
have a reasonable expectation of a set of types and you want to throw
if it is unexpected. Right now, the best way to do that is to simply
set a function signature and pass the mixed type to the function to
have the engine do it for you; or write out a bunch of instanceofs
when that isn't worth it. However, this is cumbersome.

I'd also like to say that I'm not strongly attached to the |null
behavior I'm proposing, but there are better ways to assert a variable
is equal to a value. It makes more sense, from a practical programming
point-of-view, to simply return the value given if none of the types
match.

Robert Landers
Software Engineer
Utrecht NL


Re: [PHP-DEV] [RFC] Release cycle update, take #2

2024-03-21 Thread Daniil Gentili

>> I would like to propose a new process RFC for updates to PHP release
>> cycle:
>>
>> https://wiki.php.net/rfc/release_cycle_update

I would also like to propose an addition, allowing patch releases to be made 
outside of the normal release schedule if a major core feature is broken by a 
fix in the previous patch release.

These out-of-schedule releases should only contain a clean revert of the fix 
that broke the major core feature.

I believe the "major core feature" set should include at least the garbage 
collector and string functions, it may be extended if needed.

I'm advocating for this change due to the fact that critical garbage collector 
bugs were introduced and released at least twice in the last year:

- First with a GC patch that completely broke garbage collection causing 
segfaults if fibers are used (https://github.com/php/php-src/pull/9810)
- And then with a GC patch that broke garbage collection causing segfaults when 
using weakmaps (https://github.com/php/php-src/pull/10932)

Specifically regarding the first bug, the fix for it was actually delayed by 30 
days, instead of 15 (the time left until the next release, when the fix PR was 
merged), as a security release was made the day after the fix was merged, 
delaying the entire release cycle by 30 days.

I believe that bugs in major core features, introduced by fix PRs should be 
reverted ASAP, especially if they aren't noticed until a release, instead of 
breaking a core feature of the language for one month (or more if a security 
release delays the normal release cycle).

Also in general, I find it wrong that security releases should delay the normal 
cycle.

Regards,
Daniil Gentili.


[PHP-DEV] [RFC] Release cycle update, take #2

2024-03-21 Thread Derick Rethans
Hi!

On Fri, 10 Nov 2023, Jakub Zelenka wrote:

> I would like to propose a new process RFC for updates to PHP release 
> cycle:
> 
> https://wiki.php.net/rfc/release_cycle_update

I have just published version 0.2 of this proposal — I am taken this 
over from Jakub by agreement.

I've slightly reordered it, addressed some comments from this thread, 
and added one new item: aligning the end of releases until Dec 31st, 
20xx.

Now that we have policy documents, my next step is to prepare PRs to 
https://github.com/php/policies/blob/main/feature-proposals.rst for each 
item, after a more general reset of the document to reflect current 
practises.

The RFC is at https://wiki.php.net/rfc/release_cycle_update

cheers,
Derick

Re: [PHP-DEV] [RFC[ Property accessor hooks, take 2

2024-03-21 Thread Robert Landers
On Thu, Mar 21, 2024 at 5:15 PM Larry Garfield  wrote:
>
> On Thu, Mar 21, 2024, at 9:10 AM, Robert Landers wrote:
> > Hello,
> >
> > I'm a bit confused on inheritance. In the following example of a
> > proxy, do I need to be aware of a parent's hook and handle it
> > specially?
> >
> > class Loud
> > {
> > public string $name {
> > get {
> > return strtoupper($this->name);
> > }
> > }
> > }
> >
> > class LoudProxy extends Loud
> > {
> >public string $name {
> >get {
> >// detected the parent has a hook? //
> >$return = parent::$name::get();
> >// do something with return //
> >return $return;
> >}
> >}
> > }
> >
> > what happens if the Loud class later removes its hook implementation
> > (ex: moves it to the set hook)? Will my proxy now cause an error?
> >
> > Would simply calling $this->name call the parents hook?
>
> Per the RFC:
>
> "If there is no hook on the parent property, its default get/set behavior 
> will be used. "
>
> so parent::$name::get() will "read the parent property", which will go 
> through a hook if one is defined, and just read the raw value if not.  So 
> there is no detection logic needed, and the parent can add/remove a hook 
> without affecting the child.
>
> Calling $this->name in LoudProxy's get hook will access backing property on 
> LoudProxy itself, ignoring the parent entirely.
>
> --Larry Garfield

Awesome! Thanks! I just wasn't sure what "default" meant in this context.

Robert Landers
Software Engineer
Utrecht NL


Re: [PHP-DEV] Proposal: AS assertions

2024-03-21 Thread Rowan Tommins [IMSoP]

On 21/03/2024 15:02, Robert Landers wrote:

I don't think you are getting what I am saying.

$a as int|float

would be an int, float, or thrown exception.

$a as int|float|null

would be an int, float, or null.



I get what you're saying, but I disagree that it's a good idea.

If $a is 'hello', both of those statements should throw exactly the same 
error, for exactly the same reason - the input is not compatible with 
the type you have specified.





Another way of thinking about is:

$x = $a as null

What do you expect $x to be?



The same as $x inside this function:

function foo(null $x) { var_dump($x); }
foo($a);

Which is null if $a is null, and a TypeError if $a is anything else: 
https://3v4l.org/5UR5A



Regards,

--
Rowan Tommins
[IMSoP]


Re: [PHP-DEV] [RFC[ Property accessor hooks, take 2

2024-03-21 Thread Larry Garfield
On Thu, Mar 21, 2024, at 9:10 AM, Robert Landers wrote:
> Hello,
>
> I'm a bit confused on inheritance. In the following example of a
> proxy, do I need to be aware of a parent's hook and handle it
> specially?
>
> class Loud
> {
> public string $name {
> get {
> return strtoupper($this->name);
> }
> }
> }
>
> class LoudProxy extends Loud
> {
>public string $name {
>get {
>// detected the parent has a hook? //
>$return = parent::$name::get();
>// do something with return //
>return $return;
>}
>}
> }
>
> what happens if the Loud class later removes its hook implementation
> (ex: moves it to the set hook)? Will my proxy now cause an error?
>
> Would simply calling $this->name call the parents hook?

Per the RFC:

"If there is no hook on the parent property, its default get/set behavior will 
be used. "

so parent::$name::get() will "read the parent property", which will go through 
a hook if one is defined, and just read the raw value if not.  So there is no 
detection logic needed, and the parent can add/remove a hook without affecting 
the child.

Calling $this->name in LoudProxy's get hook will access backing property on 
LoudProxy itself, ignoring the parent entirely.

--Larry Garfield


Re: [PHP-DEV] Proposal: AS assertions

2024-03-21 Thread Larry Garfield
On Thu, Mar 21, 2024, at 3:02 PM, Robert Landers wrote:

> I don't think you are getting what I am saying.
>
> $a as int|float
>
> would be an int, float, or thrown exception.
>
> $a as int|float|null
>
> would be an int, float, or null.
>
> Robert Landers
> Software Engineer
> Utrecht NL

Hi Rob.  I really do encourage you to read the RFC that Ilija linked to 
already.  What you're proposing is already mostly written (though for 
performance reasons may be rewritten soon), and the edge cases already largely 
resolved.

https://wiki.php.net/rfc/pattern-matching

--Larry Garfield


[PHP-DEV] System Upgrades / Rsync Server Swapover

2024-03-21 Thread Derick Rethans
Hi!

TL,DR:

1. I have just pushed a DNS change to use our new rsync server.
2. I have upgraded main.php.net from Debian 10 to 12
3. I have installed all the latest updates on most machines


Long Form

1. In the beginning of the year, Deft (formerly Server Central), reached 
out to us that the current rsync server is old, and they would like to 
continue sponsoring us with a replacement (faster) machine.

I set up this new server in February, but then the mailing list stuff 
happened and life got in the way, so I have only just switched DNS over 
after testing each property.

Except that I could not test rsync and rmtools, as I don't know where 
they live. Nor PEAR, as I don't have access, and I also don't know 
whether it uses our rsync service.

This change should not cause any issues, but let me know if some 
websites don't update after committing code through Git.

2. main.php.net still ran Debian 10, and now Debian 12. This does come 
with a new PHP version (8.2), and I have changed it to use the system 
PHP instead of using Sury's repostory. Again, I also don't expect this 
to cause issues.

3. It is good to install system update onces in a while, and I've done 
that.

Let me know if anything broke!

cheers,
Derick
-- 
https://derickrethans.nl | https://xdebug.org | https://dram.io

Author of Xdebug. Like it? Consider supporting me: https://xdebug.org/support

mastodon: @derickr@phpc.social @xdebug@phpc.social


Re: [PHP-DEV] Proposal: AS assertions

2024-03-21 Thread Robert Landers
On Thu, Mar 21, 2024 at 12:45 PM Rowan Tommins [IMSoP]
 wrote:
>
> On 20/03/2024 23:05, Robert Landers wrote:
>
> In other
> words, I can't think of a case where you'd actually want a Type|null
> and you wouldn't have to check for null anyway.
>
>
> It's not about having to check for null; it's about being able to distinguish 
> between "a null value, which was one of the expected types" and "a value of 
> an unexpected type".
>
> That's a distinction which is made everywhere else in the language: parameter 
> types, return types, property types, will all throw an error if you pass a 
> Foo when a ?Bar was expected, they won't silently coerce it to null.
>
>
>
> If you think about it, in this proposal, you could use it in a match:
>
> // $a is TypeA|TypeB|null
>
> match (true) {
>   $a as ?TypeA => 'a',
>   $a as ?TypeB => 'b',
>   $a === null => 'null',
> }
>
>
> That won't work, because match performs a strict comparison, and the as 
> expression won't return a boolean true. You would have to do this:
>
> match (true) {
>   (bool)($a as ?TypeA) => 'a',
>   (bool)($a as ?TypeB) => 'b',
>   $a === null => 'null',
> }
>
> Or this:
>
> match (true) {
>   ($a as ?TypeA) !== null => 'a',
>   ($a as ?TypeB) !== null => 'b',
>   $a === null => 'null',
> }
>
>
> Neither of which is particularly readable. What you're really looking for in 
> that case is an "is" operator:
>
> match (true) {
>   $a is TypeA => 'a',
>   $a is TypeB => 'b',
>   $a === null => 'null',
> }
>
> Which in the draft pattern matching RFC Ilija linked to can be abbreviated to:
>
> match ($a) is {
>   TypeA => 'a',
>   TypeB => 'b',
>   null => 'null',
> }
>
>
> Of course, in simple cases, you can use "instanceof" in place of "is" already:
>
> match (true) {
>   $a instanceof TypeA => 'a',
>   $a instanceof TypeB => 'b',
>   $a === null => 'null',
> }
>
>
>
> Including `null` in that type
> seems to be that you would get null if no other type matches, since
> any variable can be `null`.
>
>
> I can't think of any sense in which "any variable can be null" that is not 
> true of any other type you might put in the union. We could interpret 
> Foo|false as meaning "use false as the fallback"; or Foo|int as "use zero as 
> the fallback"; but I don't think that would be sensible.
>
> In other words, the "or null on failure" part is an option to the "as" 
> expression, it's not part of the type you're checking against. If we only 
> wanted to support "null on failure", we could have a different keyword, like 
> "?as":
>
> $bar = new Bar;
> $bar as ?Foo; // Error
> $bar ?as Foo; // null (as fallback)
>
> $null = null;
> $null as ?Foo; // null (because it's an accepted value)
> $null ?as Foo; // null (as fallback)
>
> A similar suggestion was made in a previous discussion around nullable casts 
> - to distinguish between (?int)$foo as "cast to nullable int" and (int?)$foo 
> as "cast to int, with null on error".
>
>
> Note however that combining ?as with ?? is not enough to support "chosen 
> value on failure":
>
> $bar = new Bar;
> $bar ?as ?Foo ?? Foo::createDefault(); // creates default object
>
> $null = null;
> $null ?as ?Foo ?? Foo::createDefault(); // also creates default object, even 
> though null is an expected value
>
> That's why my earlier suggestion was to specify the fallback explicitly:
>
> $bar = new Bar;
> $bar as ?Foo else null; // null
> $bar as ?Foo else Foo::createDefault(); // default object
>
> $null = null;
> $nulll as ?Foo else null; // null
> $null as ?Foo else Foo::createDefault(); // also null, because it's an 
> accepted value, so the fallback is not evaluated
>
> Probably, it should then be an error if the fallback value doesn't meet the 
> constraint:
>
> $bar = new Bar;
> $bar as Foo else null; // error: fallback value null is not of type Foo
> $bar as ?Foo else 42; // error: fallback value 42 is not of type ?Foo
>
>
>
> Regards,
> --
> Rowan Tommins
> [IMSoP]

Another way of thinking about is:

$x = $a as null

What do you expect $x to be?


Re: [PHP-DEV] Proposal: AS assertions

2024-03-21 Thread Robert Landers
On Thu, Mar 21, 2024 at 12:45 PM Rowan Tommins [IMSoP]
 wrote:
>
> On 20/03/2024 23:05, Robert Landers wrote:
>
> In other
> words, I can't think of a case where you'd actually want a Type|null
> and you wouldn't have to check for null anyway.
>
>
> It's not about having to check for null; it's about being able to distinguish 
> between "a null value, which was one of the expected types" and "a value of 
> an unexpected type".
>
> That's a distinction which is made everywhere else in the language: parameter 
> types, return types, property types, will all throw an error if you pass a 
> Foo when a ?Bar was expected, they won't silently coerce it to null.
>
>
>
> If you think about it, in this proposal, you could use it in a match:
>
> // $a is TypeA|TypeB|null
>
> match (true) {
>   $a as ?TypeA => 'a',
>   $a as ?TypeB => 'b',
>   $a === null => 'null',
> }
>
>
> That won't work, because match performs a strict comparison, and the as 
> expression won't return a boolean true. You would have to do this:
>
> match (true) {
>   (bool)($a as ?TypeA) => 'a',
>   (bool)($a as ?TypeB) => 'b',
>   $a === null => 'null',
> }
>
> Or this:
>
> match (true) {
>   ($a as ?TypeA) !== null => 'a',
>   ($a as ?TypeB) !== null => 'b',
>   $a === null => 'null',
> }
>
>
> Neither of which is particularly readable. What you're really looking for in 
> that case is an "is" operator:
>
> match (true) {
>   $a is TypeA => 'a',
>   $a is TypeB => 'b',
>   $a === null => 'null',
> }
>
> Which in the draft pattern matching RFC Ilija linked to can be abbreviated to:
>
> match ($a) is {
>   TypeA => 'a',
>   TypeB => 'b',
>   null => 'null',
> }
>
>
> Of course, in simple cases, you can use "instanceof" in place of "is" already:
>
> match (true) {
>   $a instanceof TypeA => 'a',
>   $a instanceof TypeB => 'b',
>   $a === null => 'null',
> }
>
>
>
> Including `null` in that type
> seems to be that you would get null if no other type matches, since
> any variable can be `null`.
>
>
> I can't think of any sense in which "any variable can be null" that is not 
> true of any other type you might put in the union. We could interpret 
> Foo|false as meaning "use false as the fallback"; or Foo|int as "use zero as 
> the fallback"; but I don't think that would be sensible.
>
> In other words, the "or null on failure" part is an option to the "as" 
> expression, it's not part of the type you're checking against. If we only 
> wanted to support "null on failure", we could have a different keyword, like 
> "?as":
>
> $bar = new Bar;
> $bar as ?Foo; // Error
> $bar ?as Foo; // null (as fallback)
>
> $null = null;
> $null as ?Foo; // null (because it's an accepted value)
> $null ?as Foo; // null (as fallback)
>
> A similar suggestion was made in a previous discussion around nullable casts 
> - to distinguish between (?int)$foo as "cast to nullable int" and (int?)$foo 
> as "cast to int, with null on error".
>
>
> Note however that combining ?as with ?? is not enough to support "chosen 
> value on failure":
>
> $bar = new Bar;
> $bar ?as ?Foo ?? Foo::createDefault(); // creates default object
>
> $null = null;
> $null ?as ?Foo ?? Foo::createDefault(); // also creates default object, even 
> though null is an expected value
>
> That's why my earlier suggestion was to specify the fallback explicitly:
>
> $bar = new Bar;
> $bar as ?Foo else null; // null
> $bar as ?Foo else Foo::createDefault(); // default object
>
> $null = null;
> $nulll as ?Foo else null; // null
> $null as ?Foo else Foo::createDefault(); // also null, because it's an 
> accepted value, so the fallback is not evaluated
>
> Probably, it should then be an error if the fallback value doesn't meet the 
> constraint:
>
> $bar = new Bar;
> $bar as Foo else null; // error: fallback value null is not of type Foo
> $bar as ?Foo else 42; // error: fallback value 42 is not of type ?Foo
>
>
>
> Regards,
> --
> Rowan Tommins
> [IMSoP]

I don't think you are getting what I am saying.

$a as int|float

would be an int, float, or thrown exception.

$a as int|float|null

would be an int, float, or null.

Robert Landers
Software Engineer
Utrecht NL


Re: [PHP-DEV] Proposal: AS assertions

2024-03-21 Thread Rowan Tommins [IMSoP]
On 20/03/2024 23:05, Robert Landers wrote:
> In other
> words, I can't think of a case where you'd actually want a Type|null
> and you wouldn't have to check for null anyway.


It's not about having to check for null; it's about being able to distinguish 
between "a null value, which was one of the expected types" and "a value of an 
unexpected type".

That's a distinction which is made everywhere else in the language: parameter 
types, return types, property types, will all throw an error if you pass a Foo 
when a ?Bar was expected, they won't silently coerce it to null.





> If you think about it, in this proposal, you could use it in a match:
> 
> // $a is TypeA|TypeB|null
> 
> match (true) {
>   $a as ?TypeA => 'a',
>   $a as ?TypeB => 'b',
>   $a === null => 'null',
> }


That won't work, because match performs a strict comparison, and the as 
expression won't return a boolean true. You would have to do this:

match (true) {
  (bool)($a as ?TypeA) => 'a',
  (bool)($a as ?TypeB) => 'b',
  $a === null => 'null',
}
Or this:

match (true) {
  ($a as ?TypeA) !== null => 'a',
  ($a as ?TypeB) !== null => 'b',
  $a === null => 'null',
}


Neither of which is particularly readable. What you're really looking for in 
that case is an "is" operator:
match (true) {
  $a is TypeA => 'a',
  $a is TypeB => 'b',
  $a === null => 'null',
}
Which in the draft pattern matching RFC Ilija linked to can be abbreviated to:

match ($a) is {
  TypeA => 'a',
  TypeB => 'b',
  null => 'null',
}


Of course, in simple cases, you can use "instanceof" in place of "is" already:

match (true) {
  $a instanceof TypeA => 'a',
  $a instanceof TypeB => 'b',
  $a === null => 'null',
}




> Including `null` in that type
> seems to be that you would get null if no other type matches, since
> any variable can be `null`.
> 


I can't think of any sense in which "any variable can be null" that is not true 
of any other type you might put in the union. We could interpret Foo|false as 
meaning "use false as the fallback"; or Foo|int as "use zero as the fallback"; 
but I don't think that would be sensible.
In other words, the "or null on failure" part is an option to the "as" 
expression, it's not part of the type you're checking against. If we only 
wanted to support "null on failure", we could have a different keyword, like 
"?as":

$bar = new Bar;
$bar as ?Foo; // Error
$bar ?as Foo; // null (as fallback)

$null = null;
$null as ?Foo; // null (because it's an accepted value)
$null ?as Foo; // null (as fallback)

A similar suggestion was made in a previous discussion around nullable casts - 
to distinguish between (?int)$foo as "cast to nullable int" and (int?)$foo as 
"cast to int, with null on error".


Note however that combining ?as with ?? is not enough to support "chosen value 
on failure":

$bar = new Bar;
$bar ?as ?Foo ?? Foo::createDefault(); // creates default object

$null = null;
$null ?as ?Foo ?? Foo::createDefault(); // also creates default object, even 
though null is an expected value

That's why my earlier suggestion was to specify the fallback explicitly:

$bar = new Bar;
$bar as ?Foo else null; // null
$bar as ?Foo else Foo::createDefault(); // default object

$null = null;
$nulll as ?Foo else null; // null
$null as ?Foo else Foo::createDefault(); // also null, because it's an accepted 
value, so the fallback is not evaluated

Probably, it should then be an error if the fallback value doesn't meet the 
constraint:

$bar = new Bar;
$bar as Foo else null; // error: fallback value null is not of type Foo
$bar as ?Foo else 42; // error: fallback value 42 is not of type ?Foo



Regards,
-- 
Rowan Tommins
[IMSoP]

Re: [PHP-DEV] [RFC[ Property accessor hooks, take 2

2024-03-21 Thread Robert Landers
Hello,

I'm a bit confused on inheritance. In the following example of a
proxy, do I need to be aware of a parent's hook and handle it
specially?

class Loud
{
public string $name {
get {
return strtoupper($this->name);
}
}
}

class LoudProxy extends Loud
{
   public string $name {
   get {
   // detected the parent has a hook? //
   $return = parent::$name::get();
   // do something with return //
   return $return;
   }
   }
}

what happens if the Loud class later removes its hook implementation
(ex: moves it to the set hook)? Will my proxy now cause an error?

Would simply calling $this->name call the parents hook?

Robert Landers
Software Engineer
Utrecht NL