Re: Return expression

From: Date: Mon, 02 Feb 2026 08:56:52 +0000
Subject: Re: Return expression
References: 1 2  Groups: php.internals 
Request: Send a blank email to internals+get-129976@lists.php.net to get a copy of this message
On Mon, 2 Feb 2026 at 01:20, Rob Landers <rob@bottled.codes> wrote: > > > > 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 Hi Rob, I think never as a result of return makes complete sense. never indicates something does not result in a value, e.g throw or exit where the control flow is given away, return as an expression would do the same thing, it will move a value out of this function scope to the caller, resulting in never within the function. ( the same would apply if break and continue are to be made expressions in the future ). As for return as a function call, i don't think that is needed, once return <expr> is supported as an expression, you would be able to return return("foo"); as that is just return with the RHS being a parenthesized expression; the reason i think return does not make a sense as a "function" is because i think function call rules should not apply to it, e.g what happens if you provide multiple arguments? Does it throw an ArgumentCountError? This is avoidable by making return ("a", "b") a parse error. Cheers. Seifeddine.

« previous php.internals (#129976) next »