diff --git a/lib/nodes.js b/lib/nodes.js index 04c87f2a..a7a405a2 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -845,14 +845,18 @@ continue; } if (func instanceof CodeNode && func.bound) { - func.bound = false; - constScope || (constScope = new Scope(o.scope, constructor.body, constructor)); - me || (me = constScope.freeVariable()); - pname = pvar.compile(o); - if (constructor.body.empty()) { - constructor.body.push(new ReturnNode(literal('this'))); + if (prop.context === 'this') { + func.context = className; + } else { + func.bound = false; + constScope || (constScope = new Scope(o.scope, constructor.body, constructor)); + me || (me = constScope.freeVariable()); + pname = pvar.compile(o); + if (constructor.body.empty()) { + constructor.body.push(new ReturnNode(literal('this'))); + } + constructor.body.unshift(literal("this." + (pname) + " = function(){ return " + (className) + ".prototype." + (pname) + ".apply(" + (me) + ", arguments); }")); } - constructor.body.unshift(literal("this." + (pname) + " = function(){ return " + (className) + ".prototype." + (pname) + ".apply(" + (me) + ", arguments); }")); } if (pvar) { access = prop.context === 'this' ? pvar.base.properties[0] : new AccessorNode(pvar, 'prototype'); @@ -1001,6 +1005,9 @@ this.params || (this.params = []); this.body || (this.body = new Expressions()); this.bound = tag === 'boundfunc'; + if (this.bound) { + this.context = 'this'; + } return this; }; __extends(CodeNode, BaseNode); @@ -1066,7 +1073,7 @@ code = this.body.expressions.length ? ("\n" + (this.body.compileWithDeclarations(o)) + "\n") : ''; func = ("function(" + (params.join(', ')) + ") {" + (code) + (code && this.tab) + "}"); if (this.bound) { - return ("" + (utility('bind')) + "(" + (func) + ", this)"); + return ("" + (utility('bind')) + "(" + (func) + ", " + (this.context) + ")"); } return top ? ("(" + (func) + ")") : func; }; diff --git a/src/nodes.coffee b/src/nodes.coffee index 4a9d736e..572eaa13 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -740,12 +740,15 @@ exports.ClassNode = class ClassNode extends BaseNode constructor = func continue if func instanceof CodeNode and func.bound - func.bound = false - constScope or= new Scope(o.scope, constructor.body, constructor) - me or= constScope.freeVariable() - pname = pvar.compile(o) - constructor.body.push new ReturnNode literal 'this' if constructor.body.empty() - constructor.body.unshift literal "this.#{pname} = function(){ return #{className}.prototype.#{pname}.apply(#{me}, arguments); }" + if prop.context is 'this' + func.context = className + else + func.bound = false + constScope or= new Scope(o.scope, constructor.body, constructor) + me or= constScope.freeVariable() + pname = pvar.compile(o) + constructor.body.push new ReturnNode literal 'this' if constructor.body.empty() + constructor.body.unshift literal "this.#{pname} = function(){ return #{className}.prototype.#{pname}.apply(#{me}, arguments); }" if pvar access = if prop.context is 'this' then pvar.base.properties[0] else new AccessorNode(pvar, 'prototype') val = new ValueNode(@variable, [access]) @@ -874,9 +877,10 @@ exports.CodeNode = class CodeNode extends BaseNode constructor: (@params, @body, tag) -> super() - @params or= [] - @body or= new Expressions - @bound = tag is 'boundfunc' + @params or= [] + @body or= new Expressions + @bound = tag is 'boundfunc' + @context = 'this' if @bound # Compilation creates a new scope unless explicitly asked to share with the # outer scope. Handles splat parameters in the parameter list by peeking at @@ -918,7 +922,7 @@ exports.CodeNode = class CodeNode extends BaseNode (o.scope.parameter(param)) for param in params code = if @body.expressions.length then "\n#{ @body.compileWithDeclarations(o) }\n" else '' func = "function(#{ params.join(', ') }) {#{code}#{ code and @tab }}" - return "#{utility('bind')}(#{func}, this)" if @bound + return "#{utility('bind')}(#{func}, #{@context})" if @bound if top then "(#{func})" else func topSensitive: -> diff --git a/test/test_classes.coffee b/test/test_classes.coffee index 1f2bf5c7..ab9d7e58 100644 --- a/test/test_classes.coffee +++ b/test/test_classes.coffee @@ -131,6 +131,7 @@ ok instance.name() is 'class' # Classes with methods that are pre-bound to the instance. +# ... or statically, to the class. class Dog constructor: (name) -> @@ -139,12 +140,19 @@ class Dog bark: => "#{@name} woofs!" + @static: => + new this('Dog') + spark = new Dog('Spark') fido = new Dog('Fido') fido.bark = spark.bark ok fido.bark() is 'Spark woofs!' +obj = func: Dog.static + +ok obj.func().name is 'Dog' + # Testing a bound function in a bound function. class Mini