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
|
// 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 = /^\//;
|
||||||
|
|
|
@ -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 = /^\//
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue