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

org.scalajs.linker.interface.ESFeatures.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.linker.interface

import Fingerprint.FingerprintBuilder

/** ECMAScript features to use when linking to JavaScript.
 *
 *  The options in `ESFeatures` specify what features of modern versions of
 *  JavaScript are used by the Scala.js linker.
 *
 *  - Options whose name is of the form `useX` *force* the linker to use the
 *    corresponding features, guaranteeing that the specific semantics that
 *    they provide will be used.
 *  - Options whose name is of the form `allowX` *allow* the linker to use the
 *    corresponding features if it supports them. Support for such options can
 *    be dropped in any subsequent version of the linker, including patch
 *    versions.
 *  - Options whose name is of the form `avoidX` *hint* at the linker to avoid
 *    the corresponding features *when it does not affect observable
 *    semantics*. They are related to optimizations (for performance or code
 *    size). The linker is free to ignore those options.
 *  - The `esVersion` setting does not follow any of the schemes above. It is
 *    both a hint not to include support for old versions of ECMAScript, and a
 *    command to enable library or language features that rely on recent
 *    versions of ECMAScript.
 *
 *  As of Scala.js 1.6.0, the setting `useECMAScriptSemantics2015` is derived
 *  from `esVersion`. In the future, it might become independently
 *  configurable.
 */
final class ESFeatures private (
    /* We define `val`s separately below so that we can attach Scaladoc to them
     * (putting Scaladoc comments on constructor param `val`s has no effect).
     */
    _esVersion: ESVersion,
    _allowBigIntsForLongs: Boolean,
    _avoidClasses: Boolean,
    _avoidLetsAndsConsts: Boolean
) {
  import ESFeatures._

  private def this() = {
    this(
        _esVersion = ESVersion.ES2015,
        _allowBigIntsForLongs = false,
        _avoidClasses = true,
        _avoidLetsAndsConsts = true
    )
  }

  /** The ECMAScript version that is assumed to be supported by the runtime.
   *
   *  Default: `ESVersion.ES2015`
   *
   *  The linker and the libraries may use this value to:
   *
   *  - provide more features that rely on recent ECMAScript language features, and/or
   *  - dead-code-eliminate away polyfills.
   *
   *  Prefer reading this value over `useECMAScript2015Semantics` to perform
   *  feature tests.
   */
  val esVersion: ESVersion = _esVersion

  /** Use the ECMAScript 2015 semantics of Scala.js language features.
   *
   *  Default: `true`
   *
   *  As of Scala.js 1.6.0, this is `true` if and only if
   *  `esVersion >= ESVersion.ES2015`. In the future, it might become
   *  independently configurable.
   *
   *  When `true`, the following behaviors are guaranteed:
   *
   *  - JavaScript classes are true `class`'es, therefore a) they can extend
   *    native JavaScript `class`'es and b) they inherit static members from
   *    their parent class.
   *  - Lambdas for `js.Function`s that are not also `js.ThisFunction`s are
   *    JavaScript arrow functions (`=>`). Lambdas for `js.ThisFunction`s are
   *    `function` functions.
   *  - Throwable classes are proper JavaScript error classes, recognized as
   *    such by debuggers (only with the JavaScript backend; not in Wasm).
   *  - In Script (`NoModule`) mode, top-level exports are defined as `let`s.
   *
   *  When `false`, the following behaviors apply instead:
   *
   *  - All classes defined in Scala.js are `function`s instead of `class`'es.
   *    Non-native JS classes cannot extend native JS `class`'es and they do
   *    not inherit static members from their parent class.
   *  - All lambdas for `js.Function`s are `function`s.
   *  - Throwable classes have JavaScript's `Error.prototype` in their
   *    prototype chain, but they are not considered proper error classes.
   *  - In Script (`NoModule`) mode, top-level exports are defined as `var`s.
   *
   *  Prefer reading this value instead of `esVersion` to determine which
   *  semantics apply. Doing so will be future-proof if and when this setting
   *  becomes configurable independently from `esVersion`.
   */
  val useECMAScript2015Semantics = esVersion >= ESVersion.ES2015

  /** Use ECMAScript 2015 features.
   *
   *  Prefer reading `esVersion` or `useECMAScript2015Semantics` instead,
   *  depending on the use case.
   *
   *  This is always equal to `useECMAScript2015Semantics`.
   */
  @deprecated("use esVersion or useECMAScript2015Semantics instead", "1.6.0")
  val useECMAScript2015 = useECMAScript2015Semantics

  /** EXPERIMENTAL: Primitive `Long`s *may* be compiled as primitive JavaScript
   *  `bigint`s.
   *
   *  Default: `false`
   *
   *  Future versions of Scala.js may decide to ignore this setting.
   */
  val allowBigIntsForLongs = _allowBigIntsForLongs

  /** Avoid `class`'es when using `function`s and `prototype`s has the same
   *  observable semantics.
   *
   *  Default: `true`
   *
   *  SpiderMonkey is known to exhibit terrible performance with JavaScript
   *  `class`'es, with up to an order of magnitude of performance degradation.
   *
   *  Setting this option to `true` provides a hint to the Scala.js linker to
   *  avoid using `class`'es when using other JavaScript features (typically
   *  `function`s and `prototype`s) has the same observable semantics, in order
   *  to improve expected performance. Setting it to `false` provides a hint
   *  not to avoid `class`'es. Either way, the linker is free to ignore this
   *  option.
   *
   *  Avoiding `class`'es has a negative impact on code size. If the code is
   *  only targeted at engines that are known to have good performance with
   *  `class`'es, it is desirable to set this option to `false`. If the code
   *  is targeted at browsers (among others), it is recommended to set it to
   *  `true`.
   *
   *  This option never affects the code emitted for JavaScript classes
   *  (classes extending `js.Any`), since that would have an impact on
   *  observable semantics.
   *
   *  This option is always ignored when `esVersion < ESVersion.ES2015`.
   */
  val avoidClasses = _avoidClasses

  /** Avoid `let`s and `const`s when using `var`s has the same observable
   *  semantics.
   *
   *  Default: `true`
   *
   *  Due to their semantics in JavaScript (their Temporal Dead Zone, TDZ),
   *  `let`s and `const`s are more difficult for engines to optimize than
   *  `var`s. There have been known cases of dramatic performance issues with
   *  them, such as the Webkit issue
   *  [[https://bugs.webkit.org/show_bug.cgi?id=199866]].
   *
   *  Setting this option to `true` provides a hint to the Scala.js linker to
   *  avoid using them when using a `var` has the same observable semantics, in
   *  order to improve expected performance. Setting it to `false` provides a
   *  hint not to avoid `let`s and `const`s. Either way, the linker is free to
   *  ignore this option.
   *
   *  Using `let`s and `const`s has benefits for humans writing code as they
   *  help readability and debugging, but there is little to no benefit in
   *  using them when the code is compiler-generated.
   *
   *  This option is always ignored when `esVersion < ESVersion.ES2015`.
   */
  val avoidLetsAndConsts = _avoidLetsAndsConsts

  def withESVersion(esVersion: ESVersion): ESFeatures =
    copy(esVersion = esVersion)

  /** Specifies whether the linker should use ECMAScript 2015 features.
   *
   *  If `false`, this method sets the `esVersion` to `ESVersion.ES5_1`.
   *  Otherwise, if `esVersion` was below `ES2015`, it sets it to `ES2015`.
   *  Otherwise, it returns the same configuration of `ESFeatures`.
   */
  @deprecated(
      "use withESVersion(ESVersion.ES5_1) or withESVersion(ESVersion.ES2015) instead",
      "1.6.0")
  def withUseECMAScript2015(useECMAScript2015: Boolean): ESFeatures = {
    if (!useECMAScript2015) withESVersion(ESVersion.ES5_1)
    else if (esVersion == ESVersion.ES5_1) withESVersion(ESVersion.ES2015)
    else this
  }

  def withAllowBigIntsForLongs(allowBigIntsForLongs: Boolean): ESFeatures =
    copy(allowBigIntsForLongs = allowBigIntsForLongs)

  def withAvoidClasses(avoidClasses: Boolean): ESFeatures =
    copy(avoidClasses = avoidClasses)

  def withAvoidLetsAndConsts(avoidLetsAndConsts: Boolean): ESFeatures =
    copy(avoidLetsAndConsts = avoidLetsAndConsts)

  override def equals(that: Any): Boolean = that match {
    case that: ESFeatures =>
      this.esVersion == that.esVersion &&
      this.allowBigIntsForLongs == that.allowBigIntsForLongs &&
      this.avoidClasses == that.avoidClasses &&
      this.avoidLetsAndConsts == that.avoidLetsAndConsts
    case _ =>
      false
  }

  override def hashCode(): Int = {
    import scala.util.hashing.MurmurHash3._
    var acc = HashSeed
    acc = mix(acc, esVersion.##)
    acc = mix(acc, allowBigIntsForLongs.##)
    acc = mix(acc, avoidClasses.##)
    acc = mixLast(acc, avoidLetsAndConsts.##)
    finalizeHash(acc, 4)
  }

  override def toString(): String = {
    s"""ESFeatures(
       |  esVersion = $esVersion,
       |  useECMAScript2015Semantics = $useECMAScript2015Semantics,
       |  allowBigIntsForLongs = $allowBigIntsForLongs,
       |  avoidClasses = $avoidClasses,
       |  avoidLetsAndConsts = $avoidLetsAndConsts
       |)""".stripMargin
  }

  private def copy(
      esVersion: ESVersion = this.esVersion,
      allowBigIntsForLongs: Boolean = this.allowBigIntsForLongs,
      avoidClasses: Boolean = this.avoidClasses,
      avoidLetsAndConsts: Boolean = this.avoidLetsAndConsts
  ): ESFeatures = {
    new ESFeatures(
        _esVersion = esVersion,
        _allowBigIntsForLongs = allowBigIntsForLongs,
        _avoidClasses = avoidClasses,
        _avoidLetsAndsConsts = avoidLetsAndConsts
    )
  }
}

object ESFeatures {
  private val HashSeed =
    scala.util.hashing.MurmurHash3.stringHash(classOf[ESFeatures].getName)

  /** Default configuration of ECMAScript features.
   *
   *  - `esVersion`: `ESVersion.ES2015`
   *  - `useECMAScript2015Semantics`: true
   *  - `allowBigIntsForLongs`: false
   *  - `avoidClasses`: true
   *  - `avoidLetsAndConsts`: true
   */
  val Defaults: ESFeatures = new ESFeatures()

  private[interface] implicit object ESFeaturesFingerprint
      extends Fingerprint[ESFeatures] {

    override def fingerprint(esFeatures: ESFeatures): String = {
      new FingerprintBuilder("ESFeatures")
        .addField("esVersion", esFeatures.esVersion)
        .addField("allowBigIntsForLongs", esFeatures.allowBigIntsForLongs)
        .addField("avoidClasses", esFeatures.avoidClasses)
        .addField("avoidLetsAndConsts", esFeatures.avoidLetsAndConsts)
        .build()
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy