Skip to content

Commit 04b30a6

Browse files
committed
Merge pull request jashkenas#3786 from lydell/loop-safety
Fix jashkenas#3778: Make for loops more consistent
2 parents 17a271a + 996a171 commit 04b30a6

File tree

3 files changed

+48
-25
lines changed

3 files changed

+48
-25
lines changed

lib/coffee-script/nodes.js

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

src/nodes.coffee

+16-11
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,15 @@ exports.Base = class Base
102102
# If `level` is passed, then returns `[val, ref]`, where `val` is the compiled value, and `ref`
103103
# is the compiled reference. If `level` is not passed, this returns `[val, ref]` where
104104
# the two values are raw nodes which have not been compiled.
105-
cache: (o, level, reused) ->
106-
unless @isComplex()
107-
ref = if level then @compileToFragments o, level else this
108-
[ref, ref]
109-
else
110-
ref = new Literal reused or o.scope.freeVariable 'ref'
105+
cache: (o, level, isComplex) ->
106+
complex = if isComplex? then isComplex this else @isComplex()
107+
if complex
108+
ref = new Literal o.scope.freeVariable 'ref'
111109
sub = new Assign ref, this
112110
if level then [sub.compileToFragments(o, level), [@makeCode(ref.value)]] else [sub, ref]
111+
else
112+
ref = if level then @compileToFragments o, level else this
113+
[ref, ref]
113114

114115
cacheToCodeFragments: (cacheValues) ->
115116
[fragmentsToText(cacheValues[0]), fragmentsToText(cacheValues[1])]
@@ -793,9 +794,10 @@ exports.Range = class Range extends Base
793794
# But only if they need to be cached to avoid double evaluation.
794795
compileVariables: (o) ->
795796
o = merge o, top: true
796-
[@fromC, @fromVar] = @cacheToCodeFragments @from.cache o, LEVEL_LIST
797-
[@toC, @toVar] = @cacheToCodeFragments @to.cache o, LEVEL_LIST
798-
[@step, @stepVar] = @cacheToCodeFragments step.cache o, LEVEL_LIST if step = del o, 'step'
797+
isComplex = del o, 'isComplex'
798+
[@fromC, @fromVar] = @cacheToCodeFragments @from.cache o, LEVEL_LIST, isComplex
799+
[@toC, @toVar] = @cacheToCodeFragments @to.cache o, LEVEL_LIST, isComplex
800+
[@step, @stepVar] = @cacheToCodeFragments step.cache o, LEVEL_LIST, isComplex if step = del o, 'step'
799801
[@fromNum, @toNum] = [@fromVar.match(NUMBER), @toVar.match(NUMBER)]
800802
@stepNum = @stepVar.match(NUMBER) if @stepVar
801803

@@ -1971,15 +1973,16 @@ exports.For = class For extends While
19711973
kvar = (@range and name) or index or ivar
19721974
kvarAssign = if kvar isnt ivar then "#{kvar} = " else ""
19731975
if @step and not @range
1974-
[step, stepVar] = @cacheToCodeFragments @step.cache o, LEVEL_LIST
1976+
[step, stepVar] = @cacheToCodeFragments @step.cache o, LEVEL_LIST, isComplexOrAssignable
19751977
stepNum = stepVar.match NUMBER
19761978
name = ivar if @pattern
19771979
varPart = ''
19781980
guardPart = ''
19791981
defPart = ''
19801982
idt1 = @tab + TAB
19811983
if @range
1982-
forPartFragments = source.compileToFragments merge(o, {index: ivar, name, @step})
1984+
forPartFragments = source.compileToFragments merge o,
1985+
{index: ivar, name, @step, isComplex: isComplexOrAssignable}
19831986
else
19841987
svar = @source.compile o, LEVEL_LIST
19851988
if (name or @own) and not IDENTIFIER.test svar
@@ -2291,6 +2294,8 @@ isLiteralThis = (node) ->
22912294
(node instanceof Code and node.bound) or
22922295
(node instanceof Call and node.isSuper)
22932296

2297+
isComplexOrAssignable = (node) -> node.isComplex() or node.isAssignable?()
2298+
22942299
# Unfold a node's child if soak, then tuck the node under created `If`
22952300
unfoldSoak = (o, parent, name) ->
22962301
return unless ifn = parent[name].unfoldSoak o

test/comprehensions.coffee

+11
Original file line numberDiff line numberDiff line change
@@ -555,3 +555,14 @@ test "splats in destructuring in comprehensions", ->
555555
test "#156: expansion in destructuring in comprehensions", ->
556556
list = [[0, 1, 2], [2, 3, 4], [4, 5, 6]]
557557
arrayEq (last for [..., last] in list), [2, 4, 6]
558+
559+
test "#3778: Consistently always cache for loop range boundaries and steps, even
560+
if they are simple identifiers", ->
561+
a = 1; arrayEq [1, 2, 3], (for n in [1, 2, 3] by a then a = 4; n)
562+
a = 1; arrayEq [1, 2, 3], (for n in [1, 2, 3] by +a then a = 4; n)
563+
a = 1; arrayEq [1, 2, 3], (for n in [a..3] then a = 4; n)
564+
a = 1; arrayEq [1, 2, 3], (for n in [+a..3] then a = 4; n)
565+
a = 3; arrayEq [1, 2, 3], (for n in [1..a] then a = 4; n)
566+
a = 3; arrayEq [1, 2, 3], (for n in [1..+a] then a = 4; n)
567+
a = 1; arrayEq [1, 2, 3], (for n in [1..3] by a then a = 4; n)
568+
a = 1; arrayEq [1, 2, 3], (for n in [1..3] by +a then a = 4; n)

0 commit comments

Comments
 (0)