diff --git a/lib/ast.js b/lib/ast.js index d755998f5..b12145432 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -274,10 +274,14 @@ SourceNode::to-string = (...args) -> return [sub, ref, [ref.value]]; } }, - compileLoopReference: function(o, name, ret){ - var ref$, asn, tmp; + compileLoopReference: function(o, name, ret, safeAccess){ + var ref$, code, asn, tmp; if (this instanceof Var && o.scope.check(this.value) || this instanceof Unary && ((ref$ = this.op) === '+' || ref$ === '-') && (-1 / 0 < (ref$ = +this.it.value) && ref$ < 1 / 0) || this instanceof Literal && !this.isComplex()) { - return [ref$ = this.compile(o), ref$]; + code = this.compile(o, LEVEL_PAREN); + if (safeAccess && !(this instanceof Var)) { + code = "(" + code + ")"; + } + return [code, code]; } asn = Assign(Var(tmp = o.scope.temporary(name)), this); ret || (asn['void'] = true); @@ -3695,7 +3699,7 @@ exports.For = For = (function(superclass){ this.item = Var(o.scope.temporary('x')); } if (this.item || this.object && this.own || this['let']) { - ref$ = this.source.compileLoopReference(o, 'ref', !this.object), svar = ref$[0], srcPart = ref$[1]; + ref$ = this.source.compileLoopReference(o, 'ref', !this.object, true), svar = ref$[0], srcPart = ref$[1]; svar === srcPart || temps.push(svar); } else { svar = srcPart = this.source.compile(o, LEVEL_PAREN); diff --git a/src/ast.ls b/src/ast.ls index ced3a7a8f..c8b4d804c 100644 --- a/src/ast.ls +++ b/src/ast.ls @@ -199,11 +199,13 @@ SourceNode::to-string = (...args) -> if once then [sub, ref <<< {+temp}] else [sub, ref, [ref.value]] # Compiles to a variable/source pair suitable for looping. - compile-loop-reference: (o, name, ret) -> + compile-loop-reference: (o, name, ret, safe-access) -> if this instanceof Var and o.scope.check @value or this instanceof Unary and @op in <[ + - ]> and -1/0 < +@it.value < 1/0 or this instanceof Literal and not @is-complex! - return [@compile o] * 2 + code = @compile o, LEVEL_PAREN + code = "(#code)" if safe-access and this not instanceof Var + return [code] * 2 asn = Assign Var(tmp = o.scope.temporary name), this ret or asn.void = true [tmp; asn.compile o, if ret then LEVEL_CALL else LEVEL_PAREN] @@ -2352,7 +2354,7 @@ class exports.For extends While else @item = Var o.scope.temporary \x if @ref if @item or @object and @own or @let - [svar, srcPart] = @source.compile-loop-reference o, \ref, not @object + [svar, srcPart] = @source.compile-loop-reference o, \ref, not @object, true svar is srcPart or temps.push svar else svar = srcPart = @source.compile o, LEVEL_PAREN diff --git a/test/loop.ls b/test/loop.ls index 5ff9939a2..6f3b03365 100644 --- a/test/loop.ls +++ b/test/loop.ls @@ -686,3 +686,13 @@ eq 1, i o = { [k, -> v] for let k, v of {a: 1, b: 2} } eq 1 o.a! eq 2 o.b! + +# Certain literals could result in illegal JavaScript if not carefully +# handled. These are all nonsensical use cases and could just as easily +# be LiveScript syntax errors. The thing to avoid is for them to be JavaScript +# syntax errors; lsc should never produce illegal JavaScript on any input, +# silly or otherwise. +deep-equal [] [0 for x in 42] +deep-equal [] [0 for x in -42] +throws "Cannot read property 'length' of null" -> [0 for x in null] +throws "Cannot read property 'length' of undefined" -> [0 for x in void]