proposal-do-expressions icon indicating copy to clipboard operation
proposal-do-expressions copied to clipboard

Extending ParenthesizedExpression to allow Statement

Open rbuckton opened this issue 7 years ago • 14 comments

In the throw expressions proposal we've been discussing the possibility of changing ParenthesizedExpression to use a subset of Statement rather than Expression as a solution to the issues blocking Stage 3 for throw expressions. The grammar would be something like this (with existing references to Statement specifying ~Paren):

CoverParenthesizedExpressionAndArrowParameterList[Yield, Await, Return] :
    `(` Statement[?Yield, ?Await, ?Return, +Paren] `)`
    ...

ParenthesizedExpression[Yield, Await, Return] :
    `(` Statement[?Yield, ?Await, ?Return, +Paren] `)`
    ...

Statement[Yield, Await, Return, Paren] :
    [~Paren] BlockStatement[?Yield, ?Await, ?Return]
    [~Paren] VariableStatement[?Yield, ?Await, ?Return]
    [~Paren] ExpressionStatement[?Yield, ?Await, ?Return]
    IfStatement[?Yield, ?Await, ?Return]
    BreakableStatement[?Yield, ?Await, ?Return]
    ContinueStatement[?Yield, ?Await]
    BreakStatement[?Yield, ?Await]
    [+Return] ReturnStatement[?Yield, ?Await]
    WithStatement[?Yield, ?Await, ?Return]
    LabelledStatement[?Yield, ?Await, ?Return]
    ThrowStatement[?Yield, ?Await, ?Return]
    TryStatement[?Yield, ?Await, ?Return]
    DebuggerStatement
    EmptyStatement

This would allow most of the statements in Statement (excluding Block (since it would still be parsed as ObjectLiteral), declarations, and ExpressionStatement (since it's handled by the Expression case). We could even replace `(` Expression `)` in ParenthesizedExpression and allow a modified ExpressionStatement instead (as ASI rules would allow us to elide the trailing ;). In effect, the only differences between this and do expressions are that it only allows one statement and cannot create a block scope.

If we were to propose this, we would still need a mechanism to handle block scope. That would either be:

  • do {} (possibly as a do statement with an implied while (false))
  • (;{ }) - block with ; before { (odd since we would parse EmptyStatement first and don't allow StatementList here)
  • ({; }) - block with ; after { (definitely not an ObjectLiteral, still a bit strange)

My questions are as follows:

  • Should we propose `(` Statement `)`?
  • Should it be part of this proposal?
  • Should it be its own proposal that subsumes the do and throw expression proposals?

rbuckton avatar Jan 29 '18 19:01 rbuckton

I think it would be more useful to enable using statements as expressions anywhere they would currently result in an error. Including but not limited to:

  • Right side of arrow function (cond => if (cond) { 1 } else { 2 })
  • Right side of equals sign (x = if (cond) { 1 } else { 2 })
  • Argument to function (String(if (cond) { 1 } else { 2 }))

etc

pitaj avatar Jan 29 '18 20:01 pitaj

We ran into issues with this in committee when promoting throw expressions. An arbitrary statement in an expression position most likely won't make it to Stage 3. After discussing this outside of committee, those dissenting opinions to throw expressions would be more permissive of this change assuming the rest of the committee also reaches consensus.

rbuckton avatar Jan 29 '18 20:01 rbuckton

Why would using parens be preferred over do { }?

ljharb avatar Jan 29 '18 21:01 ljharb

@ljharb: no unnecessary block scope, less boilerplate.

rbuckton avatar Jan 29 '18 21:01 rbuckton

do () could avoid the scope; I find the explicit do pretty important to avoid confusion and make intentions clear.

ljharb avatar Jan 29 '18 21:01 ljharb

If do can be used without braces (do if (cond) { 1 } else { 2 }) then it seems like less boilerplate than parenthesis. That is my opinion based on how you must add parenthesis to both ends of the "expression" unlike do.

Edit: on the other hand, parenthesis are used as grouping anyways, so in some case requiring do would result in more characters, since the parenthesis would already be there.

pitaj avatar Jan 29 '18 21:01 pitaj

do () could avoid the scope; I find the explicit do pretty important to avoid confusion and make intentions clear.

Consider that there's little difference between `(` Expression `)` and `(` ExpressionStatement `)` (other than FunctionExpression, ClassExpression, and ObjectLiteral), and you can see how other statements could easily slot in. do {} would likely still be necessary for Block, however. I don't see the do as important in those cases, because const x = do (if (y) z) seems no more or less clear than const x = (if (y) z).

If do can be used without braces (do if (cond) { 1 } else { 2 }) then it seems like less boilerplate than parenthesis. That is my opinion based on how you must add parenthesis to both ends of the "expression" unlike do.

do if (cond) { 1 } else { 2 }
(if (cond) { 1 } else { 2 })

If it matters, using () results in one fewer character. The other problem about do without {} or () is confusion about where a statement might end:

do if (cond) 1; else 2; // is this semi part of `else` or ending the ExpressionStatement?
[x]

vs.

(if (cond) 1; else 2;) // definitely ends the `else`
[x]

rbuckton avatar Jan 29 '18 21:01 rbuckton

If we were to propose this, we would still need a mechanism to handle block scope. That would either be:

do {} (possibly as a do statement with an implied while (false)) (;{ }) - block with ; before { (odd since we would parse EmptyStatement first and don't allow StatementList here) ({; }) - block with ; after { (definitely not an ObjectLiteral, still a bit strange)

How about (={ }) or ({= }) ?

  • block with = before { - looks maybe a bit less odd than ; let x = (={ });
  • block with = after { - its also definitely not an object literal let x = ({= });

c69 avatar Feb 07 '18 09:02 c69

As someone who makes use of do BLOCK in Perl (which functions as described here), I am hoping it gets ratified as proposed. Granted that the mere mention of it also being a Perl feature may just amount to a kiss-of-death.

Where I would find the feature particularly useful is in being able to initialise a const value resulting from a conditional expression that could be more readable when expressed as if ... else if ... else, rather than as a shifty-looking ternary.

const foo = do {
  if (...) {
    'outcomeA';
  } else if (...) {
    'outcomeB';
  } else {
    'outcomeC';
  }
};

Without the do expression, I have to resort to using something mutable:

let foo;
if (...) {
  foo = 'outcomeA';
} else if (...) {
  foo = 'outcomeB';
} else {
  foo = 'outcomeC';
}

One less line, but now I feel kind of dirty.

I've seen similar examples use let but I feel these don't convey the potential value.

perfumister avatar Jul 22 '18 16:07 perfumister

I think introducing a block scope is a major use case for this proposal, so there would need to be syntax for it. And if there is syntax for that, we don't need syntax for anything else, because any statement can be put in a block. So I don't see much advantage to this over the current proposal.

bakkot avatar Jun 01 '20 19:06 bakkot

@bakkot I guess this issue could be adjusted to using const x = (let x = Math.random(); x * x) instead of const x = do { let x = Math.random(); x * x }?

hax avatar Jul 19 '20 07:07 hax

@hax The major problem with that is that it would not be clear whether you were parsing a statement list or an expression until arbitrarily far in. For example, consider

const x = (
  foo(/* arbitrary complexity here */);
  function foo(){}
);

That's a problem both for readers, who now have to look substantially ahead to understand what they're looking at, and for engines, which for performance reasons really would like to be able to parse in a single pass.

It also gets really complicated with object literals: consider the distinction between

const x = (
  {
    foo()
    {
      bar()
    }
  }
) // legal today, makes an object with a `foo` method

and

const x = (
  {
    foo()
    {
      bar()
    }
  };
) // would be a block with two function calls, presumably

bakkot avatar Jan 30 '21 05:01 bakkot

It also gets really complicated with object literals

Great example! I always caught by object literals vs block statement :)

hax avatar Jan 31 '21 05:01 hax

In my original post above, I explicitly call out block scope as I definitely would not want a cover grammar for block vs object literal. Especially since such a cover grammar would be impossible:

{ while(foo) {} }

In a StatementList that's a block with a while loop. In an Expression that's an object literal with a method. There's no cover grammar for that.

Above I mentioned that introducing a true Block would still require do or some other syntax like (; StatementList ).

rbuckton avatar Feb 01 '21 04:02 rbuckton