Skip to content

Commit

Permalink
Allow super in methods with dynamic names
Browse files Browse the repository at this point in the history
As discussed in jashkenas#3039 (comment).
This is the first step to implement dynamic object literal keys (see jashkenas#3597).

This also fixes jashkenas#1392.

In short, `super` is now allowed:

    # in class definitions:
    class A
      instanceMethod: -> super
      @staticmethod: -> super
      @staticMethod2 = -> super

    # in assignment where the next to last access is 'prototype':
    A::m = -> super
    A.prototype.m = -> super
    a.b()[5]::m = -> super
    A::[x()] = -> super
    class B
      @::m = -> super
  • Loading branch information
lydell committed Jan 16, 2015
1 parent 669e7fe commit ac7e031
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 45 deletions.
59 changes: 37 additions & 22 deletions lib/coffee-script/nodes.js

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

53 changes: 30 additions & 23 deletions src/nodes.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ exports.Value = class Value extends Base
last(@properties) instanceof Slice

looksStatic: (className) ->
@base.value is className and @properties.length and
@base.value is className and @properties.length is 1 and
@properties[0].name?.value isnt 'prototype'

# The value can be unwrapped as its inner node, if there are no attached
Expand Down Expand Up @@ -613,10 +613,21 @@ exports.Call = class Call extends Base
superReference: (o) ->
method = o.scope.namedMethod()
if method?.klass
accesses = [new Access(new Literal '__super__')]
{klass, name, variable} = method
if klass.isComplex()
bref = new Literal o.scope.parent.freeVariable 'base'
base = new Value new Parens new Assign bref, klass
variable.base = base
variable.properties.splice 0, klass.properties.length
if name.isComplex() or (name instanceof Index and name.index.isAssignable())
nref = new Literal o.scope.parent.freeVariable 'name'
name = new Index new Assign nref, name.index
variable.properties.pop()
variable.properties.push name
accesses = [new Access new Literal '__super__']
accesses.push new Access new Literal 'constructor' if method.static
accesses.push new Access new Literal method.name
(new Value (new Literal method.klass), accesses).compile o
accesses.push if nref? then new Index nref else name
(new Value bref ? klass, accesses).compile o
else if method?.ctor
"#{method.name}.__super__.constructor"
else
Expand Down Expand Up @@ -1009,7 +1020,6 @@ exports.Class = class Class extends Base
if node instanceof Literal and node.value is 'this'
node.value = name
else if node instanceof Code
node.klass = name
node.context = name if node.bound

# Ensure that all functions bound to the instance are proxied in the
Expand Down Expand Up @@ -1160,21 +1170,28 @@ exports.Assign = class Assign extends Base
return @compileSplice o if @variable.isSplice()
return @compileConditional o if @context in ['||=', '&&=', '?=']
return @compileSpecialMath o if @context in ['**=', '//=', '%%=']
compiledName = @variable.compileToFragments o, LEVEL_LIST
name = fragmentsToText compiledName
if @value instanceof Code
if @value.static
@value.klass = @variable.base
@value.name = @variable.properties[0]
@value.variable = @variable
else if @variable.properties?.length >= 2
[properties..., prototype, name] = @variable.properties
if prototype.name?.value is 'prototype'
@value.klass = new Value @variable.base, properties
@value.name = name
@value.variable = @variable
unless @context
varBase = @variable.unwrapAll()
unless varBase.isAssignable()
@variable.error "\"#{@variable.compile o}\" cannot be assigned"
unless varBase.hasProperties?()
if @param
o.scope.add name, 'var'
o.scope.add varBase.value, 'var'
else
o.scope.find name
if @value instanceof Code and match = METHOD_DEF.exec name
@value.klass = match[1] if match[2]
@value.name = match[3] ? match[4] ? match[5]
o.scope.find varBase.value
val = @value.compileToFragments o, LEVEL_LIST
compiledName = @variable.compileToFragments o, LEVEL_LIST
return (compiledName.concat @makeCode(": "), val) if @context is 'object'
answer = compiledName.concat @makeCode(" #{ @context or '=' } "), val
if o.level <= LEVEL_LIST then answer else @wrapInBraces answer
Expand Down Expand Up @@ -2233,24 +2250,14 @@ LEVEL_ACCESS = 6 # ...[0]
# Tabs are two spaces for pretty printing.
TAB = ' '

IDENTIFIER_STR = "[$A-Za-z_\\x7f-\\uffff][$\\w\\x7f-\\uffff]*"
IDENTIFIER = /// ^ #{IDENTIFIER_STR} $ ///
IDENTIFIER = /// ^ (?!\d) [$\w\x7f-\uffff]+ $ ///
SIMPLENUM = /^[+-]?\d+$/
HEXNUM = /^[+-]?0x[\da-f]+/i
NUMBER = ///^[+-]?(?:
0x[\da-f]+ | # hex
\d*\.?\d+ (?:e[+-]?\d+)? # decimal
)$///i

METHOD_DEF = /// ^
(#{IDENTIFIER_STR})
(\.prototype)?
(?: \.(#{IDENTIFIER_STR})
| \[("(?:[^\\"\r\n]|\\.)*"|'(?:[^\\'\r\n]|\\.)*')\]
| \[(0x[\da-fA-F]+ | \d*\.?\d+ (?:[eE][+-]?\d+)?)\]
)
$ ///

# Is a literal value a string/regex?
IS_STRING = /^['"]/
IS_REGEX = /^\//
Expand Down
62 changes: 62 additions & 0 deletions test/classes.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -828,3 +828,65 @@ test "#3232: super in static methods (not object-assigned)", ->

ok Bar.baz()
ok Bar.qux()

test "#1392 calling `super` in methods defined on namespaced classes", ->
class Base
m: -> 5
n: -> 4
namespace =
A: ->
B: ->
namespace.A extends Base

namespace.A::m = -> super
eq 5, (new namespace.A).m()
namespace.B::m = namespace.A::m
namespace.A::m = null
eq 5, (new namespace.B).m()

count = 0
getNamespace = -> count++; namespace
getNamespace().A::n = -> super
eq 4, (new namespace.A).n()
eq 1, count

class C
@a: ->
@a extends Base
@a::m = -> super
eq 5, (new C.a).m()

test "dynamic method names and super", ->
class Base
@m: -> 6
m: -> 5
n: -> 4
A = ->
A extends Base

m = 'm'
A::[m] = -> super
m = 'n'
eq 5, (new A).m()

name = -> count++; 'n'

count = 0
A::[name()] = -> super
eq 4, (new A).n()
eq 1, count

m = 'm'
count = 0
class B extends Base
@[name()] = -> super
@::[m] = -> super
b = new B
m = 'n'
eq 6, B.m()
eq 5, b.m()
eq 1, count

class C extends B
@m: -> super
eq 5, (new C).m()

0 comments on commit ac7e031

Please sign in to comment.