From 3fbbe935815d596a6b8a76328e9945302cd30476 Mon Sep 17 00:00:00 2001 From: Zdenko Vujasinovic Date: Mon, 7 Aug 2017 07:07:41 +0200 Subject: [PATCH 1/4] expansion-rest bug fix --- lib/coffeescript/rewriter.js | 2 +- src/rewriter.coffee | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/coffeescript/rewriter.js b/lib/coffeescript/rewriter.js index 6e93267007..fbe11c066c 100644 --- a/lib/coffeescript/rewriter.js +++ b/lib/coffeescript/rewriter.js @@ -422,7 +422,7 @@ // Recognize standard implicit calls like // f a, f() b, f? c, h[0] d etc. // Added support for spread dots on the left side: f ...a - if ((indexOf.call(IMPLICIT_FUNC, tag) >= 0 && token.spaced || tag === '?' && i > 0 && !tokens[i - 1].spaced) && (indexOf.call(IMPLICIT_CALL, nextTag) >= 0 || nextTag === '...' || indexOf.call(IMPLICIT_UNSPACED_CALL, nextTag) >= 0 && !nextToken.spaced && !nextToken.newLine)) { + if ((indexOf.call(IMPLICIT_FUNC, tag) >= 0 && token.spaced || tag === '?' && i > 0 && !tokens[i - 1].spaced) && (indexOf.call(IMPLICIT_CALL, nextTag) >= 0 || (nextTag === '...' && !this.findTagsBackwards(i, ['INDEX_START', '['])) || indexOf.call(IMPLICIT_UNSPACED_CALL, nextTag) >= 0 && !nextToken.spaced && !nextToken.newLine)) { if (tag === '?') { tag = token[0] = 'FUNC_EXIST'; } diff --git a/src/rewriter.coffee b/src/rewriter.coffee index b0c4fd55d9..76f8f04946 100644 --- a/src/rewriter.coffee +++ b/src/rewriter.coffee @@ -267,7 +267,8 @@ exports.Rewriter = class Rewriter # Added support for spread dots on the left side: f ...a if (tag in IMPLICIT_FUNC and token.spaced or tag is '?' and i > 0 and not tokens[i - 1].spaced) and - (nextTag in IMPLICIT_CALL or nextTag is '...' or + (nextTag in IMPLICIT_CALL or + (nextTag is '...' and not @findTagsBackwards(i, ['INDEX_START', '['])) or nextTag in IMPLICIT_UNSPACED_CALL and not nextToken.spaced and not nextToken.newLine) tag = token[0] = 'FUNC_EXIST' if tag is '?' From 3d5f8ec1466e0ec9395bb425553577ed39dc7d71 Mon Sep 17 00:00:00 2001 From: Zdenko Vujasinovic Date: Wed, 9 Aug 2017 07:00:33 +0200 Subject: [PATCH 2/4] tests; improved implicit call recognition with dots on the left in the `rewriter` --- lib/coffeescript/rewriter.js | 10 +++++----- src/rewriter.coffee | 2 +- test/slicing_and_splicing.coffee | 12 ++++++++++++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/coffeescript/rewriter.js b/lib/coffeescript/rewriter.js index fbe11c066c..bedb0cb662 100644 --- a/lib/coffeescript/rewriter.js +++ b/lib/coffeescript/rewriter.js @@ -280,7 +280,7 @@ stack = []; start = null; return this.scanTokens(function(token, i, tokens) { - var endImplicitCall, endImplicitObject, forward, implicitObjectContinues, inImplicit, inImplicitCall, inImplicitControl, inImplicitObject, isImplicit, isImplicitCall, isImplicitObject, k, newLine, nextTag, nextToken, offset, prevTag, prevToken, ref, s, sameLine, stackIdx, stackItem, stackTag, stackTop, startIdx, startImplicitCall, startImplicitObject, startsLine, tag; + var endImplicitCall, endImplicitObject, forward, implicitObjectContinues, inImplicit, inImplicitCall, inImplicitControl, inImplicitObject, isImplicit, isImplicitCall, isImplicitObject, k, newLine, nextTag, nextToken, offset, prevTag, prevToken, ref, ref1, s, sameLine, stackIdx, stackItem, stackTag, stackTop, startIdx, startImplicitCall, startImplicitObject, startsLine, tag; [tag] = token; [prevTag] = prevToken = i > 0 ? tokens[i - 1] : []; [nextTag] = nextToken = i < tokens.length - 1 ? tokens[i + 1] : []; @@ -422,7 +422,7 @@ // Recognize standard implicit calls like // f a, f() b, f? c, h[0] d etc. // Added support for spread dots on the left side: f ...a - if ((indexOf.call(IMPLICIT_FUNC, tag) >= 0 && token.spaced || tag === '?' && i > 0 && !tokens[i - 1].spaced) && (indexOf.call(IMPLICIT_CALL, nextTag) >= 0 || (nextTag === '...' && !this.findTagsBackwards(i, ['INDEX_START', '['])) || indexOf.call(IMPLICIT_UNSPACED_CALL, nextTag) >= 0 && !nextToken.spaced && !nextToken.newLine)) { + if ((indexOf.call(IMPLICIT_FUNC, tag) >= 0 && token.spaced || tag === '?' && i > 0 && !tokens[i - 1].spaced) && (indexOf.call(IMPLICIT_CALL, nextTag) >= 0 || (nextTag === '...' && (ref = this.tag(i + 2), indexOf.call(IMPLICIT_CALL, ref) >= 0) && !this.findTagsBackwards(i, ['INDEX_START', '['])) || indexOf.call(IMPLICIT_UNSPACED_CALL, nextTag) >= 0 && !nextToken.spaced && !nextToken.newLine)) { if (tag === '?') { tag = token[0] = 'FUNC_EXIST'; } @@ -456,9 +456,9 @@ if (tag === ':') { // Go back to the (implicit) start of the object. s = (function() { - var ref; + var ref1; switch (false) { - case ref = this.tag(i - 1), indexOf.call(EXPRESSION_END, ref) < 0: + case ref1 = this.tag(i - 1), indexOf.call(EXPRESSION_END, ref1) < 0: return start[1]; case this.tag(i - 2) !== '@': return i - 2; @@ -466,7 +466,7 @@ return i - 1; } }).call(this); - startsLine = s === 0 || (ref = this.tag(s - 1), indexOf.call(LINEBREAKS, ref) >= 0) || tokens[s - 1].newLine; + startsLine = s === 0 || (ref1 = this.tag(s - 1), indexOf.call(LINEBREAKS, ref1) >= 0) || tokens[s - 1].newLine; // Are we just continuing an already declared object? if (stackTop()) { [stackTag, stackIdx] = stackTop(); diff --git a/src/rewriter.coffee b/src/rewriter.coffee index 76f8f04946..a69038a5af 100644 --- a/src/rewriter.coffee +++ b/src/rewriter.coffee @@ -268,7 +268,7 @@ exports.Rewriter = class Rewriter if (tag in IMPLICIT_FUNC and token.spaced or tag is '?' and i > 0 and not tokens[i - 1].spaced) and (nextTag in IMPLICIT_CALL or - (nextTag is '...' and not @findTagsBackwards(i, ['INDEX_START', '['])) or + (nextTag is '...' and @tag(i + 2) in IMPLICIT_CALL and not @findTagsBackwards(i, ['INDEX_START', '['])) or nextTag in IMPLICIT_UNSPACED_CALL and not nextToken.spaced and not nextToken.newLine) tag = token[0] = 'FUNC_EXIST' if tag is '?' diff --git a/test/slicing_and_splicing.coffee b/test/slicing_and_splicing.coffee index a126ec00ed..65bd348540 100644 --- a/test/slicing_and_splicing.coffee +++ b/test/slicing_and_splicing.coffee @@ -64,6 +64,18 @@ test "#1722: operator precedence in unbounded slice compilation", -> test "#2349: inclusive slicing to numeric strings", -> arrayEq [0, 1], [0..10][.."1"] +test "#4631: slicing with space before and/or after the dots", -> + a = (s) -> s + b = [4, 5, 6] + c = [7, 8, 9] + arrayEq [2, 3, 4], shared[2 ... 5] + arrayEq [3, 4, 5], shared[3... 6] + arrayEq [4, 5, 6], shared[4 ...7] + arrayEq shared[(a b...)...(a c...)] , shared[(a ...b)...(a ...c)] + arrayEq shared[(a b...) ... (a c...)], shared[(a ...b) ... (a ...c)] + arrayEq shared[(a b...)... (a c...)] , shared[(a ...b)... (a ...c)] + arrayEq shared[(a b...) ...(a c...)] , shared[(a ...b) ...(a ...c)] + # Splicing From 3b880d052449f4edb2544f6f27786a8e9937f942 Mon Sep 17 00:00:00 2001 From: Zdenko Vujasinovic Date: Fri, 11 Aug 2017 21:49:25 +0200 Subject: [PATCH 3/4] whitespace cleanup --- src/rewriter.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rewriter.coffee b/src/rewriter.coffee index a69038a5af..6fd9e37acd 100644 --- a/src/rewriter.coffee +++ b/src/rewriter.coffee @@ -267,7 +267,7 @@ exports.Rewriter = class Rewriter # Added support for spread dots on the left side: f ...a if (tag in IMPLICIT_FUNC and token.spaced or tag is '?' and i > 0 and not tokens[i - 1].spaced) and - (nextTag in IMPLICIT_CALL or + (nextTag in IMPLICIT_CALL or (nextTag is '...' and @tag(i + 2) in IMPLICIT_CALL and not @findTagsBackwards(i, ['INDEX_START', '['])) or nextTag in IMPLICIT_UNSPACED_CALL and not nextToken.spaced and not nextToken.newLine) From a4dde9c3640a72efbddd348e8a525a2270ca37d6 Mon Sep 17 00:00:00 2001 From: Zdenko Vujasinovic Date: Tue, 15 Aug 2017 01:45:07 +0200 Subject: [PATCH 4/4] more tests --- test/assignment.coffee | 130 +++++++++++++++++++++++++++++++- test/function_invocation.coffee | 33 ++++++++ test/functions.coffee | 67 ++++++++++++++++ test/ranges.coffee | 16 ++++ 4 files changed, 245 insertions(+), 1 deletion(-) diff --git a/test/assignment.coffee b/test/assignment.coffee index 84a728abca..7b5ea8409f 100644 --- a/test/assignment.coffee +++ b/test/assignment.coffee @@ -185,6 +185,12 @@ test "destructuring assignment with splats", -> arrayEq [b,c,d], y eq e, z + # Should not trigger implicit call, e.g. rest ... => rest(...) + [x,y ...,z] = [a,b,c,d,e] + eq a, x + arrayEq [b,c,d], y + eq e, z + test "deep destructuring assignment with splats", -> a={}; b={}; c={}; d={}; e={}; f={}; g={}; h={}; i={} [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", -> eq a, y arrayEq [b,c,d], z + # Should not trigger implicit call, e.g. rest ... => rest(...) + {a: b: [y, z ...]} = obj + eq a, y + arrayEq [b,c,d], z + test "destructuring assignment against an expression", -> a={}; b={} [y, z] = if true then [a, b] else [b, a] @@ -263,6 +274,15 @@ test "destructuring assignment with splats and default values", -> eq b, 1 deepEqual d, {} + # Should not trigger implicit call, e.g. rest ... => rest(...) + { + a: {b} = c + d ... + } = obj + + eq b, 1 + deepEqual d, {} + test "destructuring assignment with splat with default value", -> obj = {} c = {val: 1} @@ -276,6 +296,18 @@ test "destructuring assignment with multiple splats in different objects", -> deepEqual a, val: 1 deepEqual b, val: 2 + # Should not trigger implicit call, e.g. rest ... => rest(...) + { + a: { + a ... + } + b: { + b ... + } + } = obj + deepEqual a, val: 1 + deepEqual b, val: 2 + test "destructuring assignment with dynamic keys and splats", -> i = 0 foo = -> ++i @@ -299,6 +331,15 @@ test "destructuring assignment with objects and splats: Babel tests", -> n = { x, y, z... } deepEqual n, { x: 1, y: 2, a: 3, b: 4 } + # Should not trigger implicit call, e.g. rest ... => rest(...) + { x, y, z ... } = { x: 1, y: 2, a: 3, b: 4 } + eq x, 1 + eq y, 2 + deepEqual z, { a: 3, b: 4 } + + n = { x, y, z ... } + deepEqual n, { x: 1, y: 2, a: 3, b: 4 } + test "deep destructuring assignment with objects: ES2015", -> a1={}; b1={}; c1={}; d1={} obj = { @@ -320,6 +361,13 @@ test "deep destructuring assignment with objects: ES2015", -> eq bb, b1 eq r2.b2, obj.b2 + # Should not trigger implicit call, e.g. rest ... => rest(...) + {a: w, b: {c: {d: {b1: bb, r1 ...}}}, r2 ...} = obj + eq r1.e, c1 + eq r2.b, undefined + eq bb, b1 + eq r2.b2, obj.b2 + test "deep destructuring assignment with defaults: ES2015", -> obj = b: { c: 1, baz: 'qux' } @@ -343,16 +391,55 @@ test "deep destructuring assignment with defaults: ES2015", -> eq hello, 'world' deepEqual h, some: 'prop' + # Should not trigger implicit call, e.g. rest ... => rest(...) + { + a ... + b: { + c, + d ... + } + e: { + f: hello + g: { + h ... + } = i + } = j + } = obj + + deepEqual a, foo: 'bar' + eq c, 1 + deepEqual d, baz: 'qux' + eq hello, 'world' + deepEqual h, some: 'prop' + test "object spread properties: ES2015", -> obj = {a: 1, b: 2, c: 3, d: 4, e: 5} obj2 = {obj..., c:9} eq obj2.c, 9 eq obj.a, obj2.a + # Should not trigger implicit call, e.g. rest ... => rest(...) + obj2 = { + obj ... + c:9 + } + eq obj2.c, 9 + eq obj.a, obj2.a + obj2 = {obj..., a: 8, c: 9, obj...} eq obj2.c, 3 eq obj.a, obj2.a + # Should not trigger implicit call, e.g. rest ... => rest(...) + obj2 = { + obj ... + a: 8 + c: 9 + obj ... + } + eq obj2.c, 3 + eq obj.a, obj2.a + obj3 = {obj..., b: 7, g: {obj2..., c: 1}} eq obj3.g.c, 1 eq obj3.b, 7 @@ -370,10 +457,42 @@ test "object spread properties: ES2015", -> eq obj4.f.g, 5 deepEqual obj4.f, obj.c.f + # Should not trigger implicit call, e.g. rest ... => rest(...) + (({ + a + b + r ... + }) -> + eq 1, a + deepEqual r, {c: 3, d: 44, e: 55} + ) { + obj2 ... + d: 44 + e: 55 + } + + # Should not trigger implicit call, e.g. rest ... => rest(...) + obj4 = { + a: 10 + obj.c ... + } + eq obj4.a, 10 + eq obj4.d, 3 + eq obj4.f.g, 5 + deepEqual obj4.f, obj.c.f + obj5 = {obj..., ((k) -> {b: k})(99)...} eq obj5.b, 99 deepEqual obj5.c, obj.c + # Should not trigger implicit call, e.g. rest ... => rest(...) + obj5 = { + obj ... + ((k) -> {b: k})(99) ... + } + eq obj5.b, 99 + deepEqual obj5.c, obj.c + fn = -> {c: {d: 33, e: 44, f: {g: 55}}} obj6 = {obj..., fn()...} eq obj6.c.d, 33 @@ -382,7 +501,16 @@ test "object spread properties: ES2015", -> obj7 = {obj..., fn()..., {c: {d: 55, e: 66, f: {77}}}...} eq obj7.c.d, 55 deepEqual obj6.c, {d: 33, e: 44, f: {g: 55}} - + + # Should not trigger implicit call, e.g. rest ... => rest(...) + obj7 = { + obj ... + fn() ... + {c: {d: 55, e: 66, f: {77}}} ... + } + eq obj7.c.d, 55 + deepEqual obj6.c, {d: 33, e: 44, f: {g: 55}} + obj = a: b: diff --git a/test/function_invocation.coffee b/test/function_invocation.coffee index 5c8779df94..237633c22c 100644 --- a/test/function_invocation.coffee +++ b/test/function_invocation.coffee @@ -347,12 +347,26 @@ test "passing splats to functions", -> arrayEq [2..6], others eq 7, last + # Should not trigger implicit call, e.g. rest ... => rest(...) + arrayEq [0..4], id id [0..4] ... + fn = (a, b, c ..., d) -> [a, b, c, d] + range = [0..3] + [first, second, others, last] = fn range ..., 4, [5 ... 8] ... + eq 0, first + eq 1, second + arrayEq [2..6], others + eq 7, last + test "splat variables are local to the function", -> outer = "x" clobber = (avar, outer...) -> outer clobber "foo", "bar" eq "x", outer +test "Issue 4631: left and right spread dots with preceding space", -> + a = [] + f = (a) -> a + 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) test "Issue 894: Splatting against constructor-chained functions.", -> @@ -387,6 +401,16 @@ test "splats with super() within classes.", -> super nums... ok (new Child).meth().join(' ') is '3 2 1' + # Should not trigger implicit call, e.g. rest ... => rest(...) + class Parent + meth: (args ...) -> + args + class Child extends Parent + meth: -> + nums = [3, 2, 1] + super nums ... + ok (new Child).meth().join(' ') is '3 2 1' + test "#1011: passing a splat to a method of a number", -> eq '1011', 11.toString [2]... @@ -394,12 +418,21 @@ test "#1011: passing a splat to a method of a number", -> eq '1011', 69.0.toString [4]... eq '1011', (131.0).toString [5]... + # Should not trigger implicit call, e.g. rest ... => rest(...) + eq '1011', 11.toString [2] ... + eq '1011', (31).toString [3] ... + eq '1011', 69.0.toString [4] ... + eq '1011', (131.0).toString [5] ... test "splats and the `new` operator: functions that return `null` should construct their instance", -> args = [] child = new (constructor = -> null) args... ok child instanceof constructor + # Should not trigger implicit call, e.g. rest ... => rest(...) + child = new (constructor = -> null) args ... + ok child instanceof constructor + test "splats and the `new` operator: functions that return functions should construct their return value", -> args = [] fn = -> diff --git a/test/functions.coffee b/test/functions.coffee index 47fa9ae305..34123af5c1 100644 --- a/test/functions.coffee +++ b/test/functions.coffee @@ -110,6 +110,12 @@ test "splats", -> arrayEq [0, 1], (((splat..., _, _1) -> splat) 0, 1, 2, 3) arrayEq [2], (((_, _1, splat..., _2) -> splat) 0, 1, 2, 3) + # Should not trigger implicit call, e.g. rest ... => rest(...) + arrayEq [0, 1, 2], (((splat ...) -> splat) 0, 1, 2) + arrayEq [2, 3], (((_, _1, splat ...) -> splat) 0, 1, 2, 3) + arrayEq [0, 1], (((splat ..., _, _1) -> splat) 0, 1, 2, 3) + arrayEq [2], (((_, _1, splat ..., _2) -> splat) 0, 1, 2, 3) + test "destructured splatted parameters", -> arr = [0,1,2] splatArray = ([a...]) -> a @@ -117,6 +123,10 @@ test "destructured splatted parameters", -> arrayEq splatArray(arr), arr arrayEq splatArrayRest(arr,0,1,2), arr + # Should not trigger implicit call, e.g. rest ... => rest(...) + splatArray = ([a ...]) -> a + splatArrayRest = ([a ...],b ...) -> arrayEq(a,b); b + test "@-parameters: automatically assign an argument's value to a property of the context", -> nonce = {} @@ -127,10 +137,18 @@ test "@-parameters: automatically assign an argument's value to a property of th ((splat..., @prop) ->).apply context = {}, [0, 0, nonce] eq nonce, context.prop + # Should not trigger implicit call, e.g. rest ... => rest(...) + ((splat ..., @prop) ->).apply context = {}, [0, 0, nonce] + eq nonce, context.prop + # Allow the argument itself to be a splat ((@prop...) ->).call context = {}, 0, nonce, 0 eq nonce, context.prop[1] + # Should not trigger implicit call, e.g. rest ... => rest(...) + ((@prop ...) ->).call context = {}, 0, nonce, 0 + eq nonce, context.prop[1] + # The argument should not be able to be referenced normally code = '((@prop) -> prop).call {}' doesNotThrow -> CoffeeScript.compile code @@ -149,12 +167,26 @@ test "@-parameters and splats with constructors", -> eq a, obj.first eq b, obj.last + # Should not trigger implicit call, e.g. rest ... => rest(...) + class Klass + constructor: (@first, splat ..., @last) -> + + obj = new Klass a, 0, 0, b + eq a, obj.first + eq b, obj.last + test "destructuring in function definition", -> (([{a: [b], c}]...) -> eq 1, b eq 2, c ) {a: [1], c: 2} + # Should not trigger implicit call, e.g. rest ... => rest(...) + (([{a: [b], c}] ...) -> + eq 1, b + eq 2, c + ) {a: [1], c: 2} + context = {} (([{a: [b, c = 2], @d, e = 4}]...) -> eq 1, b @@ -198,6 +230,17 @@ test "rest element destructuring in function definition", -> deepEqual r, {c: 3, d: 4, e: 5} ) {a:1, b:2, c:3, d:4, e:5}, 9 + # Should not trigger implicit call, e.g. rest ... => rest(...) + (({ + a: p + b + r ... + }, q) -> + eq p, 1 + eq q, 9 + deepEqual r, {c: 3, d: 4, e: 5} + ) {a:1, b:2, c:3, d:4, e:5}, 9 + a1={}; b1={}; c1={}; d1={} obj1 = { a: a1 @@ -256,6 +299,10 @@ test "#4005: `([a = {}]..., b) ->` weirdness", -> fn = ([a = {}]..., b) -> [a, b] deepEqual fn(5), [{}, 5] + # Should not trigger implicit call, e.g. rest ... => rest(...) + fn = ([a = {}] ..., b) -> [a, b] + deepEqual fn(5), [{}, 5] + test "default values", -> nonceA = {} nonceB = {} @@ -299,6 +346,14 @@ test "default values with splatted arguments", -> eq 1, withSplats(1,1,1) eq 2, withSplats(1,1,1,1) + # Should not trigger implicit call, e.g. rest ... => rest(...) + withSplats = (a = 2, b ..., c = 3, d = 5) -> a * (b.length + 1) * c * d + eq 30, withSplats() + eq 15, withSplats(1) + eq 5, withSplats(1,1) + eq 1, withSplats(1,1,1) + eq 2, withSplats(1,1,1,1) + test "#156: parameter lists with expansion", -> expandArguments = (first, ..., lastButOne, last) -> eq 1, first @@ -328,6 +383,12 @@ test "variable definitions and splat", -> eq 0, a eq 0, b + # Should not trigger implicit call, e.g. rest ... => rest(...) + f = (a, middle ..., b) -> [a, middle, b] + arrayEq [1, [2, 3, 4], 5], f 1, 2, 3, 4, 5 + eq 0, a + eq 0, b + test "default values with function calls", -> doesNotThrow -> CoffeeScript.compile "(x = f()) ->" @@ -354,6 +415,12 @@ test "reserved keyword at-splat", -> eq 1, a eq 2, b + # Should not trigger implicit call, e.g. rest ... => rest(...) + f = (@case ...) -> @case + [a, b] = f(1, 2) + eq 1, a + eq 2, b + test "#1574: Destructuring and a parameter named _arg", -> f = ({a, b}, _arg, _arg1) -> [a, b, _arg, _arg1] arrayEq [1, 2, 3, 4], f a: 1, b: 2, 3, 4 diff --git a/test/ranges.coffee b/test/ranges.coffee index 8de444a83d..a6037c5dd4 100644 --- a/test/ranges.coffee +++ b/test/ranges.coffee @@ -28,6 +28,19 @@ test "basic exclusive ranges", -> arrayEq [], [0...0] arrayEq [], [-1...-1] + # Should not trigger implicit call, e.g. rest ... => rest(...) + arrayEq [1, 2, 3] , [1 ... 4] + arrayEq [0, 1, 2] , [0 ... 3] + arrayEq [0, 1] , [0 ... 2] + arrayEq [0] , [0 ... 1] + arrayEq [-1] , [-1 ... 0] + arrayEq [-1, 0] , [-1 ... 1] + arrayEq [-1, 0, 1], [-1 ... 2] + + arrayEq [], [1 ... 1] + arrayEq [], [0 ... 0] + arrayEq [], [-1 ... -1] + test "downward ranges", -> arrayEq shared, [9..0].reverse() arrayEq [5, 4, 3, 2] , [5..2] @@ -66,6 +79,9 @@ test "ranges with expressions as endpoints", -> arrayEq [2, 3, 4, 5, 6], [(a+1)..2*b] arrayEq [2, 3, 4, 5] , [(a+1)...2*b] + # Should not trigger implicit call, e.g. rest ... => rest(...) + arrayEq [2, 3, 4, 5] , [(a+1) ... 2*b] + test "large ranges are generated with looping constructs", -> down = [99..0] eq 100, (len = down.length)