Allow super in methods with dynamic names

As discussed in https://github.com/jashkenas/coffeescript/issues/3039#issuecomment-68916918.
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
This commit is contained in:
Simon Lydell 2015-01-11 16:56:08 +01:00
parent 04b30a6cc4
commit ee8f889cbd
3 changed files with 129 additions and 45 deletions

View File

@ -1,6 +1,6 @@
// Generated by CoffeeScript 1.9.0 // Generated by CoffeeScript 1.9.0
(function() { (function() {
var Access, Arr, Assign, Base, Block, Call, Class, Code, CodeFragment, Comment, Existence, Expansion, Extends, For, HEXNUM, IDENTIFIER, IDENTIFIER_STR, IS_REGEX, IS_STRING, If, In, Index, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, METHOD_DEF, NEGATE, NO, NUMBER, Obj, Op, Param, Parens, RESERVED, Range, Return, SIMPLENUM, STRICT_PROSCRIBED, Scope, Slice, Splat, Switch, TAB, THIS, Throw, Try, UTILITIES, Value, While, YES, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, isComplexOrAssignable, isLiteralArguments, isLiteralThis, last, locationDataToString, merge, multident, parseNum, ref1, ref2, some, starts, throwSyntaxError, unfoldSoak, utility, var Access, Arr, Assign, Base, Block, Call, Class, Code, CodeFragment, Comment, Existence, Expansion, Extends, For, HEXNUM, IDENTIFIER, IS_REGEX, IS_STRING, If, In, Index, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, NEGATE, NO, NUMBER, Obj, Op, Param, Parens, RESERVED, Range, Return, SIMPLENUM, STRICT_PROSCRIBED, Scope, Slice, Splat, Switch, TAB, THIS, Throw, Try, UTILITIES, Value, While, YES, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, isComplexOrAssignable, isLiteralArguments, isLiteralThis, last, locationDataToString, merge, multident, parseNum, ref1, ref2, some, starts, throwSyntaxError, unfoldSoak, utility,
extend1 = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, extend1 = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty, hasProp = {}.hasOwnProperty,
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
@ -765,7 +765,7 @@
Value.prototype.looksStatic = function(className) { Value.prototype.looksStatic = function(className) {
var ref3; var ref3;
return this.base.value === className && this.properties.length && ((ref3 = this.properties[0].name) != null ? ref3.value : void 0) !== 'prototype'; return this.base.value === className && this.properties.length === 1 && ((ref3 = this.properties[0].name) != null ? ref3.value : void 0) !== 'prototype';
}; };
Value.prototype.unwrap = function() { Value.prototype.unwrap = function() {
@ -901,15 +901,28 @@
}; };
Call.prototype.superReference = function(o) { Call.prototype.superReference = function(o) {
var accesses, method; var accesses, base, bref, klass, method, name, nref, variable;
method = o.scope.namedMethod(); method = o.scope.namedMethod();
if (method != null ? method.klass : void 0) { if (method != null ? method.klass : void 0) {
klass = method.klass, name = method.name, variable = method.variable;
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() || (name instanceof Index && 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 = [new Access(new Literal('__super__'))];
if (method["static"]) { if (method["static"]) {
accesses.push(new Access(new Literal('constructor'))); accesses.push(new Access(new Literal('constructor')));
} }
accesses.push(new Access(new Literal(method.name))); accesses.push(nref != null ? new Index(nref) : name);
return (new Value(new Literal(method.klass), accesses)).compile(o); return (new Value(bref != null ? bref : klass, accesses)).compile(o);
} else if (method != null ? method.ctor : void 0) { } else if (method != null ? method.ctor : void 0) {
return method.name + ".__super__.constructor"; return method.name + ".__super__.constructor";
} else { } else {
@ -1413,7 +1426,6 @@
if (node instanceof Literal && node.value === 'this') { if (node instanceof Literal && node.value === 'this') {
return node.value = name; return node.value = name;
} else if (node instanceof Code) { } else if (node instanceof Code) {
node.klass = name;
if (node.bound) { if (node.bound) {
return node.context = name; return node.context = name;
} }
@ -1600,7 +1612,7 @@
}; };
Assign.prototype.compileNode = function(o) { Assign.prototype.compileNode = function(o) {
var answer, compiledName, isValue, match, name, ref3, ref4, ref5, ref6, val, varBase; var answer, compiledName, isValue, j, name, properties, prototype, ref3, ref4, ref5, ref6, ref7, val, varBase;
if (isValue = this.variable instanceof Value) { if (isValue = this.variable instanceof Value) {
if (this.variable.isArray() || this.variable.isObject()) { if (this.variable.isArray() || this.variable.isObject()) {
return this.compilePatternMatch(o); return this.compilePatternMatch(o);
@ -1615,8 +1627,20 @@
return this.compileSpecialMath(o); return this.compileSpecialMath(o);
} }
} }
compiledName = this.variable.compileToFragments(o, LEVEL_LIST); if (this.value instanceof Code) {
name = fragmentsToText(compiledName); if (this.value["static"]) {
this.value.klass = this.variable.base;
this.value.name = this.variable.properties[0];
this.value.variable = this.variable;
} else if (((ref5 = this.variable.properties) != null ? ref5.length : void 0) >= 2) {
ref6 = this.variable.properties, properties = 3 <= ref6.length ? slice.call(ref6, 0, j = ref6.length - 2) : (j = 0, []), prototype = ref6[j++], name = ref6[j++];
if (((ref7 = prototype.name) != null ? ref7.value : void 0) === 'prototype') {
this.value.klass = new Value(this.variable.base, properties);
this.value.name = name;
this.value.variable = this.variable;
}
}
}
if (!this.context) { if (!this.context) {
varBase = this.variable.unwrapAll(); varBase = this.variable.unwrapAll();
if (!varBase.isAssignable()) { if (!varBase.isAssignable()) {
@ -1624,19 +1648,14 @@
} }
if (!(typeof varBase.hasProperties === "function" ? varBase.hasProperties() : void 0)) { if (!(typeof varBase.hasProperties === "function" ? varBase.hasProperties() : void 0)) {
if (this.param) { if (this.param) {
o.scope.add(name, 'var'); o.scope.add(varBase.value, 'var');
} else { } else {
o.scope.find(name); o.scope.find(varBase.value);
} }
} }
} }
if (this.value instanceof Code && (match = METHOD_DEF.exec(name))) {
if (match[2]) {
this.value.klass = match[1];
}
this.value.name = (ref5 = (ref6 = match[3]) != null ? ref6 : match[4]) != null ? ref5 : match[5];
}
val = this.value.compileToFragments(o, LEVEL_LIST); val = this.value.compileToFragments(o, LEVEL_LIST);
compiledName = this.variable.compileToFragments(o, LEVEL_LIST);
if (this.context === 'object') { if (this.context === 'object') {
return compiledName.concat(this.makeCode(": "), val); return compiledName.concat(this.makeCode(": "), val);
} }
@ -3141,9 +3160,7 @@
TAB = ' '; TAB = ' ';
IDENTIFIER_STR = "[$A-Za-z_\\x7f-\\uffff][$\\w\\x7f-\\uffff]*"; IDENTIFIER = /^(?!\d)[$\w\x7f-\uffff]+$/;
IDENTIFIER = RegExp("^" + IDENTIFIER_STR + "$");
SIMPLENUM = /^[+-]?\d+$/; SIMPLENUM = /^[+-]?\d+$/;
@ -3151,8 +3168,6 @@
NUMBER = /^[+-]?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)$/i; NUMBER = /^[+-]?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)$/i;
METHOD_DEF = RegExp("^(" + IDENTIFIER_STR + ")(\\.prototype)?(?:\\.(" + IDENTIFIER_STR + ")|\\[(\"(?:[^\\\\\"\\r\\n]|\\\\.)*\"|'(?:[^\\\\'\\r\\n]|\\\\.)*')\\]|\\[(0x[\\da-fA-F]+|\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)\\])$");
IS_STRING = /^['"]/; IS_STRING = /^['"]/;
IS_REGEX = /^\//; IS_REGEX = /^\//;

View File

@ -513,7 +513,7 @@ exports.Value = class Value extends Base
last(@properties) instanceof Slice last(@properties) instanceof Slice
looksStatic: (className) -> 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' @properties[0].name?.value isnt 'prototype'
# The value can be unwrapped as its inner node, if there are no attached # The value can be unwrapped as its inner node, if there are no attached
@ -614,10 +614,21 @@ exports.Call = class Call extends Base
superReference: (o) -> superReference: (o) ->
method = o.scope.namedMethod() method = o.scope.namedMethod()
if method?.klass 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 'constructor' if method.static
accesses.push new Access new Literal method.name accesses.push if nref? then new Index nref else name
(new Value (new Literal method.klass), accesses).compile o (new Value bref ? klass, accesses).compile o
else if method?.ctor else if method?.ctor
"#{method.name}.__super__.constructor" "#{method.name}.__super__.constructor"
else else
@ -1011,7 +1022,6 @@ exports.Class = class Class extends Base
if node instanceof Literal and node.value is 'this' if node instanceof Literal and node.value is 'this'
node.value = name node.value = name
else if node instanceof Code else if node instanceof Code
node.klass = name
node.context = name if node.bound node.context = name if node.bound
# Ensure that all functions bound to the instance are proxied in the # Ensure that all functions bound to the instance are proxied in the
@ -1162,21 +1172,28 @@ exports.Assign = class Assign extends Base
return @compileSplice o if @variable.isSplice() return @compileSplice o if @variable.isSplice()
return @compileConditional o if @context in ['||=', '&&=', '?='] return @compileConditional o if @context in ['||=', '&&=', '?=']
return @compileSpecialMath o if @context in ['**=', '//=', '%%='] return @compileSpecialMath o if @context in ['**=', '//=', '%%=']
compiledName = @variable.compileToFragments o, LEVEL_LIST if @value instanceof Code
name = fragmentsToText compiledName 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 unless @context
varBase = @variable.unwrapAll() varBase = @variable.unwrapAll()
unless varBase.isAssignable() unless varBase.isAssignable()
@variable.error "\"#{@variable.compile o}\" cannot be assigned" @variable.error "\"#{@variable.compile o}\" cannot be assigned"
unless varBase.hasProperties?() unless varBase.hasProperties?()
if @param if @param
o.scope.add name, 'var' o.scope.add varBase.value, 'var'
else else
o.scope.find name o.scope.find varBase.value
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]
val = @value.compileToFragments o, LEVEL_LIST val = @value.compileToFragments o, LEVEL_LIST
compiledName = @variable.compileToFragments o, LEVEL_LIST
return (compiledName.concat @makeCode(": "), val) if @context is 'object' return (compiledName.concat @makeCode(": "), val) if @context is 'object'
answer = compiledName.concat @makeCode(" #{ @context or '=' } "), val answer = compiledName.concat @makeCode(" #{ @context or '=' } "), val
if o.level <= LEVEL_LIST then answer else @wrapInBraces answer if o.level <= LEVEL_LIST then answer else @wrapInBraces answer
@ -2237,8 +2254,7 @@ LEVEL_ACCESS = 6 # ...[0]
# Tabs are two spaces for pretty printing. # Tabs are two spaces for pretty printing.
TAB = ' ' TAB = ' '
IDENTIFIER_STR = "[$A-Za-z_\\x7f-\\uffff][$\\w\\x7f-\\uffff]*" IDENTIFIER = /// ^ (?!\d) [$\w\x7f-\uffff]+ $ ///
IDENTIFIER = /// ^ #{IDENTIFIER_STR} $ ///
SIMPLENUM = /^[+-]?\d+$/ SIMPLENUM = /^[+-]?\d+$/
HEXNUM = /^[+-]?0x[\da-f]+/i HEXNUM = /^[+-]?0x[\da-f]+/i
NUMBER = ///^[+-]?(?: NUMBER = ///^[+-]?(?:
@ -2246,15 +2262,6 @@ NUMBER = ///^[+-]?(?:
\d*\.?\d+ (?:e[+-]?\d+)? # decimal \d*\.?\d+ (?:e[+-]?\d+)? # decimal
)$///i )$///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 a literal value a string/regex?
IS_STRING = /^['"]/ IS_STRING = /^['"]/
IS_REGEX = /^\// IS_REGEX = /^\//

View File

@ -828,3 +828,65 @@ test "#3232: super in static methods (not object-assigned)", ->
ok Bar.baz() ok Bar.baz()
ok Bar.qux() 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()