-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Extending an ES6 class with a CS class fails #4233
Comments
(Actually, you’re doing |
This is still a major blocker for us - any way you guys can push a patch to interoperate with classes?? |
@bcherny Someone would have to suggest a fix first. I don't believe that there's going to be a way to do this. It could be done with |
I see what you're saying, what a bummer. What about an opt-in compiler flag, something like |
The CoffeeScript maintainers have rejected compilation target options many times in the past, but it may be accepted if that's the only way to do something. |
I think an ES6 compilation target was almost-agreed upon? |
What's the compile target today? It seems that CS already makes exceptions for generators. This one special case for interoperable classes would be a huge win for my team! |
I'm not certain on the details, but I think generators are different, because there, you opt in to ES6 dependance by using generators. Generators compile to ES6 code. Classes are different, as people use CoffeeScript classes in code that is expected to work in old browsers, so you'd need a flag or something. |
👍 |
You could use the same trick angular uses to determine whether or not to use ...
function B () {
if (/^class /.test(A.__super__.constructor)) {
// A extends an ES6 class so can't be called directly
return new (Function.prototype.bind.apply(B.__super__.constructor, [ null ].concat(slice.call(arguments)))
} else {
// A extends something else (presumably a function), so apply is fine
return B.__super__.constructor.apply(this, arguments)
}
} If |
@connec That's a great point! Pinging @michaelficarra for comment.. |
One issue with that actually is the You could deal with some cases by testing class BadAncestor
constructor: ->
return Object.create(BadAncestor.prototype)
class Descendant extends BadAncestor
new Descendant # BadAncestor {} currently / Descendant {} if changed |
@connec I'm not sure I understand. From your code sample, it looks like this is already an issue today. How would interop with ES6 Classes make this any different? |
Sorry, I jumped around a bit, I'll try and explain it properly. For context, just in case, Javascript has this funky behaviour whereby using new with a constructor that returns an object will evaluate to the returned object, whereas returning any non-object will evaluate to the new instance. function A () {
return { hello: 'world' }
}
new A() // { hello: 'world' }
function B () {
return 'not an object'
}
new B() // B {} The current The proposed compilation of Initially I thought it might be possible to check if the result of calling the superclass constructor was an instance of the superclass, and if so assume the superclass constructor didn't return anything. However this doesn't work if the superclass constructor explicitly returns an instance of the superclass... What a mess. I'm inclined to believe the only way for this to work properly is to just emit an |
@connec Thanks for the explanation - I didn't understand that the broken case is a super's constructor returning something. Coming from more strictly OO languages, it actually seems pretty problematic that this is ES6's behavior today (I guess it makes sense, since class A {
constructor(){
return {}
}
}
class B extends A {}
new B instanceof A // false
new B instanceof B // false Anyway, I've created runnable test cases for the 4 scenarios, which I think address all the variants: |
Actually, what if we make this even simpler (what @carlsmith was getting at):
That's it. No need for worrying about reproducing ES6 class extend semantics with functions. It's opt-in, same as generators. |
What's the process for getting this implemented? Is there a particular contributor that needs to sign off on this? I would put up a PR myself, but I am not confident in how to best make this change. |
@bcherny this would be a breaking change for 1.x, cause coffeescript classes can have inner bodies and plain properties (not methods) that ES6 class can't have. The way forwards is to go for Coffeescript version 6, see my #4078 (comment) |
@anodynos I'm not sure what you mean, exactly. What's an inner body? And properties we can compile fine: class A
@b = 1
constructor: ->
@c = 3 Compiled to ES5 today: var A;
A = (function() {
A.b = 1;
function A() {
this.c = 3;
}
return A;
})(); Compiled to ES2015 classes: var A;
A = (function() {
A.b = 1;
class A {
constructor() {
this.c = 3;
}
}
return A;
})(); |
Well, everything is possible :-)
These dont exist in ES6 - of course can be emulated with a getter/setter, but its a breaking change... Inner bodies also dont exist is ES6, eg
The sad thing is that current CoffeeScript 1.x has extremely limited support for ES6 and everyone is on the ES6 train right now :-( |
I had thought about executable class bodies as one of the really neat things about CS that I would sorely miss. You could achieve the same effect, though. Also, ES6 still supports class A
counter = 0
# other executable class body statements
# Non-function prototype property
b: 'nice'
constructor: ->
@id = ++counter let A = (() => {
let counter
class A {
constructor () {
this.id = ++counter
}
}
// Non-function prototype property
A.prototype.b = 'nice'
(function () {
counter = 0
// other executable class body statements
}).call(A)
return A
})() The main issue with this is that it does spread the code out a bit from how it was written (e.g. all functions would be 'hoisted' into the class definition). |
Dont break CS class, just add new word _class or something else that implements ES6 class |
@yogurt1 a flag seems cleaner to me. With your approach, you are coupling the concerns of writing code and setting the compiler's target. @michaelficarra @vendethiel Now that we've talked over the semantics of this change (the differences with what we have today are not large), would either of you guys be interested in patching this? |
A related note: The functions of ES6 classes are not enumerable. To properly extend an ES6 class in Coffeescript one has to use e.g. class ES6Class {
static foo() {}
} class CSClass extends ES6Class {} The expected behaviour is for Related discussion lodash/lodash#649. |
Cross-posting; please see coffeescript6/discuss#22 (comment) and #4330 |
Our solution for this is CoffeeScript 2 and its support for the ES As I understand things, it isn’t possible to extend an ES class without using the Solutions that aren’t acceptable:
We hope that CoffeeScript 2 will have so few breaking changes that there should be little hesitation in upgrading, or starting a new project with it. We hope that the better support for ES2015+ outweighs the hassle of needing to attach Babel or another transpiler if you need to generate JavaScript for browsers. |
@GeoffreyBooth What's the roadmap for CS2? When (very roughly) can we expect it to be a replacement for CS? |
@bcherny You can start using the There’s no timeline, but we’re close to an initial alpha release. See https://github.com/coffeescript6/discuss. |
CoffeeScript calls classes as functions (rather than newing them as constructors) when instantiating a class that inherits from an ES6 class.
The fix is to interoperate with ES6 classes - no need to go all the way and compile Coffee to ES6 classes. We should check to see if a class is an ES6 class, and treat it differently (eg. see what angular does here).
Test case:
js:
coffee:
The text was updated successfully, but these errors were encountered: