summaryrefslogtreecommitdiff
path: root/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Types.scala
blob: 4af493afd06639f0170795a9511c97e34c3babc3 (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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
/*                     __                                               *\
**     ________ ___   / /  ___      __ ____  Scala.js IR                **
**    / __/ __// _ | / /  / _ | __ / // __/  (c) 2014, LAMP/EPFL        **
**  __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \    http://scala-js.org/       **
** /____/\___/_/ |_/____/_/ | |__/ /____/                               **
**                          |/____/                                     **
\*                                                                      */


package scala.scalajs.ir

import scala.annotation.tailrec

object Types {

  /** Type of an expression in the IR. */
  abstract sealed class Type {
    def show(): String = {
      val writer = new java.io.StringWriter
      val printer = new Printers.IRTreePrinter(writer)
      printer.printType(this)
      writer.toString()
    }
  }

  /** Any type (the top type of this type system).
   *  A variable of this type can contain any value, including `undefined`
   *  and `null` and any raw JS value. This type supports a very limited set
   *  of Scala operations, the ones common to all values. Basically only
   *  reference equality tests and instance tests. It also supports all
   *  JavaScript operations, since all Scala objects are also genuine
   *  JavaScript objects.
   *  The type java.lang.Object in the back-end maps to [[AnyType]] because it
   *  can hold raw JS values (not only instances of Scala.js classes).
   */
  case object AnyType extends Type

  /** Nothing type (the bottom type of this type system).
   *  Expressions from which one can never come back are typed as [[Nothing]].
   *  For example, `throw` and `return`.
   */
  case object NothingType extends Type

  /** The type of `undefined`. */
  case object UndefType extends Type

  /** Boolean type.
   *  It does not accept `null` nor `undefined`.
   */
  case object BooleanType extends Type

  /** 32-bit signed integer type.
   *  It does not accept `null` nor `undefined`.
   */
  case object IntType extends Type

  /** 64-bit signed integer type.
   *  It does not accept `null` nor `undefined`.
   */
  case object LongType extends Type

  /** Float type (32-bit).
   *  It does not accept `null` nor `undefined`.
   */
  case object FloatType extends Type

  /** Double type (64-bit).
   *  It does not accept `null` nor `undefined`.
   */
  case object DoubleType extends Type

  /** String type.
   *  It does not accept `null` nor `undefined`.
   */
  case object StringType extends Type

  /** The type of `null`.
   *  It does not accept `undefined`.
   *  The null type is a subtype of all class types and array types.
   */
  case object NullType extends Type

  /** Reference types (allowed for classOf[], is/asInstanceOf[]). */
  sealed abstract class ReferenceType extends Type

  /** Class (or interface) type. */
  final case class ClassType(className: String) extends ReferenceType

  /** Array type. */
  final case class ArrayType(baseClassName: String, dimensions: Int) extends ReferenceType

  object ArrayType {
    def apply(innerType: ReferenceType): ArrayType = innerType match {
      case ClassType(className)      => ArrayType(className, 1)
      case ArrayType(className, dim) => ArrayType(className, dim + 1)
    }
  }

  /** Record type.
   *  Used by the optimizer to inline classes as records with multiple fields.
   *  They are desugared as several local variables by JSDesugaring.
   *  Record types cannot cross method boundaries, so they cannot appear as
   *  the type of fields or parameters, nor as result types of methods.
   *  The compiler itself never generates record types.
   */
  final case class RecordType(fields: List[RecordType.Field]) extends Type {
    def findField(name: String): RecordType.Field =
      fields.find(_.name == name).get
  }

  object RecordType {
    final case class Field(name: String, originalName: Option[String],
        tpe: Type, mutable: Boolean)
  }

  /** No type. */
  case object NoType extends Type

  /** Tests whether a type `lhs` is a subtype of `rhs` (or equal).
   *  [[NoType]] is never a subtype or supertype of anything (including
   *  itself). All other types are subtypes of themselves.
   *  @param isSubclass A function testing whether a class/interface is a
   *                    subclass of another class/interface.
   */
  def isSubtype(lhs: Type, rhs: Type)(
      isSubclass: (String, String) => Boolean): Boolean = {
    import Definitions._

    (lhs != NoType && rhs != NoType) && {
      (lhs == rhs) ||
      ((lhs, rhs) match {
        case (_, AnyType)     => true
        case (NothingType, _) => true

        case (ClassType(lhsClass), ClassType(rhsClass)) =>
          isSubclass(lhsClass, rhsClass)

        case (NullType, ClassType(_))    => true
        case (NullType, ArrayType(_, _)) => true

        case (UndefType, ClassType(cls)) =>
          isSubclass(BoxedUnitClass, cls)
        case (BooleanType, ClassType(cls)) =>
          isSubclass(BoxedBooleanClass, cls)
        case (IntType, ClassType(cls)) =>
          isSubclass(BoxedIntegerClass, cls) ||
          cls == BoxedByteClass ||
          cls == BoxedShortClass ||
          cls == BoxedDoubleClass
        case (LongType, ClassType(cls)) =>
          isSubclass(BoxedLongClass, cls)
        case (FloatType, ClassType(cls)) =>
          isSubclass(BoxedFloatClass, cls) ||
          cls == BoxedDoubleClass
        case (DoubleType, ClassType(cls)) =>
          isSubclass(BoxedDoubleClass, cls)
        case (StringType, ClassType(cls)) =>
          isSubclass(StringClass, cls)

        case (IntType, DoubleType)   => true
        case (FloatType, DoubleType) => true

        case (ArrayType(lhsBase, lhsDims), ArrayType(rhsBase, rhsDims)) =>
          if (lhsDims < rhsDims) {
            false // because Array[A] </: Array[Array[A]]
          } else if (lhsDims > rhsDims) {
            rhsBase == ObjectClass // because Array[Array[A]] <: Array[Object]
          } else { // lhsDims == rhsDims
            // lhsBase must be <: rhsBase
            if (isPrimitiveClass(lhsBase) || isPrimitiveClass(rhsBase)) {
              lhsBase == rhsBase
            } else {
              isSubclass(lhsBase, rhsBase)
            }
          }

        case _ =>
          false
      })
    }
  }
}