Skip to content

Commit 1df8abf

Browse files
author
Marc Häfner
committed
Refactor closure compilation
* Break up `Closure` and merge `Closure.wrap` into `Base.compileClosure` * Construct class closure directly in `Class.compileNode` * Reuse `isLiteralArguments` in `Range.compileArray` * Move all helpers to bottom of file * Add test for #3063
1 parent 45b60c9 commit 1df8abf

File tree

3 files changed

+99
-119
lines changed

3 files changed

+99
-119
lines changed

lib/coffee-script/nodes.js

Lines changed: 49 additions & 61 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/nodes.coffee

Lines changed: 43 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,17 @@ exports.Base = class Base
7979
if jumpNode = @jumps()
8080
jumpNode.error 'cannot use a pure statement in an expression'
8181
o.sharedScope = yes
82-
Closure.wrap(this).compileNode o
82+
func = new Code [], Block.wrap [this]
83+
args = []
84+
if (argumentsNode = @contains isLiteralArguments) or @contains isLiteralThis
85+
args = [new Literal 'this']
86+
if argumentsNode
87+
meth = 'apply'
88+
args.push new Literal 'arguments'
89+
else
90+
meth = 'call'
91+
func = new Value func, [new Access new Literal meth]
92+
(new Call func, args).compileNode o
8393

8494
# If the code generation wishes to use the result of a complex expression
8595
# in multiple places, ensure that the expression is only ever evaluated once,
@@ -848,7 +858,7 @@ exports.Range = class Range extends Base
848858
cond = "#{@fromVar} <= #{@toVar}"
849859
body = "var #{vars}; #{cond} ? #{i} <#{@equals} #{@toVar} : #{i} >#{@equals} #{@toVar}; #{cond} ? #{i}++ : #{i}--"
850860
post = "{ #{result}.push(#{i}); }\n#{idt}return #{result};\n#{o.indent}"
851-
hasArgs = (node) -> node?.contains (n) -> n instanceof Literal and n.value is 'arguments' and not n.asKey
861+
hasArgs = (node) -> node?.contains isLiteralArguments
852862
args = ', arguments' if hasArgs(@from) or hasArgs(@to)
853863
[@makeCode "(function() {#{pre}\n#{idt}for (#{body})#{post}}).apply(this#{args ? ''})"]
854864

@@ -1078,9 +1088,16 @@ exports.Class = class Class extends Base
10781088
# equivalent syntax tree and compile that, in pieces. You can see the
10791089
# constructor, property assignments, and inheritance getting built out below.
10801090
compileNode: (o) ->
1091+
if jumpNode = @body.jumps()
1092+
jumpNode.error 'Class bodies cannot contain pure statements'
1093+
if argumentsNode = @body.contains isLiteralArguments
1094+
argumentsNode.error "Class bodies shouldn't reference arguments"
1095+
10811096
name = @determineName() or '_Class'
1082-
name = "_#{name}" if name.reserved
1097+
name = "_#{name}" if name.reserved
10831098
lname = new Literal name
1099+
func = new Code [], Block.wrap [@body]
1100+
args = []
10841101

10851102
@hoistDirectivePrologue()
10861103
@setContext name
@@ -1091,18 +1108,15 @@ exports.Class = class Class extends Base
10911108
@body.expressions.unshift @ctor unless @ctor instanceof Code
10921109
@body.expressions.push lname
10931110

1094-
call = Closure.wrap @body
1095-
1096-
if @parent and call.args
1097-
@superClass = new Literal o.scope.freeVariable 'super', no
1098-
@body.expressions.unshift new Extends lname, @superClass
1099-
call.args.push @parent
1100-
params = call.variable.params or call.variable.base.params
1101-
params.push new Param @superClass
1111+
if @parent
1112+
superClass = new Literal o.scope.freeVariable 'super', no
1113+
@body.expressions.unshift new Extends lname, superClass
1114+
func.params.push new Param superClass
1115+
args.push @parent
11021116

11031117
@body.expressions.unshift @directives...
11041118

1105-
klass = new Parens call, yes
1119+
klass = new Parens new Call func, args
11061120
klass = new Assign @variable, klass if @variable
11071121
klass.compileToFragments o
11081122

@@ -2080,50 +2094,6 @@ exports.If = class If extends Base
20802094
unfoldSoak: ->
20812095
@soak and this
20822096

2083-
# Faux-Nodes
2084-
# ----------
2085-
# Faux-nodes are never created by the grammar, but are used during code
2086-
# generation to generate other combinations of nodes.
2087-
2088-
#### Closure
2089-
2090-
# A faux-node used to wrap an expressions body in a closure.
2091-
Closure =
2092-
2093-
# Wrap the expressions body, unless it contains a pure statement,
2094-
# in which case, no dice. If the body mentions `this` or `arguments`,
2095-
# then make sure that the closure wrapper preserves the original values.
2096-
wrap: (expressions, statement, noReturn) ->
2097-
return expressions if expressions.jumps()
2098-
func = new Code [], Block.wrap [expressions]
2099-
args = []
2100-
argumentsNode = expressions.contains @isLiteralArguments
2101-
if argumentsNode and expressions.classBody
2102-
argumentsNode.error "Class bodies shouldn't reference arguments"
2103-
if argumentsNode or expressions.contains @isLiteralThis
2104-
meth = new Literal if argumentsNode then 'apply' else 'call'
2105-
args = [new Literal 'this']
2106-
args.push new Literal 'arguments' if argumentsNode
2107-
func = new Value func, [new Access meth]
2108-
func.noReturn = noReturn
2109-
call = new Call func, args
2110-
if statement then Block.wrap [call] else call
2111-
2112-
isLiteralArguments: (node) ->
2113-
node instanceof Literal and node.value is 'arguments' and not node.asKey
2114-
2115-
isLiteralThis: (node) ->
2116-
(node instanceof Literal and node.value is 'this' and not node.asKey) or
2117-
(node instanceof Code and node.bound) or
2118-
(node instanceof Call and node.isSuper)
2119-
2120-
# Unfold a node's child if soak, then tuck the node under created `If`
2121-
unfoldSoak = (o, parent, name) ->
2122-
return unless ifn = parent[name].unfoldSoak o
2123-
parent[name] = ifn.body
2124-
ifn.body = new Value parent
2125-
ifn
2126-
21272097
# Constants
21282098
# ---------
21292099

@@ -2190,8 +2160,8 @@ METHOD_DEF = ///
21902160
IS_STRING = /^['"]/
21912161
IS_REGEX = /^\//
21922162

2193-
# Utility Functions
2194-
# -----------------
2163+
# Helper Functions
2164+
# ----------------
21952165

21962166
# Helper for ensuring that utility functions are assigned at the top level.
21972167
utility = (name) ->
@@ -2212,3 +2182,18 @@ parseNum = (x) ->
22122182
parseInt x, 16
22132183
else
22142184
parseFloat x
2185+
2186+
isLiteralArguments = (node) ->
2187+
node instanceof Literal and node.value is 'arguments' and not node.asKey
2188+
2189+
isLiteralThis = (node) ->
2190+
(node instanceof Literal and node.value is 'this' and not node.asKey) or
2191+
(node instanceof Code and node.bound) or
2192+
(node instanceof Call and node.isSuper)
2193+
2194+
# Unfold a node's child if soak, then tuck the node under created `If`
2195+
unfoldSoak = (o, parent, name) ->
2196+
return unless ifn = parent[name].unfoldSoak o
2197+
parent[name] = ifn.body
2198+
ifn.body = new Value parent
2199+
ifn

test/classes.coffee

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,3 +800,10 @@ test "#2796: ditto, ditto, ditto", ->
800800

801801
new Base
802802
eq answer, 'right!'
803+
804+
test "#3063: Class bodies cannot contain pure statements", ->
805+
throws -> CoffeeScript.compile """
806+
class extends S
807+
return if S.f
808+
@f: => this
809+
"""

0 commit comments

Comments
 (0)