Skip to content
Merged
50 changes: 45 additions & 5 deletions lib/coffeescript/nodes.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 31 additions & 3 deletions src/nodes.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -1308,6 +1308,10 @@ exports.Class = class Class extends Base
# Anonymous classes are only valid in expressions
node = new Parens node

if @boundMethods.length and @parent
@variable ?= new IdentifierLiteral o.scope.freeVariable '_class'
[@variable, @variableRef] = @variable.cache o unless @variableRef?

if @variable
node = new Assign @variable, node, null, { @moduleDeclaration }

Expand All @@ -1318,9 +1322,11 @@ exports.Class = class Class extends Base
delete @compileNode

compileClassDeclaration: (o) ->
@ctor ?= @makeDefaultConstructor() if @externalCtor
@ctor ?= @makeDefaultConstructor() if @externalCtor or @boundMethods.length
@ctor?.noReturn = true

@proxyBoundMethods() if @boundMethods.length

o.indent += TAB

result = []
Expand Down Expand Up @@ -1356,6 +1362,7 @@ exports.Class = class Class extends Base

walkBody: ->
@ctor = null
@boundMethods = []
executableBody = null

initializer = []
Expand Down Expand Up @@ -1401,6 +1408,8 @@ exports.Class = class Class extends Base
@ctor = method
else if method.isStatic and method.bound
method.context = @name
else if method.bound
@boundMethods.push method

if initializer.length isnt expressions.length
@body.expressions = (expression.hoist() for expression in initializer)
Expand Down Expand Up @@ -1438,7 +1447,7 @@ exports.Class = class Class extends Base
method.name = new (if methodName.shouldCache() then Index else Access) methodName
method.name.updateLocationDataIfMissing methodName.locationData
method.ctor = (if @parent then 'derived' else 'base') if methodName.value is 'constructor'
method.error 'Methods cannot be bound functions' if method.bound
method.error 'Cannot define a constructor as a bound (fat arrow) function' if method.bound and method.ctor

method

Expand All @@ -1457,6 +1466,15 @@ exports.Class = class Class extends Base

ctor

proxyBoundMethods: ->
@ctor.thisAssignments = for method in @boundMethods
method.classVariable = @variableRef if @parent

name = new Value(new ThisLiteral, [ method.name ])
new Assign name, new Call(new Value(name, [new Access new PropertyName 'bind']), [new ThisLiteral])

null

exports.ExecutableClassBody = class ExecutableClassBody extends Base
children: [ 'class', 'body' ]

Expand Down Expand Up @@ -1540,7 +1558,7 @@ exports.ExecutableClassBody = class ExecutableClassBody extends Base
@body.traverseChildren false, (node) =>
if node instanceof ThisLiteral
node.value = @name
else if node instanceof Code and node.bound
else if node instanceof Code and node.bound and node.isStatic
node.context = @name

# Make class/prototype assignments for invalid ES properties
Expand Down Expand Up @@ -2180,6 +2198,9 @@ exports.Code = class Code extends Base
wasEmpty = @body.isEmpty()
@body.expressions.unshift thisAssignments... unless @expandCtorSuper thisAssignments
@body.expressions.unshift exprs...
if @isMethod and @bound and not @isStatic and @classVariable
boundMethodCheck = new Value new Literal utility 'boundMethodCheck', o
@body.expressions.unshift new Call(boundMethodCheck, [new Value(new ThisLiteral), @classVariable])
@body.makeReturn() unless wasEmpty or @noReturn

# Assemble the output
Expand Down Expand Up @@ -3149,6 +3170,13 @@ exports.If = class If extends Base

UTILITIES =
modulo: -> 'function(a, b) { return (+a % (b = +b) + b) % b; }'
boundMethodCheck: -> "
function(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new Error('Bound instance method accessed before binding');
}
}
"

# Shortcuts to speed up the lookup time for native functions.
hasProp: -> '{}.hasOwnProperty'
Expand Down
5 changes: 3 additions & 2 deletions test/assignment.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -562,8 +562,9 @@ test "Assignment to variables similar to helper functions", ->
extend = 3
hasProp = 4
value: 5
method: (bind, bind1) -> [bind, bind1, extend, hasProp, @value]
arrayEq [1, 2, 3, 4, 5], new B().method 1, 2
method: (bind, bind1) => [bind, bind1, extend, hasProp, @value]
{method} = new B
arrayEq [1, 2, 3, 4, 5], method 1, 2

modulo = -1 %% 3
eq 2, modulo
Expand Down
Loading