On Mon, Feb 2, 2026, at 00:41, Morgan wrote:
> Just piping up in a sort of pre-discussion way to see if there might be
> any interest in making "return" an expression - in the same way and for
> many (though not all) of the same reasons that "throw" was made an
> expression in 8.0.
>
> Both return and throw "end the current execution"; the difference is
> that "throw" does it because things went bad, while "return" does it
> because things went well and nothing more needs doing.
>
> So, for example:
>
>
> $result = query('foo') ?? return false;
>
> rather than
>
> if(($result = query('foo')) === null) return false;
>
>
> or
>
> $split = match($len) {
> 0 => throw new UnderflowException("Unexpectedly empty"),
> 1 => return false,
> 2,3,5,7 => return true,
> 4,6,8,9 => $this->smol($len),
> default => $this->foo($len, $scale)
> };
>
>
> instead of
>
> if($len === 0)
> throw new UnderflowException("Unexpectedly empty");
> if($len === 1)
> return false;
> if($len === 2 || $len === 3 || $len === 5 || $len === 7)
> return true;
> if($len === 4 || $len === 6 || $len === 8 || $len === 9)
> $split = $this->smol($len);
> else
> $split = $this->foo($len, $scale);
>
> .
>
> A return expression works entirely by its side-effects (passing control
> and the value of its operand back to the calling scope); its own type is
> "never".
>
> The weirdest behaviour I can see at this early stage is the main way it
> deviates from "reasons to have throw as an expression":
>
> fn($x) => return $x * 2
>
> would work, but not in the way you'd think it does; it expands out to
>
> function($x) {
> return return $x * 2;
> }
>
> Which (like "throw throw") is syntactically legal, but the redundant
> operator is redundant. Semantically, however, it could be problematic:
> The anonymous function's return type is technically "never", since that
> is what the type of its return statement's operand is. But that return
> statement never(!) completes, because said operand causes the function
> to end prematurely, causing the numeric value to be returned instead.
> And, of course, a numeric value is not a "never".
>
> What this will do to type declarations I don't know. But checking that
> the body of an arrow function is a return expression and at least
> notifying the author that it's redundant could be done.
>
> This I guess would also impact ordinary return statements: they are now
> expressions that evaluate to "never", and that could have consequences
> for how return types and return type declarations are determined in
> general...
>
> If necessary, I suppose, the type of a return expression could be that
> of its operand - it's just that no-one will ever see it because
> execution is always abandoned before then.
Hi Morgan,
>From an observability point of view, you lost me when you said return's type
>is "never".
https://3v4l.org/plpIf#v8.5.0
We can clearly see the type is "null", unless you mean something more abstract?
Are you thinking of return as a function call, so you could write it out like:
return(return($x*2))
I guess in that case, you could conceptually think of its arguments as function
that returns what to return? Thus the above is just sugar for:
return function() {
return fn() => $x*2;
}
Which would just collapse down to:
return $x*2;
I think you could handwave it down to that. The removal of the infinite
functions is just an optimisation.
— Rob