From 92a5207cdac7259d3770e075bc2962fc8c0e7290 Mon Sep 17 00:00:00 2001 From: Zdenko Vujasinovic Date: Wed, 16 Aug 2017 01:18:01 +0200 Subject: [PATCH 1/9] fix splat error with soak properties or expressions --- lib/coffeescript/nodes.js | 26 ++++++++++++++++++++++++-- src/nodes.coffee | 20 +++++++++++++++++++- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/lib/coffeescript/nodes.js b/lib/coffeescript/nodes.js index 1d5609bd96..bc95b50eeb 100644 --- a/lib/coffeescript/nodes.js +++ b/lib/coffeescript/nodes.js @@ -1702,7 +1702,7 @@ if (content) { fragments.push(this.makeCode('>')); fragments.push(...content.compileNode(o, LEVEL_LIST)); - fragments.push(...[this.makeCode('')]); + fragments.push(...([this.makeCode('')])); } else { fragments.push(this.makeCode(' />')); } @@ -4207,7 +4207,29 @@ } compileToFragments(o) { - return [this.makeCode('...'), ...this.name.compileToFragments(o)]; + var fragment, fragments, ix, j, len1; + // Check if @name is not an instance of `Value` or @name properties contains soak accessor, e.g. ?.b, + // and ensure correct compilation by wrapping the @name in `Parens`. + // Examples: + // [a?.b...] => [(a?.b)...] + // f(a.b?.c...) => f((a.b?.c)...) + // [a if b...] => [(a if b)...] + if (!(this.name instanceof Value) || (!(this.name.base instanceof Parens) && !this.isAssignable())) { + this.name = new Value(new Parens(this.name)); + fragments = this.name.compileToFragments(o); + // We need to replace `void 0` with `[]` in compiled fragments. + // Example: [a?.b...] + // [...(a !== null ? a.b : void 0)] => [...(a !== null ? a.b : [])] + for (ix = j = 0, len1 = fragments.length; j < len1; ix = ++j) { + fragment = fragments[ix]; + if (fragment.code === "void 0" && fragment.type === "If") { + fragments[ix].code = "[]"; + } + } + } else { + fragments = this.name.compileToFragments(o); + } + return [this.makeCode('...'), ...fragments]; } unwrap() { diff --git a/src/nodes.coffee b/src/nodes.coffee index 677d4df028..be197baf24 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -2873,8 +2873,26 @@ exports.Splat = class Splat extends Base @name.assigns name compileToFragments: (o) -> + # Check if @name is not an instance of `Value` or @name properties contains soak accessor, e.g. ?.b, + # and ensure correct compilation by wrapping the @name in `Parens`. + # Examples: + # [a?.b...] => [(a?.b)...] + # f(a.b?.c...) => f((a.b?.c)...) + # [a if b...] => [(a if b)...] + if not (@name instanceof Value) or + (not (@name.base instanceof Parens) and not @isAssignable()) + @name = new Value new Parens @name + fragments = @name.compileToFragments o + # We need to replace `void 0` with `[]` in compiled fragments. + # Example: [a?.b...] + # [...(a !== null ? a.b : void 0)] => [...(a !== null ? a.b : [])] + for fragment, ix in fragments + fragments[ix].code = "[]" if fragment.code == "void 0" and fragment.type == "If" + else + fragments = @name.compileToFragments o + [ @makeCode('...') - @name.compileToFragments(o)... ] + fragments... ] unwrap: -> @name From aed529ffd18fadf3976032e49547af204a810cac Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Tue, 22 Aug 2017 20:23:06 -0700 Subject: [PATCH 2/9] Add test based on #4260 --- test/arrays.coffee | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/arrays.coffee b/test/arrays.coffee index 5c509b28d9..29d5ba9a0d 100644 --- a/test/arrays.coffee +++ b/test/arrays.coffee @@ -38,7 +38,6 @@ test "array splat expansions with assignments", -> test "mixed shorthand objects in array lists", -> - arr = [ a:1 'b' @@ -58,7 +57,6 @@ test "mixed shorthand objects in array lists", -> eq arr[2].b, 1 eq arr[3], 'b' - test "array splats with nested arrays", -> nonce = {} a = [nonce] @@ -70,6 +68,11 @@ test "array splats with nested arrays", -> list = [1, 2, a...] arrayEq list, [1, 2, [nonce]] +test "#4260: splat after existential operator soak", -> + a = {b: [3]} + arrayEq [a?.b...], [3] + arrayEq [c?.b...], [] + test "#1274: `[] = a()` compiles to `false` instead of `a()`", -> a = false fn = -> a = true From 5d08948653b1d643663a50f3f8f7744ee4adc2fc Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Tue, 22 Aug 2017 20:29:03 -0700 Subject: [PATCH 3/9] Add test based on #1349 --- test/arrays.coffee | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/arrays.coffee b/test/arrays.coffee index 29d5ba9a0d..fa8be7c1ab 100644 --- a/test/arrays.coffee +++ b/test/arrays.coffee @@ -73,6 +73,13 @@ test "#4260: splat after existential operator soak", -> arrayEq [a?.b...], [3] arrayEq [c?.b...], [] +test "#1349: trailing if after splat", -> + a = [3] + b = yes + c = null + arrayEq [a if b...], [3] + arrayEq [a if c...], [] + test "#1274: `[] = a()` compiles to `false` instead of `a()`", -> a = false fn = -> a = true From ba50f9ea934fca9a99b61a08b3917b70d7d8be6f Mon Sep 17 00:00:00 2001 From: Zdenko Vujasinovic Date: Wed, 23 Aug 2017 13:26:49 +0200 Subject: [PATCH 4/9] tests for the leading splat variant --- test/arrays.coffee | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/arrays.coffee b/test/arrays.coffee index fa8be7c1ab..f4a9510e21 100644 --- a/test/arrays.coffee +++ b/test/arrays.coffee @@ -72,6 +72,12 @@ test "#4260: splat after existential operator soak", -> a = {b: [3]} arrayEq [a?.b...], [3] arrayEq [c?.b...], [] + arrayEq [...a?.b], [3] + arrayEq [...c?.b], [] + + # Should not trigger implicit call, e.g. rest ... => rest(...) + arrayEq [a?.b ...], [3] + arrayEq [c?.b ...], [] test "#1349: trailing if after splat", -> a = [3] @@ -79,6 +85,12 @@ test "#1349: trailing if after splat", -> c = null arrayEq [a if b...], [3] arrayEq [a if c...], [] + arrayEq [...a if b], [3] + arrayEq [...a if c], [] + + # Should not trigger implicit call, e.g. rest ... => rest(...) + arrayEq [a if b ...], [3] + arrayEq [a if c ...], [] test "#1274: `[] = a()` compiles to `false` instead of `a()`", -> a = false From 47444bf54d19a1ed677f15b3704799999874c767 Mon Sep 17 00:00:00 2001 From: Zdenko Vujasinovic Date: Thu, 24 Aug 2017 12:42:19 +0200 Subject: [PATCH 5/9] test for spaced prefix ... --- test/arrays.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/arrays.coffee b/test/arrays.coffee index f4a9510e21..bea31ada99 100644 --- a/test/arrays.coffee +++ b/test/arrays.coffee @@ -76,6 +76,8 @@ test "#4260: splat after existential operator soak", -> arrayEq [...c?.b], [] # Should not trigger implicit call, e.g. rest ... => rest(...) + arrayEq [... a?.b], [3] + arrayEq [... c?.b], [] arrayEq [a?.b ...], [3] arrayEq [c?.b ...], [] @@ -89,6 +91,8 @@ test "#1349: trailing if after splat", -> arrayEq [...a if c], [] # Should not trigger implicit call, e.g. rest ... => rest(...) + arrayEq [... a if b], [3] + arrayEq [... a if c], [] arrayEq [a if b ...], [3] arrayEq [a if c ...], [] From 777a91666b88e2e21ef0de87678c3a84a4232b02 Mon Sep 17 00:00:00 2001 From: Zdenko Vujasinovic Date: Thu, 24 Aug 2017 13:30:31 +0200 Subject: [PATCH 6/9] fixed 'if' statement in parens --- lib/coffeescript/nodes.js | 2 +- src/nodes.coffee | 3 +-- test/arrays.coffee | 11 +++++++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/coffeescript/nodes.js b/lib/coffeescript/nodes.js index bc95b50eeb..6f58fabc8a 100644 --- a/lib/coffeescript/nodes.js +++ b/lib/coffeescript/nodes.js @@ -4214,7 +4214,7 @@ // [a?.b...] => [(a?.b)...] // f(a.b?.c...) => f((a.b?.c)...) // [a if b...] => [(a if b)...] - if (!(this.name instanceof Value) || (!(this.name.base instanceof Parens) && !this.isAssignable())) { + if (!(this.name instanceof Value) || !this.isAssignable()) { this.name = new Value(new Parens(this.name)); fragments = this.name.compileToFragments(o); // We need to replace `void 0` with `[]` in compiled fragments. diff --git a/src/nodes.coffee b/src/nodes.coffee index be197baf24..bea3173540 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -2879,8 +2879,7 @@ exports.Splat = class Splat extends Base # [a?.b...] => [(a?.b)...] # f(a.b?.c...) => f((a.b?.c)...) # [a if b...] => [(a if b)...] - if not (@name instanceof Value) or - (not (@name.base instanceof Parens) and not @isAssignable()) + if not (@name instanceof Value) or not @isAssignable() @name = new Value new Parens @name fragments = @name.compileToFragments o # We need to replace `void 0` with `[]` in compiled fragments. diff --git a/test/arrays.coffee b/test/arrays.coffee index bea31ada99..e91055d599 100644 --- a/test/arrays.coffee +++ b/test/arrays.coffee @@ -74,12 +74,23 @@ test "#4260: splat after existential operator soak", -> arrayEq [c?.b...], [] arrayEq [...a?.b], [3] arrayEq [...c?.b], [] + e = yes + f = null + arrayEq [(a if e)?.b...], [3] + arrayEq [(a if f)?.b...], [] + arrayEq [...(a if e)?.b], [3] + arrayEq [...(a if f)?.b], [] # Should not trigger implicit call, e.g. rest ... => rest(...) arrayEq [... a?.b], [3] arrayEq [... c?.b], [] arrayEq [a?.b ...], [3] arrayEq [c?.b ...], [] + arrayEq [(a if e)?.b ...], [3] + arrayEq [(a if f)?.b ...], [] + arrayEq [... (a if e)?.b], [3] + arrayEq [... (a if f)?.b], [] + test "#1349: trailing if after splat", -> a = [3] From 81947d1c420adbbd16c9f27b248501edfd3d3450 Mon Sep 17 00:00:00 2001 From: Zdenko Vujasinovic Date: Sat, 26 Aug 2017 11:00:16 +0200 Subject: [PATCH 7/9] fixed replacing 'void 0' with '[]' --- lib/coffeescript/nodes.js | 29 ++++++++++++++------------ src/nodes.coffee | 14 ++++++++----- test/arrays.coffee | 43 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 67 insertions(+), 19 deletions(-) diff --git a/lib/coffeescript/nodes.js b/lib/coffeescript/nodes.js index 6f58fabc8a..5c71d932a8 100644 --- a/lib/coffeescript/nodes.js +++ b/lib/coffeescript/nodes.js @@ -1406,7 +1406,7 @@ } for (j = 0, len1 = props.length; j < len1; j++) { prop = props[j]; - fragments.push(...prop.compileToFragments(o)); + fragments.push(...(prop.compileToFragments(o))); } return fragments; } @@ -1659,7 +1659,7 @@ if (argIndex) { compiledArgs.push(this.makeCode(", ")); } - compiledArgs.push(...arg.compileToFragments(o, LEVEL_LIST)); + compiledArgs.push(...(arg.compileToFragments(o, LEVEL_LIST))); } fragments = []; if (this.isNew) { @@ -1702,7 +1702,7 @@ if (content) { fragments.push(this.makeCode('>')); fragments.push(...content.compileNode(o, LEVEL_LIST)); - fragments.push(...([this.makeCode('')])); + fragments.push(...[this.makeCode('')]); } else { fragments.push(this.makeCode(' />')); } @@ -3854,7 +3854,7 @@ // Create a destructured assignment, e.g. `[a, b, c] = [args..., b, c]` exprs.unshift(new Assign(new Value(new Arr([ new Splat(new IdentifierLiteral(splatParamName)), - ...(function() { + ...((function() { var k, len2, results; results = []; for (k = 0, len2 = paramsAfterSplat.length; k < len2; k++) { @@ -3862,7 +3862,7 @@ results.push(param.asReference(o)); } return results; - })() + })()) ])), new Value(new IdentifierLiteral(splatParamName)))); } // Add new expressions to the function body @@ -4207,7 +4207,7 @@ } compileToFragments(o) { - var fragment, fragments, ix, j, len1; + var flen, fragment, fragments, ix, j, len1; // Check if @name is not an instance of `Value` or @name properties contains soak accessor, e.g. ?.b, // and ensure correct compilation by wrapping the @name in `Parens`. // Examples: @@ -4215,15 +4215,18 @@ // f(a.b?.c...) => f((a.b?.c)...) // [a if b...] => [(a if b)...] if (!(this.name instanceof Value) || !this.isAssignable()) { - this.name = new Value(new Parens(this.name)); - fragments = this.name.compileToFragments(o); + fragments = this.name.compileToFragments(o, LEVEL_OP); // We need to replace `void 0` with `[]` in compiled fragments. - // Example: [a?.b...] - // [...(a !== null ? a.b : void 0)] => [...(a !== null ? a.b : [])] + // Examples: + // - [a?.b...] + // [...(a !== null ? a.b : void 0)] => [...(a !== null ? a.b : [])] + // - f(a?b...) + // f(...(typeof c !== "undefined" && c !== null ? c.b : [])) + flen = fragments.length; for (ix = j = 0, len1 = fragments.length; j < len1; ix = ++j) { fragment = fragments[ix]; - if (fragment.code === "void 0" && fragment.type === "If") { - fragments[ix].code = "[]"; + if (fragment.type === 'If' && fragment.code === 'void 0' && ix + 1 < flen && fragments[ix + 1].type !== 'Call') { + fragments[ix].code = '[]'; } } } else { @@ -5380,7 +5383,7 @@ fragments.push(cond.makeCode(idt2 + 'break;\n')); } if (this.otherwise && this.otherwise.expressions.length) { - fragments.push(this.makeCode(idt1 + "default:\n"), ...this.otherwise.compileToFragments(o, LEVEL_TOP), this.makeCode("\n")); + fragments.push(this.makeCode(idt1 + "default:\n"), ...(this.otherwise.compileToFragments(o, LEVEL_TOP)), this.makeCode("\n")); } fragments.push(this.makeCode(this.tab + '}')); return fragments; diff --git a/src/nodes.coffee b/src/nodes.coffee index bea3173540..0728b0542c 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -2880,13 +2880,17 @@ exports.Splat = class Splat extends Base # f(a.b?.c...) => f((a.b?.c)...) # [a if b...] => [(a if b)...] if not (@name instanceof Value) or not @isAssignable() - @name = new Value new Parens @name - fragments = @name.compileToFragments o + fragments = @name.compileToFragments o, LEVEL_OP # We need to replace `void 0` with `[]` in compiled fragments. - # Example: [a?.b...] - # [...(a !== null ? a.b : void 0)] => [...(a !== null ? a.b : [])] + # Examples: + # - [a?.b...] + # [...(a !== null ? a.b : void 0)] => [...(a !== null ? a.b : [])] + # - f(a?b...) + # f(...(typeof c !== "undefined" && c !== null ? c.b : [])) + flen = fragments.length for fragment, ix in fragments - fragments[ix].code = "[]" if fragment.code == "void 0" and fragment.type == "If" + fragments[ix].code = '[]' if fragment.type is 'If' and fragment.code is 'void 0' and + ix + 1 < flen and fragments[ix + 1].type isnt 'Call' else fragments = @name.compileToFragments o diff --git a/test/arrays.coffee b/test/arrays.coffee index e91055d599..ca54d85818 100644 --- a/test/arrays.coffee +++ b/test/arrays.coffee @@ -70,16 +70,43 @@ test "array splats with nested arrays", -> test "#4260: splat after existential operator soak", -> a = {b: [3]} + foo = (a) -> [a] ? [] + arrayEq [a?.b...], [3] arrayEq [c?.b...], [] arrayEq [...a?.b], [3] arrayEq [...c?.b], [] + arrayEq foo(a?.b...), [3] + arrayEq foo(...a?.b), [3] + arrayEq [foo(a?.b)...], [a.b] # `void 0` should not be replaced with `[]` + arrayEq [...foo(a?.b)], [a.b] # `void 0` should not be replaced with `[]` + arrayEq [foo(a?.b...)...], [3] + arrayEq [...foo(...a?.b)], [3] + arrayEq foo(c?.b...), [undefined] + arrayEq foo(...c?.b), [undefined] + arrayEq [foo(c?.b)...], [undefined] # `void 0` should not be replaced with `[]` + arrayEq [...foo(c?.b)], [undefined] # `void 0` should not be replaced with `[]` + arrayEq [foo(c?.b...)...], [undefined] + arrayEq [...foo(...c?.b)], [undefined] + e = yes f = null arrayEq [(a if e)?.b...], [3] arrayEq [(a if f)?.b...], [] arrayEq [...(a if e)?.b], [3] arrayEq [...(a if f)?.b], [] + arrayEq foo((a if e)?.b...), [3] + arrayEq foo(...(a if e)?.b), [3] + arrayEq [foo((a if e)?.b)...], [a.b] # `void 0` should not be replaced with `[]` + arrayEq [...foo((a if e)?.b)], [a.b] # `void 0` should not be replaced with `[]` + arrayEq [foo((a if e)?.b...)...], [3] + arrayEq [...foo(...(a if e)?.b)], [3] + arrayEq foo((a if f)?.b...), [undefined] + arrayEq foo(...(a if f)?.b), [undefined] + arrayEq [foo((a if f)?.b)...], [undefined] # `void 0` should not be replaced with `[]` + arrayEq [...foo((a if f)?.b)], [undefined] # `void 0` should not be replaced with `[]` + arrayEq [foo((a if f)?.b...)...], [undefined] + arrayEq [...foo(...(a if f)?.b)], [undefined] # Should not trigger implicit call, e.g. rest ... => rest(...) arrayEq [... a?.b], [3] @@ -90,16 +117,30 @@ test "#4260: splat after existential operator soak", -> arrayEq [(a if f)?.b ...], [] arrayEq [... (a if e)?.b], [3] arrayEq [... (a if f)?.b], [] - + arrayEq foo(a?.b ...), [3] + arrayEq foo(... a?.b), [3] test "#1349: trailing if after splat", -> a = [3] b = yes c = null + foo = (a) -> [a] arrayEq [a if b...], [3] arrayEq [a if c...], [] arrayEq [...a if b], [3] arrayEq [...a if c], [] + arrayEq foo((a if b)...), [3] + arrayEq foo(...(a if b)), [3] + arrayEq [foo((a if b))...], [a] # `void 0` should not be replaced with `[]` + arrayEq [...foo((a if b))], [a] # `void 0` should not be replaced with `[]` + arrayEq [foo((a if b)...)...], [3] + arrayEq [...foo(...(a if b))], [3] + arrayEq foo((a if c)...), [undefined] + arrayEq foo(...(a if c)), [undefined] + arrayEq [foo((a if c))...], [[]] # `void 0` should not be replaced with `[]` + arrayEq [...foo((a if c))], [[]] # `void 0` should not be replaced with `[]` + arrayEq [foo((a if c)...)...], [undefined] + arrayEq [...foo(...(a if c))], [undefined] # Should not trigger implicit call, e.g. rest ... => rest(...) arrayEq [... a if b], [3] From 2efeaff952eb16543c662e2a04b7ad2c7878c882 Mon Sep 17 00:00:00 2001 From: Zdenko Vujasinovic Date: Sun, 27 Aug 2017 08:40:34 +0200 Subject: [PATCH 8/9] remove 'void 0' replacement; add Splat::compileNode --- lib/coffeescript/nodes.js | 39 +++++-------------------- src/nodes.coffee | 25 ++-------------- test/arrays.coffee | 60 +++++++++------------------------------ 3 files changed, 23 insertions(+), 101 deletions(-) diff --git a/lib/coffeescript/nodes.js b/lib/coffeescript/nodes.js index 5c71d932a8..bdb9dee5aa 100644 --- a/lib/coffeescript/nodes.js +++ b/lib/coffeescript/nodes.js @@ -1406,7 +1406,7 @@ } for (j = 0, len1 = props.length; j < len1; j++) { prop = props[j]; - fragments.push(...(prop.compileToFragments(o))); + fragments.push(...prop.compileToFragments(o)); } return fragments; } @@ -1659,7 +1659,7 @@ if (argIndex) { compiledArgs.push(this.makeCode(", ")); } - compiledArgs.push(...(arg.compileToFragments(o, LEVEL_LIST))); + compiledArgs.push(...arg.compileToFragments(o, LEVEL_LIST)); } fragments = []; if (this.isNew) { @@ -3854,7 +3854,7 @@ // Create a destructured assignment, e.g. `[a, b, c] = [args..., b, c]` exprs.unshift(new Assign(new Value(new Arr([ new Splat(new IdentifierLiteral(splatParamName)), - ...((function() { + ...(function() { var k, len2, results; results = []; for (k = 0, len2 = paramsAfterSplat.length; k < len2; k++) { @@ -3862,7 +3862,7 @@ results.push(param.asReference(o)); } return results; - })()) + })() ])), new Value(new IdentifierLiteral(splatParamName)))); } // Add new expressions to the function body @@ -4206,33 +4206,8 @@ return this.name.assigns(name); } - compileToFragments(o) { - var flen, fragment, fragments, ix, j, len1; - // Check if @name is not an instance of `Value` or @name properties contains soak accessor, e.g. ?.b, - // and ensure correct compilation by wrapping the @name in `Parens`. - // Examples: - // [a?.b...] => [(a?.b)...] - // f(a.b?.c...) => f((a.b?.c)...) - // [a if b...] => [(a if b)...] - if (!(this.name instanceof Value) || !this.isAssignable()) { - fragments = this.name.compileToFragments(o, LEVEL_OP); - // We need to replace `void 0` with `[]` in compiled fragments. - // Examples: - // - [a?.b...] - // [...(a !== null ? a.b : void 0)] => [...(a !== null ? a.b : [])] - // - f(a?b...) - // f(...(typeof c !== "undefined" && c !== null ? c.b : [])) - flen = fragments.length; - for (ix = j = 0, len1 = fragments.length; j < len1; ix = ++j) { - fragment = fragments[ix]; - if (fragment.type === 'If' && fragment.code === 'void 0' && ix + 1 < flen && fragments[ix + 1].type !== 'Call') { - fragments[ix].code = '[]'; - } - } - } else { - fragments = this.name.compileToFragments(o); - } - return [this.makeCode('...'), ...fragments]; + compileNode(o) { + return [this.makeCode('...'), ...this.name.compileToFragments(o)]; } unwrap() { @@ -5383,7 +5358,7 @@ fragments.push(cond.makeCode(idt2 + 'break;\n')); } if (this.otherwise && this.otherwise.expressions.length) { - fragments.push(this.makeCode(idt1 + "default:\n"), ...(this.otherwise.compileToFragments(o, LEVEL_TOP)), this.makeCode("\n")); + fragments.push(this.makeCode(idt1 + "default:\n"), ...this.otherwise.compileToFragments(o, LEVEL_TOP), this.makeCode("\n")); } fragments.push(this.makeCode(this.tab + '}')); return fragments; diff --git a/src/nodes.coffee b/src/nodes.coffee index 0728b0542c..949892985d 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -2872,30 +2872,9 @@ exports.Splat = class Splat extends Base assigns: (name) -> @name.assigns name - compileToFragments: (o) -> - # Check if @name is not an instance of `Value` or @name properties contains soak accessor, e.g. ?.b, - # and ensure correct compilation by wrapping the @name in `Parens`. - # Examples: - # [a?.b...] => [(a?.b)...] - # f(a.b?.c...) => f((a.b?.c)...) - # [a if b...] => [(a if b)...] - if not (@name instanceof Value) or not @isAssignable() - fragments = @name.compileToFragments o, LEVEL_OP - # We need to replace `void 0` with `[]` in compiled fragments. - # Examples: - # - [a?.b...] - # [...(a !== null ? a.b : void 0)] => [...(a !== null ? a.b : [])] - # - f(a?b...) - # f(...(typeof c !== "undefined" && c !== null ? c.b : [])) - flen = fragments.length - for fragment, ix in fragments - fragments[ix].code = '[]' if fragment.type is 'If' and fragment.code is 'void 0' and - ix + 1 < flen and fragments[ix + 1].type isnt 'Call' - else - fragments = @name.compileToFragments o - + compileNode: (o) -> [ @makeCode('...') - fragments... ] + @name.compileToFragments(o)... ] unwrap: -> @name diff --git a/test/arrays.coffee b/test/arrays.coffee index ca54d85818..0963d3e591 100644 --- a/test/arrays.coffee +++ b/test/arrays.coffee @@ -70,53 +70,31 @@ test "array splats with nested arrays", -> test "#4260: splat after existential operator soak", -> a = {b: [3]} - foo = (a) -> [a] ? [] - + foo = (a) -> [a] arrayEq [a?.b...], [3] - arrayEq [c?.b...], [] + arrayEq [c?.b ? []...], [] arrayEq [...a?.b], [3] - arrayEq [...c?.b], [] + arrayEq [...c?.b ? []], [] arrayEq foo(a?.b...), [3] arrayEq foo(...a?.b), [3] - arrayEq [foo(a?.b)...], [a.b] # `void 0` should not be replaced with `[]` - arrayEq [...foo(a?.b)], [a.b] # `void 0` should not be replaced with `[]` - arrayEq [foo(a?.b...)...], [3] - arrayEq [...foo(...a?.b)], [3] - arrayEq foo(c?.b...), [undefined] - arrayEq foo(...c?.b), [undefined] - arrayEq [foo(c?.b)...], [undefined] # `void 0` should not be replaced with `[]` - arrayEq [...foo(c?.b)], [undefined] # `void 0` should not be replaced with `[]` - arrayEq [foo(c?.b...)...], [undefined] - arrayEq [...foo(...c?.b)], [undefined] - + arrayEq foo(c?.b ? []...), [undefined] + arrayEq foo(...c?.b ? []), [undefined] e = yes f = null arrayEq [(a if e)?.b...], [3] - arrayEq [(a if f)?.b...], [] + arrayEq [(a if f)?.b ? []...], [] arrayEq [...(a if e)?.b], [3] - arrayEq [...(a if f)?.b], [] + arrayEq [...(a if f)?.b ? []], [] arrayEq foo((a if e)?.b...), [3] arrayEq foo(...(a if e)?.b), [3] - arrayEq [foo((a if e)?.b)...], [a.b] # `void 0` should not be replaced with `[]` - arrayEq [...foo((a if e)?.b)], [a.b] # `void 0` should not be replaced with `[]` - arrayEq [foo((a if e)?.b...)...], [3] - arrayEq [...foo(...(a if e)?.b)], [3] - arrayEq foo((a if f)?.b...), [undefined] - arrayEq foo(...(a if f)?.b), [undefined] - arrayEq [foo((a if f)?.b)...], [undefined] # `void 0` should not be replaced with `[]` - arrayEq [...foo((a if f)?.b)], [undefined] # `void 0` should not be replaced with `[]` - arrayEq [foo((a if f)?.b...)...], [undefined] - arrayEq [...foo(...(a if f)?.b)], [undefined] + arrayEq foo((a if f)?.b ? []...), [undefined] + arrayEq foo(...(a if f)?.b ? []), [undefined] # Should not trigger implicit call, e.g. rest ... => rest(...) arrayEq [... a?.b], [3] - arrayEq [... c?.b], [] + arrayEq [... c?.b ? []], [] arrayEq [a?.b ...], [3] - arrayEq [c?.b ...], [] arrayEq [(a if e)?.b ...], [3] - arrayEq [(a if f)?.b ...], [] - arrayEq [... (a if e)?.b], [3] - arrayEq [... (a if f)?.b], [] arrayEq foo(a?.b ...), [3] arrayEq foo(... a?.b), [3] @@ -126,27 +104,17 @@ test "#1349: trailing if after splat", -> c = null foo = (a) -> [a] arrayEq [a if b...], [3] - arrayEq [a if c...], [] + arrayEq [(a if c) ? []...], [] arrayEq [...a if b], [3] - arrayEq [...a if c], [] + arrayEq [...(a if c) ? []], [] arrayEq foo((a if b)...), [3] arrayEq foo(...(a if b)), [3] - arrayEq [foo((a if b))...], [a] # `void 0` should not be replaced with `[]` - arrayEq [...foo((a if b))], [a] # `void 0` should not be replaced with `[]` - arrayEq [foo((a if b)...)...], [3] - arrayEq [...foo(...(a if b))], [3] - arrayEq foo((a if c)...), [undefined] - arrayEq foo(...(a if c)), [undefined] - arrayEq [foo((a if c))...], [[]] # `void 0` should not be replaced with `[]` - arrayEq [...foo((a if c))], [[]] # `void 0` should not be replaced with `[]` - arrayEq [foo((a if c)...)...], [undefined] - arrayEq [...foo(...(a if c))], [undefined] + arrayEq foo((a if c) ? []...), [undefined] + arrayEq foo(...(a if c) ? []), [undefined] # Should not trigger implicit call, e.g. rest ... => rest(...) arrayEq [... a if b], [3] - arrayEq [... a if c], [] arrayEq [a if b ...], [3] - arrayEq [a if c ...], [] test "#1274: `[] = a()` compiles to `false` instead of `a()`", -> a = false From 9863aae99799af2ba30d3d75c36514d509cd2128 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 26 Aug 2017 23:56:25 -0700 Subject: [PATCH 9/9] Use LEVEL_OP; follow style better --- lib/coffeescript/nodes.js | 10 +++++----- src/nodes.coffee | 10 ++++------ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/coffeescript/nodes.js b/lib/coffeescript/nodes.js index bdb9dee5aa..44da97411a 100644 --- a/lib/coffeescript/nodes.js +++ b/lib/coffeescript/nodes.js @@ -4193,21 +4193,21 @@ // or as part of a destructuring assignment. exports.Splat = Splat = (function() { class Splat extends Base { - isAssignable() { - return this.name.isAssignable() && (!this.name.isAtomic || this.name.isAtomic()); - } - constructor(name) { super(); this.name = name.compile ? name : new Literal(name); } + isAssignable() { + return this.name.isAssignable() && (!this.name.isAtomic || this.name.isAtomic()); + } + assigns(name) { return this.name.assigns(name); } compileNode(o) { - return [this.makeCode('...'), ...this.name.compileToFragments(o)]; + return [this.makeCode('...'), ...this.name.compileToFragments(o, LEVEL_OP)]; } unwrap() { diff --git a/src/nodes.coffee b/src/nodes.coffee index 949892985d..1edeeb3b8a 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -2859,22 +2859,20 @@ exports.Param = class Param extends Base # A splat, either as a parameter to a function, an argument to a call, # or as part of a destructuring assignment. exports.Splat = class Splat extends Base + constructor: (name) -> + super() + @name = if name.compile then name else new Literal name children: ['name'] isAssignable: -> @name.isAssignable() and (not @name.isAtomic or @name.isAtomic()) - constructor: (name) -> - super() - @name = if name.compile then name else new Literal name - assigns: (name) -> @name.assigns name compileNode: (o) -> - [ @makeCode('...') - @name.compileToFragments(o)... ] + [@makeCode('...'), @name.compileToFragments(o, LEVEL_OP)...] unwrap: -> @name