mirror of
https://github.com/jashkenas/coffeescript.git
synced 2026-05-03 03:00:14 -04:00
[CS2] Compile all super calls to ES2015 super (#4424)
* Compile all super calls to ES2015 super
This breaks using `super` in non-methods, meaning several tests are
failing. Self-compilation still works.
* Use bound functions for IIFEs containing `super`
`super` can only be called directly in a method, or in an arrow
function.
* Fix handling of `class @A extends A`
This behaviour worked 'for free' when the parent reference was being
cached by the executable class body wrapper. There now needs to be
special handling in place to check if the parent name matches the class
name, and if so to cache the parent reference.
* Fix tests broken by compiling ES2015 `super`
* Disallow bare super
This removes syntax support for 'bare' super calls, e.g.:
class B extends A
constructor: -> super
`super` must now always be followed with arguments like a regular
function call. This also removes the capability of implicitly forwarding
arguments. The above can be equivalently be written as:
class B extends A
constructor: -> super arguments...
* Support super with accessors
`super` with following accessor(s) is now compiled to ES2015
equivalents. In particular, expressions such as `super.name`,
`super[name]`, and also `super.name.prop` are all now valid, and can be
used as expected as calls (i.e. `super.name()`) or in expressions (i.e.
`if super.name? ...`).
`super` without accessors is compiled to a constructor super call in a
constructor, and otherwise, as before, to a super call to the method of
the same name, i.e.
speak: -> super()
...is equivalent to
speak: -> super.speak()
A neat side-effect of the changes is that existential calls now work
properly with super, meaning `super?()` will only call if the super
property exists (and is a function). This is not valid for super in
constructors.
* Prevent calling `super` methods with `new`
This fixes a bug in the previous super handling whereby using the `new`
operator with a `super` call would silently drop the `new`. This is now
an explicit compiler error, as it is invalid JS at runtime.
* Clean up some old super handling code
This was mostly code for tracking the source classes and variables for
methods, which were needed to build the old lookups on `__super__`.
* Add TODO to improve bare super parse error
* Add some TODOs to improve some of the class tests
This commit is contained in:
committed by
Geoffrey Booth
parent
cbea7b5d1c
commit
396bd4f2f2
@@ -309,6 +309,13 @@ grammar =
|
||||
o 'Parenthetical', -> new Value $1
|
||||
o 'Range', -> new Value $1
|
||||
o 'This'
|
||||
o 'Super'
|
||||
]
|
||||
|
||||
# A `super`-based expression that can be used as a value.
|
||||
Super: [
|
||||
o 'SUPER . Property', -> new Super LOC(3) new Access $3
|
||||
o 'SUPER INDEX_START Expression INDEX_END', -> new Super LOC(3) new Index $3
|
||||
]
|
||||
|
||||
# The general group of accessors into an object, by property, by prototype
|
||||
@@ -429,12 +436,7 @@ grammar =
|
||||
o 'Value OptFuncExist String', -> new TaggedTemplateCall $1, $3, $2
|
||||
o 'Value OptFuncExist Arguments', -> new Call $1, $3, $2
|
||||
o 'Invocation OptFuncExist Arguments', -> new Call $1, $3, $2
|
||||
o 'Super'
|
||||
]
|
||||
|
||||
Super: [
|
||||
o 'SUPER', -> new SuperCall
|
||||
o 'SUPER Arguments', -> new SuperCall $2
|
||||
o 'SUPER OptFuncExist Arguments', -> new SuperCall LOC(1)(new Super), $3, $2
|
||||
]
|
||||
|
||||
# An optional existence check on a function.
|
||||
|
||||
136
src/nodes.coffee
136
src/nodes.coffee
@@ -81,7 +81,9 @@ exports.Base = class Base
|
||||
o.sharedScope = yes
|
||||
func = new Code [], Block.wrap [this]
|
||||
args = []
|
||||
if (argumentsNode = @contains isLiteralArguments) or @contains isLiteralThis
|
||||
if @contains ((node) -> node instanceof SuperCall)
|
||||
func.bound = yes
|
||||
else if (argumentsNode = @contains isLiteralArguments) or @contains isLiteralThis
|
||||
args = [new ThisLiteral]
|
||||
if argumentsNode
|
||||
meth = 'apply'
|
||||
@@ -516,7 +518,7 @@ exports.Literal = class Literal extends Base
|
||||
[@makeCode @value]
|
||||
|
||||
toString: ->
|
||||
" #{if @isStatement() then super else @constructor.name}: #{@value}"
|
||||
" #{if @isStatement() then super() else @constructor.name}: #{@value}"
|
||||
|
||||
exports.NumberLiteral = class NumberLiteral extends Literal
|
||||
|
||||
@@ -610,14 +612,14 @@ exports.YieldReturn = class YieldReturn extends Return
|
||||
compileNode: (o) ->
|
||||
unless o.scope.parent?
|
||||
@error 'yield can only occur inside functions'
|
||||
super
|
||||
super o
|
||||
|
||||
|
||||
exports.AwaitReturn = class AwaitReturn extends Return
|
||||
compileNode: (o) ->
|
||||
unless o.scope.parent?
|
||||
@error 'await can only occur inside functions'
|
||||
super
|
||||
super o
|
||||
|
||||
|
||||
#### Value
|
||||
@@ -780,9 +782,10 @@ exports.Call = class Call extends Base
|
||||
# Soaked chained invocations unfold into if/else ternary structures.
|
||||
unfoldSoak: (o) ->
|
||||
if @soak
|
||||
if this instanceof SuperCall
|
||||
left = new Literal @superReference o
|
||||
if @variable instanceof Super
|
||||
left = new Literal @variable.compile o
|
||||
rite = new Value left
|
||||
@variable.error "Unsupported reference to 'super'" unless @variable.accessor?
|
||||
else
|
||||
return ifn if ifn = unfoldSoak o, this, 'variable'
|
||||
[left, rite] = new Value(@variable).cacheReference o
|
||||
@@ -818,20 +821,11 @@ exports.Call = class Call extends Base
|
||||
compiledArgs.push (arg.compileToFragments o, LEVEL_LIST)...
|
||||
|
||||
fragments = []
|
||||
if this instanceof SuperCall
|
||||
preface = @superReference o
|
||||
if preface is 'super'
|
||||
preface += '('
|
||||
else
|
||||
preface += ".call(#{@superThis(o)}"
|
||||
if compiledArgs.length then preface += ", "
|
||||
fragments.push @makeCode preface
|
||||
else
|
||||
if @isNew then fragments.push @makeCode 'new '
|
||||
fragments.push @variable.compileToFragments(o, LEVEL_ACCESS)...
|
||||
fragments.push @makeCode "("
|
||||
fragments.push compiledArgs...
|
||||
fragments.push @makeCode ")"
|
||||
if @isNew
|
||||
@variable.error "Unsupported reference to 'super'" if @variable instanceof Super
|
||||
fragments.push @makeCode 'new '
|
||||
fragments.push @variable.compileToFragments(o, LEVEL_ACCESS)...
|
||||
fragments.push @makeCode('('), compiledArgs..., @makeCode(')')
|
||||
fragments
|
||||
|
||||
#### Super
|
||||
@@ -842,20 +836,15 @@ exports.Call = class Call extends Base
|
||||
# expressions are evaluated without altering the return value of the `SuperCall`
|
||||
# expression.
|
||||
exports.SuperCall = class SuperCall extends Call
|
||||
children: ['expressions']
|
||||
|
||||
constructor: (args) ->
|
||||
super null, args ? [new Splat new IdentifierLiteral 'arguments']
|
||||
# Allow to recognize a bare `super` call without parentheses and arguments.
|
||||
@isBare = args?
|
||||
children: Call::children.concat ['expressions']
|
||||
|
||||
isStatement: (o) ->
|
||||
@expressions?.length and o.level is LEVEL_TOP
|
||||
|
||||
compileNode: (o) ->
|
||||
return super unless @expressions?.length
|
||||
return super o unless @expressions?.length
|
||||
|
||||
superCall = new Literal fragmentsToText super
|
||||
superCall = new Literal fragmentsToText super o
|
||||
replacement = new Block @expressions.slice()
|
||||
|
||||
if o.level > LEVEL_TOP
|
||||
@@ -866,33 +855,26 @@ exports.SuperCall = class SuperCall extends Call
|
||||
replacement.unshift superCall
|
||||
replacement.compileToFragments o, if o.level is LEVEL_TOP then o.level else LEVEL_LIST
|
||||
|
||||
# Grab the reference to the superclass's implementation of the current
|
||||
# method.
|
||||
superReference: (o) ->
|
||||
exports.Super = class Super extends Base
|
||||
children: ['accessor']
|
||||
|
||||
constructor: (@accessor) ->
|
||||
super()
|
||||
|
||||
compileNode: (o) ->
|
||||
method = o.scope.namedMethod()
|
||||
if method?.ctor
|
||||
'super'
|
||||
else if method?.klass
|
||||
{klass, name, variable} = method
|
||||
if klass.shouldCache()
|
||||
bref = new IdentifierLiteral o.scope.parent.freeVariable 'base'
|
||||
base = new Value new Parens new Assign bref, klass
|
||||
variable.base = base
|
||||
variable.properties.splice 0, klass.properties.length
|
||||
@error 'cannot use super outside of an instance method' unless method?.isMethod
|
||||
|
||||
@inCtor = !!method.ctor
|
||||
|
||||
unless @inCtor or @accessor?
|
||||
{name, variable} = method
|
||||
if name.shouldCache() or (name instanceof Index and name.index.isAssignable())
|
||||
nref = new IdentifierLiteral o.scope.parent.freeVariable 'name'
|
||||
name.index = new Assign nref, name.index
|
||||
accesses = [new Access new PropertyName '__super__']
|
||||
accesses.push new Access new PropertyName 'constructor' if method.isStatic
|
||||
accesses.push if nref? then new Index nref else name
|
||||
(new Value bref ? klass, accesses).compile o
|
||||
else
|
||||
@error 'cannot call super outside of an instance method.'
|
||||
@accessor = if nref? then new Index nref else name
|
||||
|
||||
# The appropriate `this` value for a `super` call.
|
||||
superThis : (o) ->
|
||||
method = o.scope.method
|
||||
(method and not method.klass and method.context) or "this"
|
||||
(new Value (new Literal 'super'), if @accessor then [ @accessor ] else []).compileToFragments o
|
||||
|
||||
#### RegexWithInterpolations
|
||||
|
||||
@@ -1195,7 +1177,11 @@ exports.Class = class Class extends Base
|
||||
@name = @determineName()
|
||||
executableBody = @walkBody()
|
||||
|
||||
if executableBody
|
||||
# Special handling to allow `class expr.A extends A` declarations
|
||||
parentName = @parent.base.value if @parent instanceof Value and not @parent.hasProperties()
|
||||
@hasNameClash = @name? and @name == parentName
|
||||
|
||||
if executableBody or @hasNameClash
|
||||
@compileNode = @compileClassDeclaration
|
||||
result = new ExecutableClassBody(@, executableBody).compileToFragments o
|
||||
@compileNode = @constructor::compileNode
|
||||
@@ -1302,8 +1288,7 @@ exports.Class = class Class extends Base
|
||||
@boundMethods.push method.name
|
||||
method.bound = false
|
||||
|
||||
# TODO Once `super` has been changed over to ES, the check for @parent can be removed
|
||||
if @parent or initializer.length != expressions.length
|
||||
if initializer.length != expressions.length
|
||||
@body.expressions = (expression.hoist() for expression in initializer)
|
||||
new Block expressions
|
||||
|
||||
@@ -1328,18 +1313,16 @@ exports.Class = class Class extends Base
|
||||
|
||||
# Returns a configured class initializer method
|
||||
addInitializerMethod: (assign) ->
|
||||
variable = assign.variable
|
||||
method = assign.value
|
||||
{ variable, value: method } = assign
|
||||
method.isMethod = yes
|
||||
method.isStatic = variable.looksStatic @name
|
||||
method.klass = new IdentifierLiteral @name
|
||||
method.variable = variable
|
||||
|
||||
if method.isStatic
|
||||
method.name = variable.properties[0]
|
||||
else
|
||||
methodName = variable.base
|
||||
method.name = new (if methodName.shouldCache() then Index else Access) methodName
|
||||
method.name.updateLocationDataIfMissing methodName.locationData
|
||||
method.ctor = (if @parent then 'derived' else 'base') if methodName.value is 'constructor'
|
||||
method.error 'Cannot define a constructor as a bound function' if method.bound and method.ctor
|
||||
|
||||
@@ -1349,7 +1332,8 @@ exports.Class = class Class extends Base
|
||||
ctor = @addInitializerMethod new Assign (new Value new PropertyName 'constructor'), new Code
|
||||
@body.unshift ctor
|
||||
|
||||
ctor.body.push new SuperCall if @parent
|
||||
if @parent
|
||||
ctor.body.push new SuperCall new Super, [new Splat new IdentifierLiteral 'arguments']
|
||||
|
||||
if @externalCtor
|
||||
applyCtor = new Value @externalCtor, [ new Access new PropertyName 'apply' ]
|
||||
@@ -1394,19 +1378,17 @@ exports.ExecutableClassBody = class ExecutableClassBody extends Base
|
||||
|
||||
o.classScope = wrapper.makeScope o.scope
|
||||
|
||||
if @class.hasNameClash
|
||||
parent = new IdentifierLiteral o.classScope.freeVariable 'superClass'
|
||||
wrapper.params.push new Param parent
|
||||
args.push @class.parent
|
||||
@class.parent = parent
|
||||
|
||||
if @externalCtor
|
||||
externalCtor = new IdentifierLiteral o.classScope.freeVariable 'ctor', reserve: no
|
||||
@class.externalCtor = externalCtor
|
||||
@externalCtor.variable.base = externalCtor
|
||||
|
||||
if @class.parent
|
||||
parent = new IdentifierLiteral o.classScope.freeVariable 'superClass', reserve: no
|
||||
params.push new Param parent
|
||||
args.push @class.parent
|
||||
|
||||
@class.parent = parent
|
||||
@body.unshift new Literal "#{@name}.__super__ = #{parent.value}.prototype"
|
||||
|
||||
if @name != @class.name
|
||||
@body.expressions.unshift new Assign (new IdentifierLiteral @name), @class
|
||||
else
|
||||
@@ -1442,8 +1424,6 @@ exports.ExecutableClassBody = class ExecutableClassBody extends Base
|
||||
child.expressions[i] = @addProperties node.base.properties
|
||||
else if node instanceof Assign and node.variable.looksStatic @name
|
||||
node.value.isStatic = yes
|
||||
else if node instanceof Code and node.isMethod
|
||||
node.klass = new IdentifierLiteral @name
|
||||
child.expressions = flatten child.expressions
|
||||
cont
|
||||
|
||||
@@ -1670,15 +1650,10 @@ exports.Assign = class Assign extends Base
|
||||
return @compileSpecialMath o if @context in ['**=', '//=', '%%=']
|
||||
if @value instanceof Code
|
||||
if @value.isStatic
|
||||
@value.klass = @variable.base
|
||||
@value.name = @variable.properties[0]
|
||||
@value.variable = @variable
|
||||
@value.name = @variable.properties[0]
|
||||
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
|
||||
@value.name = name if prototype.name?.value is 'prototype'
|
||||
unless @context
|
||||
varBase = @variable.unwrapAll()
|
||||
unless varBase.isAssignable()
|
||||
@@ -1904,8 +1879,8 @@ exports.Code = class Code extends Base
|
||||
# function body.
|
||||
compileNode: (o) ->
|
||||
if @ctor
|
||||
@variable.error 'Class constructor may not be async' if @isAsync
|
||||
@variable.error 'Class constructor may not be a generator' if @isGenerator
|
||||
@name.error 'Class constructor may not be async' if @isAsync
|
||||
@name.error 'Class constructor may not be a generator' if @isGenerator
|
||||
|
||||
if @bound
|
||||
@context = o.scope.method.context if o.scope.method?.bound
|
||||
@@ -2263,7 +2238,7 @@ exports.While = class While extends Base
|
||||
|
||||
makeReturn: (res) ->
|
||||
if res
|
||||
super
|
||||
super res
|
||||
else
|
||||
@returns = not @jumps loop: yes
|
||||
this
|
||||
@@ -2985,7 +2960,6 @@ UTILITIES =
|
||||
}
|
||||
ctor.prototype = parent.prototype;
|
||||
child.prototype = new ctor();
|
||||
child.__super__ = parent.prototype;
|
||||
return child;
|
||||
}
|
||||
"
|
||||
@@ -3052,9 +3026,7 @@ isLiteralArguments = (node) ->
|
||||
node instanceof IdentifierLiteral and node.value is 'arguments'
|
||||
|
||||
isLiteralThis = (node) ->
|
||||
node instanceof ThisLiteral or
|
||||
(node instanceof Code and node.bound) or
|
||||
node instanceof SuperCall
|
||||
node instanceof ThisLiteral or (node instanceof Code and node.bound)
|
||||
|
||||
shouldCacheOrIsAssignable = (node) -> node.shouldCache() or node.isAssignable?()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user