tc39 / proposal-do-expressions

Proposal for `do` expressions
MIT License
1.12k stars 14 forks source link

audit semantically tricky syntactic contexts #3

Open dherman opened 7 years ago

dherman commented 7 years ago

The goal of do-expressions is to be a completely transparent construct, as free of caveats as possible. But there may be some syntactic contexts that create trickiness for the semantics. From the January 2017 meeting, a few interesting cases that came up:

AWB: ... there are a few places where serious considerations are needed about the meaning (argument lists, initialzation expressions of a generator function for parameters and stuff). There is a need to do a semantic review of places this might occur and either decide what the semantics are or to decide in that context.... there are ways to syntactically handle this stuff. ... WH: This also comes up in for loops, e.g., for (do {break;};; ) {} ... WH: If you have a labeled const, then you could have a do expression in the initializer which jumps back to the label. What happens to the variable in that case? If it gets created, what happens when it gets reinitialized?

KG: You can't label a const. That's the point that I wanted to make, you can label var declarations but you can't label lexical ones. You can early exit from a var dec but not a const one. That's very strange.

AWB: That's kind of arbitrary, we didn't understand why you would want ot label any of the new declarations. That could be relatxed if we thought it was needed for consistently

In general it's necessary to audit all the expression contexts in the ES grammar and make sure the semantics for do-expressions (particularly completions, variable scoping, and label scoping) makes sense in each context. In any contexts where it doesn't, we need to figure out what implications that has on the design, either syntactically, semantically, or both.

allenwb commented 7 years ago

During ES6 development I actually did this for the looping constructs and the semantics was included in the ES6 drafts until relatively near completion (it was probably removed in the last half of 2014). One of those draft would probably be a good starting point.

You may or may not agree with the actual semantics I provided. My recollection is that generally, break/continue in the control part of a loop were treated similarly to what occurs if they appear in the loop body. EG.

while (do {continue}) ;

and

while (true) continue;

did essentially the same thing.

I believe that most other non-loop places where a do block containing break/continue/return could occur is already handled by the existing abrupt completion handling run-time logic (that logic already has to deal with exception abrupt completiouns that can occur anywhere).

I also assume that you would want:

foo: while (true) {
   if (true) do {break foo};
}

to have the same behavior as:

foo: while (true) {
   if (true) break foo;
}

In order to detect references to undefined labels from within do expressions, the static semantic routines ContainsUndefinedBreak and ContainsUndefinedContinue need to be extended to recur down all expression positions and not just statement positions. Unfortunately this means that these routines probably also need to be added for all of the expression productions so that the checks propagate to do blocks deeply nested within expressions.

Jamesernator commented 7 years ago

In general having break/continue in the loop head seems a bad idea, might be alright for while loops but for-in/for-of it really just makes no sense:

const it = { 
    next() { done: false, value: 1 }, 
    [Symbol.iterator]() { return this }
}
// The loop is continued before the iterator is even obtained?
for (const i of do { continue ; it }) {

}
// Or worse no iterator at all
for (const i of do { continue }) {

}

Regarding var in do-expressions, I feel like in the weird case of parameters it should add the variable to the environment record for the parameters (see part 28), not for the body (this is what v8's experimental version does already) e.g.:

function foo(x=do { var y = 10 }) {
    const x = 10 // This is allowed
    const y = 20 // I think this should also be allowed
                 // if var y were considered part of the body this would
                 // be a SyntaxError
}

It's not Stage-4 yet but I think it's a case that could open a whole can-of-worms later if not planned for and that's class fields:

class Foo {
    x = do {
        // Where on earth is this hoisted to?
        var y = 20
    }

    z = do {
        y // y visible? Or would each field get its own var-record
    }
}