Skip to content

Commit aef54ae

Browse files
zdenkoGeoffreyBooth
authored andcommitted
[CS2] Fix #4631: Expansion that becomes rest parameter causes runtime error (#4634)
* expansion-rest bug fix * tests; improved implicit call recognition with dots on the left in the `rewriter` * whitespace cleanup * more tests
1 parent eff160e commit aef54ae

7 files changed

+264
-7
lines changed

lib/coffeescript/rewriter.js

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

src/rewriter.coffee

+2-1
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,8 @@ exports.Rewriter = class Rewriter
267267
# Added support for spread dots on the left side: f ...a
268268
if (tag in IMPLICIT_FUNC and token.spaced or
269269
tag is '?' and i > 0 and not tokens[i - 1].spaced) and
270-
(nextTag in IMPLICIT_CALL or nextTag is '...' or
270+
(nextTag in IMPLICIT_CALL or
271+
(nextTag is '...' and @tag(i + 2) in IMPLICIT_CALL and not @findTagsBackwards(i, ['INDEX_START', '['])) or
271272
nextTag in IMPLICIT_UNSPACED_CALL and
272273
not nextToken.spaced and not nextToken.newLine)
273274
tag = token[0] = 'FUNC_EXIST' if tag is '?'

test/assignment.coffee

+129-1
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,12 @@ test "destructuring assignment with splats", ->
185185
arrayEq [b,c,d], y
186186
eq e, z
187187

188+
# Should not trigger implicit call, e.g. rest ... => rest(...)
189+
[x,y ...,z] = [a,b,c,d,e]
190+
eq a, x
191+
arrayEq [b,c,d], y
192+
eq e, z
193+
188194
test "deep destructuring assignment with splats", ->
189195
a={}; b={}; c={}; d={}; e={}; f={}; g={}; h={}; i={}
190196
[u, [v, w..., x], y..., z] = [a, [b, c, d, e], f, g, h, i]
@@ -229,6 +235,11 @@ test "destructuring assignment with objects and splats", ->
229235
eq a, y
230236
arrayEq [b,c,d], z
231237

238+
# Should not trigger implicit call, e.g. rest ... => rest(...)
239+
{a: b: [y, z ...]} = obj
240+
eq a, y
241+
arrayEq [b,c,d], z
242+
232243
test "destructuring assignment against an expression", ->
233244
a={}; b={}
234245
[y, z] = if true then [a, b] else [b, a]
@@ -263,6 +274,15 @@ test "destructuring assignment with splats and default values", ->
263274
eq b, 1
264275
deepEqual d, {}
265276

277+
# Should not trigger implicit call, e.g. rest ... => rest(...)
278+
{
279+
a: {b} = c
280+
d ...
281+
} = obj
282+
283+
eq b, 1
284+
deepEqual d, {}
285+
266286
test "destructuring assignment with splat with default value", ->
267287
obj = {}
268288
c = {val: 1}
@@ -276,6 +296,18 @@ test "destructuring assignment with multiple splats in different objects", ->
276296
deepEqual a, val: 1
277297
deepEqual b, val: 2
278298

299+
# Should not trigger implicit call, e.g. rest ... => rest(...)
300+
{
301+
a: {
302+
a ...
303+
}
304+
b: {
305+
b ...
306+
}
307+
} = obj
308+
deepEqual a, val: 1
309+
deepEqual b, val: 2
310+
279311
test "destructuring assignment with dynamic keys and splats", ->
280312
i = 0
281313
foo = -> ++i
@@ -299,6 +331,15 @@ test "destructuring assignment with objects and splats: Babel tests", ->
299331
n = { x, y, z... }
300332
deepEqual n, { x: 1, y: 2, a: 3, b: 4 }
301333

334+
# Should not trigger implicit call, e.g. rest ... => rest(...)
335+
{ x, y, z ... } = { x: 1, y: 2, a: 3, b: 4 }
336+
eq x, 1
337+
eq y, 2
338+
deepEqual z, { a: 3, b: 4 }
339+
340+
n = { x, y, z ... }
341+
deepEqual n, { x: 1, y: 2, a: 3, b: 4 }
342+
302343
test "deep destructuring assignment with objects: ES2015", ->
303344
a1={}; b1={}; c1={}; d1={}
304345
obj = {
@@ -320,6 +361,13 @@ test "deep destructuring assignment with objects: ES2015", ->
320361
eq bb, b1
321362
eq r2.b2, obj.b2
322363

364+
# Should not trigger implicit call, e.g. rest ... => rest(...)
365+
{a: w, b: {c: {d: {b1: bb, r1 ...}}}, r2 ...} = obj
366+
eq r1.e, c1
367+
eq r2.b, undefined
368+
eq bb, b1
369+
eq r2.b2, obj.b2
370+
323371
test "deep destructuring assignment with defaults: ES2015", ->
324372
obj =
325373
b: { c: 1, baz: 'qux' }
@@ -343,16 +391,55 @@ test "deep destructuring assignment with defaults: ES2015", ->
343391
eq hello, 'world'
344392
deepEqual h, some: 'prop'
345393

394+
# Should not trigger implicit call, e.g. rest ... => rest(...)
395+
{
396+
a ...
397+
b: {
398+
c,
399+
d ...
400+
}
401+
e: {
402+
f: hello
403+
g: {
404+
h ...
405+
} = i
406+
} = j
407+
} = obj
408+
409+
deepEqual a, foo: 'bar'
410+
eq c, 1
411+
deepEqual d, baz: 'qux'
412+
eq hello, 'world'
413+
deepEqual h, some: 'prop'
414+
346415
test "object spread properties: ES2015", ->
347416
obj = {a: 1, b: 2, c: 3, d: 4, e: 5}
348417
obj2 = {obj..., c:9}
349418
eq obj2.c, 9
350419
eq obj.a, obj2.a
351420

421+
# Should not trigger implicit call, e.g. rest ... => rest(...)
422+
obj2 = {
423+
obj ...
424+
c:9
425+
}
426+
eq obj2.c, 9
427+
eq obj.a, obj2.a
428+
352429
obj2 = {obj..., a: 8, c: 9, obj...}
353430
eq obj2.c, 3
354431
eq obj.a, obj2.a
355432

433+
# Should not trigger implicit call, e.g. rest ... => rest(...)
434+
obj2 = {
435+
obj ...
436+
a: 8
437+
c: 9
438+
obj ...
439+
}
440+
eq obj2.c, 3
441+
eq obj.a, obj2.a
442+
356443
obj3 = {obj..., b: 7, g: {obj2..., c: 1}}
357444
eq obj3.g.c, 1
358445
eq obj3.b, 7
@@ -370,10 +457,42 @@ test "object spread properties: ES2015", ->
370457
eq obj4.f.g, 5
371458
deepEqual obj4.f, obj.c.f
372459

460+
# Should not trigger implicit call, e.g. rest ... => rest(...)
461+
(({
462+
a
463+
b
464+
r ...
465+
}) ->
466+
eq 1, a
467+
deepEqual r, {c: 3, d: 44, e: 55}
468+
) {
469+
obj2 ...
470+
d: 44
471+
e: 55
472+
}
473+
474+
# Should not trigger implicit call, e.g. rest ... => rest(...)
475+
obj4 = {
476+
a: 10
477+
obj.c ...
478+
}
479+
eq obj4.a, 10
480+
eq obj4.d, 3
481+
eq obj4.f.g, 5
482+
deepEqual obj4.f, obj.c.f
483+
373484
obj5 = {obj..., ((k) -> {b: k})(99)...}
374485
eq obj5.b, 99
375486
deepEqual obj5.c, obj.c
376487

488+
# Should not trigger implicit call, e.g. rest ... => rest(...)
489+
obj5 = {
490+
obj ...
491+
((k) -> {b: k})(99) ...
492+
}
493+
eq obj5.b, 99
494+
deepEqual obj5.c, obj.c
495+
377496
fn = -> {c: {d: 33, e: 44, f: {g: 55}}}
378497
obj6 = {obj..., fn()...}
379498
eq obj6.c.d, 33
@@ -382,7 +501,16 @@ test "object spread properties: ES2015", ->
382501
obj7 = {obj..., fn()..., {c: {d: 55, e: 66, f: {77}}}...}
383502
eq obj7.c.d, 55
384503
deepEqual obj6.c, {d: 33, e: 44, f: {g: 55}}
385-
504+
505+
# Should not trigger implicit call, e.g. rest ... => rest(...)
506+
obj7 = {
507+
obj ...
508+
fn() ...
509+
{c: {d: 55, e: 66, f: {77}}} ...
510+
}
511+
eq obj7.c.d, 55
512+
deepEqual obj6.c, {d: 33, e: 44, f: {g: 55}}
513+
386514
obj =
387515
a:
388516
b:

test/function_invocation.coffee

+33
Original file line numberDiff line numberDiff line change
@@ -347,12 +347,26 @@ test "passing splats to functions", ->
347347
arrayEq [2..6], others
348348
eq 7, last
349349

350+
# Should not trigger implicit call, e.g. rest ... => rest(...)
351+
arrayEq [0..4], id id [0..4] ...
352+
fn = (a, b, c ..., d) -> [a, b, c, d]
353+
range = [0..3]
354+
[first, second, others, last] = fn range ..., 4, [5 ... 8] ...
355+
eq 0, first
356+
eq 1, second
357+
arrayEq [2..6], others
358+
eq 7, last
359+
350360
test "splat variables are local to the function", ->
351361
outer = "x"
352362
clobber = (avar, outer...) -> outer
353363
clobber "foo", "bar"
354364
eq "x", outer
355365

366+
test "Issue 4631: left and right spread dots with preceding space", ->
367+
a = []
368+
f = (a) -> a
369+
eq yes, (f ...a) is (f ... a) is (f a...) is (f a ...) is f(a...) is f(...a) is f(a ...) is f(... a)
356370

357371
test "Issue 894: Splatting against constructor-chained functions.", ->
358372

@@ -387,19 +401,38 @@ test "splats with super() within classes.", ->
387401
super nums...
388402
ok (new Child).meth().join(' ') is '3 2 1'
389403

404+
# Should not trigger implicit call, e.g. rest ... => rest(...)
405+
class Parent
406+
meth: (args ...) ->
407+
args
408+
class Child extends Parent
409+
meth: ->
410+
nums = [3, 2, 1]
411+
super nums ...
412+
ok (new Child).meth().join(' ') is '3 2 1'
413+
390414

391415
test "#1011: passing a splat to a method of a number", ->
392416
eq '1011', 11.toString [2]...
393417
eq '1011', (31).toString [3]...
394418
eq '1011', 69.0.toString [4]...
395419
eq '1011', (131.0).toString [5]...
396420

421+
# Should not trigger implicit call, e.g. rest ... => rest(...)
422+
eq '1011', 11.toString [2] ...
423+
eq '1011', (31).toString [3] ...
424+
eq '1011', 69.0.toString [4] ...
425+
eq '1011', (131.0).toString [5] ...
397426

398427
test "splats and the `new` operator: functions that return `null` should construct their instance", ->
399428
args = []
400429
child = new (constructor = -> null) args...
401430
ok child instanceof constructor
402431

432+
# Should not trigger implicit call, e.g. rest ... => rest(...)
433+
child = new (constructor = -> null) args ...
434+
ok child instanceof constructor
435+
403436
test "splats and the `new` operator: functions that return functions should construct their return value", ->
404437
args = []
405438
fn = ->

0 commit comments

Comments
 (0)