Skip to content

Bugfix 1183 #2252

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 24, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions lib/coffee-script/nodes.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions lib/coffee-script/scope.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 11 additions & 5 deletions src/nodes.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,9 @@ exports.Call = class Call extends Base
# Grab the reference to the superclass's implementation of the current
# method.
superReference: (o) ->
{method} = o.scope
# Keep walking up the scope chain until we find the original reference
# to a method. Stop at the first one.
method = o.scope.getMethodRecurse()
throw SyntaxError 'cannot call super outside of a function.' unless method
{name} = method
throw SyntaxError 'cannot call super on an anonymous function.' unless name?
Expand All @@ -508,6 +510,9 @@ exports.Call = class Call extends Base
else
"#{name}.__super__.constructor"

superThis : (o) ->
if o.scope?.method?.context? then o.scope.method.context else "this"

# Soaked chained invocations unfold into if/else ternary structures.
unfoldSoak: (o) ->
if @soak
Expand Down Expand Up @@ -566,21 +571,21 @@ exports.Call = class Call extends Base
args = @filterImplicitObjects @args
args = (arg.compile o, LEVEL_LIST for arg in args).join ', '
if @isSuper
@superReference(o) + ".call(this#{ args and ', ' + args })"
@superReference(o) + ".call(#{@superThis o}#{ args and ', ' + args })"
else
(if @isNew then 'new ' else '') + @variable.compile(o, LEVEL_ACCESS) + "(#{args})"

# `super()` is converted into a call against the superclass's implementation
# of the current function.
compileSuper: (args, o) ->
"#{@superReference(o)}.call(this#{ if args.length then ', ' else '' }#{args})"
"#{@superReference(o)}.call(#{@superThis o}#{ if args.length then ', ' else '' }#{args})"

# If you call a function with a splat, it's converted into a JavaScript
# `.apply()` call to allow an array of arguments to be passed.
# If it's a constructor, then things get real tricky. We have to inject an
# inner constructor in order to be able to pass the varargs.
compileSplat: (o, splatArgs) ->
return "#{ @superReference o }.apply(this, #{splatArgs})" if @isSuper
return "#{ @superReference o }.apply(#{@superThis o}, #{splatArgs})" if @isSuper
if @isNew
idt = @tab + TAB
return """
Expand Down Expand Up @@ -1924,7 +1929,8 @@ Closure =

literalThis: (node) ->
(node instanceof Literal and node.value is 'this' and not node.asKey) or
(node instanceof Code and node.bound)
(node instanceof Code and node.bound) or
(node instanceof Call and node.isSuper)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maxtaco -- @michaelficarra, as part of my refactoring of this commit in 34be878, I removed this change. No tests failed after I did so. Was this needed for some behavior?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, let me page this back in.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. Either this was gratuitous or the tests weren't comprehensive. Either way, not good.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember thinking, "man, this is going to be ugly to write a regtest for." Still playing around with it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I was wrong about the above, it's a pretty simple case. Here is but one instance when the current code gets it wrong:

class A
  constructor : ->
     @i = 10
  foo :  ->
     console.log "A:foo #{@i}"

class B extends A
  foo :  ->
    x = switch x
      when 'a'
        something()
      else
        super()

b = new B()
b.foo()

With my fix, this snippet works and prints "A:foo 10". The new proposed fix prints "A:foo undefined" What's going on is this. Sometimes the code in the method that calls super will be compiled into a wrapped closure. If that's so, it has to trigger the check that calls the wrapper closure with this from the parent. With the new proposed fix, the closure is called with no arguments.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reasonable, although that particular test case is a wee bit golfed ;)

Want to send the patch?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seriously, I'm in a mad rush. Sure, I'll send something off later tonight. What's the preferred way to force a node.compileClosure on line 47 in nodes.coffee? That's what my ugly switch was trying to do.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The preferred way to force it is to make the particular node's isStatement check return true.


# Unfold a node's child if soak, then tuck the node under created `If`
unfoldSoak = (o, parent, name) ->
Expand Down
12 changes: 12 additions & 0 deletions src/scope.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ exports.Scope = class Scope
else
@positions[name] = @variables.push({name, type}) - 1

# When `super somearg` is called, we need to find the name of the current
# method we're in, so we know how to address the same method of the
# super class. This can get complicated if super is being called
# from a closure. getMethodRecurse() will walk up the scope
# tree until it finds the first method object that has a name filled
# in. It will return the topmost method object otherwise, which may
# be null if this is being called from outside a function.
getMethodRecurse: ->
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get a comment above this line with a small description or some usage intentions?

if @method?.name? then @method
else if @parent then @parent.getMethodRecurse()
else @method # {} for a function, and null for a non-function

# Look up a variable name in lexical scope, and declare it if it does not
# already exist.
find: (name, options) ->
Expand Down
32 changes: 32 additions & 0 deletions test/scope.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,35 @@ test "#1973: redefining Array/Object constructors shouldn't confuse __X helpers"
obj = {arr}
for own k of obj
eq arr, obj[k]

test "#1183: super + fat arrows", ->
dolater = (cb) -> cb()

class A
constructor: ->
@_i = 0
foo : (cb) ->
dolater =>
@_i += 1
cb()

class B extends A
constructor : ->
super
foo : (cb) ->
dolater =>
dolater =>
@_i += 2
super cb

b = new B()
b.foo => eq b._i, 3

test "#1183: super + wrap", ->
class A
m : -> 10
class B extends A
constructor : -> super
B::m = -> r = try super()
eq (new B()).m(), 10