{"action":"created","issue":{"url":"https://api.github.com/repos/scala-js/scala-js/issues/1795","labels_url":"https://api.github.com/repos/scala-js/scala-js/issues/1795/labels{/name}","comments_url":"https://api.github.com/repos/scala-js/scala-js/issues/1795/comments","events_url":"https://api.github.com/repos/scala-js/scala-js/issues/1795/events","html_url":"https://github.com/scala-js/scala-js/issues/1795","id":94740007,"number":1795,"title":"Proposal: Defining \"JavaScript classes\" in Scala.js","user":{"login":"sjrd","id":535934,"avatar_url":"https://avatars.githubusercontent.com/u/535934?v=3","gravatar_id":"","url":"https://api.github.com/users/sjrd","html_url":"https://github.com/sjrd","followers_url":"https://api.github.com/users/sjrd/followers","following_url":"https://api.github.com/users/sjrd/following{/other_user}","gists_url":"https://api.github.com/users/sjrd/gists{/gist_id}","starred_url":"https://api.github.com/users/sjrd/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sjrd/subscriptions","organizations_url":"https://api.github.com/users/sjrd/orgs","repos_url":"https://api.github.com/users/sjrd/repos","events_url":"https://api.github.com/users/sjrd/events{/privacy}","received_events_url":"https://api.github.com/users/sjrd/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/scala-js/scala-js/labels/enhancement","name":"enhancement","color":"84b6eb"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":12,"created_at":"2015-07-13T14:55:26Z","updated_at":"2015-07-14T17:52:44Z","closed_at":null,"body":"This has been the Achilles' heel of Scala.js: how to define, in Scala.js, a class inheriting from a JavaScript class. This essentially means defining a \"JavaScript class\" (extending js.Object or some other raw JS class) from Scala.js.\r\n\r\nI have been thinking this through for about 2 years (!), and have finally, I think, come up with appropriate semantics for such a language feature. This proposal lays down an enhancement proposal for Scala.js that specifies all this.\r\n\r\n## The problem\r\n\r\nI have a JavaScript class `Foo`, defined as follows (ES6 syntax):\r\n```js\r\nclass Foo {\r\n  constructor(x, y) {\r\n    this.x = x;\r\n    this.y = y;\r\n  }\r\n  print() {\r\n    console.log(`(${x},${y})`)\r\n  }\r\n}\r\n```\r\nA potential facade for this class could be:\r\n```scala\r\nclass Foo(val x: Int, val y: Int) extends js.Object {\r\n  def print(): Unit = js.native\r\n}\r\n```\r\nI want to *extend* this class from a class `Bar` defined in Scala.js, to add a third value, and override the `print` method to also display the third value. (Please forgive me the contrived example which is even likely not to respect the LSP.) If I were to define it in JavaScript, I would write:\r\n```js\r\nclass Bar extends Foo {\r\n  constructor(x, y, z) {\r\n    super(x, y);\r\n    this.z = z;\r\n  }\r\n  print() {\r\n    console.log(`(${x},${y},${z})`)\r\n  }\r\n}\r\n```\r\nIdeally, I want to write something close to this:\r\n```scala\r\nclass Bar(x: Int, y: Int, val z: Int) extends Foo(x, y) {\r\n  override def print(): Unit =\r\n    println((x, y, z))\r\n}\r\n```\r\n\r\n## Class notation\r\n\r\nA class which extends `js.Any` and has the annotation `js.annotation.JSScalaDefined` is not a facade type, but a Scala.js-defined JavaScript class:\r\n```scala\r\n@JSScalaDefined\r\nclass Bar extends js.Object\r\n```\r\nNote: `@JSScalaDefined` is awkward. In the future, we might want to invert the meanings: no annotation means Scala.js-defined; and `@js.native` means facade. See later for details about that.\r\n\r\nThe type `Bar` is still a *raw JS type*, so accesses to members of that type are always done \"the JS way\" (no mangling of member names).\r\n\r\nThe declaration of this class does *not* define a \"constructor function\" accessible from JavaScript. The constructors of a Scala.js-defined JS class are only visible to Scala.js code.\r\n\r\n`@JSExport` can be used to export constructors to JavaScript code, just like Scala.js Scala classes.\r\n\r\nRationale: these classes can be dead-code-eliminated if they are not instantiated. It also plays well with anonymous and nested such classes.\r\n\r\n## Method declaration\r\n\r\nA Scala.js-defined JS class can define methods. All public *and protected* methods are always \"exported\" to JS with semantics close to `@JSExport` for methods in Scala classes.\r\n```scala\r\n@JSScalaDefined\r\nclass Bar extends js.Object {\r\n  def foobar(a: Int, b: String): String =\r\n    a.toString + b\r\n}\r\n```\r\nFormally, parameters and result type are always erased/boxed in the visible type (like in exports).\r\n\r\nOverloading is possible, and is resolved almost like `@JSExport`. Unlike exports, the runtime overloading logic will call the variants with *static dispatch*. To be clear, instead of calling `this.foobar__I__T(a, b)`, it will call `Bar.prototype.foobar__I__T.call(this, a, b)`. This is necessary for *super calls* coming from sublasses of `Bar` to deal correctly with combinations of overloading and overriding.\r\n\r\nThis also means that, when overriding an overloaded method, the runtime overloading logic must be reemitted, even if the set of alternatives does not change (contrary to how exports handle this).\r\n\r\n## References to `this`\r\n\r\nReferences to members of `this` in the body of the methods (implicitly or explicitly) are dealt with just like any other raw JS type, which means that runtime overloading is always used, at the end of the day. This is necessary to have consistent behavior between `this.foo(5)` and `{ val self = this; self.foo(5) }`.\r\n\r\n## Getter and setter declaration\r\n\r\nLike in exports, a `def` without parentheses is visible as a *getter* in JavaScript. A `def` whose Scala name ends in `_=` is visible as a *setter* in JavaScript.\r\n\r\n## Field declaration\r\n\r\n*Unlike* in exports, `val`s and `var`s are *not* implemented with getters and setters. They become *direct fields* of the object. This is necessary to have meaningful overriding of a field inherited from a parent class.\r\n\r\nA `val` that implements or overrides an inherited `def` without parentheses will have the appropriate semantics as soon as the field *exists* on the object (since it will be seen before a getter/setter inherited from the prototype). However, when creating the field in the first place, we must bypass a potentially inherited getter/setter. We can do this with `Object.defineProperty`.\r\n\r\n(This might not play well with strong mode, though. But we probably don't want Scala.js-defined JS classes to have strong mode semantics anyway.)\r\n\r\nCompared to how this would work in Scala classes, this has surprisingly few caveats. The only semantic discrepancy I can foresee happens for `val x: Int = 3`, then `override val x: Int = 5`: after the superclass has initialized `x` and before the subclass has initialized `x`, on the JVM `x` would be seen as 0. On JS it would be seen as 3. But anyway reading `x` in that interval is probably a bug in the first place.\r\n\r\nThe Scala specification prevents to override a `val` or a `val` with getter/setter `def`s. This also reflects the JS limitation that \"overriding\" a field with getter/setters does not really work as expected (because getter/setters are on the prototype, which is checked after the object itself).\r\n\r\n## `@JSName`\r\n\r\nIt is of course invalid to `@JSExport` or `@JSExportAll` members of a Scala.js-defined JS class. It is however possible to use `@JSName` to change the name visible in JavaScript, just like for any raw JS type.\r\n\r\n## Private members\r\n\r\nPrivate members are mangled in a way that, hopefully, will not clash with any JavaScript identifier. In ES6, we can use unique Symbols for that.\r\n\r\nOpen question: should overloading in private methods use compile-time dispatch or runtime-dispatch?\r\n\r\n## Backwards compatibility\r\n\r\nAs presented above, this is fully backwards source compatible with the current semantics of Scala.js. It can also probably be made backwards binary compatible.\r\n\r\n## Breaking backwards compatibility\r\n\r\nAs noted above, the `@JSScalaDefined` annotation is very awkward. In the future, I would like that `@JSScalaDefined` be the default. To get a facade type, one would need to annotate the class with `@js.native`.\r\n\r\nThe migration path would be:\r\n* In 0.6.x: have `@JSScalaDefined` and `@js.native`. A class with neither of those will be reported as a warning, saying `@js.native` should be added to future-proof it.\r\n* In the next major release: classes without the annotation would be Scala.js-defined. `@JSScalaDefined` becomes deprecated, and should be removed.\r\n\r\n## (Anonymous) nested classes\r\n\r\nIt is possible and valid to have Scala.js-defined JS classes nested in other classes (unlike facade types). In that case, it is not valid to `@JSExport` its constructors (unless `@JSExport` of nested classes is revived, see #354).\r\n\r\nSince anonymous classes cannot have annotations, with the current state it is in fact not possible to declare an anonymous Scala.js-defined JS class, because it needs `@JSScalaDefined`. To overcome this, and since anonymous facades are invalid anyway and cause compile errors at the moment, an anonymous class inheriting from `js.Any` is automatically a Scala.js-defined JS class.\r\n\r\n## Extend a Scala.js-defined JS class from JavaScript\r\n\r\nTo close the circle, we would possibly want to be able to extend a Scala.js-defined JS class from a JavaScript-defined class. This is not an immediate requirement, but we should keep this use case in mind when devising the implementation.\r\n\r\n## Implementation concerns\r\n\r\nA Scala.js-defined JS class would be a new `ClassKind` of `ClassDef` in the IR. It would be a raw JS type, but with member definitions.\r\n\r\nAll public and protected fields end up having String names, since they must be visible in JS. For methods, we probably want to emit private implementations of all methods, with static types and overloads, and have one dispatch and box/unbox bridge with a string name (thus similar to exports, except that the implementations are private).\r\n\r\nThe creation of the fields poses certain difficulties. They must be created (possibly with zeros) during the JS constructor executes, for performance reasons. As a first approximation, we can mimic the constructor architecture of Scala.js Scala classes (constructor creates the field + init method for the body). But this might become problematic if we want to extend such a class from JavaScript.\r\n\r\n## Open questions: traits and abstract members\r\n\r\nUntil now, I have carefully avoided discussing traits and abstract members.\r\n\r\nAbstract members are currently forbidden in facade JS classes. They should be allowed, so that they can be forcefully implemented in Scala.js-defined JS classes inheriting from such facades.\r\n\r\nIn facades, members in traits are non-abstract and `js.native`, for convenience (so we don't have to repeat their definition in classes implemented these traits). This will not play well with Scala.js-defined JS classes inherited from raw JS traits. We should probably deprecate this pattern, and require that raw JS traits always be pure interfaces, with only abstract members; and, when compiling a Scala.js-defined JS class, check that no method is a bridge to (non-existing) impl class of a raw JS trait. There would be no way to declare \"Scala.js-defined JS traits\", so mixin composition is not available when writing Scala.js-defined JS classes. We should keep an eye out for mixins in upcoming ES, and make sure we can play well with those in the future."},"comment":{"url":"https://api.github.com/repos/scala-js/scala-js/issues/comments/121322322","html_url":"https://github.com/scala-js/scala-js/issues/1795#issuecomment-121322322","issue_url":"https://api.github.com/repos/scala-js/scala-js/issues/1795","id":121322322,"user":{"login":"gzm0","id":1140323,"avatar_url":"https://avatars.githubusercontent.com/u/1140323?v=3","gravatar_id":"","url":"https://api.github.com/users/gzm0","html_url":"https://github.com/gzm0","followers_url":"https://api.github.com/users/gzm0/followers","following_url":"https://api.github.com/users/gzm0/following{/other_user}","gists_url":"https://api.github.com/users/gzm0/gists{/gist_id}","starred_url":"https://api.github.com/users/gzm0/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gzm0/subscriptions","organizations_url":"https://api.github.com/users/gzm0/orgs","repos_url":"https://api.github.com/users/gzm0/repos","events_url":"https://api.github.com/users/gzm0/events{/privacy}","received_events_url":"https://api.github.com/users/gzm0/received_events","type":"User","site_admin":false},"created_at":"2015-07-14T17:52:43Z","updated_at":"2015-07-14T17:52:43Z","body":"> For top-level classes, there will be no member other than the vals and vars declared explicitly in the code. So no there's no leak. Nested classes will have fields for captures, however.\r\n\r\nOK. But we will leak Scala.js specific members on the prototype I assume. Is there a way we can make these invisible? I'm thinking of something like defining them statically in a block which is private to the class definition and then `call`ing them. This of course would come at the cost of having to do dynamic dispatch again when invoking a super call (now that I think of it, we might have to do this anyway for the semantics to be correct when invoking super).\r\n\r\nSomething like this:\r\n\r\n```javascript\r\nvar myCtor = function() {\r\n  this.a = 1;\r\n  this.b = 2;\r\n};\r\n\r\n(function() {\r\n  var foo__I = function() { return 1; };\r\n  var foo__I__I = function(i) { return i * 2; };\r\n  myCtor.prototype.foo = function(i) {\r\n    if (i == (void 0)) return foo__I.call(this);\r\n    else return foo__I__I.call(this, i);\r\n  };\r\n})();\r\n```\r\n\r\nAbout visibility:\r\n\r\n- I think protected should be entirely disallowed. It does not make sense\r\n- What is the relationship with companions? (matters how we implement private methods)\r\n\r\nI think for private methods we have two choices:\r\n\r\n**Make them pure helpers**\r\n- Compile-time dispatch\r\n- Companion cannot call them (!)\r\n\r\n**Make them \"normal\"**\r\n- Runtime dispatch\r\n- Companion can call them\r\n\r\nI prefer option one, since having private methods at all is IMO already a stretch (but a useful one). I think we should not give an illusion of private methods if we cannot actually implement it that way in JS (and with fully static dispatch we can, just define them in an enclosing scope, no one sees them)."}}