diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 5d543512..ae497b09 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.3 (function() { - var Access, Arr, Assign, Base, Block, Call, Class, Closure, Code, CodeFragment, Comment, Existence, Extends, For, HEXNUM, IDENTIFIER, IDENTIFIER_STR, IS_REGEX, IS_STRING, If, In, Index, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, METHOD_DEF, NEGATE, NO, NUMBER, Obj, Op, Param, Parens, RESERVED, Range, Return, SIMPLENUM, STRICT_PROSCRIBED, Scope, Slice, Splat, Switch, TAB, THIS, Throw, Try, UTILITIES, Value, While, YES, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, last, locationDataToString, merge, multident, parseNum, some, starts, throwSyntaxError, unfoldSoak, utility, _ref, _ref1, + var Access, Arr, Assign, Base, Block, Call, Class, Code, CodeFragment, Comment, Existence, Extends, For, HEXNUM, IDENTIFIER, IDENTIFIER_STR, IS_REGEX, IS_STRING, If, In, Index, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, METHOD_DEF, NEGATE, NO, NUMBER, Obj, Op, Param, Parens, RESERVED, Range, Return, SIMPLENUM, STRICT_PROSCRIBED, Scope, Slice, Splat, Switch, TAB, THIS, Throw, Try, UTILITIES, Value, While, YES, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, isLiteralArguments, isLiteralThis, last, locationDataToString, merge, multident, parseNum, some, starts, throwSyntaxError, unfoldSoak, utility, _ref, _ref1, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, @@ -87,12 +87,24 @@ }; Base.prototype.compileClosure = function(o) { - var jumpNode; + var args, argumentsNode, func, jumpNode, meth; if (jumpNode = this.jumps()) { jumpNode.error('cannot use a pure statement in an expression'); } o.sharedScope = true; - return Closure.wrap(this).compileNode(o); + func = new Code([], Block.wrap([this])); + args = []; + if ((argumentsNode = this.contains(isLiteralArguments)) || this.contains(isLiteralThis)) { + args = [new Literal('this')]; + if (argumentsNode) { + meth = 'apply'; + args.push(new Literal('arguments')); + } else { + meth = 'call'; + } + func = new Value(func, [new Access(new Literal(meth))]); + } + return (new Call(func, args)).compileNode(o); }; Base.prototype.cache = function(o, level, reused) { @@ -1177,9 +1189,7 @@ } post = "{ " + result + ".push(" + i + "); }\n" + idt + "return " + result + ";\n" + o.indent; hasArgs = function(node) { - return node != null ? node.contains(function(n) { - return n instanceof Literal && n.value === 'arguments' && !n.asKey; - }) : void 0; + return node != null ? node.contains(isLiteralArguments) : void 0; }; if (hasArgs(this.from) || hasArgs(this.to)) { args = ', arguments'; @@ -1506,12 +1516,20 @@ }; Class.prototype.compileNode = function(o) { - var call, klass, lname, name, params, _ref2; + var args, argumentsNode, func, jumpNode, klass, lname, name, superClass, _ref2; + if (jumpNode = this.body.jumps()) { + jumpNode.error('Class bodies cannot contain pure statements'); + } + if (argumentsNode = this.body.contains(isLiteralArguments)) { + argumentsNode.error("Class bodies shouldn't reference arguments"); + } name = this.determineName() || '_Class'; if (name.reserved) { name = "_" + name; } lname = new Literal(name); + func = new Code([], Block.wrap([this.body])); + args = []; this.hoistDirectivePrologue(); this.setContext(name); this.walkBody(name, o); @@ -1522,16 +1540,14 @@ this.body.expressions.unshift(this.ctor); } this.body.expressions.push(lname); - call = Closure.wrap(this.body); - if (this.parent && call.args) { - this.superClass = new Literal(o.scope.freeVariable('super', false)); - this.body.expressions.unshift(new Extends(lname, this.superClass)); - call.args.push(this.parent); - params = call.variable.params || call.variable.base.params; - params.push(new Param(this.superClass)); + if (this.parent) { + superClass = new Literal(o.scope.freeVariable('super', false)); + this.body.expressions.unshift(new Extends(lname, superClass)); + func.params.push(new Param(superClass)); + args.push(this.parent); } (_ref2 = this.body.expressions).unshift.apply(_ref2, this.directives); - klass = new Parens(call, true); + klass = new Parens(new Call(func, args)); if (this.variable) { klass = new Assign(this.variable, klass); } @@ -2969,52 +2985,6 @@ })(Base); - Closure = { - wrap: function(expressions, statement, noReturn) { - var args, argumentsNode, call, func, meth; - if (expressions.jumps()) { - return expressions; - } - func = new Code([], Block.wrap([expressions])); - args = []; - argumentsNode = expressions.contains(this.isLiteralArguments); - if (argumentsNode && expressions.classBody) { - argumentsNode.error("Class bodies shouldn't reference arguments"); - } - if (argumentsNode || expressions.contains(this.isLiteralThis)) { - meth = new Literal(argumentsNode ? 'apply' : 'call'); - args = [new Literal('this')]; - if (argumentsNode) { - args.push(new Literal('arguments')); - } - func = new Value(func, [new Access(meth)]); - } - func.noReturn = noReturn; - call = new Call(func, args); - if (statement) { - return Block.wrap([call]); - } else { - return call; - } - }, - isLiteralArguments: function(node) { - return node instanceof Literal && node.value === 'arguments' && !node.asKey; - }, - isLiteralThis: function(node) { - return (node instanceof Literal && node.value === 'this' && !node.asKey) || (node instanceof Code && node.bound) || (node instanceof Call && node.isSuper); - } - }; - - unfoldSoak = function(o, parent, name) { - var ifn; - if (!(ifn = parent[name].unfoldSoak(o))) { - return; - } - parent[name] = ifn.body; - ifn.body = new Value(parent); - return ifn; - }; - UTILITIES = { "extends": function() { return "function(child, parent) { for (var key in parent) { if (" + (utility('hasProp')) + ".call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }"; @@ -3085,4 +3055,22 @@ } }; + isLiteralArguments = function(node) { + return node instanceof Literal && node.value === 'arguments' && !node.asKey; + }; + + isLiteralThis = function(node) { + return (node instanceof Literal && node.value === 'this' && !node.asKey) || (node instanceof Code && node.bound) || (node instanceof Call && node.isSuper); + }; + + unfoldSoak = function(o, parent, name) { + var ifn; + if (!(ifn = parent[name].unfoldSoak(o))) { + return; + } + parent[name] = ifn.body; + ifn.body = new Value(parent); + return ifn; + }; + }).call(this); diff --git a/src/nodes.coffee b/src/nodes.coffee index 4339c572..17525e64 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -79,7 +79,17 @@ exports.Base = class Base if jumpNode = @jumps() jumpNode.error 'cannot use a pure statement in an expression' o.sharedScope = yes - Closure.wrap(this).compileNode o + func = new Code [], Block.wrap [this] + args = [] + if (argumentsNode = @contains isLiteralArguments) or @contains isLiteralThis + args = [new Literal 'this'] + if argumentsNode + meth = 'apply' + args.push new Literal 'arguments' + else + meth = 'call' + func = new Value func, [new Access new Literal meth] + (new Call func, args).compileNode o # If the code generation wishes to use the result of a complex expression # in multiple places, ensure that the expression is only ever evaluated once, @@ -848,7 +858,7 @@ exports.Range = class Range extends Base cond = "#{@fromVar} <= #{@toVar}" body = "var #{vars}; #{cond} ? #{i} <#{@equals} #{@toVar} : #{i} >#{@equals} #{@toVar}; #{cond} ? #{i}++ : #{i}--" post = "{ #{result}.push(#{i}); }\n#{idt}return #{result};\n#{o.indent}" - hasArgs = (node) -> node?.contains (n) -> n instanceof Literal and n.value is 'arguments' and not n.asKey + hasArgs = (node) -> node?.contains isLiteralArguments args = ', arguments' if hasArgs(@from) or hasArgs(@to) [@makeCode "(function() {#{pre}\n#{idt}for (#{body})#{post}}).apply(this#{args ? ''})"] @@ -1078,9 +1088,16 @@ exports.Class = class Class extends Base # equivalent syntax tree and compile that, in pieces. You can see the # constructor, property assignments, and inheritance getting built out below. compileNode: (o) -> + if jumpNode = @body.jumps() + jumpNode.error 'Class bodies cannot contain pure statements' + if argumentsNode = @body.contains isLiteralArguments + argumentsNode.error "Class bodies shouldn't reference arguments" + name = @determineName() or '_Class' - name = "_#{name}" if name.reserved + name = "_#{name}" if name.reserved lname = new Literal name + func = new Code [], Block.wrap [@body] + args = [] @hoistDirectivePrologue() @setContext name @@ -1091,18 +1108,15 @@ exports.Class = class Class extends Base @body.expressions.unshift @ctor unless @ctor instanceof Code @body.expressions.push lname - call = Closure.wrap @body - - if @parent and call.args - @superClass = new Literal o.scope.freeVariable 'super', no - @body.expressions.unshift new Extends lname, @superClass - call.args.push @parent - params = call.variable.params or call.variable.base.params - params.push new Param @superClass + if @parent + superClass = new Literal o.scope.freeVariable 'super', no + @body.expressions.unshift new Extends lname, superClass + func.params.push new Param superClass + args.push @parent @body.expressions.unshift @directives... - klass = new Parens call, yes + klass = new Parens new Call func, args klass = new Assign @variable, klass if @variable klass.compileToFragments o @@ -2080,50 +2094,6 @@ exports.If = class If extends Base unfoldSoak: -> @soak and this -# Faux-Nodes -# ---------- -# Faux-nodes are never created by the grammar, but are used during code -# generation to generate other combinations of nodes. - -#### Closure - -# A faux-node used to wrap an expressions body in a closure. -Closure = - - # Wrap the expressions body, unless it contains a pure statement, - # in which case, no dice. If the body mentions `this` or `arguments`, - # then make sure that the closure wrapper preserves the original values. - wrap: (expressions, statement, noReturn) -> - return expressions if expressions.jumps() - func = new Code [], Block.wrap [expressions] - args = [] - argumentsNode = expressions.contains @isLiteralArguments - if argumentsNode and expressions.classBody - argumentsNode.error "Class bodies shouldn't reference arguments" - if argumentsNode or expressions.contains @isLiteralThis - meth = new Literal if argumentsNode then 'apply' else 'call' - args = [new Literal 'this'] - args.push new Literal 'arguments' if argumentsNode - func = new Value func, [new Access meth] - func.noReturn = noReturn - call = new Call func, args - if statement then Block.wrap [call] else call - - isLiteralArguments: (node) -> - node instanceof Literal and node.value is 'arguments' and not node.asKey - - isLiteralThis: (node) -> - (node instanceof Literal and node.value is 'this' and not node.asKey) or - (node instanceof Code and node.bound) or - (node instanceof Call and node.isSuper) - -# Unfold a node's child if soak, then tuck the node under created `If` -unfoldSoak = (o, parent, name) -> - return unless ifn = parent[name].unfoldSoak o - parent[name] = ifn.body - ifn.body = new Value parent - ifn - # Constants # --------- @@ -2190,8 +2160,8 @@ METHOD_DEF = /// IS_STRING = /^['"]/ IS_REGEX = /^\// -# Utility Functions -# ----------------- +# Helper Functions +# ---------------- # Helper for ensuring that utility functions are assigned at the top level. utility = (name) -> @@ -2212,3 +2182,18 @@ parseNum = (x) -> parseInt x, 16 else parseFloat x + +isLiteralArguments = (node) -> + node instanceof Literal and node.value is 'arguments' and not node.asKey + +isLiteralThis = (node) -> + (node instanceof Literal and node.value is 'this' and not node.asKey) or + (node instanceof Code and node.bound) or + (node instanceof Call and node.isSuper) + +# Unfold a node's child if soak, then tuck the node under created `If` +unfoldSoak = (o, parent, name) -> + return unless ifn = parent[name].unfoldSoak o + parent[name] = ifn.body + ifn.body = new Value parent + ifn diff --git a/test/classes.coffee b/test/classes.coffee index 8b1d2ae6..ab8d78e4 100644 --- a/test/classes.coffee +++ b/test/classes.coffee @@ -800,3 +800,10 @@ test "#2796: ditto, ditto, ditto", -> new Base eq answer, 'right!' + +test "#3063: Class bodies cannot contain pure statements", -> + throws -> CoffeeScript.compile """ + class extends S + return if S.f + @f: => this + """