diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index ba27f9bb..8770d371 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -750,7 +750,7 @@ Call.prototype.superReference = function(o) { var accesses, method, name; - method = o.scope.method; + method = o.scope.getMethodRecurse(); if (!method) { throw SyntaxError('cannot call super outside of a function.'); } @@ -770,6 +770,15 @@ } }; + Call.prototype.superThis = function(o) { + var _ref2, _ref3; + if (((_ref2 = o.scope) != null ? (_ref3 = _ref2.method) != null ? _ref3.context : void 0 : void 0) != null) { + return o.scope.method.context; + } else { + return "this"; + } + }; + Call.prototype.unfoldSoak = function(o) { var call, ifn, left, list, rite, _i, _len, _ref2, _ref3; if (this.soak) { @@ -866,20 +875,20 @@ return _results; })()).join(', '); if (this.isSuper) { - return this.superReference(o) + (".call(this" + (args && ', ' + args) + ")"); + return this.superReference(o) + (".call(" + (this.superThis(o)) + (args && ', ' + args) + ")"); } else { return (this.isNew ? 'new ' : '') + this.variable.compile(o, LEVEL_ACCESS) + ("(" + args + ")"); } }; Call.prototype.compileSuper = function(args, o) { - return "" + (this.superReference(o)) + ".call(this" + (args.length ? ', ' : '') + args + ")"; + return "" + (this.superReference(o)) + ".call(" + (this.superThis(o)) + (args.length ? ', ' : '') + args + ")"; }; Call.prototype.compileSplat = function(o, splatArgs) { var base, fun, idt, name, ref; if (this.isSuper) { - return "" + (this.superReference(o)) + ".apply(this, " + splatArgs + ")"; + return "" + (this.superReference(o)) + ".apply(" + (this.superThis(o)) + ", " + splatArgs + ")"; } if (this.isNew) { idt = this.tab + TAB; diff --git a/lib/coffee-script/scope.js b/lib/coffee-script/scope.js index 8837f41b..6f2da29f 100644 --- a/lib/coffee-script/scope.js +++ b/lib/coffee-script/scope.js @@ -40,6 +40,17 @@ } }; + Scope.prototype.getMethodRecurse = function() { + var _ref1; + if (((_ref1 = this.method) != null ? _ref1.name : void 0) != null) { + return this.method; + } else if (this.parent) { + return this.parent.getMethodRecurse(); + } else { + return {}; + } + }; + Scope.prototype.find = function(name, options) { if (this.check(name, options)) { return true; diff --git a/src/nodes.coffee b/src/nodes.coffee index c30db3d4..05087e32 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -496,7 +496,9 @@ exports.Call = class Call extends Base # Grab the reference to the superclass's implementation of the current # method. superReference: (o) -> - {method} = o.scope + # keep walking up the scope chain until we find the original reference + # to a method. Stop at the first one. + method = o.scope.getMethodRecurse() throw SyntaxError 'cannot call super outside of a function.' unless method {name} = method throw SyntaxError 'cannot call super on an anonymous function.' unless name? @@ -508,6 +510,9 @@ exports.Call = class Call extends Base else "#{name}.__super__.constructor" + superThis : (o) -> + if o.scope?.method?.context? then o.scope.method.context else "this" + # Soaked chained invocations unfold into if/else ternary structures. unfoldSoak: (o) -> if @soak @@ -566,21 +571,21 @@ exports.Call = class Call extends Base args = @filterImplicitObjects @args args = (arg.compile o, LEVEL_LIST for arg in args).join ', ' if @isSuper - @superReference(o) + ".call(this#{ args and ', ' + args })" + @superReference(o) + ".call(#{@superThis o}#{ args and ', ' + args })" else (if @isNew then 'new ' else '') + @variable.compile(o, LEVEL_ACCESS) + "(#{args})" # `super()` is converted into a call against the superclass's implementation # of the current function. compileSuper: (args, o) -> - "#{@superReference(o)}.call(this#{ if args.length then ', ' else '' }#{args})" + "#{@superReference(o)}.call(#{@superThis o}#{ if args.length then ', ' else '' }#{args})" # If you call a function with a splat, it's converted into a JavaScript # `.apply()` call to allow an array of arguments to be passed. # If it's a constructor, then things get real tricky. We have to inject an # inner constructor in order to be able to pass the varargs. compileSplat: (o, splatArgs) -> - return "#{ @superReference o }.apply(this, #{splatArgs})" if @isSuper + return "#{ @superReference o }.apply(#{@superThis o}, #{splatArgs})" if @isSuper if @isNew idt = @tab + TAB return """ diff --git a/src/scope.coffee b/src/scope.coffee index 0f34e27a..30ccc1b5 100644 --- a/src/scope.coffee +++ b/src/scope.coffee @@ -30,6 +30,11 @@ exports.Scope = class Scope else @positions[name] = @variables.push({name, type}) - 1 + getMethodRecurse: -> + if @method?.name? then @method + else if @parent then @parent.getMethodRecurse() + else {} + # Look up a variable name in lexical scope, and declare it if it does not # already exist. find: (name, options) -> diff --git a/test/scope.coffee b/test/scope.coffee index c2784226..9bf679bf 100644 --- a/test/scope.coffee +++ b/test/scope.coffee @@ -41,3 +41,26 @@ test "#1973: redefining Array/Object constructors shouldn't confuse __X helpers" obj = {arr} for own k of obj eq arr, obj[k] + +test "super + fat arrows", -> + dolater = (cb) -> cb() + + class A + constructor: -> + @_i = 0 + foo : (cb) -> + dolater => + @_i += 1 + cb() + + class B extends A + constructor : -> + super + foo : (cb) -> + dolater => + dolater => + @_i += 2 + super cb + + b = new B() + b.foo => eq b._i, 3