Uniform block scoping

Andreas Rossberg rossberg at google.com
Wed Aug 6 05:04:27 PDT 2014


On 31 July 2014 00:05, Brendan Eich <brendan at mozilla.org> wrote:
> Andreas Rossberg wrote:
>> I think this subtle discrepancy is both unfortunate and unnecessary
>> [1]. Moreover, with ES7 do expressions, I would like it to hold that
>>
>>    (...) =>  {...}    ≡    (...) =>  do {...}
>
> I channeled you as best I could, and Dmitry Lomov kindly channeled you on
> this point, but more than a few TC39ers objected that the left arrow
> function, with a body block instead of a body expression, has different
> semantics already, ignoring whether let x; in the body block could shadow a
> parameter x. First, 'return' is the only way to return a result in the left
> example, whereas thanks to do-expression being an expression, the completion
> value (reformed) of the right ... is the return value, even without
> 'return'.

Yeah, I glossed over the return value, but to be precise, it would simply be:

    (...) =>  {...}    ≡    (...) =>  do {...; undefined}


> The general problem is that body blocks are not exactly blocks, due to
> legacy cruft -- and this legacy cannot be separated from 'let' ideals
> because we want programmers to refactor from 'var' to 'let'.
>
> So we must give greater weight (compared to the ideal case of a body block
> just being a block) to refactoring hazards that result in silent but deadly
> bugs rather than early errors.
>
> In particular,
>
> function f(a) {
>   ... // TDZ
>   let a = ...;
>   ...
> }
>
> and
>
> function g() {
>   try {
>     ...
>   } catch (e) {
>     ... // TDZ
>     let e = ...;
>     ...
>   }
> }
>
> should be early errors because there's no useful shadowing going on with
> 'let' -- the TDZ means the outer binding cannot be used in the commented
> places -- but any prior version using 'var' would have worked and possibly
> allowed (coverage-dependent) uses of the "outer" (in catch's case, var
> hoists across the catch head in sloppy code; in the parameter case there's
> only ever one 'a' binding) use in the TDZ.
>
> So an early error does no actual harm in either case, and helps avoid bugs
> slipping past incomplete test coverage.

Well, this is a valid concern, but I'd like to point out that it is a
flawed argument for defending the status quo.

If we really cared about legacy refactoring mistakes going unnoticed,
then the suitable (and consistent) semantics would be to make _all_
intra-function shadowing (or more precisely, multiple declarations) an
error. Otherwise, we are just picking random cases. For example, I
don't see how the above argument can justify the current discrepancy
between these two:

  for (let x ...) { let x; ... }  // fine
  try ... catch(x) { let x; ... }  // error

The former is just as much a potential bug as the latter, or as all
other cases of redeclarations.

/Andreas


More information about the es-discuss mailing list