- Notifications
You must be signed in to change notification settings - Fork2k
[CS2] Compile all super calls to ES2015 super#4424
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Conversation
I think calling |
I'm all for option 1. See#3796. |
kirly-af commentedJan 19, 2017
I personally never used |
I think since this is already the big breaking change release, option#1 is the way to go. |
OK, I'll go for option 1., and we can discuss about@jashkenas' comment as a separate new feature. @mitar calling super within anonymous functions is possible, so long as they are arrow functions. classA{method(){console.log('A#method')}}classBextendsA{constructor(){(()=>super())()}method(){(()=>super.method())()console.log('B#method')}}newB().method()// A#method// B#method |
I also think we should go with option 1, since we can always add the more elaborate versions later. This PR has a bug though I think. Take this input: classAfoo:->console.log'woohoo!'classBextendsAbar:->super.foo() This branch produces: varA,B;A=classA{foo(){returnconsole.log('woohoo!');}};B=classBextendsA{bar(){returnsuper.bar(...arguments).foo();}}; but it should produce: varA,B;A=classA{foo(){returnconsole.log('woohoo!');}};B=classBextendsA{bar(){returnsuper.foo();}}; |
Not a bug - that's how CoffeeScript's Interestingly, it seems that Personally, I think being able to look up arbitrary methods on the superclass is an undesirable feature. An overridden method shouldn't need to be aware thatother methods may be overridden. I.e. if It also encourages people to think that |
I agree with this (naturally), and I think I've made that argument in these pages before. But! If we're switching over to ES6 semantics for classes for CS2, shouldn't we switch over to ES6 semantics for super at the same time? |
A |
connec commentedJan 20, 2017 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Well, I'd argue that with classes we've changed very few semantics - we've adopted, by necessity, the @GeoffreyBooth it's theother form that was planned not implemented ( I'm not sure I agree that weneed to support it. I feel like we're just adding needless redundancy since 99%1 of uses are just Whilst maintaining that it would be best not to, we could make both uses live side-by-side. If we do go down that route we might want to talk about letting 'bare 1. Made up. |
GeoffreyBooth commentedJan 20, 2017 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Using |
I think that once again, it'll be a step backwards — but we should make it work like JS |
OK, lets add Next question is bare To outline the suggested compilation: classBaseclassSuperExamplesextendsBase# Should throw a SyntaxError along the lines of "'super' must be called or accessed"bareSuper:->super# Should compile to `super.shortSuper()`shortSuper:->super()# Should compile to `super.esSuper()`esSuper:->super.esSuper() |
Yes, we should make it work like ES |
connec commentedJan 20, 2017 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
It looks like I was hasty to say"it's a 'syntactic form' that is only valid within a class initializer" - it seems it's also valid within methods in object initializers: ({foo(){super.foo()}// OK})({foo:function(){super.foo()}// SyntaxError: 'super' keyword unexpected here}) Of course we don't currently compile object assigns with method values to that syntax. I had planned to look into that at some point to clean up the code I had to put in |
But how are you supposed to get that "method" back onto the prototype from outside? |
Heh, I was just writing a similar example in node, and got the same result. The MDN example shows using it via Object.setPrototypeOf(a,Lion)newa().speak() |
connec commentedJan 20, 2017 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
This works: functionA(){}A.prototype={foo(){console.log('A#foo')}}functionB(){}B.prototype={foo(){super.foo()console.log('B#foo')}}Object.setPrototypeOf(B.prototype,A.prototype)newB().foo()// A#foo// B#foo So, it seems that Edit: It seems the methods must be within their original object as well, the following doesn't work: A.prototype.foo=({foo(){console.log('A#foo')}}).foo...B.prototype.foo=({foo(){super.foo();console.log('B#foo')}}).fooObject.setPrototypeOf(B.prototype,A.prototype)newB().foo()// TypeError: (intermediate value).foo is not a function |
No, as you demonstrate below, there's still no way to add methods to an existing prototype and make use of |
So it basically seems that base={foo(){return'base'}}child1={foo(){returnsuper.foo()+' -> child1'}}child2={foo(){returnsuper.foo()+' -> child2'}}try{child1.foo()}catch(e){console.log(e)// TypeError: (intermediate value).foo is not a function}Object.setPrototypeOf(child1,base)console.log(child1.foo())// base -> child1Object.setPrototypeOf(child2,base)console.log(child2.foo())// base -> child2Object.setPrototypeOf(child2,child1)console.log(child2.foo())// base -> child1 -> child2method=child2.fooconsole.log(method())// base -> child1 -> child2functionA(){}A.prototype.method=child2.fooconsole.log(newA().method())// base -> child1 -> child2 |
connec commentedJan 26, 2017 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
OK, I've pushed some more updates that adds support for // Generated by CoffeeScript 2.0.0-alphavarA,B;A=classA{foo(){returnconsole.log('woohoo!');}};B=classBextendsA{bar(){returnsuper.foo();}}; I've also fixed a couple of incidental bugs that were exposed by these changes, including:
|
Awesome work! Assuming all the tests pass, is there anything else to be done for Aside from adding support for |
@GeoffreyBooth I think we need to have a discussion about Regarding |
GeoffreyBooth commentedJan 26, 2017 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
We can discuss |
I'd love to keep this sort of thing, and avoid the word |
@GeoffreyBooth I think that would be sufficient, yeh. I'll review those tests asap and double check there's nothing else we can throw out. Also if@lydell or@jashkenas (or anyone with more familiarity with the grammar) have any comments on thegrammar changes they'd be very welcome. |
Super: [ | ||
o 'SUPER', -> new SuperCall | ||
o 'SUPER Arguments', -> new SuperCall $2 | ||
o 'SUPER OptFuncExist Arguments', -> new SuperCall LOC(1)(new Super), $3, $2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
- Just double-checking: Was the
'SUPER Arguments'
case redundant because of'SUPER OptFuncExist Arguments'
? - What about the
'SUPER'
case?
jashkenasJan 26, 2017 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Whoa there — I don't know about all of the ES6 grammar rules and wrinkles ... but wouldn't it make more sense to bringSUPER
in as aValue
, instead of special-casing all of the different ways you might access it?
Edit: Nevermind -- looking more than just cursorily, you did exactly that!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Yeh, soSUPER Arguments
has been directly subsumed bySUPER OptFuncExists Arguments
(which allowssuper?()
to parse).
TheSUPER
case has been dropped as, in light of supportingsuper
with accessors, it becomes confusing whichever way we spin it:
Keep bare
super
as a super call to the method of the same name, forwarding arguments. This is fine until we have to deal withsuper.foo
, which now looks ambiguous: is itsuper.foo
as it appears, orsuper().foo
? What about(super).foo
? Tbh this use ofsuper
is useful enough that I'd be happy to leave it in, but it does lead to a number of ways of doing the same thing withsuper
.Allow bare
super
, and handle it the same waysuper()
is handled (compiler error in aconstructor
, and otherwise a reference tosuper.<current method>
). This would be safe and consistent, except it could lead to subtle bugs and misunderstandings for people upgrading to CS2 from CS1.
SO, a baresuper
will no longer parse. Unfortunately theerror message isn't very friendly, and any tips to improve that would be great!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I see, makes sense. 👍
@lydell did you have any further notes on this or is it ready to merge? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Just a few really minor things that shouldn't hold up a merge.
test "soaked 'super' in constructor", -> | ||
assertErrorFormat 'class extends A then constructor: -> super?()', ''' | ||
[stdin]:1:38: error: Unsupported reference to 'super' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
What aboutillegal instead ofunsupported? (The latter almost sounds like this is a TODO comment, doesn't it?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Hrm, I copied the current node.js error message verbatim. I suspect that might be because ifis a bit of a TODO in JS (given past discussions on ES about allowing baresuper
, amongst other things).
Since we're explicitly banning it, happy to change to "illegal" 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I see. It’s fine either way then, I guess.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I've decided to leave this asUnsupported
for consistency with JS.
test/error_messages.coffee Outdated
test "bare 'super' is no longer allowed", -> | ||
assertErrorFormat 'class extends A then constructor: -> super', ''' | ||
[stdin]:1:35: error: unexpected -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
We should add a TODO comment about improving this error message..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
👍
eq 2, super/ b/g | ||
eq 2, super() / b/g | ||
eq 2, super()/b/g | ||
eq 2, super()/ b/g | ||
eq true, super /b/g |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
This line still has a baresuper
? Also, I'm not sure if these tests makes any sense any longer when the parentheses are needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I'm not sure either tbh, I guess it depends whether we consider "super()
" a 'callable token'. The test on this line is still valid, though, although I guess it's testing general parens-less calling, rather than anything regex-specific (though, I guess it's checking that a regex in that format is parsed as an arguments...).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I've not made any change for this. This test is pretty large, and seems concerned with testing the parsing of regexen vs. division. This section was added to check that things worked properly when the preceding node is aSuperCall
, so from that point of view the test is the same (except now it could just as easily use any other function besidessuper
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I see. I think this test makes sense the way it is, then. 👍
I'll aim to address those last comments and review the class super tests this evening. |
@connec along with making the changes discussed recently, can you please updatehttps://github.com/jashkenas/coffeescript/wiki/%5BWIP%5D-Breaking-changes-in-CoffeeScript-2? Once the info about breaking changes for classes is in there, I think I'll move it into the docs proper. |
This breaks using `super` in non-methods, meaning several tests arefailing. Self-compilation still works.
`super` can only be called directly in a method, or in an arrowfunction.
This behaviour worked 'for free' when the parent reference was beingcached by the executable class body wrapper. There now needs to bespecial handling in place to check if the parent name matches the classname, and if so to cache the parent reference.
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 regularfunction call. This also removes the capability of implicitly forwardingarguments. The above can be equivalently be written as: class B extends A constructor: -> super arguments...
`super` with following accessor(s) is now compiled to ES2015equivalents. In particular, expressions such as `super.name`,`super[name]`, and also `super.name.prop` are all now valid, and can beused 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 aconstructor, and otherwise, as before, to a super call to the method ofthe same name, i.e. speak: -> super()...is equivalent to speak: -> super.speak()A neat side-effect of the changes is that existential calls now workproperly with super, meaning `super?()` will only call if the superproperty exists (and is a function). This is not valid for super inconstructors.
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 nowan explicit compiler error, as it is invalid JS at runtime.
This was mostly code for tracking the source classes and variables formethods, which were needed to build the old lookups on `__super__`.
@GeoffreyBooth /@lydell I've responded to the review issues. I've left some additional TODOs for the class tests that were added in#4354, rather than trying to tackle them in this PR (all the tests pass, but some should now 'fail' that were testing for broken things that now work...). In short, I think this is ready to go. |
Good job,@connec! |
Super quick first pass. This breaks using
super
in non-methods, meaning several tests are failing. Self-compilation still works.As I mentioned incoffeescript6/discuss#22 (comment), the main decision to be made here is around support for
super
in non-class methods. Opinions from@jashkenas,@GeoffreyBooth,@lydell or anyone else very welcome!Here are a couple of the options:
super
outside of methods in class initializers. This would clean up some code in the compiler for tracking the classes of methods, special handling of prototype assignments in class bodies, etc.super
, otherwise, compile it as we do now. This would just add a couple of special cases in the handling ofSuperCall
nodes.__super__
, method class recording, etc. and replace it all withObject.getPrototypeOf(this.constructor)
. This could optionally turnsuper
into a fully runtime feature that will compile anywhere, and would provide clean ES2015 output.Personally, I'm leaning towards 1. for the significant impact I expect it will have on the compiler, and the fact that, realistically, the prototype-based approach is gonna be rare in an ES2015 world. If really, really needed,
super
in a dynamic method can still be specified explicitly (e.g.BaseClass::method.apply(this, arguments)
).If the compatibility break of such a move is too great, I'd be interested in exploring 3. I think that 2. would not be popular as it would force us to continue using class IIFEs to ensure correct parent class caching, etc. meaning 'ugly' JS output.