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]

Reply via email to