Skip to content

Commit 871079b

Browse files
zdenkoGeoffreyBooth
authored andcommitted
Fix #4876: remove polyfill for object rest/spread (#4884)
* object rest/spread * merge branch 'master' into 'object_rest_spread' * explore * Revert "merge branch 'master' into 'object_rest_spread'" This reverts commit eeeffef. * Revert "explore" This reverts commit d21cd72. * split tests * improvements * unassignable rest property * cleanup * formatting, cleanup * handle non-final object-destructured splat * merge with @helixbass's branch
1 parent be2b243 commit 871079b

File tree

8 files changed

+621
-841
lines changed

8 files changed

+621
-841
lines changed

Cakefile

+1
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,7 @@ runTests = (CoffeeScript) ->
452452
skipUnless 'async () => {}', ['async.coffee', 'async_iterators.coffee']
453453
skipUnless 'async function* generator() { yield 42; }', ['async_iterators.coffee']
454454
skipUnless 'var a = 2 ** 2; a **= 3', ['exponentiation.coffee']
455+
skipUnless 'var {...a} = {}', ['object_rest_spread.coffee']
455456
skipUnless '/foo.bar/s.test("foo\tbar")', ['regex_dotall.coffee']
456457
files = fs.readdirSync('test').filter (filename) ->
457458
filename not in testFilesToSkip

lib/coffeescript/grammar.js

+5-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/coffeescript/nodes.js

+101-236
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/grammar.coffee

+1-1
Original file line numberDiff line numberDiff line change
@@ -216,11 +216,11 @@ grammar =
216216
o 'Identifier'
217217
o 'Property'
218218
o 'ThisProperty'
219-
o '[ Expression ]', -> new Value new ComputedPropertyName $2
220219
]
221220

222221
ObjAssignable: [
223222
o 'SimpleObjAssignable'
223+
o '[ Expression ]', -> new Value new ComputedPropertyName $2
224224
o 'AlphaNumeric'
225225
]
226226

src/nodes.coffee

+48-163
Original file line numberDiff line numberDiff line change
@@ -1489,15 +1489,23 @@ exports.Obj = class Obj extends Base
14891489
return yes for prop in @properties when prop instanceof Splat
14901490
no
14911491

1492+
# Move rest property to the end of the list.
1493+
# `{a, rest..., b} = obj` -> `{a, b, rest...} = obj`
1494+
# `foo = ({a, rest..., b}) ->` -> `foo = {a, b, rest...}) ->`
1495+
reorderProperties: ->
1496+
props = @properties
1497+
splatProps = (i for prop, i in props when prop instanceof Splat)
1498+
props[splatProps[1]].error "multiple spread elements are disallowed" if splatProps?.length > 1
1499+
splatProp = props.splice splatProps[0], 1
1500+
@objects = @properties = [].concat props, splatProp
1501+
14921502
compileNode: (o) ->
1503+
@reorderProperties() if @hasSplat() and @lhs
14931504
props = @properties
14941505
if @generated
14951506
for node in props when node instanceof Value
14961507
node.error 'cannot have an implicit value in an implicit object'
14971508

1498-
# Object spread properties. https://github.com/tc39/proposal-object-rest-spread/blob/master/Spread.md
1499-
return @compileSpread o if @hasSplat() and not @csx
1500-
15011509
idt = o.indent += TAB
15021510
lastNode = @lastNode @properties
15031511

@@ -1558,7 +1566,7 @@ exports.Obj = class Obj extends Base
15581566
else
15591567
# `{ [expression] }` output as `{ [expression]: expression }`.
15601568
prop = new Assign key, prop.base.value, 'object'
1561-
else if not prop.bareLiteral?(IdentifierLiteral)
1569+
else if not prop.bareLiteral?(IdentifierLiteral) and prop not instanceof Splat
15621570
prop = new Assign prop, prop, 'object'
15631571
if indent then answer.push @makeCode indent
15641572
answer.push prop.compileToFragments(o, LEVEL_TOP)...
@@ -1577,30 +1585,6 @@ exports.Obj = class Obj extends Base
15771585
prop = prop.unwrapAll()
15781586
prop.eachName iterator if prop.eachName?
15791587

1580-
# Object spread properties. https://github.com/tc39/proposal-object-rest-spread/blob/master/Spread.md
1581-
# `obj2 = {a: 1, obj..., c: 3, d: 4}` → `obj2 = _extends({}, {a: 1}, obj, {c: 3, d: 4})`
1582-
compileSpread: (o) ->
1583-
props = @properties
1584-
# Store object spreads.
1585-
splatSlice = []
1586-
propSlices = []
1587-
slices = []
1588-
addSlice = ->
1589-
slices.push new Obj propSlices if propSlices.length
1590-
slices.push splatSlice... if splatSlice.length
1591-
splatSlice = []
1592-
propSlices = []
1593-
for prop in props
1594-
if prop instanceof Splat
1595-
splatSlice.push new Value prop.name
1596-
addSlice()
1597-
else
1598-
propSlices.push prop
1599-
addSlice()
1600-
slices.unshift new Obj unless slices[0] instanceof Obj
1601-
_extends = new Value new Literal utility '_extends', o
1602-
(new Call _extends, slices).compileToFragments o
1603-
16041588
compileCSXAttributes: (o) ->
16051589
props = @properties
16061590
answer = []
@@ -2213,12 +2197,11 @@ exports.Assign = class Assign extends Base
22132197
# know that, so that those nodes know that they’re assignable as
22142198
# destructured variables.
22152199
@variable.base.lhs = yes
2216-
# Check if @variable contains Obj with splats.
2217-
hasSplat = @variable.contains (node) -> node instanceof Obj and node.hasSplat()
2218-
return @compileDestructuring o if not @variable.isAssignable() or @variable.isArray() and hasSplat
2219-
# Object destructuring. Can be removed once ES proposal hits Stage 4.
2220-
objDestructAnswer = @compileObjectDestruct(o) if @variable.isObject() and hasSplat
2221-
return objDestructAnswer if objDestructAnswer
2200+
unless @variable.isAssignable()
2201+
if @variable.isObject() and @variable.base.hasSplat()
2202+
return @compileObjectDestruct o
2203+
else
2204+
return @compileDestructuring o
22222205

22232206
return @compileSplice o if @variable.isSplice()
22242207
return @compileConditional o if @context in ['||=', '&&=', '?=']
@@ -2289,92 +2272,18 @@ exports.Assign = class Assign extends Base
22892272
else
22902273
answer
22912274

2292-
# Check object destructuring variable for rest elements;
2293-
# can be removed once ES proposal hits Stage 4.
2275+
# Object rest property is not assignable: `{{a}...}`
22942276
compileObjectDestruct: (o) ->
2295-
# Returns a safe (cached) reference to the key for a given property
2296-
getPropKey = (prop) ->
2297-
if prop instanceof Assign
2298-
[prop.variable, key] = prop.variable.cache o
2299-
key
2300-
else
2301-
prop
2302-
2303-
# Returns the name of a given property for use with excludeProps
2304-
# Property names are quoted (e.g. `a: b` -> 'a'), and everything else uses the key reference
2305-
# (e.g. `'a': b -> 'a'`, `"#{a}": b` -> <cached>`)
2306-
getPropName = (prop) ->
2307-
key = getPropKey prop
2308-
cached = prop instanceof Assign and prop.variable isnt key
2309-
if cached or not key.isAssignable()
2310-
key
2311-
else
2312-
new Literal "'#{key.compileWithoutComments o}'"
2313-
2314-
# Recursive function for searching and storing rest elements in objects.
2315-
# e.g. `{[properties...]} = source`.
2316-
traverseRest = (properties, source) =>
2317-
restElements = []
2318-
restIndex = undefined
2319-
source = new Value source unless source.properties?
2320-
2321-
for prop, index in properties
2322-
nestedSourceDefault = nestedSource = nestedProperties = null
2323-
if prop instanceof Assign
2324-
# prop is `k: expr`, we need to check `expr` for nested splats
2325-
if prop.value.isObject?()
2326-
# prop is `k = {...} `
2327-
continue unless prop.context is 'object'
2328-
# prop is `k: {...}`
2329-
nestedProperties = prop.value.base.properties
2330-
else if prop.value instanceof Assign and prop.value.variable.isObject()
2331-
# prop is `k: {...} = default`
2332-
nestedProperties = prop.value.variable.base.properties
2333-
[prop.value.value, nestedSourceDefault] = prop.value.value.cache o
2334-
if nestedProperties
2335-
nestedSource = new Value source.base, source.properties.concat [new Access getPropKey prop]
2336-
nestedSource = new Value new Op '?', nestedSource, nestedSourceDefault if nestedSourceDefault
2337-
restElements.push traverseRest(nestedProperties, nestedSource)...
2338-
else if prop instanceof Splat
2339-
prop.error "multiple rest elements are disallowed in object destructuring" if restIndex?
2340-
restIndex = index
2341-
restElements.push {
2342-
name: prop.name.unwrapAll()
2343-
source
2344-
excludeProps: new Arr (getPropName p for p in properties when p isnt prop)
2345-
}
2346-
2347-
if restIndex?
2348-
# Remove rest element from the properties after iteration
2349-
properties.splice restIndex, 1
2350-
2351-
restElements
2352-
2353-
# Cache the value for reuse with rest elements.
2354-
valueRefTemp =
2355-
if @value.shouldCache()
2356-
new IdentifierLiteral o.scope.freeVariable 'ref', reserve: false
2357-
else
2358-
@value.base
2359-
2360-
# Find all rest elements.
2361-
restElements = traverseRest @variable.base.properties, valueRefTemp
2362-
return no unless restElements and restElements.length > 0
2363-
2364-
[@value, valueRef] = @value.cache o
2365-
result = new Block [@]
2366-
2367-
for restElement in restElements
2368-
value = new Call new Value(new Literal utility 'objectWithoutKeys', o), [restElement.source, restElement.excludeProps]
2369-
result.push new Assign new Value(restElement.name), value, null, param: if @param then 'alwaysDeclare' else null
2370-
2371-
fragments = result.compileToFragments o
2372-
if o.level is LEVEL_TOP
2373-
# Remove leading tab and trailing semicolon
2374-
fragments.shift()
2375-
fragments.pop()
2376-
2377-
fragments
2277+
@variable.base.reorderProperties()
2278+
{properties: props} = @variable.base
2279+
[..., splat] = props
2280+
splatProp = splat.name
2281+
assigns = []
2282+
refVal = new Value new IdentifierLiteral o.scope.freeVariable 'ref'
2283+
props.splice -1, 1, new Splat refVal
2284+
assigns.push new Assign(new Value(new Obj props), @value).compileToFragments o, LEVEL_LIST
2285+
assigns.push new Assign(new Value(splatProp), refVal).compileToFragments o, LEVEL_LIST
2286+
@joinFragmentArrays assigns, ', '
23782287

23792288
# Brief implementation of recursive pattern matching, when assigning array or
23802289
# object literals to a value. Peeks at their properties to assign inner names.
@@ -2409,12 +2318,19 @@ exports.Assign = class Assign extends Base
24092318

24102319
isSplat = splats?.length > 0
24112320
isExpans = expans?.length > 0
2412-
isObject = @variable.isObject()
2413-
isArray = @variable.isArray()
24142321

24152322
vvar = value.compileToFragments o, LEVEL_LIST
24162323
vvarText = fragmentsToText vvar
24172324
assigns = []
2325+
pushAssign = (variable, val) =>
2326+
assigns.push new Assign(variable, val, null, param: @param, subpattern: yes).compileToFragments o, LEVEL_LIST
2327+
2328+
if isSplat
2329+
splatVar = objects[splats[0]].name.unwrap()
2330+
if splatVar instanceof Arr or splatVar instanceof Obj
2331+
splatVarRef = new IdentifierLiteral o.scope.freeVariable 'ref'
2332+
objects[splats[0]].name = splatVarRef
2333+
splatVarAssign = -> pushAssign new Value(splatVar), splatVarRef
24182334

24192335
# At this point, there are several things to destructure. So the `fn()` in
24202336
# `{a, b} = fn()` must be cached, for example. Make vvar into a simple
@@ -2438,10 +2354,6 @@ exports.Assign = class Assign extends Base
24382354
# Helper which outputs `[].splice` code.
24392355
compSplice = slicer "splice"
24402356

2441-
# Check if `objects` array contains object spread (`{a, r...}`), e.g. `[a, b, {c, r...}]`.
2442-
hasObjSpreads = (objs) ->
2443-
(i for obj, i in objs when obj.base instanceof Obj and obj.base.hasSplat())
2444-
24452357
# Check if `objects` array contains any instance of `Assign`, e.g. {a:1}.
24462358
hasObjAssigns = (objs) ->
24472359
(i for obj, i in objs when obj instanceof Assign and obj.context is 'object')
@@ -2451,15 +2363,14 @@ exports.Assign = class Assign extends Base
24512363
return yes for obj in objs when not obj.isAssignable()
24522364
no
24532365

2454-
# `objects` are complex when there is object spread ({a...}), object assign ({a:1}),
2366+
# `objects` are complex when there is object assign ({a:1}),
24552367
# unassignable object, or just a single node.
24562368
complexObjects = (objs) ->
2457-
hasObjSpreads(objs).length or hasObjAssigns(objs).length or objIsUnassignable(objs) or olen is 1
2369+
hasObjAssigns(objs).length or objIsUnassignable(objs) or olen is 1
24582370

24592371
# "Complex" `objects` are processed in a loop.
24602372
# Examples: [a, b, {c, r...}, d], [a, ..., {b, r...}, c, d]
24612373
loopObjects = (objs, vvar, vvarTxt) =>
2462-
objSpreads = hasObjSpreads objs
24632374
for obj, i in objs
24642375
# `Elision` can be skipped.
24652376
continue if obj instanceof Elision
@@ -2478,20 +2389,19 @@ exports.Assign = class Assign extends Base
24782389
# `obj` is [a...], {a...} or a
24792390
vvar = switch
24802391
when obj instanceof Splat then new Value obj.name
2481-
when i in objSpreads then new Value obj.base
24822392
else obj
24832393
vval = switch
24842394
when obj instanceof Splat then compSlice(vvarTxt, i)
24852395
else new Value new Literal(vvarTxt), [new Index new NumberLiteral i]
24862396
message = isUnassignable vvar.unwrap().value
24872397
vvar.error message if message
2488-
assigns.push new Assign(vvar, vval, null, param: @param, subpattern: yes).compileToFragments o, LEVEL_LIST
2398+
pushAssign vvar, vval
24892399

24902400
# "Simple" `objects` can be split and compiled to arrays, [a, b, c] = arr, [a, b, c...] = arr
24912401
assignObjects = (objs, vvar, vvarTxt) =>
24922402
vvar = new Value new Arr(objs, yes)
24932403
vval = if vvarTxt instanceof Value then vvarTxt else new Value new Literal(vvarTxt)
2494-
assigns.push new Assign(vvar, vval, null, param: @param, subpattern: yes).compileToFragments o, LEVEL_LIST
2404+
pushAssign vvar, vval
24952405

24962406
processObjects = (objs, vvar, vvarTxt) ->
24972407
if complexObjects objs
@@ -2528,6 +2438,7 @@ exports.Assign = class Assign extends Base
25282438
else
25292439
# There is no `Splat` or `Expansion` in `objects`.
25302440
processObjects objects, vvar, vvarText
2441+
splatVarAssign?()
25312442
assigns.push vvar unless top or @subpattern
25322443
fragments = @joinFragmentArrays assigns, ', '
25332444
if o.level < LEVEL_LIST then fragments else @wrapInParentheses fragments
@@ -2689,7 +2600,7 @@ exports.Code = class Code extends Base
26892600
param.error 'an expansion parameter cannot be the only parameter in a function definition'
26902601
haveSplatParam = yes
26912602
if param.splat
2692-
if param.name instanceof Arr
2603+
if param.name instanceof Arr or param.name instanceof Obj
26932604
# Splat arrays are treated oddly by ES; deal with them the legacy
26942605
# way in the function body. TODO: Should this be handled in the
26952606
# function parameter list, and if so, how?
@@ -2743,17 +2654,7 @@ exports.Code = class Code extends Base
27432654
if param.name instanceof Arr or param.name instanceof Obj
27442655
# This parameter is destructured.
27452656
param.name.lhs = yes
2746-
# Compile `foo({a, b...}) ->` to `foo(arg) -> {a, b...} = arg`.
2747-
# Can be removed once ES proposal hits Stage 4.
2748-
if param.name instanceof Obj and param.name.hasSplat()
2749-
splatParamName = o.scope.freeVariable 'arg'
2750-
o.scope.parameter splatParamName
2751-
ref = new Value new IdentifierLiteral splatParamName
2752-
exprs.push new Assign new Value(param.name), ref, null, param: 'alwaysDeclare'
2753-
# Compile `foo({a, b...} = {}) ->` to `foo(arg = {}) -> {a, b...} = arg`.
2754-
if param.value? and not param.assignedInBody
2755-
ref = new Assign ref, param.value, null, param: yes
2756-
else unless param.shouldCache()
2657+
unless param.shouldCache()
27572658
param.name.eachName (prop) ->
27582659
o.scope.parameter prop.value
27592660
else
@@ -3028,7 +2929,10 @@ exports.Splat = class Splat extends Base
30282929

30292930
children: ['name']
30302931

2932+
shouldCache: -> no
2933+
30312934
isAssignable: ->
2935+
return no if @name instanceof Obj or @name instanceof Parens
30322936
@name.isAssignable() and (not @name.isAtomic or @name.isAtomic())
30332937

30342938
assigns: (name) ->
@@ -3883,33 +3787,14 @@ exports.If = class If extends Base
38833787

38843788
UTILITIES =
38853789
modulo: -> 'function(a, b) { return (+a % (b = +b) + b) % b; }'
3886-
objectWithoutKeys: -> "
3887-
function(o, ks) {
3888-
var res = {};
3889-
for (var k in o) ([].indexOf.call(ks, k) < 0 && {}.hasOwnProperty.call(o, k)) && (res[k] = o[k]);
3890-
return res;
3891-
}
3892-
"
3790+
38933791
boundMethodCheck: -> "
38943792
function(instance, Constructor) {
38953793
if (!(instance instanceof Constructor)) {
38963794
throw new Error('Bound instance method accessed before binding');
38973795
}
38983796
}
38993797
"
3900-
_extends: -> "
3901-
Object.assign || function (target) {
3902-
for (var i = 1; i < arguments.length; i++) {
3903-
var source = arguments[i];
3904-
for (var key in source) {
3905-
if (Object.prototype.hasOwnProperty.call(source, key)) {
3906-
target[key] = source[key];
3907-
}
3908-
}
3909-
}
3910-
return target;
3911-
}
3912-
"
39133798

39143799
# Shortcuts to speed up the lookup time for native functions.
39153800
hasProp: -> '{}.hasOwnProperty'

0 commit comments

Comments
 (0)