scalajsp の出力を観賞してみた

諸般の事情あって Scala.js の出力する js 用の型を取り出したくなったので、とりまということで .sjsir の情報を出力するツール scalajsp の出力を観賞してみました。この記事は観賞するだけですのでそれ以上の有益な分析結果などはありません。あしからず。

Scala のバージョンは 2.12.0, Scala.js のバージョンは 0.6.13 です。

## .sjsir と scalajsp

.sjsir は、Scala.js の出力する .class に対応する中間バイナリファイルです。これを Scala.js のリンカが結合した上でゴニョゴニョして最終的な js を出力します。.sjsir にどんな情報が入っているかがちゃんと書いてあるところは見当たらなかったのですが、 scalajsp を使うと見られるらしいので使ってみました。なお、scalajsp はスタンドアローン版 Scala.js をインストールするか、sbt で `scalajsp` タスクを実行することで使用できます。ちなみに Scala.js のソースコードでいうと[このあたり](https://github.com/scala-js/scala-js/blob/7640e858efbf8dbf52c99ca8a82cfb2350836e02/tools/shared/src/main/scala/org/scalajs/core/tools/linker/analyzer/Analysis.scala#L45)に相当する内容が出力されます。

## 観賞用コード

### class

まずクラスです。

```scala
@JSExport
class Point(_x: Double, _y: Double) {
  @JSExport
  val x: Double = _x
  @JSExport
  var y: Double = _y
  @JSExport
  def abs: Double = Math.sqrt(x*x + y*y)
  @JSExport
  def sum: Double = x + y
  @JSExport
  def sum_=(v: Double): Unit = y = v - x
}
```

```scala:Point.sjsir
class LPoint extends O {
  val x$1: double
  var y$1: double
  def x__D(): double = {
    this.x$1
  }
  def y__D(): double = {
    this.y$1
  }
  def y$und$eq__D__V(x$1: double) {
    this.y$1 = x$1
  }
  def abs__D(): double = {
    mod:jl_Math$.sqrt__D__D(((this.x__D() *[double] this.x__D()) +[double] (this.y__D() *[double] this.y__D())))
  }
  def sum__D(): double = {
    (this.x__D() +[double] this.y__D())
  }
  def sum$und$eq__D__V(v: double) {
    this.y$und$eq__D__V((v -[double] this.x__D()))
  }
  def $$js$exported$prop$x__O(): any = {
    this.x__D()
  }
  def $$js$exported$prop$y__O(): any = {
    this.y__D()
  }
  def $$js$exported$prop$y__D__O(x$1: double): any = {
    this.y$und$eq__D__V(x$1);
    (void 0)
  }
  def $$js$exported$prop$abs__O(): any = {
    this.abs__D()
  }
  def $$js$exported$prop$sum__O(): any = {
    this.sum__D()
  }
  def $$js$exported$prop$sum__D__O(v: double): any = {
    this.sum$und$eq__D__V(v);
    (void 0)
  }
  def init___D__D(_x: double, _y: double) {
    this.O::init___();
    this.x$1 = _x;
    this.y$1 = _y
  }
  get "sum"(): any = {
    this.$$js$exported$prop$sum__O()
  }
  set "sum"(arg$1: any) {
    val prep0: double = arg$1.asInstanceOf[D];
    this.$$js$exported$prop$sum__D__O(prep0)
  }
  get "abs"(): any = {
    this.$$js$exported$prop$abs__O()
  }
  get "y"(): any = {
    this.$$js$exported$prop$y__O()
  }
  set "y"(arg$1: any) {
    val prep0: double = arg$1.asInstanceOf[D];
    this.$$js$exported$prop$y__D__O(prep0)
  }
  get "x"(): any = {
    this.$$js$exported$prop$x__O()
  }
  export "Point"(arg$1: any, arg$2: any) {
    val prep0: double = arg$1.asInstanceOf[D];
    val prep1: double = arg$2.asInstanceOf[D];
    this.LPoint::init___D__D(prep0, prep1)
  }
}
```

なんかjsっぽさを含んだScalaベースの謎言語のコードが出力されています。最初型情報がほしいと思って見てみたわけですが、直接 export されるメソッドの方は引数も返り値も any になってしまって情報が失われています。丸投げ先のメソッドには残っているようなのでそちらを見ればいいということがわかりました。

ところで今回はソースコードもどきが出力されましたが、 `-i` をつけると以下のように上の出力には含まれていないようなメタ情報を見ることができます。

```Point.sjsir
encodedName: LPoint
isExported: true
kind: Class
superClass: Some(O)
methods:
  x__D:
    
  y__D:
    
  y$und$eq__D__V:
    
  abs__D:
    methodsCalled:
      LPoint: [y__D, x__D]
      jl_Math$: [sqrt__D__D]
    accessedModules: [jl_Math$]
  sum__D:
    methodsCalled:
      LPoint: [y__D, x__D]
    
  sum$und$eq__D__V:
    methodsCalled:
      LPoint: [y$und$eq__D__V, x__D]
    
  $$js$exported$prop$x__O:
    methodsCalled:
      LPoint: [x__D]
    
  $$js$exported$prop$y__O:
    methodsCalled:
      LPoint: [y__D]
    
  $$js$exported$prop$y__D__O:
    methodsCalled:
      LPoint: [y$und$eq__D__V]
    
  $$js$exported$prop$abs__O:
    methodsCalled:
      LPoint: [abs__D]
    
  $$js$exported$prop$sum__O:
    methodsCalled:
      LPoint: [sum__D]
    
  $$js$exported$prop$sum__D__O:
    methodsCalled:
      LPoint: [sum$und$eq__D__V]
    
  init___D__D:
    methodsCalledStatically:
      O: [init___]
    
  sum:
    isExported: true
    methodsCalled:
      LPoint: [$$js$exported$prop$sum__D__O, $$js$exported$prop$sum__O]
    
  abs:
    isExported: true
    methodsCalled:
      LPoint: [$$js$exported$prop$abs__O]
    
  y:
    isExported: true
    methodsCalled:
      LPoint: [$$js$exported$prop$y__O, $$js$exported$prop$y__D__O]
    
  x:
    isExported: true
    methodsCalled:
      LPoint: [$$js$exported$prop$x__O]
    
  __exportedInits:
    isExported: true
    methodsCalledStatically:
      LPoint: [init___D__D]
```

### object

オブジェクトです。

```scala
@JSExport
object HelloWorld {
  @JSExport
  def main(): Unit = {
    println("Hello world!")
  }
}
```

```scala:HelloWorld$.sjsir
module class LHelloWorld$ extends O {
  def main__V() {
    mod:s_Predef$.println__O__V("Hello world!")
  }
  def $$js$exported$meth$main__O(): any = {
    this.main__V();
    (void 0)
  }
  def init___() {
    this.O::init___();
    mod:LHelloWorld$<-this
  }
  def "main"(): any = {
    this.$$js$exported$meth$main__O()
  }
  export module "HelloWorld"
}
```

`object` ではなく `module class` という扱いのようです。Scalac の内部的に module と呼ばれることがあるからか、それとも js の module から来ているのかちょっとだけ興味が湧くところです。


### trait

トレイトです。

```scala
trait PointT {
  val _x: Double
  val _y: Double

  @JSExport
  val x: Double = _x
  @JSExport
  var y: Double = _y
  @JSExport
  def abs: Double = Math.sqrt(x*x + y*y)
  @JSExport
  def sum: Double = x + y
  @JSExport
  def sum_=(v: Double): Unit = y = v - x
}
```

```scala:PointT.sjs.ir
interface LPointT {
  def PointT$$undsetter$und$x$und$eq__D__V(x$1: double) <abstract>
  def $$undx__D(): double = <abstract>
  def $$undy__D(): double = <abstract>
  def x__D(): double = <abstract>
  def y__D(): double = <abstract>
  def y$und$eq__D__V(x$1: double) <abstract>
  def abs__D(): double = {
    mod:jl_Math$.sqrt__D__D(((this.x__D() *[double] this.x__D()) +[double] (this.y__D() *[double] this.y__D())))
  }
  def sum__D(): double = {
    (this.x__D() +[double] this.y__D())
  }
  def sum$und$eq__D__V(v: double) {
    this.y$und$eq__D__V((v -[double] this.x__D()))
  }
  def $$js$exported$prop$x__O(): any = {
    this.x__D()
  }
  def $$js$exported$prop$y__O(): any = {
    this.y__D()
  }
  def $$js$exported$prop$y__D__O(x$1: double): any = {
    this.y$und$eq__D__V(x$1);
    (void 0)
  }
  def $$js$exported$prop$abs__O(): any = {
    this.abs__D()
  }
  def $$js$exported$prop$sum__O(): any = {
    this.sum__D()
  }
  def $$js$exported$prop$sum__D__O(v: double): any = {
    this.sum$und$eq__D__V(v);
    (void 0)
  }
  def $$init$__V() {
    this.PointT$$undsetter$und$x$und$eq__D__V(this.$$undx__D());
    this.y$und$eq__D__V(this.$$undy__D())
  }
}
```

トレイトは interface という名前になるようです。

### @ScalaJSDefined

`@JSExport` 以外に `@ScalaJSDefined` というアノテーションを使うことでより直接的に js のものを定義できる機能もあるのでそれの出力も見てみます。

```scala
@ScalaJSDefined
class Foo extends js.Object {
  val x: Int = 4
  def bar(x: Int): Int = x + 1
}
```

```scala:Foo.sjsir
js class LFoo extends sjs_js_Object {
  val "x": int
  def "constructor"() {
    super();
    this["x"] = 4
  }
  def "bar"(arg$1: any): any = {
    val prep0: int = arg$1.asInstanceOf[I];
    LFoo::bar__LFoo__I__I(this, prep0)
  }
  static def bar__LFoo__I__I($this: any, x: int): int = {
    (x +[int] 1)
  }
}
```

これは `js class` という扱いになるようです。扱いがかなり特殊になっているようです。また、直接的に`constructor`メソッドが作られていたり、丸投げ先のメソッドがstaticになっているなど興味深い点がいくつもあるようです。