summaryrefslogtreecommitdiff
path: root/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/ClassInfos.scala
blob: 026d66415c0ebfeccd8bc85bb4e09fa0ccb3d8d3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/* Scala.js compiler
 * Copyright 2013 LAMP/EPFL
 * @author  Sébastien Doeraene
 */

package scala.scalajs.compiler

import scala.language.implicitConversions

import scala.collection.mutable
import scala.tools.nsc._

import java.io.{ File, PrintWriter, BufferedOutputStream, FileOutputStream }

import scala.scalajs.ir
import ir.{Trees => js, Types => jstpe, ClassKind}
import ir.Infos._

trait ClassInfos extends SubComponent { self: GenJSCode =>
  import global._
  import jsAddons._

  /** Class data that are never eliminated by dce, so we don't need to
   *  record them.
   */
  private val AlwaysPresentClassData = {
    import ir.Definitions._
    Set("V", "Z", "C", "B", "S", "I", "J", "F", "D",
        ObjectClass, StringClass)
  }

  class ClassInfoBuilder(val symbol: ClassSymbol) {
    val name = classNameOf(symbol)
    val encodedName = encodeClassFullName(symbol)
    var isExported: Boolean = false
    val ancestorCount = symbol.ancestors.count(!_.isInterface)
    val kind = {
      if (isStaticModule(symbol))            ClassKind.ModuleClass
      else if (symbol.isInterface)           ClassKind.Interface
      else if (isRawJSType(symbol.tpe))      ClassKind.RawJSType
      else if (isHijackedBoxedClass(symbol)) ClassKind.HijackedClass
      else if (symbol.isImplClass)           ClassKind.TraitImpl
      else                                   ClassKind.Class
    }
    val superClass =
      if (kind.isClass || kind == ClassKind.HijackedClass)
        encodeClassFullName(symbol.superClass)
      else
        ""
    val ancestors = (symbol :: symbol.ancestors) map encodeClassFullName

    var optimizerHints: OptimizerHints = OptimizerHints.empty

    val methodInfos = mutable.ListBuffer.empty[MethodInfoBuilder]

    def addMethod(encodedName: String, isAbstract: Boolean = false,
        isExported: Boolean = false): MethodInfoBuilder = {
      val b = new MethodInfoBuilder(encodedName, isAbstract, isExported)
      methodInfos += b
      b
    }

    def result(): ClassInfo = {
      ClassInfo(name, encodedName, isExported, ancestorCount, kind,
          superClass, ancestors, optimizerHints,
          methodInfos.map(_.result()).result())
    }
  }

  class MethodInfoBuilder(val encodedName: String,
      val isAbstract: Boolean = false,
      val isExported: Boolean = false) {

    val calledMethods = mutable.Set.empty[(String, String)] // (tpe, method)
    val calledMethodsStatic = mutable.Set.empty[(String, String)] // (class, method)
    val instantiatedClasses = mutable.Set.empty[String]
    val accessedModules = mutable.Set.empty[String]
    val accessedClassData = mutable.Set.empty[String]
    var optimizerHints: OptimizerHints = OptimizerHints.empty

    def callsMethod(ownerIdent: js.Ident, method: js.Ident): Unit =
      calledMethods += ((patchClassName(ownerIdent.name), method.name))

    def callsMethod(owner: Symbol, method: js.Ident): Unit =
      calledMethods += ((patchClassName(encodeClassFullName(owner)), method.name))

    def callsMethodStatic(ownerIdent: js.Ident, method: js.Ident): Unit =
      calledMethodsStatic += ((patchClassName(ownerIdent.name), method.name))

    def instantiatesClass(classSym: Symbol): Unit =
      instantiatedClasses += patchClassName(encodeClassFullName(classSym))

    def accessesModule(moduleClassSym: Symbol): Unit =
      accessedModules += patchModuleName(encodeModuleFullName(moduleClassSym))

    def accessesClassData(refType: jstpe.ReferenceType): Unit = {
      val className = refType match {
        case jstpe.ClassType(name)    => name
        case jstpe.ArrayType(base, _) => base
      }
      if (!AlwaysPresentClassData.contains(className))
        accessedClassData += className
    }

    def createsAnonFunction(funInfo: ClassInfoBuilder): Unit = {
      for (methodInfo <- funInfo.methodInfos) {
        calledMethods ++= methodInfo.calledMethods
        calledMethodsStatic ++= methodInfo.calledMethodsStatic
        instantiatedClasses ++= methodInfo.instantiatedClasses
        accessedModules ++= methodInfo.accessedModules
        accessedClassData ++= methodInfo.accessedClassData
      }
    }

    private def patchClassName(name: String): String = name match {
      case "jl_String$" => "sjsr_RuntimeString$"
      case _ => name
    }

    private def patchModuleName(name: String): String = name match {
      case "jl_String" => "sjsr_RuntimeString"
      case _ => name
    }

    def result(): MethodInfo = {
      MethodInfo(
          encodedName,
          isAbstract,
          isExported,
          calledMethods.toList.groupBy(_._1).mapValues(_.map(_._2)),
          calledMethodsStatic.toList.groupBy(_._1).mapValues(_.map(_._2)),
          instantiatedClasses.toList,
          accessedModules.result.toList,
          accessedClassData.result.toList,
          optimizerHints
      )
    }
  }

  private def classNameOf(sym: Symbol): String =
    if (needsModuleClassSuffix(sym)) sym.fullName + nme.MODULE_SUFFIX_STRING
    else sym.fullName
}