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

[CS2] Fix #4651: object spread destructuring bug #4652

Merged
merged 8 commits into from
Sep 1, 2017
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions lib/coffeescript/nodes.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 15 additions & 4 deletions src/nodes.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -2229,8 +2229,11 @@ exports.Assign = class Assign extends Base
nestedProperties = prop.value.variable.base.properties
[prop.value.value, nestedSourceDefault] = prop.value.value.cache o
if nestedProperties
nestedSource = new Value source.base, source.properties.concat [new Access getPropKey prop]
nestedSource = new Value new Op '?', nestedSource, nestedSourceDefault if nestedSourceDefault
if source.properties
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this another case that would work better if whatever source is was wrapped in a Value? @helixbass

Alternatively, should the case where there are no properties not wrap source in a Value? I can't really remember how this all works, but in the case that source has no properties, the next level of key won't be added (source.properties.concat [new Access getPropKey prop]). It must be working if the tests pass, but I don't really understand why...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@connec I’m not sure I follow your comment. Is there something that needs to change here?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it's just not clear to me why we need to check if source.properties is set. I imagine this is roughly equivalent to source instanceof Value, and my 2 questions are:

  1. Would it be easier to ensure source is a Value (e.g. in the grammar/wherever source is created)?
  2. If source is not a value (e.g. not source.properties) then we do not execute the line where we concat the property key. Why does this work?

I'll try and find some time to poke around to answer my own questions, since it's probably just my own misunderstandings. Since the tests work etc. this can probably go in, and if I find any issues I can submit a new ticket/fix.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@connec you're right there's some bugginess here, I'm poking around and ran into eg this:

{c:{a...}} = d: 1

compiles to this:

({
  c: {}
} = ref = {
  d: 1
});
a = objectWithoutKeys(ref, []);

This would seem to be a case of nestedSource not getting set correctly (the first arg to objectWithoutKeys() shouldn't be ref, I guess it should be ref.c?)

If I can figure it out I'll push a fix to this branch, otherwise I'll at least push breaking tests

nestedSource = new Value source.base, source.properties.concat [new Access getPropKey prop]
nestedSource = new Value new Op '?', nestedSource, nestedSourceDefault if nestedSourceDefault
else
nestedSource = source
restElements.push traverseRest(nestedProperties, nestedSource)...
else if prop instanceof Splat
prop.error "multiple rest elements are disallowed in object destructuring" if restIndex?
Expand All @@ -2247,8 +2250,16 @@ exports.Assign = class Assign extends Base

restElements

# Cache the value for reuse with rest elements
[@value, valueRef] = @value.cache o
# Cache the value for reuse with rest elements.
# `Obj` should be always cached.
# Examples:
# {a, r...} = {a:1, b:2, c:3}
# {a, r...} = {a:1, obj...}
shouldCache = (value) ->
return yes if value.base instanceof Obj
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we change Obj::shouldCache to always be true? I guess there may be unintended consequences of that, but I can't think of a situation where we'd rather duplicate an object literal rather than reusing it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've already tried that, but then tests fail.
I also think object destructuring with spreads is the only place where this is necessary.
Besides, spreads might soon reach stage-4.

value.shouldCache()

[@value, valueRef] = @value.cache o, false, shouldCache

# Find all rest elements.
restElements = traverseRest @variable.base.properties, valueRef
Expand Down
21 changes: 21 additions & 0 deletions test/assignment.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,27 @@ test "destructuring assignment with multiple splats in different objects", ->
deepEqual a, val: 1
deepEqual b, val: 2

o = {
props: {
p: {
n: 1
m: 5
}
s: 6
}
}
{p: {m}, r...} = o.props
eq m, o.props.p.m
deepEqual r, s: 6

@props = o.props
{p: {m}, r...} = @props
eq m, @props.p.m
deepEqual r, s: 6

{p: {m}, r...} = {o.props..., p:{m:9}}
eq m, 9

# Should not trigger implicit call, e.g. rest ... => rest(...)
{
a: {
Expand Down