On 18/12/2025 00:17, Tim Düsterhus wrote:
Yes.
let ($foo, $bar) { … }
is equivalent to
let ($foo) {
let($bar) { … }
}
Yeah, I guess I didn't realise the implications of that equivalence.
From the syntax alone, I vaguely assumed that the two declarations
happened "simultaneously", and didn't think very hard what that meant.
If you can do that, presumably you can do this:
let(
$foo = bar($baz), // What is $baz referring to? Particularly if
it is a by-reference out parameter.
$baz = 1,
)
Which is a direct translation of an example you gave here:
<https://externals.io/message/129059#129583>
The `$baz` in `bar($baz)` is referring to whatever value `$baz` has at
that point in time.
As written, that sentence doesn't really say anything; but from the
context of the discussion, I get what you're trying to say.
The point though is that any answer you give is just a design decision
you've made; and the exact same decision could be made with more
traditional syntax, and apply to the original example.
1. The “temporal dead zone” is not something that can exist.
2. And users do not need to wonder if declarations are hoisted.
This is just plain false. The exact same ambiguity exists, you have just
chosen how to resolve it.
For the example in the email you linked, I am including it here once
more (with an additional $baz = 2 assignment at the start):
$baz = 2;
{
let $foo = bar($baz);
let $baz = 1;
}
1. If there is a temporal dead zone, the call `bar($baz)` is invalid
(throws an Error).
2. If declarations are hoisted, the call to `bar($baz)` could be (1)
an access to an undefined variable (if it behaves as if there was an
`unset($baz)`, which would be behavior that is technically different
from the TDZ). It could also be a valid access to a variable
containing `null` (if all variables are initialized to `null`).
*Theoretically* it could also be `1`, if only constant expressions are
legal and the initializer is also hoisted.
3. If the lifetime of the block-scoped `$baz` only starts at the point
of declaration - effectively an invisible nested block - it behaves as
if it was `bar(2)`, since the current value of `$baz` is `2`.
Agreed.
It's probably worth calling out that (1) is effectively a subset of (2)
designed to avoid the confusion that full hoisting causes. There's also
a variant of (3) where variable shadowing is forbidden, so the "let $baz
= 1" would throw an Error.
And all of those options are available to a comma-separated version as well.
To me the syntax of the `let()` construct very strongly suggests (3)
I don't really see why. As I said above, the comma-separated list made
me think of "simultaneous" action, which I think would imply (1),
because accessing a variable "while it's being declared" would make no
sense.
Option 3 is not necessarily a "wrong" choice, but it's a choice you have
made, and you could equally use more traditional syntax and make that
same choice.
This is what I meant by “there is a less rigid relationship between
the individual statements” in the previous email. Note that it also
said “Forcing all the declarations into a single statement would
resolve that
ambiguity […]”, since that would be isomorphic to the `let()`
construct if the declaration is forced to be at the top of the block.
I don't think separating the declarations with "," vs ";" makes any
difference. The only way to fully avoid the ambiguity is to limit the
initialisers to constant expressions. As soon as you allow "let $foo =
$bar" and "let $bar", with whatever punctuation you choose, there is a
chance for ambiguity about how to resolve "$bar".
The only thing that using commas automatically does is forbids jump
targets (goto labels or switch cases) in between the declarations.
On 18/12/2025 00:21, Tim Düsterhus wrote:
And with that I'm *really* going to be off for vacation.
Have a great break :)
--
Rowan Tommins
[IMSoP]