mirror of
https://github.com/jashkenas/coffeescript.git
synced 2022-11-09 12:23:24 -05:00
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:
parent
04b30a6cc4
commit
ee8f889cbd
3 changed files with 129 additions and 45 deletions
|
@ -1,6 +1,6 @@
|
|||
// Generated by CoffeeScript 1.9.0
|
||||
(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; },
|
||||
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; },
|
||||
|
@ -765,7 +765,7 @@
|
|||
|
||||
Value.prototype.looksStatic = function(className) {
|
||||
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() {
|
||||
|
@ -901,15 +901,28 @@
|
|||
};
|
||||
|
||||
Call.prototype.superReference = function(o) {
|
||||
var accesses, method;
|
||||
var accesses, base, bref, klass, method, name, nref, variable;
|
||||
method = o.scope.namedMethod();
|
||||
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__'))];
|
||||
if (method["static"]) {
|
||||
accesses.push(new Access(new Literal('constructor')));
|
||||
}
|
||||
accesses.push(new Access(new Literal(method.name)));
|
||||
return (new Value(new Literal(method.klass), accesses)).compile(o);
|
||||
accesses.push(nref != null ? new Index(nref) : name);
|
||||
return (new Value(bref != null ? bref : klass, accesses)).compile(o);
|
||||
} else if (method != null ? method.ctor : void 0) {
|
||||
return method.name + ".__super__.constructor";
|
||||
} else {
|
||||
|
@ -1413,7 +1426,6 @@
|
|||
if (node instanceof Literal && node.value === 'this') {
|
||||
return node.value = name;
|
||||
} else if (node instanceof Code) {
|
||||
node.klass = name;
|
||||
if (node.bound) {
|
||||
return node.context = name;
|
||||
}
|
||||
|
@ -1600,7 +1612,7 @@
|
|||
};
|
||||
|
||||
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 (this.variable.isArray() || this.variable.isObject()) {
|
||||
return this.compilePatternMatch(o);
|
||||
|
@ -1615,8 +1627,20 @@
|
|||
return this.compileSpecialMath(o);
|
||||
}
|
||||
}
|
||||
compiledName = this.variable.compileToFragments(o, LEVEL_LIST);
|
||||
name = fragmentsToText(compiledName);
|
||||
if (this.value instanceof Code) {
|
||||
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) {
|
||||
varBase = this.variable.unwrapAll();
|
||||
if (!varBase.isAssignable()) {
|
||||
|
@ -1624,19 +1648,14 @@
|
|||
}
|
||||
if (!(typeof varBase.hasProperties === "function" ? varBase.hasProperties() : void 0)) {
|
||||
if (this.param) {
|
||||
o.scope.add(name, 'var');
|
||||
o.scope.add(varBase.value, 'var');
|
||||
} 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);
|
||||
compiledName = this.variable.compileToFragments(o, LEVEL_LIST);
|
||||
if (this.context === 'object') {
|
||||
return compiledName.concat(this.makeCode(": "), val);
|
||||
}
|
||||
|
@ -3141,9 +3160,7 @@
|
|||
|
||||
TAB = ' ';
|
||||
|
||||
IDENTIFIER_STR = "[$A-Za-z_\\x7f-\\uffff][$\\w\\x7f-\\uffff]*";
|
||||
|
||||
IDENTIFIER = RegExp("^" + IDENTIFIER_STR + "$");
|
||||
IDENTIFIER = /^(?!\d)[$\w\x7f-\uffff]+$/;
|
||||
|
||||
SIMPLENUM = /^[+-]?\d+$/;
|
||||
|
||||
|
@ -3151,8 +3168,6 @@
|
|||
|
||||
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_REGEX = /^\//;
|
||||
|
|
|
@ -513,7 +513,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
|
||||
|
@ -614,10 +614,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
|
||||
|
@ -1011,7 +1022,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
|
||||
|
@ -1162,21 +1172,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
|
||||
|
@ -2237,8 +2254,7 @@ 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 = ///^[+-]?(?:
|
||||
|
@ -2246,15 +2262,6 @@ NUMBER = ///^[+-]?(?:
|
|||
\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 = /^\//
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue