summaryrefslogblamecommitdiff
path: root/examples/scala-js/ir/src/main/scala/scala/scalajs/ir/Types.scala
blob: 4af493afd06639f0170795a9511c97e34c3babc3 (plain) (tree)





















































































































































































                                                                                          
/*                     __                                               *\
**     ________ ___   / /  ___      __ ____  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
      })
    }
  }
}