On 15/01/2026 09:03, Tim Düsterhus wrote:
Existing constructs in PHP that take a list of multiple “items” behave
equivalent to consecutive single-item entries. Or in less abstract terms:
const FOO = 1, BAR = 2;
is equivalent to:
const FOO = 1;
const BAR = 2;
So to me it is only natural to extend that logic to the `let()`
construct, which only leaves the let($a) let ($b) desugaring which
then implies (3).
There's a jump here. In a grouped "const", "global", or "static"
statement, the expansion is to a *series of statements* with a common
prefix. In your proposed syntax, the expansion is to a *nested set of
blocks*.
That makes the semantics more complicated, and in my opinion more
surprising...
Rather than "const", a better example is probably "static", where this
is legal:
```
$bar = 42;
static $foo=$bar + 1, $bar = 1;
echo "$foo,$bar";
```
It desugars to a series of separate statements:
```
$bar = 42;
static $foo=$bar + 1;
static $bar = 1;
echo "$foo,$bar";
```
The static $bar silently shadows the local one, and you get an output of
43,1
Interestingly, this is *not* legal PHP:
```
static $foo=$bar + 1, $bar = 1, $foo = -1;
```
"Fatal error: Duplicate declaration of static variable $foo"
Note that these are two largely arbitrary design decisions:
- A static variable with the same name as a local one shadows the
previous variable, with no hoisting or dead zone
- A static variable with the same name as an existing static variable is
forbidden
This is exactly matching the proposed semantics of `let()`.
Not quite.
It works for this:
```
$bar = 42;
let($foo=$bar + 1, $bar = 1) {
echo "$foo,$bar";
}
```
The block-scoped $bar shadows the function-local $bar, and the output is
43,1 just like with "static".
But what if we declare the same variable twice:
```
$bar = 42;
let($foo=$bar + 1, $bar = 1, $foo=-1) {
echo "$foo,$bar";
}
```
My intuition from "static", and from every other language I've used, is
that this would be an Error: there's a single block, and you're trying
to declare two different local variables called "$foo" inside that block.
Instead, what happens is that the second declaration of $foo shadows the
first.
The justification is that this is not actually one block at all, it's
secretly a whole set of nested blocks:
```
$bar = 42;
{
let($foo=$bar + 1) {
let($bar = 1) {
let($foo=-1) {
echo "$foo,$bar";
}
}
}
}
```
Yes, but the “more traditional syntax” would also leave the
possibility of (1) or (2) - unless restricted to the start of the
block as in C90.
I suspect this is where we've been talking past each other a bit:
looking at the syntax, I interpreted the comma-separated list as
defining a set of variables in the same scope.
That leaves all the same options as the "traditional" syntax:
$bar = 42;
let($foo=$bar+1, $bar=1) {
echo $foo;
}
- You could treat $bar as "hoisted" within the let() construct, giving
$foo==2
- You could treat $bar as creating a Dead Zone within the let()
construct, giving an Error
- You could treat the declarations as sequential, and shadow the value,
giving $foo==43
What I completely missed, and seemed obvious to you, is that there are
actually two separate scopes being declared, nested inside each other.
(Even so, there is another possibility: variable shadowing could be
forbidden, as in C#, making the "$bar=1" declaration an error).
So I guess it all comes down to the same thing: the syntax just doesn't
seem intuitive to me, because I keep expecting it to behave like JS, C,
Java, Perl, etc.
I can't get past the fact that copying from one of those languages would
be so much more obvious.
--
Rowan Tommins
[IMSoP]