Skip to content

Commit ab40571

Browse files
committed
Merge pull request #3227 from marchaefner/fixClassCompilation
Fix scope of external constructor reference / Refactor `Closure`
2 parents 45b60c9 + 5d13d14 commit ab40571

File tree

3 files changed

+111
-127
lines changed

3 files changed

+111
-127
lines changed

lib/coffee-script/nodes.js

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

src/nodes.coffee

Lines changed: 48 additions & 61 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

@@ -1021,7 +1031,7 @@ exports.Class = class Class extends Base
10211031
if func instanceof Code
10221032
assign = @ctor = func
10231033
else
1024-
@externalCtor = o.scope.freeVariable 'class'
1034+
@externalCtor = o.classScope.freeVariable 'class'
10251035
assign = new Assign new Literal(@externalCtor), func
10261036
else
10271037
if assign.variable.this
@@ -1078,31 +1088,35 @@ 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 = []
1101+
o.classScope = func.makeScope o.scope
10841102

10851103
@hoistDirectivePrologue()
10861104
@setContext name
10871105
@walkBody name, o
10881106
@ensureConstructor name
10891107
@addBoundFunctions o
10901108
@body.spaced = yes
1091-
@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.classScope.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

@@ -1291,6 +1305,8 @@ exports.Code = class Code extends Base
12911305

12921306
jumps: NO
12931307

1308+
makeScope: (parentScope) -> new Scope parentScope, @body, this
1309+
12941310
# Compilation creates a new scope unless explicitly asked to share with the
12951311
# outer scope. Handles splat parameters in the parameter list by peeking at
12961312
# the JavaScript `arguments` object. If the function is bound with the `=>`
@@ -1306,7 +1322,7 @@ exports.Code = class Code extends Base
13061322
boundfunc.updateLocationDataIfMissing @locationData
13071323
return boundfunc.compileNode(o)
13081324

1309-
o.scope = new Scope o.scope, @body, this
1325+
o.scope = del(o, 'classScope') or @makeScope o.scope
13101326
o.scope.shared = del(o, 'sharedScope')
13111327
o.indent += TAB
13121328
delete o.bare
@@ -2080,50 +2096,6 @@ exports.If = class If extends Base
20802096
unfoldSoak: ->
20812097
@soak and this
20822098

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-
21272099
# Constants
21282100
# ---------
21292101

@@ -2190,8 +2162,8 @@ METHOD_DEF = ///
21902162
IS_STRING = /^['"]/
21912163
IS_REGEX = /^\//
21922164

2193-
# Utility Functions
2194-
# -----------------
2165+
# Helper Functions
2166+
# ----------------
21952167

21962168
# Helper for ensuring that utility functions are assigned at the top level.
21972169
utility = (name) ->
@@ -2212,3 +2184,18 @@ parseNum = (x) ->
22122184
parseInt x, 16
22132185
else
22142186
parseFloat x
2187+
2188+
isLiteralArguments = (node) ->
2189+
node instanceof Literal and node.value is 'arguments' and not node.asKey
2190+
2191+
isLiteralThis = (node) ->
2192+
(node instanceof Literal and node.value is 'this' and not node.asKey) or
2193+
(node instanceof Code and node.bound) or
2194+
(node instanceof Call and node.isSuper)
2195+
2196+
# Unfold a node's child if soak, then tuck the node under created `If`
2197+
unfoldSoak = (o, parent, name) ->
2198+
return unless ifn = parent[name].unfoldSoak o
2199+
parent[name] = ifn.body
2200+
ifn.body = new Value parent
2201+
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)