Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reality and spec differ on property key resolution timing for o[p] = f() #3295

Open
rkirsling opened this issue Mar 8, 2024 · 14 comments · May be fixed by #3307
Open

Reality and spec differ on property key resolution timing for o[p] = f() #3295

rkirsling opened this issue Mar 8, 2024 · 14 comments · May be fixed by #3307

Comments

@rkirsling
Copy link
Member

rkirsling commented Mar 8, 2024

This test for evaluation order of the assignment operator is quite interesting, as no browser-hosted engines pass it.

Specifically, we see the following:

var o = {};
var p = { toString() { print('p'); return 3; } };
var f = () => { print('f'); return 4; };
o[p] = f();
#### JavaScriptCore, SpiderMonkey, V8
f
p

But the above is a bit misleading. It's not really the case that engines aren't evaluating subexpressions from left to right; what actually differs is the timing of property key resolution:

var o = {};
var p = { toString() { print('resolve property key'); return 3; } };
var f = () => { print('eval rhs'); return 4; };
var pf = () => { print('eval lhs subscript'); return p; };

o[pf()] = f();
#### JavaScriptCore, SpiderMonkey, V8
eval lhs subscript
eval rhs
resolve property key

That is, engines perform ToPropertyKey as part of (get-by-value and) put-by-value, whereas the spec expects it to be performed as part of LHS evaluation.

For comparison, if we change the expression to o[pf()] += f(), we get:

#### V8
eval lhs subscript
resolve property key
eval rhs
resolve property key

#### SpiderMonkey
eval lhs subscript
resolve property key
eval rhs

#### JavaScriptCore
<omitted; we currently match V8 but will match SM soon>

Again, from the engine perspective, property key resolution is part of get- and put-by-value operations, so if we approach this naively, it'll happen twice. To fix this, we front (RequireObjectCoercible and) ToPropertyKey, such that get- and put-by-value ops can be provided with a pre-resolved property key. Regardless, we can consider reality and spec in alignment for "compound" assignment, because the get-by-value precedes RHS evaluation.


To summarize, when encountering the expression o[p] = f():

  • The spec effectively sees put(getReference(o, p), f())
  • Browser-hosted engines effectively see put(o, p, f())

And I'm not sure that I'd call either of these "unintuitive" per se! But I do think the most important question is whether the current spec is web compatible—if it is, it's probably most sensible not to change it.

@bergus
Copy link

bergus commented Mar 8, 2024

I'd be very hesitant to change the evaluation order in the spec, introducing an inconsistency in the standard left-to-right order observed everywhere else. It would make the language harder to understand and learn.

I doubt this is a "web-reality issue" as in "websites would break if browser engines decided to fix this bug in their implementations".

@rkirsling
Copy link
Member Author

I mean, it's 100% reality that this has been the behavior that JS users could consistently rely upon; whether it's compatible to overturn it is an open question (which seems worth finding an answer to).

@devsnek
Copy link
Member

devsnek commented Mar 8, 2024

non-browser engines seem to get this correct, would be unfortunate to change this up on them.

@rkirsling

This comment was marked as outdated.

@rkirsling rkirsling changed the title Reality and spec differ on eval order of o[p] = f() Reality and spec differ on property key resolution timing for o[p] = f() Mar 9, 2024
@ljharb

This comment was marked as outdated.

@rkirsling
Copy link
Member Author

I've rewritten the original post to incorporate the findings of my previous comment. Should be much clearer now, I hope. 🙇🤞

@iainireland
Copy link

From an implementation perspective, resolving the property key as part of the put-by-value allows us to represent them as a single operation, instead of splitting them up. It isn't likely to matter in higher tiers, but the current browser behaviour is likely a little bit faster in interpreters / baseline compilers. All else being equal, I think SpiderMonkey would lean towards updating the spec to match web reality.

@rkirsling
Copy link
Member Author

@syg Any thoughts from V8 here?

@syg
Copy link
Contributor

syg commented Mar 20, 2024

@syg Any thoughts from V8 here?

+1 to @iainireland's comment above. Would prefer spec to match engine reality. The V8 bug for compound assignment is a longstanding one, and we (V8) should fix it, though I can't promise it'll be high priority to do so.

@rkirsling
Copy link
Member Author

Thanks for the feedback! I guess the question then is how we'd want the proposed spec change to look.

Basically, we need to pull ToPropertyKey out of EvaluatePropertyAccessWithExpressionKey. But if we literally do just that, it seems we'll need to redefine what a Reference Record is, such that it's always able to hold a value which has yet to be resolved to a property key...even though this is only needed for = itself.

Alternatively, we could special-case Evaluation of = such that in the non-destructuring, non-"named anonymous function" path, we perform some new "SpecialEvaluation" of LeftHandSideExpression.

One way or another, one of these two sentences can't remain true:

... For example, the left-hand operand of an assignment is expected to produce a Reference Record.

A Reference Record is a resolved name or property binding; ...

@bakkot bakkot added the editor call to be discussed in the next editor call label Mar 26, 2024
@michaelficarra
Copy link
Member

Okay I've confirmed that it's fine to just loosen the type of [[ReferencedName]] in Reference Records and then unconditionally force the value everywhere it's accessed. It doesn't introduce any "interesting" new user code annotation points. I still think it's cleaner to have a separate kind of Reference Record (or a boolean) to track whether the value has been forced to a property key, but either is fine.

@syg
Copy link
Contributor

syg commented Mar 28, 2024

For more context, @bakkot and I prefer loosening the type of [[ReferencedName]] in Reference Records.

@woess
Copy link
Contributor

woess commented Apr 16, 2024

I think this change (i.e. #3307), by side effect, also affects destructuring assignments. Namely, I believe the following test262 tests would have to be updated to reflect the new evaluation order:

  1. language/expressions/assignment/destructuring/keyed-destructuring-property-reference-target-evaluation-order.js
  2. language/expressions/assignment/destructuring/iterator-destructuring-property-reference-target-evaluation-order.js

i.e. for 1. (and similarly for 2.):

({[sourceKey()]: target()[targetKey()]} = source());

evaluation order would change from:
source, source-key, source-key-tostring, target, target-key, target-key-tostring, get, set
to:
source, source-key, source-key-tostring, target, target-key, get, target-key-tostring, set
due to ToPropertyKey now being evaluated as part of 6. Return ? PutValue(lref, rhsValue).

I suppose this is intentional? since it appears to match web reality (for V8 and SM at least).
If you agree, please update these test262 tests, too.

@rkirsling
Copy link
Member Author

Note that everyone disagrees on the former test:

#### expected
source,source-key,source-key-tostring,target,target-key,target-key-tostring,get,set

#### JavaScriptCore
source,source-key,source-key-tostring,get,target,target-key,target-key-tostring,set

#### SpiderMonkey
source,target,target-key,source-key,source-key-tostring,get,target-key-tostring,set

#### V8
source,source-key,source-key-tostring,target,target-key,get,target-key-tostring,set

...but SM and V8 do agree on the latter:

#### expected
source,iterator,target,target-key,target-key-tostring,iterator-step,iterator-done,set

#### JavaScriptCore
source,iterator,iterator-step,iterator-done,target,target-key,target-key-tostring,set

#### SpiderMonkey, V8
source,iterator,target,target-key,iterator-step,iterator-done,target-key-tostring,set

...and I do think you're correct that V8's behavior should be the newly expected one.

rkirsling added a commit to rkirsling/test262 that referenced this issue Apr 18, 2024
rkirsling added a commit to rkirsling/test262 that referenced this issue Apr 18, 2024
rkirsling added a commit to rkirsling/test262 that referenced this issue Apr 18, 2024
ptomato pushed a commit to tc39/test262 that referenced this issue Apr 22, 2024
@bakkot bakkot removed the editor call to be discussed in the next editor call label May 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
9 participants