On 2026-02-02 13:18, Rob Landers wrote:
From an observability point of view, you lost me when you said return's
type is "never".
https://3v4l.org/plpIf#v8.5.0 <https://3v4l.org/plpIf#v8.5.0>
We can clearly see the type is "null", unless you mean something more
abstract?
I'm referring to the expression's value as it may exist in a larger
expression, primarily for type checking. To use the match{} example,
what value does $split get when $len == 1, because in that case the
statement boils down to "$split = return false;"? Evaluation never comes
back to the assignment to assign $split anything, in the same way that a
call to a function of type never shouldn't see evaluation coming back to
the call site.
If $split were something still visible after function return ("$o->p"),
since the assignment didn't happen it would still have its previous value.
But as I alluded and you noted, this wouldn't be observable: the caller
gets the value of the return expression's operand (or null, if no such
operand was provided, in which case why are you trying to see the value
of a function of type void?)
If you were doing static analysis, making the type of a return
expression that of its operand could be an issue: in that match{}
example there is no assertion that $split is supposed to contain a
boolean. Let's say it's supposed to be an int. "int|bool" would be an
incorrect inference; since never is a bottom type and thus a subtype of
every type, "int|never" == "int".
On the other hand, "int|Exception" is just as invalid, but throwing
exceptions from inside expressions is pretty well-behaved; I think
modelling return's semantics the same way would work, but I don't know
enough of the internals to make that judgement.
Basically, the type of a return expression would be the type of a throw.