diff --git a/lib/coffeescript/nodes.js b/lib/coffeescript/nodes.js index 1d5609bd96..1156e970c3 100644 --- a/lib/coffeescript/nodes.js +++ b/lib/coffeescript/nodes.js @@ -3142,7 +3142,7 @@ // we've been assigned to, for correct internal references. If the variable // has not been seen yet within the current scope, declare it. compileNode(o) { - var answer, compiledName, isValue, j, name, properties, prototype, ref1, ref2, ref3, ref4, ref5, val, varBase; + var answer, compiledName, isValue, j, name, objDestructAnswer, properties, prototype, ref1, ref2, ref3, ref4, ref5, val, varBase; isValue = this.variable instanceof Value; if (isValue) { // When compiling `@variable`, remember if it is part of a function parameter. @@ -3163,7 +3163,10 @@ return node instanceof Obj && node.hasSplat(); })) { // Object destructuring. Can be removed once ES proposal hits Stage 4. - return this.compileObjectDestruct(o); + objDestructAnswer = this.compileObjectDestruct(o); + } + if (objDestructAnswer) { + return objDestructAnswer; } } if (this.variable.isSplice()) { @@ -3234,7 +3237,7 @@ // Check object destructuring variable for rest elements; // can be removed once ES proposal hits Stage 4. compileObjectDestruct(o) { - var fragments, getPropKey, getPropName, j, len1, restElement, restElements, result, setScopeVar, traverseRest, value, valueRef; + var fragments, getPropKey, getPropName, j, len1, restElement, restElements, result, setScopeVar, traverseRest, value, valueRef, valueRefTemp; // Per https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Assignment_without_declaration, // if we’re destructuring without declaring, the destructuring assignment // must be wrapped in parentheses: `({a, b} = obj)`. Helper function @@ -3288,11 +3291,19 @@ var base1, index, j, len1, nestedProperties, nestedSource, nestedSourceDefault, p, prop, restElements, restIndex; restElements = []; restIndex = void 0; + if (source.properties == null) { + source = new Value(source); + } for (index = j = 0, len1 = properties.length; j < len1; index = ++j) { prop = properties[index]; + nestedSourceDefault = nestedSource = nestedProperties = null; setScopeVar(prop.unwrap()); if (prop instanceof Assign) { if (typeof (base1 = prop.value).isObject === "function" ? base1.isObject() : void 0) { + if (prop.context !== 'object') { + // prop is `k = {...} ` + continue; + } // prop is `k: {...}` nestedProperties = prop.value.base.properties; } else if (prop.value instanceof Assign && prop.value.variable.isObject()) { @@ -3335,10 +3346,20 @@ } return restElements; }; - // Cache the value for reuse with rest elements - [this.value, valueRef] = this.value.cache(o); + // Cache the value for reuse with rest elements. + if (this.value.shouldCache()) { + valueRefTemp = new IdentifierLiteral(o.scope.freeVariable('ref', { + reserve: false + })); + } else { + valueRefTemp = this.value.base; + } // Find all rest elements. - restElements = traverseRest(this.variable.base.properties, valueRef); + restElements = traverseRest(this.variable.base.properties, valueRefTemp); + if (!(restElements && restElements.length > 0)) { + return false; + } + [this.value, valueRef] = this.value.cache(o); result = new Block([this]); for (j = 0, len1 = restElements.length; j < len1; j++) { restElement = restElements[j]; diff --git a/src/nodes.coffee b/src/nodes.coffee index 677d4df028..2e30bfe4f1 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -2122,8 +2122,9 @@ exports.Assign = class Assign extends Base @variable.base.lhs = yes return @compileDestructuring o unless @variable.isAssignable() # Object destructuring. Can be removed once ES proposal hits Stage 4. - return @compileObjectDestruct(o) if @variable.isObject() and @variable.contains (node) -> + objDestructAnswer = @compileObjectDestruct(o) if @variable.isObject() and @variable.contains (node) -> node instanceof Obj and node.hasSplat() + return objDestructAnswer if objDestructAnswer return @compileSplice o if @variable.isSplice() return @compileConditional o if @context in ['||=', '&&=', '?='] @@ -2216,12 +2217,16 @@ exports.Assign = class Assign extends Base traverseRest = (properties, source) => restElements = [] restIndex = undefined + source = new Value source unless source.properties? for prop, index in properties + nestedSourceDefault = nestedSource = nestedProperties = null setScopeVar prop.unwrap() if prop instanceof Assign # prop is `k: expr`, we need to check `expr` for nested splats if prop.value.isObject?() + # prop is `k = {...} ` + continue unless prop.context is 'object' # prop is `k: {...}` nestedProperties = prop.value.base.properties else if prop.value instanceof Assign and prop.value.variable.isObject() @@ -2247,13 +2252,19 @@ 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. + if @value.shouldCache() + valueRefTemp = new IdentifierLiteral o.scope.freeVariable 'ref', reserve: false + else + valueRefTemp = @value.base # Find all rest elements. - restElements = traverseRest @variable.base.properties, valueRef + restElements = traverseRest @variable.base.properties, valueRefTemp + return no unless restElements and restElements.length > 0 + [@value, valueRef] = @value.cache o result = new Block [@] + for restElement in restElements value = new Call new Value(new Literal utility 'objectWithoutKeys', o), [restElement.source, restElement.excludeProps] result.push new Assign restElement.name, value diff --git a/test/assignment.coffee b/test/assignment.coffee index 7b5ea8409f..1588f23e30 100644 --- a/test/assignment.coffee +++ b/test/assignment.coffee @@ -296,6 +296,29 @@ 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, q..., t = {obj...}}, r...} = o.props + eq m, o.props.p.m + deepEqual r, s: 6 + deepEqual q, n: 1 + deepEqual t, obj + + @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: {