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 property 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 14, 2015
1 parent 669e7fe commit 2f9aebe
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 47 deletions.
69 changes: 46 additions & 23 deletions lib/coffee-script/nodes.js

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

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

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

# The value can be unwrapped as its inner node, if there are no attached
# properties.
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 propertyName(prototype) 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 Expand Up @@ -2296,3 +2303,10 @@ unfoldSoak = (o, parent, name) ->
parent[name] = ifn.body
ifn.body = new Value parent
ifn

# Extract `name` from `.name` or `["name"]`.
propertyName = (property) -> switch
when property instanceof Access
property.name.value
when property instanceof Index and property.index instanceof Value and property.index.isString()
property.index.base.value[1...-1]
97 changes: 97 additions & 0 deletions test/classes.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -828,3 +828,100 @@ 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()

test "different ways to write 'prototype'", ->
class Base
a: -> 1
b: -> 2
c: -> 3
d: -> 4
e: -> 5

A = ->
A extends Base
A::a = -> super
A['prototype'].b = -> super
A["prototype"].c = -> super
A['''prototype'''].d = -> super
A["""prototype"""].e = -> super
a = new A
eq 1, a.a()
eq 2, a.b()
eq 3, a.c()
eq 4, a.d()
eq 5, a.e()

class B extends Base
@::a = -> super
@['prototype'].b = -> super
@["prototype"].c = -> super
@['''prototype'''].d = -> super
@["""prototype"""].e = -> super
b = new B
eq 1, b.a()
eq 2, b.b()
eq 3, b.c()
eq 4, b.d()
eq 5, b.e()

0 comments on commit 2f9aebe

Please sign in to comment.