diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 2b6be415..ecd0adb9 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -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 = /^\//; diff --git a/src/nodes.coffee b/src/nodes.coffee index 569c4bec..7a6dcd81 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -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 = /^\// diff --git a/test/classes.coffee b/test/classes.coffee index 143b46e0..7212cbe7 100644 --- a/test/classes.coffee +++ b/test/classes.coffee @@ -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()