Skip to content

Commit c8c8c16

Browse files
zdenkoGeoffreyBooth
authored andcommitted
Fix #4843: bad output when assigning to @prop in destructuring assignment with defaults (#4848)
* fix #4843 * improvements * typo * small fix
1 parent c1283ea commit c8c8c16

File tree

3 files changed

+61
-17
lines changed

3 files changed

+61
-17
lines changed

lib/coffeescript/nodes.js

Lines changed: 27 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/nodes.coffee

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2605,14 +2605,24 @@ exports.Code = class Code extends Base
26052605

26062606
# Check for duplicate parameters and separate `this` assignments.
26072607
paramNames = []
2608-
@eachParamName (name, node, param) ->
2608+
@eachParamName (name, node, param, obj) ->
26092609
node.error "multiple parameters named '#{name}'" if name in paramNames
26102610
paramNames.push name
2611+
26112612
if node.this
26122613
name = node.properties[0].name.value
26132614
name = "_#{name}" if name in JS_FORBIDDEN
2614-
target = new IdentifierLiteral o.scope.freeVariable name
2615-
param.renameParam node, target
2615+
target = new IdentifierLiteral o.scope.freeVariable name, reserve: no
2616+
# `Param` is object destructuring with a default value: ({@prop = 1}) ->
2617+
# In a case when the variable name is already reserved, we have to assign
2618+
# a new variable name to the destructured variable: ({prop:prop1 = 1}) ->
2619+
replacement =
2620+
if param.name instanceof Obj and obj instanceof Assign and
2621+
obj.operatorToken.value is '='
2622+
new Assign (new IdentifierLiteral name), target, 'object' #, operatorToken: new Literal ':'
2623+
else
2624+
target
2625+
param.renameParam node, replacement
26162626
thisAssignments.push new Assign node, target
26172627

26182628
# Parse the parameters, adding them to the list of parameters to put in the
@@ -2901,12 +2911,14 @@ exports.Param = class Param extends Base
29012911
# `name` is the name of the parameter and `node` is the AST node corresponding
29022912
# to that name.
29032913
eachName: (iterator, name = @name) ->
2904-
atParam = (obj) => iterator "@#{obj.properties[0].name.value}", obj, @
2914+
atParam = (obj, originalObj = null) => iterator "@#{obj.properties[0].name.value}", obj, @, originalObj
29052915
# * simple literals `foo`
29062916
return iterator name.value, name, @ if name instanceof Literal
29072917
# * at-params `@foo`
29082918
return atParam name if name instanceof Value
29092919
for obj in name.objects ? []
2920+
# Save original obj.
2921+
nObj = obj
29102922
# * destructured parameter with default value
29112923
if obj instanceof Assign and not obj.context?
29122924
obj = obj.variable
@@ -2928,7 +2940,7 @@ exports.Param = class Param extends Base
29282940
@eachName iterator, obj.base
29292941
# * at-params within destructured parameters `{@foo}`
29302942
else if obj.this
2931-
atParam obj
2943+
atParam obj, nObj
29322944
# * simple destructured parameters {foo}
29332945
else iterator obj.base.value, obj.base, @
29342946
else if obj instanceof Elision
@@ -2945,7 +2957,14 @@ exports.Param = class Param extends Base
29452957
if parent instanceof Obj
29462958
key = node
29472959
key = node.properties[0].name if node.this
2948-
new Assign new Value(key), newNode, 'object'
2960+
# No need to assign a new variable for the destructured variable if the variable isn't reserved.
2961+
# Examples:
2962+
# `({@foo}) ->` should compile to `({foo}) { this.foo = foo}`
2963+
# `foo = 1; ({@foo}) ->` should compile to `foo = 1; ({foo:foo1}) { this.foo = foo1 }`
2964+
if node.this and key.value is newNode.value
2965+
new Value newNode
2966+
else
2967+
new Assign new Value(key), newNode, 'object'
29492968
else
29502969
newNode
29512970

test/function_invocation.coffee

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,15 @@ test "Simple Destructuring function arguments with same-named variables in scope
329329
eq f([2]), 2
330330
eq x, 1
331331

332+
test "#4843: Bad output when assigning to @prop in destructuring assignment with defaults", ->
333+
works = "maybe"
334+
drinks = "beer"
335+
class A
336+
constructor: ({@works = 'no', @drinks = 'wine'}) ->
337+
a = new A {works: 'yes', drinks: 'coffee'}
338+
eq a.works, 'yes'
339+
eq a.drinks, 'coffee'
340+
332341
test "caching base value", ->
333342

334343
obj =

0 commit comments

Comments
 (0)