Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:129973 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by lists.php.net (Postfix) with ESMTPS id 9D9841A00BC for ; Mon, 2 Feb 2026 00:18:56 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1769991541; bh=EWp1hQW8DwCvYtf6JjvMRIiHXnBquz/hAYWuHFHLj8U=; h=Date:From:To:In-Reply-To:References:Subject:From; b=XQNvuGVi6cJjFqhh7diO0P0ZfNbqkscpQbb1kjvQrny+x65tYJSpaF4D2jarb8aIg 2AIi61Ae2U8JajMXZx20S7DDyaX++BAX+3cdQOMfjsqeLsnEWEYspWbniAosfxZgps yjRUSpTPv53XN5koyupX7k81YTXhp/OHeZUou06zJiElc0iIkT3ibjv6bDHjEsx/RV 7qYFkCurn9FZw2ggRQl3Och/sVBwWVtaZF2a9oxRl+q57WRNa0s/vgYV4ymLSebQs2 t/AGtVaYSzYl+YUUKejfLC5KQo1H4iJT0npS2wPzyWHkBAAAqoQ6cIBFn6qN9dANkL ppME1q/ipWDiQ== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 0BDFB180050 for ; Mon, 2 Feb 2026 00:19:00 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-0.1 required=5.0 tests=BAYES_50,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_MISSING,HTML_MESSAGE, RCVD_IN_DNSWL_LOW,SPF_HELO_PASS,SPF_PASS autolearn=no autolearn_force=no version=4.0.1 X-Spam-Virus: No X-Envelope-From: Received: from fhigh-a2-smtp.messagingengine.com (fhigh-a2-smtp.messagingengine.com [103.168.172.153]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Mon, 2 Feb 2026 00:18:59 +0000 (UTC) Received: from phl-compute-12.internal (phl-compute-12.internal [10.202.2.52]) by mailfhigh.phl.internal (Postfix) with ESMTP id 3C8321400064 for ; Sun, 1 Feb 2026 19:18:54 -0500 (EST) Received: from phl-imap-05 ([10.202.2.95]) by phl-compute-12.internal (MEProxy); Sun, 01 Feb 2026 19:18:54 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bottled.codes; h=cc:content-type:content-type:date:date:from:from:in-reply-to :in-reply-to:message-id:mime-version:references:reply-to:subject :subject:to:to; s=fm1; t=1769991534; x=1770077934; bh=q+Q2MlIzjg Ix48m6hQX16SG8pv3GXSk4Pqfjc9AIRnA=; b=HpGETKbN48kSqqn9QV3tBqntmB 20BIolImLBm8lPSesvOCUE0IxDXp8GCIr2jpbWqzGm9O4l+NIBLXLNEmN2fwXbI1 NUPW6101SovKkBR55Nf3OUtxy77yzp7zJVc16zeeOOxCdowjP3C5HdLmShDix+Qr 0m3h9eXy2ZCBrblILQodXE8kqtg0lCJOJGeo4RXTZcXmAd0iq+x/WoWFbRtSTEaG XqP1+qsSDM+ZavjLhHFtokgWbFztOMMcGHEppVp6zjBkX6ez8X7nLfh8WGhM9ZXl Ffkj6m331sPOTWs2zU58twgH4FSG6CnXRpyVwraLZHxhZ05Ska6iS7PR0zjg== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-type:content-type:date:date :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:subject:subject:to :to:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm3; t= 1769991534; x=1770077934; bh=q+Q2MlIzjgIx48m6hQX16SG8pv3GXSk4Pqf jc9AIRnA=; b=hED1DnG+61cT/IyYQLswfCrMBW1+KJZkGOqLJNiJWXENK339Oc2 ZVaJRKcEA9lQ768irhJGRYz57xXxlVjeEvttTMPNhH6sizTUt7OUkJvQVLMjk2Zb j/i/RJmjJgFhaHsdgEADrz5NXH4vOF8+tsUco2w0NCXjR+UgGrEV5fB6nnG3jSOh 4vXkGdeTJeFHuokHmrFshL3TEA6IL+TC+f37JdxSLY5g2kI65w2/MAiz/eQlBoCe K4mbdt4u5bTMZy2O4170xcQEiWzfKyitqLuuE7ChUwyOF1JxQp3OZmicVdL1hPF1 Au1Uk6g/z8WqnaAU/+OFx38P7pmSJvS1wTw== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefgedrtddtgddujeeivdduucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfurfetoffkrfgpnffqhgenuceu rghilhhouhhtmecufedttdenucenucfjughrpefoggffhffvkfgjfhfutgesrgdtreerre dtjeenucfhrhhomhepfdftohgsucfnrghnuggvrhhsfdcuoehrohgssegsohhtthhlvggu rdgtohguvghsqeenucggtffrrghtthgvrhhnpedtiedtvddvvefhudffhfegleffteegff evkeehkeefleeuuddtieevkedvteejvdenucffohhmrghinhepfehvgehlrdhorhhgnecu vehluhhsthgvrhfuihiivgeptdenucfrrghrrghmpehmrghilhhfrhhomheprhhosgessg hothhtlhgvugdrtghouggvshdpnhgspghrtghpthhtohepuddpmhhouggvpehsmhhtphho uhhtpdhrtghpthhtohepihhnthgvrhhnrghlsheslhhishhtshdrphhhphdrnhgvth X-ME-Proxy: Feedback-ID: ifab94697:Fastmail Received: by mailuser.phl.internal (Postfix, from userid 501) id E252B182007A; Sun, 1 Feb 2026 19:18:53 -0500 (EST) X-Mailer: MessagingEngine.com Webmail Interface Precedence: list list-help: list-unsubscribe: list-post: List-Id: x-ms-reactions: disallow MIME-Version: 1.0 X-ThreadId: ApKm5apYHwGF Date: Mon, 02 Feb 2026 01:18:32 +0100 To: internals@lists.php.net Message-ID: In-Reply-To: References: Subject: Re: [PHP-DEV] Return expression Content-Type: multipart/alternative; boundary=37631e07667b44e489d07643f290cde8 From: rob@bottled.codes ("Rob Landers") --37631e07667b44e489d07643f290cde8 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable 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 b= e=20 > any interest in making "return" an expression - in the same way and fo= r=20 > many (though not all) of the same reasons that "throw" was made an=20 > expression in 8.0. >=20 > Both return and throw "end the current execution"; the difference is=20 > that "throw" does it because things went bad, while "return" does it=20 > because things went well and nothing more needs doing. >=20 > So, for example: >=20 >=20 > $result =3D query('foo') ?? return false; >=20 > rather than >=20 > if(($result =3D query('foo')) =3D=3D=3D null) return false; >=20 >=20 > or >=20 > $split =3D match($len) { > 0 =3D> throw new UnderflowException("Unexpectedly empty"), > 1 =3D> return false, > 2,3,5,7 =3D> return true, > 4,6,8,9 =3D> $this->smol($len), > default =3D> $this->foo($len, $scale) > }; >=20 >=20 > instead of >=20 > if($len =3D=3D=3D 0) > throw new UnderflowException("Unexpectedly empty"); > if($len =3D=3D=3D 1) > return false; > if($len =3D=3D=3D 2 || $len =3D=3D=3D 3 || $len =3D=3D=3D 5 || $len =3D= =3D=3D 7) > return true; > if($len =3D=3D=3D 4 || $len =3D=3D=3D 6 || $len =3D=3D=3D 8 || $len =3D= =3D=3D 9) > $split =3D $this->smol($len); > else > $split =3D $this->foo($len, $scale); >=20 > . >=20 > A return expression works entirely by its side-effects (passing contro= l=20 > and the value of its operand back to the calling scope); its own type = is=20 > "never". >=20 > The weirdest behaviour I can see at this early stage is the main way i= t=20 > deviates from "reasons to have throw as an expression": >=20 > fn($x) =3D> return $x * 2 >=20 > would work, but not in the way you'd think it does; it expands out to >=20 > function($x) { > return return $x * 2; > } >=20 > Which (like "throw throw") is syntactically legal, but the redundant=20 > operator is redundant. Semantically, however, it could be problematic:=20 > The anonymous function's return type is technically "never", since tha= t=20 > is what the type of its return statement's operand is. But that return=20 > statement never(!) completes, because said operand causes the function=20 > to end prematurely, causing the numeric value to be returned instead.=20 > And, of course, a numeric value is not a "never". >=20 > What this will do to type declarations I don't know. But checking that=20 > the body of an arrow function is a return expression and at least=20 > notifying the author that it's redundant could be done. >=20 > This I guess would also impact ordinary return statements: they are no= w=20 > expressions that evaluate to "never", and that could have consequences=20 > for how return types and return type declarations are determined in=20 > general... >=20 > If necessary, I suppose, the type of a return expression could be that=20 > of its operand - it's just that no-one will ever see it because=20 > 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 ab= stract? 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 f= unction that returns what to return? Thus the above is just sugar for: return function() { return fn() =3D> $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. =E2=80=94 Rob --37631e07667b44e489d07643f290cde8 Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable


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 inte= rest 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 retu= rn and throw "end the current execution"; the difference is 
<= div>that "throw" does it because things went bad, while "return" does it=  
because things went well and nothing more needs doing.<= /div>

So, for example:


<= /div>
$result =3D query('foo') ?? return false;

=
rather than

if(($result =3D query('foo')) = =3D=3D=3D null) return false;


or=

$split =3D match($len) {
0 =3D> t= hrow new UnderflowException("Unexpectedly empty"),
1 =3D> r= eturn false,
2,3,5,7 =3D> return true,
4,6,8,9 =3D= > $this->smol($len),
default =3D> $this->foo($len,= $scale)
};


instead of=

if($len =3D=3D=3D 0)
throw new Under= flowException("Unexpectedly empty");
if($len =3D=3D=3D 1)
return false;
if($len =3D=3D=3D 2 || $len =3D=3D=3D 3 ||= $len =3D=3D=3D 5 || $len =3D=3D=3D 7)
return true;
= if($len =3D=3D=3D 4 || $len =3D=3D=3D 6 || $len =3D=3D=3D 8 || $len =3D=3D= =3D 9)
$split =3D $this->smol($len);
else
$split =3D $this->foo($len, $scale);

.

A return expression works entirely by its side-ef= fects (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 i= s the main way it 
deviates from "reasons to have throw a= s an expression":

fn($x) =3D> return $x * 2<= /div>

would work, but not in the way you'd think it d= oes; 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:&= nbsp;
The anonymous function's return type is technically "nev= er", since that 
is what the type of its return statement= 's operand is. But that return 
statement never(!) comple= tes, because said operand causes the function 
to end pre= maturely, causing the numeric value to be returned instead. 
<= div>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 expressi= on 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 tha= t 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 op= erand - it's just that no-one will ever see it because 
e= xecution is always abandoned before then.

Hi Morgan,

From an observability point = of view, you lost me when you said return's type is "never".
<= br>

We can clearly see the type i= s "null", unless you mean something more abstract?

<= div>Are you thinking of return as a function call, so you could write it= out like:

return(return($x*2))

<= /div>
I guess in that case, you could conceptually think of its argu= ments as function that returns what to return? Thus the above is just su= gar for:

return function() {
  r= eturn fn() =3D> $x*2;
}

Which woul= d 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.

<= div id=3D"sig121229152">=E2=80=94 Rob
--37631e07667b44e489d07643f290cde8--