Skip to content

Commit 967d8e9

Browse files
committed
Allow super in methods with dynamic names
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
1 parent bec8f27 commit 967d8e9

File tree

3 files changed

+181
-47
lines changed

3 files changed

+181
-47
lines changed

lib/coffee-script/nodes.js

+46-23
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/nodes.coffee

+38-24
Original file line numberDiff line numberDiff line change
@@ -512,8 +512,8 @@ exports.Value = class Value extends Base
512512
last(@properties) instanceof Slice
513513

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

518518
# The value can be unwrapped as its inner node, if there are no attached
519519
# properties.
@@ -613,10 +613,21 @@ exports.Call = class Call extends Base
613613
superReference: (o) ->
614614
method = o.scope.namedMethod()
615615
if method?.klass
616-
accesses = [new Access(new Literal '__super__')]
616+
{klass, name, variable} = method
617+
if klass.isComplex()
618+
bref = new Literal o.scope.parent.freeVariable 'base'
619+
base = new Value new Parens new Assign bref, klass
620+
variable.base = base
621+
variable.properties.splice 0, klass.properties.length
622+
if name.isComplex() or (name instanceof Index and name.index.isAssignable())
623+
nref = new Literal o.scope.parent.freeVariable 'name'
624+
name = new Index new Assign nref, name.index
625+
variable.properties.pop()
626+
variable.properties.push name
627+
accesses = [new Access new Literal '__super__']
617628
accesses.push new Access new Literal 'constructor' if method.static
618-
accesses.push new Access new Literal method.name
619-
(new Value (new Literal method.klass), accesses).compile o
629+
accesses.push if nref? then new Index nref else name
630+
(new Value bref ? klass, accesses).compile o
620631
else if method?.ctor
621632
"#{method.name}.__super__.constructor"
622633
else
@@ -1009,7 +1020,7 @@ exports.Class = class Class extends Base
10091020
if node instanceof Literal and node.value is 'this'
10101021
node.value = name
10111022
else if node instanceof Code
1012-
node.klass = name
1023+
node.klass = new Value new Literal name
10131024
node.context = name if node.bound
10141025

10151026
# Ensure that all functions bound to the instance are proxied in the
@@ -1160,21 +1171,27 @@ exports.Assign = class Assign extends Base
11601171
return @compileSplice o if @variable.isSplice()
11611172
return @compileConditional o if @context in ['||=', '&&=', '?=']
11621173
return @compileSpecialMath o if @context in ['**=', '//=', '%%=']
1163-
compiledName = @variable.compileToFragments o, LEVEL_LIST
1164-
name = fragmentsToText compiledName
1174+
if @value instanceof Code
1175+
if @value.static
1176+
@value.name = @variable.properties[0]
1177+
@value.variable = @variable
1178+
else if @variable.properties?.length >= 2
1179+
[properties..., prototype, name] = @variable.properties
1180+
if propertyName(prototype) is 'prototype'
1181+
@value.klass = new Value @variable.base, properties
1182+
@value.name = name
1183+
@value.variable = @variable
11651184
unless @context
11661185
varBase = @variable.unwrapAll()
11671186
unless varBase.isAssignable()
11681187
@variable.error "\"#{@variable.compile o}\" cannot be assigned"
11691188
unless varBase.hasProperties?()
11701189
if @param
1171-
o.scope.add name, 'var'
1190+
o.scope.add varBase.value, 'var'
11721191
else
1173-
o.scope.find name
1174-
if @value instanceof Code and match = METHOD_DEF.exec name
1175-
@value.klass = match[1] if match[2]
1176-
@value.name = match[3] ? match[4] ? match[5]
1192+
o.scope.find varBase.value
11771193
val = @value.compileToFragments o, LEVEL_LIST
1194+
compiledName = @variable.compileToFragments o, LEVEL_LIST
11781195
return (compiledName.concat @makeCode(": "), val) if @context is 'object'
11791196
answer = compiledName.concat @makeCode(" #{ @context or '=' } "), val
11801197
if o.level <= LEVEL_LIST then answer else @wrapInBraces answer
@@ -2237,24 +2254,14 @@ LEVEL_ACCESS = 6 # ...[0]
22372254
# Tabs are two spaces for pretty printing.
22382255
TAB = ' '
22392256

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

2249-
METHOD_DEF = /// ^
2250-
(#{IDENTIFIER_STR})
2251-
(\.prototype)?
2252-
(?: \.(#{IDENTIFIER_STR})
2253-
| \[("(?:[^\\"\r\n]|\\.)*"|'(?:[^\\'\r\n]|\\.)*')\]
2254-
| \[(0x[\da-fA-F]+ | \d*\.?\d+ (?:[eE][+-]?\d+)?)\]
2255-
)
2256-
$ ///
2257-
22582265
# Is a literal value a string/regex?
22592266
IS_STRING = /^['"]/
22602267
IS_REGEX = /^\//
@@ -2296,3 +2303,10 @@ unfoldSoak = (o, parent, name) ->
22962303
parent[name] = ifn.body
22972304
ifn.body = new Value parent
22982305
ifn
2306+
2307+
# Extract `name` from `.name` or `["name"]`.
2308+
propertyName = (property) -> switch
2309+
when property instanceof Access
2310+
property.name.value
2311+
when property instanceof Index and property.index instanceof Value and property.index.isString()
2312+
property.index.base.value[1...-1]

test/classes.coffee

+97
Original file line numberDiff line numberDiff line change
@@ -828,3 +828,100 @@ test "#3232: super in static methods (not object-assigned)", ->
828828

829829
ok Bar.baz()
830830
ok Bar.qux()
831+
832+
test "#1392 calling `super` in methods defined on namespaced classes", ->
833+
class Base
834+
m: -> 5
835+
n: -> 4
836+
namespace =
837+
A: ->
838+
B: ->
839+
namespace.A extends Base
840+
841+
namespace.A::m = -> super
842+
eq 5, (new namespace.A).m()
843+
namespace.B::m = namespace.A::m
844+
namespace.A::m = null
845+
eq 5, (new namespace.B).m()
846+
847+
count = 0
848+
getNamespace = -> count++; namespace
849+
getNamespace().A::n = -> super
850+
eq 4, (new namespace.A).n()
851+
eq 1, count
852+
853+
class C
854+
@a: ->
855+
@a extends Base
856+
@a::m = -> super
857+
eq 5, (new C.a).m()
858+
859+
test "dynamic method names and super", ->
860+
class Base
861+
@m: -> 6
862+
m: -> 5
863+
n: -> 4
864+
A = ->
865+
A extends Base
866+
867+
m = 'm'
868+
A::[m] = -> super
869+
m = 'n'
870+
eq 5, (new A).m()
871+
872+
name = -> count++; 'n'
873+
874+
count = 0
875+
A::[name()] = -> super
876+
eq 4, (new A).n()
877+
eq 1, count
878+
879+
m = 'm'
880+
count = 0
881+
class B extends Base
882+
@[name()] = -> super
883+
@::[m] = -> super
884+
b = new B
885+
m = 'n'
886+
eq 6, B.m()
887+
eq 5, b.m()
888+
eq 1, count
889+
890+
class C extends B
891+
@m: -> super
892+
eq 5, (new C).m()
893+
894+
test "different ways to write 'prototype'", ->
895+
class Base
896+
a: -> 1
897+
b: -> 2
898+
c: -> 3
899+
d: -> 4
900+
e: -> 5
901+
902+
A = ->
903+
A extends Base
904+
A::a = -> super
905+
A['prototype'].b = -> super
906+
A["prototype"].c = -> super
907+
A['''prototype'''].d = -> super
908+
A["""prototype"""].e = -> super
909+
a = new A
910+
eq 1, a.a()
911+
eq 2, a.b()
912+
eq 3, a.c()
913+
eq 4, a.d()
914+
eq 5, a.e()
915+
916+
class B extends Base
917+
@::a = -> super
918+
@['prototype'].b = -> super
919+
@["prototype"].c = -> super
920+
@['''prototype'''].d = -> super
921+
@["""prototype"""].e = -> super
922+
b = new B
923+
eq 1, b.a()
924+
eq 2, b.b()
925+
eq 3, b.c()
926+
eq 4, b.d()
927+
eq 5, b.e()

0 commit comments

Comments
 (0)