diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 2092ea5b..4985d115 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -736,7 +736,7 @@ Call.prototype.superReference = function(o) { var accesses, method, name; - method = o.scope.method; + method = o.scope.namedMethod(); if (!method) { throw SyntaxError('cannot call super outside of a function.'); } @@ -756,6 +756,11 @@ } }; + Call.prototype.superThis = function(o) { + var _ref2; + return ((_ref2 = o.scope.method) != null ? _ref2.context : void 0) || "this"; + }; + Call.prototype.unfoldSoak = function(o) { var call, ifn, left, list, rite, _i, _len, _ref2, _ref3; if (this.soak) { @@ -852,20 +857,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 3c322f91..54a4d47c 100644 --- a/lib/coffee-script/scope.js +++ b/lib/coffee-script/scope.js @@ -38,6 +38,13 @@ } }; + Scope.prototype.namedMethod = function() { + if (this.method.name || !this.parent) { + return this.method; + } + return this.parent.namedMethod(); + }; + Scope.prototype.find = function(name) { if (this.check(name)) { return true; diff --git a/src/nodes.coffee b/src/nodes.coffee index 5320b838..73cb0ae8 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -496,7 +496,7 @@ exports.Call = class Call extends Base # Grab the reference to the superclass's implementation of the current # method. superReference: (o) -> - {method} = o.scope + method = o.scope.namedMethod() 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 +508,10 @@ exports.Call = class Call extends Base else "#{name}.__super__.constructor" + # The appropriate `this` value for a `super` call. + superThis : (o) -> + o.scope.method?.context or "this" + # Soaked chained invocations unfold into if/else ternary structures. unfoldSoak: (o) -> if @soak @@ -566,21 +570,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 17ae4afa..69d06c0c 100644 --- a/src/scope.coffee +++ b/src/scope.coffee @@ -30,6 +30,15 @@ exports.Scope = class Scope else @positions[name] = @variables.push({name, type}) - 1 + # When `super` is called, we need to find the name of the current method we're + # in, so that we know how to invoke the same method of the parent class. This + # can get complicated if super is being called from an inner function. + # `namedMethod` will walk up the scope tree until it either finds the first + # function object that has a name filled in, or bottoms out. + namedMethod: -> + return @method if @method.name or !@parent + @parent.namedMethod() + # Look up a variable name in lexical scope, and declare it if it does not # already exist. find: (name) -> diff --git a/test/scope.coffee b/test/scope.coffee index a69fed3c..21f129ba 100644 --- a/test/scope.coffee +++ b/test/scope.coffee @@ -46,3 +46,35 @@ test "#2255: global leak with splatted @-params", -> ok not x? arrayEq [0], ((@x...) -> @x).call {}, 0 ok not x? + +test "#1183: 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 + +test "#1183: super + wrap", -> + class A + m : -> 10 + class B extends A + constructor : -> super + B::m = -> r = try super() + eq (new B()).m(), 10 +