proposal-explicit-resource-management icon indicating copy to clipboard operation
proposal-explicit-resource-management copied to clipboard

Is `for (using of = obj;;);` valid syntax?

Open arai-a opened this issue 11 months ago • 7 comments

(discovered in https://bugzilla.mozilla.org/show_bug.cgi?id=1934205 )

In the proposal, ForInOfStatement definition is modified and the using handling is added into ForDeclaration with [Using] parameter, where the parameter is set only after a negative lookahead for ~for of~ using of:

https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-for-in-and-for-of-statements&secAll=true

ForInOfStatement[Yield, Await, Return] :
  for ( [lookahead ≠ let [] LeftHandSideExpression[?Yield, ?Await] in Expression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return]
  for ( var ForBinding[?Yield, ?Await, +Pattern] in Expression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return]
  for ( ForDeclaration[?Yield, ?Await, ~Using] in Expression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return]
  for ( [lookahead ∉ { let, async of }] LeftHandSideExpression[?Yield, ?Await] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return]
  for ( var ForBinding[?Yield, ?Await, +Pattern] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return]
  for ( [lookahead ≠ using of] ForDeclaration[?Yield, ?Await, +Using] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return]
  [+Await] for await ( [lookahead ≠ let] LeftHandSideExpression[?Yield, ?Await] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return]
  [+Await] for await ( var ForBinding[?Yield, ?Await, +Pattern] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return]
  [+Await] for await ( [lookahead ≠ using of] ForDeclaration[?Yield, ?Await, +Using] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return]

ForDeclaration[Yield, Await, Using] :
  LetOrConst ForBinding[?Yield, ?Await, +Pattern]
  [+Using] using [no LineTerminator here] ForBinding[?Yield, ?Await, ~Pattern]
  [+Using, +Await] await [no LineTerminator here] using [no LineTerminator here] ForBinding[?Yield, +Await, ~Pattern]

So, for (using of cannot become a prefix of the for-in/for-of with using declaration, but it can be a prefix of for-of with the using being an identifier in LeftHandSideExpression.

On the other hand, the proposal doesn't touch the ForStatement definition, and the using handling is added into LexicalDeclaration unconditionally.

https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-for-statement&secAll=true

ForStatement[Yield, Await, Return] :
  ...
  for ( LexicalDeclaration[~In, ?Yield, ?Await] Expression[+In, ?Yield, ?Await]opt ; Expression[+In, ?Yield, ?Await]opt ) Statement[?Yield, ?Await, ?Return]

https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-let-const-using-and-await-using-declarations&secAll=true

LexicalDeclaration[In, Yield, Await] :
  LetOrConst BindingList[?In, ?Yield, ?Await, +Pattern] ;
  UsingDeclaration[?In, ?Yield, ?Await]
  [+Await] AwaitUsingDeclaration[?In, ?Yield]

https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-let-const-using-and-await-using-declarations

UsingDeclaration[In, Yield, Await] :
  using [no LineTerminator here] BindingList[?In, ?Yield, ?Await, ~Pattern] ;

AwaitUsingDeclaration[In, Yield] :
  CoverAwaitExpressionAndAwaitUsingDeclarationHead[?Yield] [no LineTerminator here] BindingList[?In, ?Yield, +Await, ~Pattern] ;

This means ForStatement matches for (using of = obj;;);. Is this expected?

TypeScript doesn't recognize the syntax, and Babel, esbuild, Chromium, and SpiderMonkey throw syntax error. Apparently the negative lookahead is applied also to ForStatement in all of them.

If for (using of = obj;;); should throw SyntaxError, the ForStatement syntax should be modified to have a negative lookahead, with propagating the existence of using to LexicalDeclaration, or maybe replacing that part with something similar to ForDeclaration.

arai-a avatar Dec 20 '24 12:12 arai-a

It's valid in for statements, and all of those tools supportint using declarations but not in either/both for statement types are wrong. Here's some related PRs showing that it's clearly intended to be supported.

  • for ... of: https://github.com/tc39/proposal-explicit-resource-management/pull/171
  • C-style for: https://github.com/tc39/proposal-explicit-resource-management/pull/140

dead-claudia avatar Dec 22 '24 08:12 dead-claudia

Just to make sure, here's my understanding and my intent.

Those PRs looks related only to the C-style for + using. I agree that the for + using is valid in the proposal, and all those implementation parses for (using x = obj;;);. This issue is about the of being a binding identifier in the context, so, using being followed by of.

Is it correct that of can be a binding identifier in C-style for + using declaration, and for (using of can be a prefix for both for (using of obj); (regular for-of without using declaration, where using is an identifier) and for (using of = obj;;); (C-style for with using declaration, where of is an identifier) ?

If that's the case, I think there should be some explicit note about that, given that all implementation misinterpreted it.

arai-a avatar Dec 22 '24 08:12 arai-a

@arai-a Oh, sorry, I misunderstood. Here's per the spec as it is:

  • for (using of of a) is not valid: https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-for-in-and-for-of-statements&secAll=true
  • for (using of = expr; cond; update) expr is valid: https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-for-statement&secAll=true

Feels like a spec bug, to be honest. The spec should either reject both or allow both.

IMHO it makes more sense to allow both. Allowing it is as simple as adding explicit productions for for ( using in <expr> ) <stmt>, for ( using of <expr> ) <stmt>, and for ( using of of <expr> ) <stmt> and adding using to the negative lookahead in the for ( <lhs> of <expr> ) <stmt> clause, and then specifying runtime semantics accordingly for each.

for ( using <ident> in <expr> ) <stmt> should be wholly disallowed IMHO and that's probably also a spec oversight.

dead-claudia avatar Dec 22 '24 22:12 dead-claudia

Allowing for (using of of <expr>) is not possible, in term of tokenization and parsing.

If for (using of of <expr>) is allowed, it means for (using of of can be a prefix of both of the following:

  • for (using of <expr starts with of>) : for-of without using declaration, where using being LHS, and the expr being iterated
  • for (using of of <expr>) : for-of with using declaration, where the 1st of being a binding identifier and the expr being iterated

In this case, tokenization (DIV vs RegExp) after the 2nd of conflicts between those cases, and it's not deterministic.

For example, if we have for (using of of /a/g); source, it matches both of the following:

  • for-of without using, where the "of DIV a DIV g" being the iterated value. This is not SyntaxError in the current spec without this proposal.
  • for-of with using, where a RegExp literal /a/g being the iterated value.

Thus, if we're to match the behaviors of C-style for and for-of, the only way would be to reject of being a binding identifier of using declaration in both cases.

arai-a avatar Dec 22 '24 23:12 arai-a

Possible modification would be to first add the Using parameter to LexicalDeclaration, with making both UsingDeclaration and AwaitUsingDeclaration conditional on the parameter:

LexicalDeclaration[In, Yield, Using, Await] :
  LetOrConst BindingList[?In, ?Yield, ?Await, +Pattern] ;
  [+Using] UsingDeclaration[?In, ?Yield, ?Await]
  [+Using, +Await] AwaitUsingDeclaration[?In, ?Yield]

and then split the ForStatement with LexicalDeclaration case into 2, one without Using parameter, and one with Using parameter but only when not starts with using of:

ForStatement[Yield, Await, Return] :
  ...
  for ( LexicalDeclaration[~In, ?Yield, ~Using, ?Await]
        Expression[+In, ?Yield, ?Await]opt ;
        Expression[+In, ?Yield, ?Await]opt ) Statement[?Yield, ?Await, ?Return]
  for ( [lookahead ≠ using of] LexicalDeclaration[~In, ?Yield, +Using, ?Await]
        Expression[+In, ?Yield, ?Await]opt ;
        Expression[+In, ?Yield, ?Await]opt ) Statement[?Yield, ?Await, ?Return]

and then modify the other occurrences of LexicalDeclaration to have +Using parameter.

Or maybe add another symbol that replaces LexicalDeclaration in ForStatement, so that the other existing LexicalDeclaration occurrences aren't affected by this change.

arai-a avatar Dec 23 '24 00:12 arai-a

Allowing for (using of of <expr>) is not possible, in term of tokenization and parsing.

If for (using of of <expr>) is allowed, it means for (using of of can be a prefix of both of the following:

  • for (using of <expr starts with of>) : for-of without using declaration, where using being LHS, and the expr being iterated
  • for (using of of <expr>) : for-of with using declaration, where the 1st of being a binding identifier and the expr being iterated

Oh, whoops, that was the one case I missed. 🤦‍♀️ I knew it'd be possible with some grammar hackery, just was wrong about the specifics.

I still stand by my opinion on either allowing both or prohibiting both, but to be clear, that's not what the current spec says.

dead-claudia avatar Dec 23 '24 03:12 dead-claudia

Also, misread the spec (again): for using ... in isn't valid.

dead-claudia avatar Dec 23 '24 03:12 dead-claudia

I believe the spec is correct: there should be no restriction for the LexicalDeclaration within ForStatement: If for(using of = obj;;;); is invalid while using of = obj is valid, this is a refactoring hazard as moving variable declarations into ForStatement should be generally safe as long as the scope is valid.

Therefore I think it is an implementation bug: I have proposed a fix for Babel in https://github.com/babel/babel/pull/17254. The cost of the fix should be at most a lookahead when the parser sees using of, which is presumably pretty rare in real-world code base.

JLHwung avatar Apr 23 '25 14:04 JLHwung

I've added a test to test262 to verify this: https://github.com/tc39/test262/pull/4483/commits/fc3079a585d827fdd094f323674e1e2d66f531d4

This also appears to be a bug in TypeScript:

Image

rbuckton avatar May 21 '25 22:05 rbuckton

I can verify this is parsed incorrectly in V8 13.6.233.8:

Welcome to Node.js v24.0.2.
Type ".help" for more information.
> for (using x = null;;) break;
undefined
> for (using of = null;;) break;
for (using of = null;;) break;
              ^

Uncaught SyntaxError: Unexpected token '='
> process.versions.v8
'13.6.233.8-node.10'

@syg

rbuckton avatar May 21 '25 22:05 rbuckton

what the hell

syg avatar May 22 '25 20:05 syg

It matches var, let, and const in the same position.

rbuckton avatar May 22 '25 20:05 rbuckton

Is the fix equivalent to another lookahead checking for = or ;?

Edit: Oh I guess just = because it's a const.

syg avatar May 22 '25 20:05 syg

why is "of" allowed as a variable name in JavaScript?


"in" is not allowed

SyntaxError: Unexpected token 'in'

alexsch01 avatar Jun 14 '25 10:06 alexsch01

why is "of" allowed as a variable name in JavaScript?


"in" is not allowed

SyntaxError: Unexpected token 'in'

of was never reserved. Same reason async and let are still allowed. It's just a contextual keyword.

dead-claudia avatar Jun 15 '25 17:06 dead-claudia