Skip to content

Commit 79c5c30

Browse files
committed
Allow super in methods with dynamic names
As discussed in #3039 (comment). This is the first step to implement dynamic object literal keys (see #3597). This also fixes #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
1 parent 669e7fe commit 79c5c30

File tree

3 files changed

+129
-45
lines changed

3 files changed

+129
-45
lines changed

lib/coffee-script/nodes.js

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

src/nodes.coffee

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

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

518518
# The value can be unwrapped as its inner node, if there are no attached
@@ -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,6 @@ 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
10131023
node.context = name if node.bound
10141024

10151025
# Ensure that all functions bound to the instance are proxied in the
@@ -1160,21 +1170,28 @@ exports.Assign = class Assign extends Base
11601170
return @compileSplice o if @variable.isSplice()
11611171
return @compileConditional o if @context in ['||=', '&&=', '?=']
11621172
return @compileSpecialMath o if @context in ['**=', '//=', '%%=']
1163-
compiledName = @variable.compileToFragments o, LEVEL_LIST
1164-
name = fragmentsToText compiledName
1173+
if @value instanceof Code
1174+
if @value.static
1175+
@value.klass = @variable.base
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 prototype.name?.value 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
@@ -2233,24 +2250,14 @@ LEVEL_ACCESS = 6 # ...[0]
22332250
# Tabs are two spaces for pretty printing.
22342251
TAB = ' '
22352252

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

2245-
METHOD_DEF = /// ^
2246-
(#{IDENTIFIER_STR})
2247-
(\.prototype)?
2248-
(?: \.(#{IDENTIFIER_STR})
2249-
| \[("(?:[^\\"\r\n]|\\.)*"|'(?:[^\\'\r\n]|\\.)*')\]
2250-
| \[(0x[\da-fA-F]+ | \d*\.?\d+ (?:[eE][+-]?\d+)?)\]
2251-
)
2252-
$ ///
2253-
22542261
# Is a literal value a string/regex?
22552262
IS_STRING = /^['"]/
22562263
IS_REGEX = /^\//

test/classes.coffee

+62
Original file line numberDiff line numberDiff line change
@@ -828,3 +828,65 @@ 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()

0 commit comments

Comments
 (0)