All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scalajs.ir.Names.scala Maven / Gradle / Ivy

/*
 * Scala.js (https://www.scala-js.org/)
 *
 * Copyright EPFL.
 *
 * Licensed under Apache License 2.0
 * (https://www.apache.org/licenses/LICENSE-2.0).
 *
 * See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.
 */

package org.scalajs.ir

import scala.annotation.{switch, tailrec}

import Types._

object Names {
  // scalastyle:off equals.hash.code
  // we define hashCode() once in Name, but equals() separately in its subclasses

  sealed abstract class Name(val encoded: UTF8String) {
    type ThisName <: Name

    // Eagerly compute the hash code
    private val _hashCode = UTF8String.hashCode(encoded)

    override def hashCode(): Int = _hashCode

    protected final def equalsName(that: ThisName): Boolean = {
      this._hashCode == that._hashCode && // fail fast on different hash codes
      UTF8String.equals(this.encoded, that.encoded)
    }

    def compareTo(that: ThisName): Int = {
      // scalastyle:off return
      val thisEncoded = this.encoded
      val thatEncoded = that.encoded
      val thisEncodedLen = thisEncoded.length
      val thatEncodedLen = thatEncoded.length
      val minLen = Math.min(thisEncodedLen, thatEncodedLen)
      var i = 0
      while (i != minLen) {
        val cmp = java.lang.Byte.compare(thisEncoded(i), thatEncoded(i))
        if (cmp != 0)
          return cmp
        i += 1
      }
      Integer.compare(thisEncodedLen, thatEncodedLen)
      // scalastyle:on return
    }

    protected def stringPrefix: String

    final def nameString: String =
      encoded.toString()

    override def toString(): String =
      stringPrefix + "<" + nameString + ">"
  }

  /** The name of a local variable or capture parameter.
   *
   *  Local names must be non-empty, and can contain any Unicode code point
   *  except `/ . ; [`.
   */
  final class LocalName private (encoded: UTF8String)
      extends Name(encoded) with Comparable[LocalName] {

    type ThisName = LocalName

    override def equals(that: Any): Boolean = {
      (this eq that.asInstanceOf[AnyRef]) || (that match {
        case that: LocalName => equalsName(that)
        case _               => false
      })
    }

    protected def stringPrefix: String = "LocalName"

    final def withPrefix(prefix: LocalName): LocalName =
      new LocalName(prefix.encoded ++ this.encoded)

    final def withPrefix(prefix: String): LocalName =
      LocalName(UTF8String(prefix) ++ this.encoded)

    final def withSuffix(suffix: LocalName): LocalName =
      new LocalName(this.encoded ++ suffix.encoded)

    final def withSuffix(suffix: String): LocalName =
      LocalName(this.encoded ++ UTF8String(suffix))
  }

  object LocalName {
    def apply(name: UTF8String): LocalName =
      new LocalName(validateSimpleEncodedName(name))

    def apply(name: String): LocalName =
      LocalName(UTF8String(name))

    private[Names] def fromSimpleFieldName(name: SimpleFieldName): LocalName =
      new LocalName(name.encoded)
  }

  /** The name of the label of a `Labeled` block.
   *
   *  Label names must be non-empty, and can contain any Unicode code point
   *  except `/ . ; [`.
   */
  final class LabelName private (encoded: UTF8String)
      extends Name(encoded) with Comparable[LabelName] {

    type ThisName = LabelName

    override def equals(that: Any): Boolean = {
      (this eq that.asInstanceOf[AnyRef]) || (that match {
        case that: LabelName => equalsName(that)
        case _               => false
      })
    }

    protected def stringPrefix: String = "LabelName"

    final def withSuffix(suffix: LabelName): LabelName =
      new LabelName(this.encoded ++ suffix.encoded)

    final def withSuffix(suffix: String): LabelName =
      LabelName(this.encoded ++ UTF8String(suffix))
  }

  object LabelName {
    def apply(name: UTF8String): LabelName =
      new LabelName(validateSimpleEncodedName(name))

    def apply(name: String): LabelName =
      LabelName(UTF8String(name))
  }

  /** The simple name of a field (excluding its enclosing class).
   *
   *  Field names must be non-empty, and can contain any Unicode code point
   *  except `/ . ; [`.
   */
  final class SimpleFieldName private (encoded: UTF8String)
      extends Name(encoded) with Comparable[SimpleFieldName] {

    type ThisName = SimpleFieldName

    override def equals(that: Any): Boolean = {
      (this eq that.asInstanceOf[AnyRef]) || (that match {
        case that: SimpleFieldName => equalsName(that)
        case _                     => false
      })
    }

    protected def stringPrefix: String = "SimpleFieldName"

    final def withSuffix(suffix: String): SimpleFieldName =
      SimpleFieldName(this.encoded ++ UTF8String(suffix))

    final def toLocalName: LocalName =
      LocalName.fromSimpleFieldName(this)
  }

  object SimpleFieldName {
    def apply(name: UTF8String): SimpleFieldName =
      new SimpleFieldName(validateSimpleEncodedName(name))

    def apply(name: String): SimpleFieldName =
      SimpleFieldName(UTF8String(name))
  }

  /** The full name of a field, including its simple name and its enclosing
   *  class name.
   */
  final class FieldName private (
      val className: ClassName, val simpleName: SimpleFieldName)
      extends Comparable[FieldName] {

    import FieldName._

    private val _hashCode: Int = {
      import scala.util.hashing.MurmurHash3._
      var acc = -1025990011 // "FieldName".hashCode()
      acc = mix(acc, className.##)
      acc = mix(acc, simpleName.##)
      finalizeHash(acc, 2)
    }

    override def equals(that: Any): Boolean = {
      (this eq that.asInstanceOf[AnyRef]) || (that match {
        case that: FieldName =>
          this._hashCode == that._hashCode && // fail fast on different hash codes
          this.className == that.className &&
          this.simpleName == that.simpleName
        case _ =>
          false
      })
    }

    override def hashCode(): Int = _hashCode

    def compareTo(that: FieldName): Int = {
      val classNameCmp = this.className.compareTo(that.className)
      if (classNameCmp != 0)
        classNameCmp
      else
        this.simpleName.compareTo(that.simpleName)
    }

    protected def stringPrefix: String = "FieldName"

    def nameString: String =
      className.nameString + "::" + simpleName.nameString

    override def toString(): String =
      "FieldName<" + nameString + ">"
  }

  object FieldName {
    def apply(className: ClassName, simpleName: SimpleFieldName): FieldName =
      new FieldName(className, simpleName)
  }

  /** The simple name of a method (excluding its signature).
   *
   *  Simple names must be non-empty, and can contain any Unicode code point
   *  except `/ . ; [`. In addition, they must not contain the code point `<`
   *  unless they are one of ``, `` or ``.
   */
  final class SimpleMethodName private (encoded: UTF8String)
      extends Name(encoded) with Comparable[SimpleMethodName] {

    type ThisName = SimpleMethodName

    override def equals(that: Any): Boolean = {
      (this eq that.asInstanceOf[AnyRef]) || (that match {
        case that: SimpleMethodName => equalsName(that)
        case _                      => false
      })
    }

    protected def stringPrefix: String = "SimpleMethodName"

    /** Returns `true` iff this is the name of an instance constructor. */
    def isConstructor: Boolean =
      this eq SimpleMethodName.Constructor // globally unique, so `eq` is fine

    /** Returns `true` iff this is the name of a static initializer. */
    def isStaticInitializer: Boolean =
      this eq SimpleMethodName.StaticInitializer // globally unique, so `eq` is fine

    /** Returns `true` iff this is the name of a class initializer. */
    def isClassInitializer: Boolean =
      this eq SimpleMethodName.ClassInitializer // globally unique, so `eq` is fine
  }

  object SimpleMethodName {
    private final val ConstructorSimpleEncodedName: UTF8String =
      UTF8String("")

    private final val StaticInitializerSimpleEncodedName: UTF8String =
      UTF8String("")

    private final val ClassInitializerSimpleEncodedName: UTF8String =
      UTF8String("")

    /** The unique `SimpleMethodName` with encoded name ``. */
    val Constructor: SimpleMethodName =
      new SimpleMethodName(ConstructorSimpleEncodedName)

    /** The unique `SimpleMethodName` with encoded name ``. */
    val StaticInitializer: SimpleMethodName =
      new SimpleMethodName(StaticInitializerSimpleEncodedName)

    /** The unique `SimpleMethodName` with encoded name ``. */
    val ClassInitializer: SimpleMethodName =
      new SimpleMethodName(ClassInitializerSimpleEncodedName)

    def apply(name: UTF8String): SimpleMethodName = {
      val len = name.length
      if (len == 0)
        throwInvalidEncodedName(name)

      /* Handle constructor names and static initializer names. When we find
       * those, we normalize the returned instance to be one of the unique
       * instances, ensuring that they remain globally unique.
       */
      if (name(0) == '<') {
        // Must be one of '', '' or ''
        len match {
          case 6 if UTF8String.equals(name, ConstructorSimpleEncodedName) =>
            Constructor
          case 8 if UTF8String.equals(name, StaticInitializerSimpleEncodedName) =>
            StaticInitializer
          case 8 if UTF8String.equals(name, ClassInitializerSimpleEncodedName) =>
            ClassInitializer
          case _ =>
            throwInvalidEncodedName(name)
        }
      } else {
        // Normal method name
        new SimpleMethodName(
            validateSimpleEncodedName(name, 0, len, openAngleBracketOK = false))
      }
    }

    def apply(name: String): SimpleMethodName =
      SimpleMethodName(UTF8String(name))
  }

  @deprecated("Use SimpleMethodName.Constructor instead", "1.14.0")
  def ConstructorSimpleName: SimpleMethodName =
    SimpleMethodName.Constructor

  @deprecated("Use SimpleMethodName.StaticInitializer instead", "1.14.0")
  def StaticInitializerSimpleName: SimpleMethodName =
    SimpleMethodName.StaticInitializer

  @deprecated("Use SimpleMethodName.ClassInitializer instead", "1.14.0")
  def ClassInitializerSimpleName: SimpleMethodName =
    SimpleMethodName.ClassInitializer

  /** The full name of a method, including its simple name and its signature.
   */
  final class MethodName private (val simpleName: SimpleMethodName,
      val paramTypeRefs: List[TypeRef], val resultTypeRef: TypeRef,
      val isReflectiveProxy: Boolean)
      extends Comparable[MethodName] {

    import MethodName._

    private val _hashCode: Int = {
      import scala.util.hashing.MurmurHash3._
      var acc = 1270301484 // "MethodName".hashCode()
      acc = mix(acc, simpleName.##)
      acc = mix(acc, paramTypeRefs.##)
      acc = mix(acc, resultTypeRef.##)
      acc = mixLast(acc, isReflectiveProxy.##)
      finalizeHash(acc, 4)
    }

    override def equals(that: Any): Boolean = {
      (this eq that.asInstanceOf[AnyRef]) || (that match {
        case that: MethodName =>
          this._hashCode == that._hashCode && // fail fast on different hash codes
          this.simpleName == that.simpleName &&
          this.paramTypeRefs == that.paramTypeRefs &&
          this.resultTypeRef == that.resultTypeRef &&
          this.isReflectiveProxy == that.isReflectiveProxy
        case _ =>
          false
      })
    }

    override def hashCode(): Int = _hashCode

    def compareTo(that: MethodName): Int = {
      @tailrec
      def compareParamTypeRefs(xs: List[TypeRef], ys: List[TypeRef]): Int = (xs, ys) match {
        case (x :: xr, y :: yr) =>
          val cmp = x.compareTo(y)
          if (cmp != 0) cmp
          else compareParamTypeRefs(xr, yr)
        case _ =>
          java.lang.Boolean.compare(xs.isEmpty, ys.isEmpty)
      }

      val simpleCmp = this.simpleName.compareTo(that.simpleName)
      if (simpleCmp != 0) {
        simpleCmp
      } else {
        val paramsCmp = compareParamTypeRefs(this.paramTypeRefs, that.paramTypeRefs)
        if (paramsCmp != 0) {
          paramsCmp
        } else {
          val reflProxyCmp = java.lang.Boolean.compare(
              this.isReflectiveProxy, that.isReflectiveProxy)
          if (reflProxyCmp != 0)
            reflProxyCmp
          else
            this.resultTypeRef.compareTo(that.resultTypeRef)
        }
      }
    }

    protected def stringPrefix: String = "MethodName"

    def nameString: String = {
      val builder = new java.lang.StringBuilder

      def appendTypeRef(typeRef: TypeRef): Unit = typeRef match {
        case PrimRef(tpe) =>
          tpe match {
            case NoType      => builder.append('V')
            case BooleanType => builder.append('Z')
            case CharType    => builder.append('C')
            case ByteType    => builder.append('B')
            case ShortType   => builder.append('S')
            case IntType     => builder.append('I')
            case LongType    => builder.append('J')
            case FloatType   => builder.append('F')
            case DoubleType  => builder.append('D')
            case NullType    => builder.append('N')
            case NothingType => builder.append('E')
          }
        case ClassRef(className) =>
          builder.append('L').append(className.nameString)
        case ArrayTypeRef(base, dimensions) =>
          var i = 0
          while (i != dimensions) {
            builder.append('[')
            i += 1
          }
          appendTypeRef(base)
      }

      builder.append(simpleName.nameString)
      for (paramTypeRef <- paramTypeRefs) {
        builder.append(';')
        appendTypeRef(paramTypeRef)
      }
      builder.append(';')
      if (isReflectiveProxy)
        builder.append('R')
      else
        appendTypeRef(resultTypeRef)
      builder.toString()
    }

    override def toString(): String =
      "MethodName<" + nameString + ">"

    def displayName: String = {
      simpleName.nameString + "(" +
      paramTypeRefs.map(_.displayName).mkString(",") + ")" +
      (if (isReflectiveProxy) "R" else resultTypeRef.displayName)
    }

    /** Returns `true` iff this is the name of an instance constructor. */
    def isConstructor: Boolean = simpleName.isConstructor

    /** Returns `true` iff this is the name of a static initializer. */
    def isStaticInitializer: Boolean = simpleName.isStaticInitializer

    /** Returns `true` iff this is the name of a class initializer. */
    def isClassInitializer: Boolean = simpleName.isClassInitializer
  }

  object MethodName {
    private val ReflectiveProxyResultTypeRef = ClassRef(ObjectClass)
    private final val ReflectiveProxyResultTypeName = "java.lang.Object"

    def apply(simpleName: SimpleMethodName, paramTypeRefs: List[TypeRef],
        resultTypeRef: TypeRef, isReflectiveProxy: Boolean): MethodName = {
      if ((simpleName.isConstructor || simpleName.isStaticInitializer ||
          simpleName.isClassInitializer) && resultTypeRef != VoidRef) {
        throw new IllegalArgumentException(
            "A constructor or static initializer must have a void result type")
      }
      if (isReflectiveProxy && resultTypeRef != ReflectiveProxyResultTypeRef) {
        throw new IllegalArgumentException(
            "A reflective proxy must have a result type of " +
            ReflectiveProxyResultTypeName)
      }
      new MethodName(simpleName, paramTypeRefs, resultTypeRef,
          isReflectiveProxy)
    }

    // Convenience constructors

    def apply(simpleName: SimpleMethodName, paramTypeRefs: List[TypeRef],
        resultTypeRef: TypeRef): MethodName = {
      apply(simpleName, paramTypeRefs, resultTypeRef, isReflectiveProxy = false)
    }

    def apply(simpleName: String, paramTypeRefs: List[TypeRef],
        resultTypeRef: TypeRef): MethodName = {
      apply(SimpleMethodName(simpleName), paramTypeRefs, resultTypeRef)
    }

    def constructor(paramTypeRefs: List[TypeRef]): MethodName = {
      new MethodName(SimpleMethodName.Constructor, paramTypeRefs, VoidRef,
          isReflectiveProxy = false)
    }

    def reflectiveProxy(simpleName: SimpleMethodName,
        paramTypeRefs: List[TypeRef]): MethodName = {
      apply(simpleName, paramTypeRefs, ReflectiveProxyResultTypeRef,
          isReflectiveProxy = true)
    }

    def reflectiveProxy(simpleName: String,
        paramTypeRefs: List[TypeRef]): MethodName = {
      reflectiveProxy(SimpleMethodName(simpleName), paramTypeRefs)
    }
  }

  /** The full name of a class.
   *
   *  A class name is non-empty sequence of `.`-separated simple names, where
   *  each simple name must be non-empty and can contain any Unicode code
   *  point except `/ . ; [`.
   */
  final class ClassName private (encoded: UTF8String)
      extends Name(encoded) with Comparable[ClassName] {

    type ThisName = ClassName

    override def equals(that: Any): Boolean = {
      (this eq that.asInstanceOf[AnyRef]) || (that match {
        case that: ClassName => equalsName(that)
        case _               => false
      })
    }

    protected def stringPrefix: String = "ClassName"

    def withSuffix(suffix: String): ClassName =
      ClassName(encoded ++ UTF8String(suffix))
  }

  object ClassName {
    def apply(name: UTF8String): ClassName =
      new ClassName(validateEncodedClassName(name))

    def apply(name: String): ClassName =
      ClassName(UTF8String(name))
  }

  // scalastyle:on equals.hash.code

  /** `java.lang.Object`, the root of the class hierarchy. */
  val ObjectClass: ClassName = ClassName("java.lang.Object")

  // Hijacked classes
  val BoxedUnitClass: ClassName = ClassName("java.lang.Void")
  val BoxedBooleanClass: ClassName = ClassName("java.lang.Boolean")
  val BoxedCharacterClass: ClassName = ClassName("java.lang.Character")
  val BoxedByteClass: ClassName = ClassName("java.lang.Byte")
  val BoxedShortClass: ClassName = ClassName("java.lang.Short")
  val BoxedIntegerClass: ClassName = ClassName("java.lang.Integer")
  val BoxedLongClass: ClassName = ClassName("java.lang.Long")
  val BoxedFloatClass: ClassName = ClassName("java.lang.Float")
  val BoxedDoubleClass: ClassName = ClassName("java.lang.Double")
  val BoxedStringClass: ClassName = ClassName("java.lang.String")

  /** The set of all hijacked classes. */
  val HijackedClasses: Set[ClassName] = Set(
      BoxedUnitClass,
      BoxedBooleanClass,
      BoxedCharacterClass,
      BoxedByteClass,
      BoxedShortClass,
      BoxedIntegerClass,
      BoxedLongClass,
      BoxedFloatClass,
      BoxedDoubleClass,
      BoxedStringClass
  )

  /** The class of things returned by `ClassOf` and `GetClass`. */
  val ClassClass: ClassName = ClassName("java.lang.Class")

  /** `java.lang.Cloneable`, which is an ancestor of array classes and is used
   *  by `Clone`.
   */
  val CloneableClass: ClassName = ClassName("java.lang.Cloneable")

  /** `java.io.Serializable`, which is an ancestor of array classes. */
  val SerializableClass: ClassName = ClassName("java.io.Serializable")

  /** The superclass of all throwables.
   *
   *  This is the result type of `WrapAsThrowable` nodes, as well as the input
   *  type of `UnwrapFromThrowable`.
   */
  val ThrowableClass = ClassName("java.lang.Throwable")

  /** The exception thrown by a division by 0. */
  val ArithmeticExceptionClass: ClassName =
    ClassName("java.lang.ArithmeticException")

  /** The exception thrown by an `ArraySelect` that is out of bounds. */
  val ArrayIndexOutOfBoundsExceptionClass: ClassName =
    ClassName("java.lang.ArrayIndexOutOfBoundsException")

  /** The exception thrown by an `Assign(ArraySelect, ...)` where the value cannot be stored. */
  val ArrayStoreExceptionClass: ClassName =
    ClassName("java.lang.ArrayStoreException")

  /** The exception thrown by a `NewArray(...)` with a negative size. */
  val NegativeArraySizeExceptionClass: ClassName =
    ClassName("java.lang.NegativeArraySizeException")

  /** The exception thrown by a variety of nodes for `null` arguments.
   *
   *  - `Apply` and `ApplyStatically` for the receiver,
   *  - `Select` for the qualifier,
   *  - `ArrayLength` and `ArraySelect` for the array,
   *  - `GetClass`, `Clone` and `UnwrapFromException` for their respective only arguments.
   */
  val NullPointerExceptionClass: ClassName =
    ClassName("java.lang.NullPointerException")

  /** The exception thrown by a `BinaryOp.String_charAt` that is out of bounds. */
  val StringIndexOutOfBoundsExceptionClass: ClassName =
    ClassName("java.lang.StringIndexOutOfBoundsException")

  /** The exception thrown by an `AsInstanceOf` that fails. */
  val ClassCastExceptionClass: ClassName =
    ClassName("java.lang.ClassCastException")

  /** The exception thrown by a `Class_newArray` if the first argument is `classOf[Unit]`. */
  val IllegalArgumentExceptionClass: ClassName =
    ClassName("java.lang.IllegalArgumentException")

  /** The set of classes and interfaces that are ancestors of array classes. */
  private[ir] val AncestorsOfPseudoArrayClass: Set[ClassName] = {
    /* This would logically be defined in Types, but that introduces a cyclic
     * dependency between the initialization of Names and Types.
     */
    Set(ObjectClass, CloneableClass, SerializableClass)
  }

  /** Name of a constructor without argument.
   *
   *  This is notably the signature of constructors of module classes.
   */
  final val NoArgConstructorName: MethodName =
    MethodName.constructor(Nil)

  /** This is used to construct a java.lang.Class. */
  final val ObjectArgConstructorName: MethodName =
    MethodName.constructor(List(ClassRef(ObjectClass)))

  /** Name of the static initializer method. */
  final val StaticInitializerName: MethodName =
    MethodName(SimpleMethodName.StaticInitializer, Nil, VoidRef)

  /** Name of the class initializer method. */
  final val ClassInitializerName: MethodName =
    MethodName(SimpleMethodName.ClassInitializer, Nil, VoidRef)

  /** ModuleID of the default module */
  final val DefaultModuleID: String = "main"

  // ---------------------------------------------------
  // ----- Private helpers for validation of names -----
  // ---------------------------------------------------

  private def throwInvalidEncodedName(encoded: UTF8String): Nothing =
    throw new IllegalArgumentException(s"Invalid name: $encoded")

  private def validateSimpleEncodedName(encoded: UTF8String): UTF8String =
    validateSimpleEncodedName(encoded, 0, encoded.length, openAngleBracketOK = true)

  private def validateSimpleEncodedName(encoded: UTF8String, start: Int,
      end: Int, openAngleBracketOK: Boolean): UTF8String = {

    if (start == end)
      throwInvalidEncodedName(encoded)
    var i = start
    while (i != end) {
      (encoded(i).toInt: @switch) match {
        case '.' | ';' | '[' | '/' =>
          throwInvalidEncodedName(encoded)
        case '<' =>
          if (!openAngleBracketOK)
            throwInvalidEncodedName(encoded)
        case _ =>
          /* This case is hit for other ASCII characters, but also for the
           * leading and continuation bytes of multibyte code points. They are
           * all valid, since an `EncodedName` is already guaranteed to be a
           * valid UTF-8 sequence.
           */
      }
      i += 1
    }

    encoded
  }

  private def validateEncodedClassName(encoded: UTF8String): UTF8String = {
    val len = encoded.length
    var i = 0
    while (i < len) {
      val start = i
      while (i != len && encoded(i) != '.')
        i += 1
      validateSimpleEncodedName(encoded, start, i, openAngleBracketOK = true)
      i += 1 // goes to `len + 1` iff we successfully parsed the last segment
    }

    /* Make sure that there isn't an empty segment at the end. This happens
     * either when `len == 0` (in which case the *only* segment is empty) or
     * when the last byte in `encoded` is a `.` (example: in `java.lang.`).
     */
    if (i == len)
      throwInvalidEncodedName(encoded)

    encoded
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy