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

Reply via email to