Skip to content

Instantly share code, notes, and snippets.

@gzm0
Created September 13, 2020 11:35
Show Gist options
  • Save gzm0/8f515e48bb0918c56efab31cccbaaf17 to your computer and use it in GitHub Desktop.
Save gzm0/8f515e48bb0918c56efab31cccbaaf17 to your computer and use it in GitHub Desktop.
--- compiler/src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala 2020-08-26 18:23:12.936934098 +0200
+++ PrepJSInterop.dotty.scala 2020-09-13 13:27:10.678556097 +0200
@@ -1,87 +1,84 @@
-/*
- * 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 dotty.tools.dotc
+package transform
+package sjs
-package org.scalajs.nscplugin
+import scala.collection.mutable
-import scala.tools.nsc
-import nsc._
+import ast.{Trees, tpd, untpd}
+import core._
+import reporting._
+import typer.Checking
+import util.SrcPos
+import Annotations._
+import Constants._
+import Contexts._
+import Decorators._
+import DenotTransformers._
+import Flags._
+import NameKinds.DefaultGetterName
+import NameOps._
+import Names._
+import Phases._
+import Scopes._
+import StdNames._
+import Symbols._
+import SymDenotations._
+import SymUtils._
+import Trees._
+import Types._
-import scala.collection.immutable.ListMap
-import scala.collection.mutable
+import JSInteropUtils._
import org.scalajs.ir.Trees.{JSGlobalRef, JSNativeLoadSpec}
-/** Prepares classes extending js.Any for JavaScript interop
+import dotty.tools.dotc.config.SJSPlatform.sjsPlatform
+import dotty.tools.backend.sjs.JSDefinitions.jsdefn
+
+/** A macro transform that runs after typer and before pickler to perform
+ * additional Scala.js-specific checks and transformations necessary for
+ * interoperability with JavaScript.
+ *
+ * It performs the following functions:
+ *
+ * - Sanity checks for the js.Any hierarchy
+ * - Annotate subclasses of js.Any to be treated specially
+ * - Create JSExport methods: Dummy methods that are propagated
+ * through the whole compiler chain to mark exports. This allows
+ * exports to have the same semantics than methods.
*
- * This phase does:
- * - Sanity checks for js.Any hierarchy
- * - Annotate subclasses of js.Any to be treated specially
- * - Rewrite calls to scala.Enumeration.Value (include name string)
- * - Create JSExport methods: Dummy methods that are propagated
- * through the whole compiler chain to mark exports. This allows
- * exports to have the same semantics than methods.
+ * This is the equivalent of `PrepJSInterop` in Scala 2, minus the handling
+ * of `scala.Enumeration`.
*
- * @author Tobias Schlatter
+ * The reason for making this a macro transform is that some functions (in particular
+ * all the checks that behave differently depending on properties of classes in
+ * the enclosing class chain) are naturally top-down and don't lend themselves to the
+ * bottom-up approach of a mini phase.
+ *
+ * In addition, the addition of export forwarders must be done before pickling to
+ * be signature-compatible with scalac, and there are only macro transforms before
+ * pickling.
*/
-abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
- extends plugins.PluginComponent with PrepJSExports[G]
- with transform.Transform with CompatComponent {
-
+class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisPhase =>
import PrepJSInterop._
+ import tpd._
- /** Not for use in the constructor body: only initialized afterwards. */
- val jsAddons: JSGlobalAddons {
- val global: PrepJSInterop.this.global.type
- }
-
- /** Not for use in the constructor body: only initialized afterwards. */
- val scalaJSOpts: ScalaJSOptions
+ override def phaseName: String = PrepJSInterop.name
- import global._
- import jsAddons._
- import definitions._
- import rootMirror._
- import jsDefinitions._
- import jsInterop.JSName
-
- val phaseName: String = "jsinterop"
- override def description: String = "prepare ASTs for JavaScript interop"
-
- override def newPhase(p: nsc.Phase): StdPhase = new JSInteropPhase(p)
-
- class JSInteropPhase(prev: nsc.Phase) extends Phase(prev) {
- override def name: String = phaseName
- override def description: String = PrepJSInterop.this.description
- override def run(): Unit = {
- jsPrimitives.initPrepJSPrimitives()
- jsInterop.clearGlobalState()
- super.run()
- }
- }
+ override def isEnabled(using Context): Boolean =
+ ctx.settings.scalajs.value
- override protected def newTransformer(unit: CompilationUnit): Transformer =
- new JSInteropTransformer(unit)
+ override def changesMembers: Boolean = true // the phase adds export forwarders
- private object jsnme {
- val hasNext = newTermName("hasNext")
- val next = newTermName("next")
- val nextName = newTermName("nextName")
- val Value = newTermName("Value")
- val Val = newTermName("Val")
-
- val ArrowAssoc = newTermName("ArrowAssoc")
+ override def runOn(units: List[CompilationUnit])(using Context): List[CompilationUnit] = {
+ sjsPlatform.perRunInfo.clear()
+ super.runOn(units)
}
- class JSInteropTransformer(unit: CompilationUnit) extends Transformer {
+ protected def newTransformer(using Context): Transformer =
+ new ScalaJSPrepJSInteropTransformer
+
+ class ScalaJSPrepJSInteropTransformer extends Transformer with Checking {
+ import PrepJSExports._
/** Kind of the directly enclosing (most nested) owner. */
private var enclosingOwner: OwnerKind = OwnerKind.None
@@ -92,12 +89,6 @@
/** Nicer syntax for `allEnclosingOwners is kind`. */
private def anyEnclosingOwner: OwnerKind = allEnclosingOwners
- /** Nicer syntax for `allEnclosingOwners isnt kind`. */
- private object noEnclosingOwner {
- @inline def is(kind: OwnerKind): Boolean =
- allEnclosingOwners isnt kind
- }
-
private def enterOwner[A](kind: OwnerKind)(body: => A): A = {
require(kind.isBaseKind, kind)
val oldEnclosingOwner = enclosingOwner
@@ -112,43 +103,39 @@
}
}
- /** Tests whether this is a ScalaDoc run.
- *
- * There are some things we must not do in ScalaDoc runs because, because
- * ScalaDoc runs don't do everything we need, for example constant-folding
- * 'final val's.
- *
- * At the same time, it's no big deal to skip these things, because we
- * won't reach the backend.
+ /** Whether to check that we have proper literals in some crucial places.
*
- * We don't completely disable this phase under ScalaDoc mostly because
- * we want to keep the addition of `JSType` annotations, so that they
- * appear in the doc.
- *
- * Preparing exports, however, is a pure waste of time, which we cannot
- * do properly anyway because of the aforementioned limitation.
+ * This is always true in dotc. We keep the definition so that the code
+ * code can be as similar as possible to the scalac phase.
*/
- private def forScaladoc = global.isInstanceOf[doc.ScaladocGlobal]
-
- /** Whether to check that we have proper literals in some crucial places. */
- private def shouldCheckLiterals = !forScaladoc
+ private final val shouldCheckLiterals = true
- /** Whether to check and prepare exports. */
- private def shouldPrepareExports = !forScaladoc
+ /** Whether to check and prepare exports.
+ *
+ * This is always true in dotc. We keep the definition so that the code
+ * code can be as similar as possible to the scalac phase.
+ */
+ private final val shouldPrepareExports = true
/** DefDefs in class templates that export methods to JavaScript */
private val exporters = mutable.Map.empty[Symbol, mutable.ListBuffer[Tree]]
- override def transform(tree: Tree): Tree = {
+ override def transform(tree: Tree)(using Context): Tree = {
tree match {
+ case tree: ValDef if tree.symbol.is(Module) =>
+ /* Never apply this transformation on the term definition of modules.
+ * Instead, all relevant checks are performed on the module class definition.
+ */
+ super.transform(tree)
+
case tree: MemberDef => transformMemberDef(tree)
- case tree: Template => transformTemplateTree(tree)
+ case tree: Template => transformTemplate(tree)
case _ => transformStatOrExpr(tree)
}
}
- private def transformMemberDef(tree: MemberDef): Tree = {
- val sym = moduleToModuleClass(tree.symbol)
+ private def transformMemberDef(tree: MemberDef)(using Context): Tree = {
+ val sym = tree.symbol
checkInternalAnnotations(sym)
@@ -158,23 +145,31 @@
* - if not @js.native, verify that we do not use any other annotation
* reserved for @js.native members (namely, JS native load spec annots)
*/
- val isJSNative = sym.hasAnnotation(JSNativeAnnotation)
- if (isJSNative)
- checkJSNativeDefinition(tree.pos, sym)
- else
- checkJSNativeSpecificAnnotsOnNonJSNative(tree)
+ val isJSNative = sym.getAnnotation(jsdefn.JSNativeAnnot) match {
+ case Some(annot) =>
+ checkJSNativeDefinition(tree, annot.tree, sym)
+ true
+ case None =>
+ checkJSNativeSpecificAnnotsOnNonJSNative(tree)
+ false
+ }
checkJSNameAnnots(sym)
- val transformedTree: Tree = tree match {
- case tree: ImplDef =>
+ markExposedIfRequired(tree.symbol)
+
+ tree match {
+ case tree: TypeDef if tree.isClassDef =>
if (shouldPrepareExports)
registerClassOrModuleExports(sym)
if (isJSAny(sym))
- transformJSImplDef(tree)
+ transformJSClassDef(tree)
else
- transformScalaImplDef(tree)
+ transformScalaClassDef(tree)
+
+ case _: TypeDef =>
+ super.transform(tree)
case tree: ValOrDefDef =>
/* Prepare exports for methods, local defs and local variables.
@@ -183,43 +178,24 @@
* (Note that local-to-block can never have exports, but the error
* messages for that are handled by genExportMember).
*/
- if (shouldPrepareExports && (sym.isMethod || sym.isLocalToBlock)) {
+ if (shouldPrepareExports && (sym.is(Method) || sym.isLocalToBlock)) {
exporters.getOrElseUpdate(sym.owner, mutable.ListBuffer.empty) ++=
genExportMember(sym)
}
- if (sym.isLocalToBlock) {
+ if (sym.isLocalToBlock)
super.transform(tree)
- } else if (isJSNative) {
+ else if (isJSNative)
transformJSNativeValOrDefDef(tree)
- } else if (enclosingOwner is OwnerKind.JSType) {
- val fixedTree = tree match {
- case tree: DefDef => fixPublicBeforeTyper(tree)
- case _ => tree
- }
- transformValOrDefDefInJSType(fixedTree)
- } else {
+ else if (enclosingOwner is OwnerKind.JSType)
+ transformValOrDefDefInJSType(tree)
+ else
transformScalaValOrDefDef(tree)
- }
-
- case _:TypeDef | _:PackageDef =>
- super.transform(tree)
}
-
- /* Give tree.symbol, not sym, so that for modules it is the module
- * symbol, not the module class symbol.
- *
- * #1899 This must be done *after* transforming the member def tree,
- * because fixPublicBeforeTyper must have run.
- */
- markExposedIfRequired(tree.symbol)
-
- transformedTree
}
- private def transformScalaImplDef(tree: ImplDef): Tree = {
- val sym = moduleToModuleClass(tree.symbol)
- val isModuleDef = tree.isInstanceOf[ModuleDef]
+ private def transformScalaClassDef(tree: TypeDef)(using Context): Tree = {
+ val sym = tree.symbol
// In native JS things, only js.Any stuff is allowed
if (enclosingOwner is OwnerKind.JSNative) {
@@ -227,68 +203,47 @@
* generated when a nested native JS class has default arguments in
* its constructor (see #1891).
*/
- if (!sym.isSynthetic) {
- reporter.error(tree.pos,
- "Native JS traits, classes and objects cannot contain inner " +
- "Scala traits, classes or objects (i.e., not extending js.Any)")
+ if (!sym.is(Synthetic)) {
+ report.error(
+ "Native JS traits, classes and objects cannot contain inner Scala traits, classes or objects (i.e., not extending js.Any)",
+ tree)
}
}
- if (sym == UnionClass)
- sym.addAnnotation(JSTypeAnnot)
+ if (sym == jsdefn.PseudoUnionClass)
+ sym.addAnnotation(jsdefn.JSTypeAnnot)
- val kind = if (sym.isSubClass(ScalaEnumClass)) {
- if (isModuleDef) OwnerKind.EnumMod
- else if (sym == ScalaEnumClass) OwnerKind.EnumImpl
- else OwnerKind.EnumClass
- } else {
- if (isModuleDef) OwnerKind.NonEnumScalaMod
- else OwnerKind.NonEnumScalaClass
- }
+ val kind =
+ if (sym.is(Module)) OwnerKind.ScalaMod
+ else OwnerKind.ScalaClass
enterOwner(kind) {
super.transform(tree)
}
}
- private def transformScalaValOrDefDef(tree: ValOrDefDef): Tree = {
- tree match {
- // Catch ValDefs in enumerations with simple calls to Value
- case ValDef(mods, name, tpt, ScalaEnumValue.NoName(optPar))
- if anyEnclosingOwner is OwnerKind.Enum =>
- val nrhs = ScalaEnumValName(tree.symbol.owner, tree.symbol, optPar)
- treeCopy.ValDef(tree, mods, name, transform(tpt), nrhs)
-
- // Exporter generation
- case _ =>
- super.transform(tree)
- }
+ private def transformScalaValOrDefDef(tree: ValOrDefDef)(using Context): Tree = {
+ // There is nothing special to do for a Scala val or def
+ super.transform(tree)
}
- private def transformTemplateTree(tree: Template): Template = {
- val Template(parents, self, body) = tree
+ private def transformTemplate(tree: Template)(using Context): Template = {
+ // First, recursively transform the template
+ val transformedTree = super.transform(tree).asInstanceOf[Template]
- /* Do not transform `self`. We do not need to perform any checks on
- * it (#3998).
- */
- val transformedParents = parents.map(transform(_))
- val nonTransformedSelf = self
- val transformedBody = body.map(transform(_))
-
- val clsSym = tree.symbol.owner
+ val clsSym = ctx.owner
// Check that @JSExportStatic fields come first
- if (clsSym.isModuleClass) { // quick check to avoid useless work
+ if (clsSym.is(ModuleClass)) { // quick check to avoid useless work
var foundStatOrNonStaticVal: Boolean = false
- for (tree <- transformedBody) {
+ for (tree <- transformedTree.body) {
tree match {
- case vd: ValDef if vd.symbol.hasAnnotation(JSExportStaticAnnotation) =>
+ case vd: ValDef if vd.symbol.hasAnnotation(jsdefn.JSExportStaticAnnot) =>
if (foundStatOrNonStaticVal) {
- reporter.error(vd.pos,
- "@JSExportStatic vals and vars must be defined before " +
- "any other val/var, and before any constructor " +
- "statement.")
+ report.error(
+ "@JSExportStatic vals and vars must be defined before any other val/var, and before any constructor statement.",
+ vd)
}
- case vd: ValDef if !vd.symbol.isLazy =>
+ case vd: ValDef if !vd.symbol.is(Lazy) =>
foundStatOrNonStaticVal = true
case _: MemberDef =>
case _ =>
@@ -298,18 +253,22 @@
}
// Add exports to the template, if there are any
- val transformedBodyWithExports = exporters.get(clsSym).fold {
- transformedBody
+ exporters.get(clsSym).fold {
+ transformedTree
} { exports =>
- transformedBody ::: exports.toList
+ cpy.Template(transformedTree)(
+ transformedTree.constr,
+ transformedTree.parents,
+ Nil,
+ transformedTree.self,
+ transformedTree.body ::: exports.toList
+ )
}
-
- treeCopy.Template(tree, transformedParents, nonTransformedSelf,
- transformedBodyWithExports)
}
- private def transformStatOrExpr(tree: Tree): Tree = {
+ private def transformStatOrExpr(tree: Tree)(using Context): Tree = {
tree match {
+ /* This might not be needed in dotty.
/* Anonymous function, need to check that it is not used as a SAM for a
* JS type, unless it is js.FunctionN or js.ThisFunctionN.
* See #2921.
@@ -317,46 +276,18 @@
case tree: Function =>
val tpeSym = tree.tpe.typeSymbol
if (isJSAny(tpeSym) && !AllJSFunctionClasses.contains(tpeSym)) {
- reporter.error(tree.pos,
+ report.error(
"Using an anonymous function as a SAM for the JavaScript " +
"type " + tpeSym.fullNameString + " is not allowed. " +
- "Use an anonymous class instead.")
+ "Use an anonymous class instead.",
+ tree)
}
super.transform(tree)
-
- // Catch Select on Enumeration.Value we couldn't transform but need to
- // we ignore the implementation of scala.Enumeration itself
- case ScalaEnumValue.NoName(_) if noEnclosingOwner is OwnerKind.EnumImpl =>
- reporter.warning(tree.pos,
- """Couldn't transform call to Enumeration.Value.
- |The resulting program is unlikely to function properly as this
- |operation requires reflection.""".stripMargin)
- super.transform(tree)
-
- case ScalaEnumValue.NullName() if noEnclosingOwner is OwnerKind.EnumImpl =>
- reporter.warning(tree.pos,
- """Passing null as name to Enumeration.Value
- |requires reflection at runtime. The resulting
- |program is unlikely to function properly.""".stripMargin)
- super.transform(tree)
-
- case ScalaEnumVal.NoName(_) if noEnclosingOwner is OwnerKind.EnumImpl =>
- reporter.warning(tree.pos,
- """Calls to the non-string constructors of Enumeration.Val
- |require reflection at runtime. The resulting
- |program is unlikely to function properly.""".stripMargin)
- super.transform(tree)
-
- case ScalaEnumVal.NullName() if noEnclosingOwner is OwnerKind.EnumImpl =>
- reporter.warning(tree.pos,
- """Passing null as name to a constructor of Enumeration.Val
- |requires reflection at runtime. The resulting
- |program is unlikely to function properly.""".stripMargin)
- super.transform(tree)
+ */
// Validate js.constructorOf[T]
case TypeApply(ctorOfTree, List(tpeArg))
- if ctorOfTree.symbol == JSPackage_constructorOf =>
+ if ctorOfTree.symbol == jsdefn.JSPackage_constructorOf =>
validateJSConstructorOf(tree, tpeArg)
super.transform(tree)
@@ -364,148 +295,61 @@
* runtime.newConstructorTag[T](js.constructorOf[T])
*/
case TypeApply(ctorOfTree, List(tpeArg))
- if ctorOfTree.symbol == JSConstructorTag_materialize =>
+ if ctorOfTree.symbol == jsdefn.JSConstructorTag_materialize =>
validateJSConstructorOf(tree, tpeArg)
- typer.typed {
- atPos(tree.pos) {
- val ctorOf = gen.mkTypeApply(
- gen.mkAttributedRef(JSPackage_constructorOf), List(tpeArg))
- gen.mkMethodCall(Runtime_newConstructorTag,
- List(tpeArg.tpe), List(ctorOf))
- }
- }
-
- /* Catch calls to Predef.classOf[T]. These should NEVER reach this phase
- * but unfortunately do. In normal cases, the typer phase replaces these
- * calls by a literal constant of the given type. However, when we compile
- * the scala library itself and Predef.scala is in the sources, this does
- * not happen.
- *
- * The trees reach this phase under the form:
- *
- * scala.this.Predef.classOf[T]
- *
- * or, as of Scala 2.12.0, as:
- *
- * scala.Predef.classOf[T]
- *
- * or so it seems, at least.
- *
- * If we encounter such a tree, depending on the plugin options, we fail
- * here or silently fix those calls.
- */
- case TypeApply(classOfTree @ Select(predef, nme.classOf), List(tpeArg))
- if predef.symbol == PredefModule =>
- if (scalaJSOpts.fixClassOf) {
- // Replace call by literal constant containing type
- if (typer.checkClassType(tpeArg)) {
- typer.typed { Literal(Constant(tpeArg.tpe.dealias.widen)) }
- } else {
- reporter.error(tpeArg.pos, s"Type ${tpeArg} is not a class type")
- EmptyTree
- }
- } else {
- reporter.error(classOfTree.pos,
- """This classOf resulted in an unresolved classOf in the jscode
- |phase. This is most likely a bug in the Scala compiler. ScalaJS
- |is probably able to work around this bug. Enable the workaround
- |by passing the fixClassOf option to the plugin.""".stripMargin)
- EmptyTree
- }
+ val ctorOf = ref(jsdefn.JSPackage_constructorOf).appliedToTypeTree(tpeArg)
+ ref(jsdefn.Runtime_newConstructorTag).appliedToType(tpeArg.tpe).appliedTo(ctorOf)
// Compile-time errors and warnings for js.Dynamic.literal
case Apply(Apply(fun, nameArgs), args)
- if fun.symbol == JSDynamicLiteral_applyDynamic ||
- fun.symbol == JSDynamicLiteral_applyDynamicNamed =>
+ if fun.symbol == jsdefn.JSDynamicLiteral_applyDynamic ||
+ fun.symbol == jsdefn.JSDynamicLiteral_applyDynamicNamed =>
// Check that the first argument list is a constant string "apply"
nameArgs match {
case List(Literal(Constant(s: String))) =>
- if (s != "apply") {
- reporter.error(tree.pos,
- s"js.Dynamic.literal does not have a method named $s")
- }
+ if (s != "apply")
+ report.error(i"js.Dynamic.literal does not have a method named $s", tree)
case _ =>
- reporter.error(tree.pos,
- s"js.Dynamic.literal.${tree.symbol.name} may not be " +
- "called directly")
+ report.error(i"js.Dynamic.literal.${tree.symbol.name} may not be called directly", tree)
}
- // Warn for known duplicate property names
- val knownPropNames = mutable.Set.empty[String]
- for (arg <- args) {
- def processPropName(propNameTree: Tree): Unit = {
- propNameTree match {
- case Literal(Constant(propName: String)) =>
- if (!knownPropNames.add(propName)) {
- reporter.warning(propNameTree.pos,
- s"""Duplicate property "$propName" shadows a """ +
- "previously defined one")
- }
- case _ =>
- // ignore
- }
- }
- arg match {
- case Apply(fun, List(propNameTree, _))
- if fun.symbol == Tuple2_apply =>
- processPropName(propNameTree)
- case Apply(fun @ TypeApply(Select(receiver, nme.MINGT), _), _)
- if currentRun.runDefinitions.isArrowAssoc(fun.symbol) =>
- receiver match {
- case Apply(TypeApply(Select(predef, jsnme.ArrowAssoc), _),
- List(propNameTree))
- if predef.symbol == PredefModule =>
- processPropName(propNameTree)
- case _ =>
- // ignore
- }
- case _ =>
- // ignore
- }
- }
+ // TODO Warn for known duplicate property names
super.transform(tree)
- case _ => super.transform(tree)
+ case _ =>
+ super.transform(tree)
}
}
- private def validateJSConstructorOf(tree: Tree, tpeArg: Tree): Unit = {
- val classValue = try {
- typer.typedClassOf(tree, tpeArg)
- } catch {
- case typeError: TypeError =>
- reporter.error(typeError.pos, typeError.msg)
- EmptyTree
- }
-
- if (classValue != EmptyTree) {
- val Literal(classConstant) = classValue
- val tpe = classConstant.typeValue.dealiasWiden
- val typeSym = tpe.typeSymbol
- if (typeSym.isTrait || typeSym.isModuleClass) {
- reporter.error(tpeArg.pos,
- s"non-trait class type required but $tpe found")
- }
+ private def validateJSConstructorOf(tree: Tree, tpeArg: Tree)(using Context): Unit = {
+ val tpe = checkClassType(tpeArg.tpe, tpeArg.srcPos, traitReq = false, stablePrefixReq = false)
+
+ tpe.underlyingClassRef(refinementOK = false) match {
+ case typeRef: TypeRef if typeRef.symbol.isOneOf(Trait | ModuleClass) =>
+ report.error(i"non-trait class type required but $tpe found", tpeArg)
+ case _ =>
+ // an error was already reported above
}
}
/** Performs checks and rewrites specific to classes / objects extending
* js.Any.
*/
- private def transformJSImplDef(implDef: ImplDef): Tree = {
- val sym = moduleToModuleClass(implDef.symbol)
+ private def transformJSClassDef(classDef: TypeDef)(using Context): Tree = {
+ val sym = classDef.symbol
- sym.addAnnotation(JSTypeAnnot)
+ sym.addAnnotation(jsdefn.JSTypeAnnot)
- val isJSLambda =
+ /*val isJSLambda =
sym.isAnonymousClass && AllJSFunctionClasses.exists(sym.isSubClass(_))
if (isJSLambda)
- transformJSLambdaImplDef(implDef)
- else
- transformNonLambdaJSImplDef(implDef)
+ transformJSLambdaClassDef(classDef)
+ else*/
+ transformNonLambdaJSClassDef(classDef)
}
+ /*
/** Performs checks and rewrites specific to JS lambdas, i.e., anonymous
* classes extending one of the JS function types.
*
@@ -513,7 +357,7 @@
* back-end, so although at this phase they look like normal anonymous
* classes, they do not behave like ones.
*/
- private def transformJSLambdaImplDef(implDef: ImplDef): Tree = {
+ private def transformJSLambdaClassDef(classDef: TypeDef)(using Context): Tree = {
/* For the purposes of checking inner members, a JS lambda acts as a JS
* native class owner.
*
@@ -521,55 +365,55 @@
* that way. It should be revisited.
*/
enterOwner(OwnerKind.JSNativeClass) {
- super.transform(implDef)
+ super.transform(classDef)
}
}
+ */
/** Performs checks and rewrites for all JS classes, traits and objects
* except JS lambdas.
*/
- private def transformNonLambdaJSImplDef(implDef: ImplDef): Tree = {
- val sym = moduleToModuleClass(implDef.symbol)
- val isJSNative = sym.hasAnnotation(JSNativeAnnotation)
+ private def transformNonLambdaJSClassDef(classDef: TypeDef)(using Context): Tree = {
+ val sym = classDef.symbol
+ val isJSNative = sym.hasAnnotation(jsdefn.JSNativeAnnot)
// Forbid @EnableReflectiveInstantiation on JS types
- sym.getAnnotation(EnableReflectiveInstantiationAnnotation).foreach {
- annot =>
- reporter.error(annot.pos,
- "@EnableReflectiveInstantiation cannot be used on types " +
- "extending js.Any.")
+ sym.getAnnotation(jsdefn.EnableReflectiveInstantiationAnnot).foreach { annot =>
+ report.error(
+ "@EnableReflectiveInstantiation cannot be used on types extending js.Any.",
+ annot.tree)
}
// Forbid package objects that extends js.Any
- if (sym.isPackageObjectClass)
- reporter.error(implDef.pos, "Package objects may not extend js.Any.")
+ if (sym.isPackageObject)
+ report.error("Package objects may not extend js.Any.", classDef)
// Check that we do not have a case modifier
- if (implDef.mods.hasFlag(Flag.CASE)) {
- reporter.error(implDef.pos, "Classes and objects extending " +
- "js.Any may not have a case modifier")
+ if (sym.is(Case)) {
+ report.error(
+ "Classes and objects extending js.Any may not have a case modifier",
+ classDef)
}
// Check the parents
for (parent <- sym.info.parents) {
parent.typeSymbol match {
- case AnyRefClass | ObjectClass =>
- // AnyRef is valid, except for non-native JS traits
- if (!isJSNative && !sym.isTrait) {
- reporter.error(implDef.pos,
- "Non-native JS classes and objects cannot directly extend " +
- "AnyRef. They must extend a JS class (native or not).")
+ case parentSym if parentSym == defn.ObjectClass =>
+ // AnyRef is valid, except for non-native JS classes and objects
+ if (!isJSNative && !sym.is(Trait)) {
+ report.error(
+ "Non-native JS classes and objects cannot directly extend AnyRef. They must extend a JS class (native or not).",
+ classDef)
}
case parentSym if isJSAny(parentSym) =>
// A non-native JS type cannot extend a native JS trait
// Otherwise, extending a JS type is valid
- if (!isJSNative && parentSym.isTrait &&
- parentSym.hasAnnotation(JSNativeAnnotation)) {
- reporter.error(implDef.pos,
- "Non-native JS types cannot directly extend native JS " +
- "traits.")
+ if (!isJSNative && parentSym.is(Trait) && parentSym.hasAnnotation(jsdefn.JSNativeAnnot)) {
+ report.error(
+ "Non-native JS types cannot directly extend native JS traits.",
+ classDef)
}
- case DynamicClass =>
+ case parentSym if parentSym == defn.DynamicClass =>
/* We have to allow scala.Dynamic to be able to define js.Dynamic
* and similar constructs.
* This causes the unsoundness filed as #1385.
@@ -578,9 +422,9 @@
/* This is a Scala class or trait other than AnyRef and Dynamic,
* which is never valid.
*/
- reporter.error(implDef.pos,
- s"${sym.nameString} extends ${parentSym.fullName} " +
- "which does not extend js.Any.")
+ report.error(
+ i"${sym.name} extends ${parentSym.fullName} which does not extend js.Any.",
+ classDef)
}
}
@@ -588,136 +432,136 @@
if (!isJSNative) {
// It cannot be in a native JS class or trait
if (enclosingOwner is OwnerKind.JSNativeClass) {
- reporter.error(implDef.pos,
- "Native JS classes and traits cannot contain non-native JS " +
- "classes, traits or objects")
+ report.error(
+ "Native JS classes and traits cannot contain non-native JS classes, traits or objects",
+ classDef)
}
// Unless it is a trait, it cannot be in a native JS object
- if (!sym.isTrait && (enclosingOwner is OwnerKind.JSNativeMod)) {
- reporter.error(implDef.pos,
- "Native JS objects cannot contain inner non-native JS " +
- "classes or objects")
+ if (!sym.is(Trait) && (enclosingOwner is OwnerKind.JSNativeMod)) {
+ report.error(
+ "Native JS objects cannot contain inner non-native JS classes or objects",
+ classDef)
}
// Local JS classes cannot be abstract (implementation restriction)
- if (!sym.isTrait && sym.isAbstractClass && sym.isLocalToBlock) {
- reporter.error(implDef.pos,
- "Implementation restriction: local JS classes cannot be abstract")
+ if (sym.is(Abstract, butNot = Trait) && sym.isLocalToBlock) {
+ report.error(
+ "Implementation restriction: local JS classes cannot be abstract",
+ classDef)
}
}
// Check for consistency of JS semantics across overriding
- for (overridingPair <- new overridingPairs.Cursor(sym).iterator) {
- val low = overridingPair.low
- val high = overridingPair.high
-
- def errorPos = {
- if (sym == low.owner) low.pos
- else if (sym == high.owner) high.pos
- else sym.pos
- }
-
- def memberDefString(membSym: Symbol): String =
- membSym.defStringSeenAs(sym.thisType.memberType(membSym))
-
- // Check for overrides with different JS names - issue #1983
- if (jsInterop.jsNameOf(low) != jsInterop.jsNameOf(high)) {
- val msg = {
- def memberDefStringWithJSName(membSym: Symbol) = {
- memberDefString(membSym) +
- membSym.locationString + " with JSName '" +
- jsInterop.jsNameOf(membSym).displayName + '\''
- }
- "A member of a JS class is overriding another member with a different JS name.\n\n" +
- memberDefStringWithJSName(low) + "\n" +
- " is conflicting with\n" +
- memberDefStringWithJSName(high) + "\n"
+ val overridingPairsCursor = new OverridingPairs.Cursor(sym)
+ while (overridingPairsCursor.hasNext) {
+ val overriding = overridingPairsCursor.overriding
+ val overridden = overridingPairsCursor.overridden
+ overridingPairsCursor.next() // prepare for next turn
+
+ val clsSym = sym
+
+ if (overriding.isTerm) {
+ def errorPos = {
+ if (clsSym == overriding.owner) overriding.srcPos
+ else if (clsSym == overridden.owner) overridden.srcPos
+ else clsSym.srcPos
}
- reporter.error(errorPos, msg)
- }
+ // Some utils inspired by RefChecks
- /* Cannot override a non-@JSOptional with an @JSOptional. Unfortunately
- * at this point the symbols do not have @JSOptional yet, so we need
- * to detect whether it would be applied.
- */
- if (!isJSNative) {
- def isJSOptional(sym: Symbol): Boolean = {
- sym.owner.isTrait && !sym.isDeferred && !sym.isConstructor &&
- !sym.owner.hasAnnotation(JSNativeAnnotation)
+ def infoString0(sym: Symbol, showLocation: Boolean): String = {
+ val sym1 = sym.underlyingSymbol
+ def info = clsSym.thisType.memberInfo(sym1)
+ val infoStr =
+ if (sym1.is(Module)) ""
+ else i" of type $info"
+ i"${if (showLocation) sym1.showLocated else sym1}$infoStr with JS name '${sym.jsName.displayName}'"
+ }
+
+ def infoString(sym: Symbol): String = infoString0(sym, sym.owner != clsSym)
+ def infoStringWithLocation(sym: Symbol): String = infoString0(sym, true)
+
+ def emitOverrideError(msg: String): Unit = {
+ report.error(
+ "error overriding %s;\n %s %s".format(
+ infoStringWithLocation(overridden), infoString(overriding), msg),
+ errorPos)
}
- if (isJSOptional(low) && !(high.isDeferred || isJSOptional(high))) {
- reporter.error(errorPos,
- s"Cannot override concrete ${memberDefString(high)} from " +
- s"${high.owner.fullName} in a non-native JS trait.")
+ // Check for overrides with different JS names - issue #1983
+ if (overriding.jsName != overridden.jsName)
+ emitOverrideError("has a different JS name")
+
+ /* Cannot override a non-@JSOptional with an @JSOptional. Unfortunately
+ * at this point the symbols do not have @JSOptional yet, so we need
+ * to detect whether it would be applied.
+ */
+ if (!isJSNative) {
+ def isJSOptional(sym: Symbol): Boolean = {
+ sym.owner.is(Trait) && !sym.is(Deferred) && !sym.isConstructor &&
+ !sym.owner.hasAnnotation(jsdefn.JSNativeAnnot)
+ }
+ if (isJSOptional(overriding) && !(overridden.is(Deferred) || isJSOptional(overridden)))
+ emitOverrideError("cannot override a concrete member in a non-native JS trait")
}
}
}
val kind = {
if (!isJSNative) {
- if (sym.isModuleClass) OwnerKind.JSMod
+ if (sym.is(ModuleClass)) OwnerKind.JSMod
else OwnerKind.JSClass
} else {
- if (sym.isModuleClass) OwnerKind.JSNativeMod
+ if (sym.is(ModuleClass)) OwnerKind.JSNativeMod
else OwnerKind.JSNativeClass
}
}
enterOwner(kind) {
- super.transform(implDef)
+ super.transform(classDef)
}
}
- private def checkJSNativeDefinition(pos: Position, sym: Symbol): Unit = {
+ private def checkJSNativeDefinition(treePos: SrcPos, annotPos: SrcPos, sym: Symbol)(using Context): Unit = {
// Check if we may have a JS native here
if (sym.isLocalToBlock) {
- reporter.error(pos,
- "@js.native is not allowed on local definitions")
+ report.error("@js.native is not allowed on local definitions", annotPos)
} else if (!sym.isClass && (anyEnclosingOwner is (OwnerKind.ScalaClass | OwnerKind.JSType))) {
- reporter.error(pos,
- "@js.native vals and defs can only appear in static Scala objects")
+ report.error("@js.native vals and defs can only appear in static Scala objects", annotPos)
} else if (sym.isClass && !isJSAny(sym)) {
- reporter.error(pos,
- "Classes, traits and objects not extending js.Any may not have " +
- "an @js.native annotation")
+ report.error("Classes, traits and objects not extending js.Any may not have an @js.native annotation", annotPos)
} else if (anyEnclosingOwner is OwnerKind.ScalaClass) {
- reporter.error(pos,
- "Scala traits and classes may not have native JS members")
+ report.error("Scala traits and classes may not have native JS members", annotPos)
} else if (enclosingOwner is OwnerKind.JSNonNative) {
- reporter.error(pos,
- "non-native JS classes, traits and objects may not have " +
- "native JS members")
- } else if (!sym.isTrait) {
- /* Compute the loading spec now, before `flatten` destroys the name.
- * We store it in a global map.
- */
- val optLoadSpec = checkAndComputeJSNativeLoadSpecOf(pos, sym)
- for (loadSpec <- optLoadSpec)
- jsInterop.storeJSNativeLoadSpec(sym, loadSpec)
+ report.error("non-native JS classes, traits and objects may not have native JS members", annotPos)
} else {
- assert(sym.isTrait, sym) // just tested in the previous `if`
- for (annot <- sym.annotations) {
- val annotSym = annot.symbol
- if (JSNativeLoadingSpecAnnots.contains(annotSym)) {
- reporter.error(annot.pos,
- s"Traits may not have an @${annotSym.nameString} annotation.")
+ // The symbol can be annotated with @js.native. Now check its JS native loading spec.
+ if (sym.is(Trait)) {
+ assert(sym.is(Trait), sym) // just tested in the previous `if`
+ for (annot <- sym.annotations) {
+ val annotSym = annot.symbol
+ if (isJSNativeLoadingSpecAnnot(annotSym))
+ report.error(i"Traits may not have an @${annotSym.name} annotation.", annot.tree)
}
+ } else {
+ /* Compute the loading spec now, before `flatten` destroys the name.
+ * We store it in a global map.
+ */
+ val optLoadSpec = checkAndComputeJSNativeLoadSpecOf(treePos, sym)
+ for (loadSpec <- optLoadSpec)
+ sjsPlatform.perRunInfo.storeJSNativeLoadSpec(sym, loadSpec)
}
}
}
- private def checkAndComputeJSNativeLoadSpecOf(pos: Position,
- sym: Symbol): Option[JSNativeLoadSpec] = {
+ private def checkAndComputeJSNativeLoadSpecOf(pos: SrcPos, sym: Symbol)(
+ using Context): Option[JSNativeLoadSpec] = {
import JSNativeLoadSpec._
def makeGlobalRefNativeLoadSpec(globalRef: String,
path: List[String]): Global = {
val validatedGlobalRef = if (!JSGlobalRef.isValidJSGlobalRefName(globalRef)) {
- reporter.error(pos,
- "The name of a JS global variable must be a valid JS " +
- s"identifier (got '$globalRef')")
+ report.error(s"The name of a JS global variable must be a valid JS identifier (got '$globalRef')", pos)
"erroneous"
} else {
globalRef
@@ -735,30 +579,26 @@
for (annot <- sym.annotations) {
val annotSym = annot.symbol
-
- if (JSNativeLoadingSpecAnnots.contains(annotSym)) {
- reporter.error(annot.pos,
- "Nested JS classes and objects cannot " +
- s"have an @${annotSym.nameString} annotation.")
- }
+ if (isJSNativeLoadingSpecAnnot(annotSym))
+ report.error(i"Nested JS classes and objects cannot have an @${annotSym.name} annotation.", annot.tree)
}
if (sym.owner.isStaticOwner) {
for (annot <- sym.annotations) {
- if (annot.symbol == JSNameAnnotation &&
- annot.args.head.tpe.typeSymbol != StringClass) {
- reporter.error(annot.pos,
- "Implementation restriction: @JSName with a js.Symbol is " +
- "not supported on nested native classes and objects")
+ if (annot.symbol == jsdefn.JSNameAnnot && !(annot.arguments.head.tpe.derivesFrom(defn.StringClass))) {
+ report.error(
+ "Implementation restriction: " +
+ "@JSName with a js.Symbol is not supported on nested native classes and objects",
+ annot.tree)
}
}
- val jsName = jsInterop.jsNameOf(sym) match {
+ val jsName = sym.jsName match {
case JSName.Literal(jsName) => jsName
case JSName.Computed(_) => "<erroneous>" // compile error above
}
- val ownerLoadSpec = jsInterop.jsNativeLoadSpecOfOption(sym.owner)
+ val ownerLoadSpec = sjsPlatform.perRunInfo.jsNativeLoadSpecOfOption(sym.owner)
val loadSpec = ownerLoadSpec match {
case None =>
// The owner is a JSGlobalScope
@@ -787,48 +627,48 @@
}
checkAndGetJSNativeLoadingSpecAnnotOf(pos, sym) match {
- case Some(annot) if annot.symbol == JSGlobalScopeAnnotation =>
- if (!sym.isModuleClass) {
- reporter.error(annot.pos,
- "@JSGlobalScope can only be used on native JS objects (with @js.native).")
+ case Some(annot) if annot.symbol == jsdefn.JSGlobalScopeAnnot =>
+ if (!sym.is(Module)) {
+ report.error(
+ "@JSGlobalScope can only be used on native JS objects (with @js.native).",
+ annot.tree)
}
None
- case Some(annot) if annot.symbol == JSGlobalAnnotation =>
+ case Some(annot) if annot.symbol == jsdefn.JSGlobalAnnot =>
if (shouldCheckLiterals)
checkJSGlobalLiteral(annot)
- val pathName = annot.stringArg(0).getOrElse {
+ val pathName = annot.argumentConstantString(0).getOrElse {
val needsExplicitJSName = {
(enclosingOwner is OwnerKind.ScalaMod) &&
- !sym.owner.isPackageObjectClass
+ !sym.owner.isPackageObject
}
if (needsExplicitJSName) {
- reporter.error(annot.pos,
- "Native JS members inside non-native objects " +
- "must have an explicit name in @JSGlobal")
+ report.error(
+ "Native JS members inside non-native objects must have an explicit name in @JSGlobal",
+ annot.tree)
}
- jsInterop.defaultJSNameOf(sym)
+ sym.defaultJSName
}
Some(parseGlobalPath(pathName))
- case Some(annot) if annot.symbol == JSImportAnnotation =>
+ case Some(annot) if annot.symbol == jsdefn.JSImportAnnot =>
if (shouldCheckLiterals)
checkJSImportLiteral(annot)
- val module = annot.stringArg(0).getOrElse {
+ val module = annot.argumentConstantString(0).getOrElse {
"" // an error is reported by checkJSImportLiteral in this case
}
- val path = annot.stringArg(1).fold[List[String]](Nil)(parsePath)
+ val path = annot.argumentConstantString(1).fold[List[String]](Nil)(parsePath)
val importSpec = Import(module, path)
- val loadSpec = annot.stringArg(2).fold[JSNativeLoadSpec] {
+ val loadSpec = annot.argumentConstantString(2).fold[JSNativeLoadSpec] {
importSpec
} { globalPathName =>
- ImportWithGlobalFallback(importSpec,
- parseGlobalPath(globalPathName))
+ ImportWithGlobalFallback(importSpec, parseGlobalPath(globalPathName))
}
Some(loadSpec)
- case None =>
+ case _ =>
/* We already emitted an error. Invent something not to cause
* cascading errors.
*/
@@ -838,336 +678,287 @@
}
/** Verify a ValOrDefDef that is annotated with `@js.native`. */
- private def transformJSNativeValOrDefDef(tree: ValOrDefDef): ValOrDefDef = {
+ private def transformJSNativeValOrDefDef(tree: ValOrDefDef)(using Context): ValOrDefDef = {
val sym = tree.symbol
- if (sym.isLazy || jsInterop.isJSSetter(sym)) {
- reporter.error(tree.pos,
- "@js.native is not allowed on vars, lazy vals and setter defs")
- } else if (jsInterop.isJSBracketAccess(sym)) {
- reporter.error(tree.pos,
- "@JSBracketAccess is not allowed on @js.native vals and defs")
- } else if (jsInterop.isJSBracketCall(sym)) {
- reporter.error(tree.pos,
- "@JSBracketCall is not allowed on @js.native vals and defs")
- }
-
- if (!sym.isAccessor)
- checkRHSCallsJSNative(tree, "@js.native members")
-
- if (sym.isMethod) { // i.e., it is not a field
- for (overridden <- sym.allOverriddenSymbols.headOption) {
- val verb = if (overridden.isDeferred) "implement" else "override"
- reporter.error(tree.pos,
- s"An @js.native member cannot $verb the inherited member " +
- overridden.fullName)
- }
+ def annotPos(annotSym: Symbol): SrcPos =
+ sym.getAnnotation(annotSym).get.tree
+
+ if (sym.is(Lazy) || sym.isJSSetter)
+ report.error("@js.native is not allowed on vars, lazy vals and setter defs", annotPos(jsdefn.JSNativeAnnot))
+ else if (sym.isJSBracketAccess)
+ report.error("@JSBracketAccess is not allowed on @js.native vals and defs", annotPos(jsdefn.JSBracketAccessAnnot))
+ else if (sym.isJSBracketCall)
+ report.error("@JSBracketCall is not allowed on @js.native vals and defs", annotPos(jsdefn.JSBracketCallAnnot))
+
+ //if (!sym.is(Accessor))
+ checkRHSCallsJSNative(tree, "@js.native members")
+
+ // Check that we do not override or implement anything from a superclass
+ val overriddenSymbols = sym.allOverriddenSymbols
+ if (overriddenSymbols.hasNext) {
+ val overridden = overriddenSymbols.next()
+ val verb = if (overridden.is(Deferred)) "implement" else "override"
+ report.error(i"An @js.native member cannot $verb the inherited member ${overridden.fullName}", tree)
}
tree
}
/** Verify a ValOrDefDef inside a js.Any */
- private def transformValOrDefDefInJSType(tree: ValOrDefDef): Tree = {
+ private def transformValOrDefDefInJSType(tree: ValOrDefDef)(using Context): Tree = {
val sym = tree.symbol
- assert(!sym.isLocalToBlock, s"$tree at ${tree.pos}")
+ assert(!sym.isLocalToBlock, i"$tree at ${tree.span}")
sym.name match {
- case nme.apply if !sym.hasAnnotation(JSNameAnnotation) =>
- if (jsInterop.isJSGetter(sym)) {
- reporter.error(sym.pos, s"A member named apply represents function " +
- "application in JavaScript. A parameterless member should be " +
- "exported as a property. You must add @JSName(\"apply\")")
+ case nme.apply if !sym.hasAnnotation(jsdefn.JSNameAnnot) =>
+ if (!sym.is(Method) || sym.isJSGetter) {
+ report.error(
+ "A member named apply represents function application in JavaScript. " +
+ "A parameterless member should be exported as a property. " +
+ "You must add @JSName(\"apply\")",
+ sym)
} else if (enclosingOwner is OwnerKind.JSNonNative) {
- reporter.error(sym.pos,
- "A non-native JS class cannot declare a method " +
- "named `apply` without `@JSName`")
- }
-
- case nme.equals_ if sym.tpe.matches(Any_equals.tpe) =>
- reporter.warning(sym.pos, "Overriding equals in a JS class does " +
- "not change how it is compared. To silence this warning, change " +
- "the name of the method and optionally add @JSName(\"equals\").")
-
- case nme.hashCode_ if sym.tpe.matches(Any_hashCode.tpe) =>
- reporter.warning(sym.pos, "Overriding hashCode in a JS class does " +
- "not change its hash code. To silence this warning, change " +
- "the name of the method and optionally add @JSName(\"hashCode\").")
+ report.error(
+ "A non-native JS class cannot declare a method named `apply` without `@JSName`",
+ sym)
+ }
+
+ case nme.equals_ if sym.info.matches(defn.Any_equals.info) =>
+ report.warning(
+ "Overriding equals in a JS class does not change how it is compared. " +
+ "To silence this warning, change the name of the method and optionally add @JSName(\"equals\").",
+ sym)
+
+ case nme.hashCode_ if sym.info.matches(defn.Any_hashCode.info) =>
+ report.warning(
+ "Overriding hashCode in a JS class does not change its hash code. " +
+ "To silence this warning, change the name of the method and optionally add @JSName(\"hashCode\").",
+ sym)
case _ =>
}
- if (jsInterop.isJSSetter(sym))
- checkSetterSignature(sym, tree.pos, exported = false)
+ if (sym.isJSSetter)
+ checkSetterSignature(sym, tree, exported = false)
- if (jsInterop.isJSBracketAccess(sym)) {
+ if (sym.isJSBracketAccess) {
if (enclosingOwner is OwnerKind.JSNonNative) {
- reporter.error(tree.pos,
- "@JSBracketAccess is not allowed in non-native JS classes")
+ report.error("@JSBracketAccess is not allowed in non-native JS classes", tree)
} else {
- val paramCount = sym.paramss.map(_.size).sum
- if (paramCount != 1 && paramCount != 2) {
- reporter.error(tree.pos,
- "@JSBracketAccess methods must have one or two parameters")
- } else if (paramCount == 2 &&
- sym.tpe.finalResultType.typeSymbol != UnitClass) {
- reporter.error(tree.pos,
- "@JSBracketAccess methods with two parameters must return Unit")
- }
+ val allParamInfos = sym.info.paramInfoss.flatten
- for (param <- sym.paramss.flatten) {
- if (isScalaRepeatedParamType(param.tpe)) {
- reporter.error(param.pos,
- "@JSBracketAccess methods may not have repeated parameters")
- } else if (param.isParamWithDefault) {
- reporter.error(param.pos,
- "@JSBracketAccess methods may not have default parameters")
- }
+ allParamInfos.size match {
+ case 1 =>
+ // ok
+ case 2 =>
+ if (!sym.info.finalResultType.isRef(defn.UnitClass))
+ report.error("@JSBracketAccess methods with two parameters must return Unit", tree)
+ case _ =>
+ report.error("@JSBracketAccess methods must have one or two parameters", tree)
}
+
+ if (allParamInfos.exists(_.isRepeatedParam))
+ report.error("@JSBracketAccess methods may not have repeated parameters", tree)
+ if (sym.hasDefaultParams)
+ report.error("@JSBracketAccess methods may not have default parameters", tree)
}
}
- if (jsInterop.isJSBracketCall(sym)) {
+ if (sym.isJSBracketCall) {
if (enclosingOwner is OwnerKind.JSNonNative) {
- reporter.error(tree.pos,
- "@JSBracketCall is not allowed in non-native JS classes")
+ report.error("@JSBracketCall is not allowed in non-native JS classes", tree)
} else {
// JS bracket calls must have at least one non-repeated parameter
- sym.tpe.paramss match {
- case (param :: _) :: _ if !isScalaRepeatedParamType(param.tpe) =>
+ sym.info.stripPoly match {
+ case mt: MethodType if mt.paramInfos.nonEmpty && !mt.paramInfos.head.isRepeatedParam =>
// ok
case _ =>
- reporter.error(tree.pos, "@JSBracketCall methods must have at " +
- "least one non-repeated parameter")
+ report.error("@JSBracketCall methods must have at least one non-repeated parameter", tree)
}
}
}
- if (sym.hasAnnotation(NativeAttr)) {
+ if (sym.hasAnnotation(defn.NativeAnnot)) {
// Native methods are not allowed
- reporter.error(tree.pos, "Methods in a js.Any may not be @native")
+ report.error("Methods in a js.Any may not be @native", tree)
}
/* In native JS types, there should not be any private member, except
* private[this] constructors.
*/
if ((enclosingOwner is OwnerKind.JSNative) && isPrivateMaybeWithin(sym)) {
- // Necessary for `private[this] val/var
- def isFieldPrivateThis: Boolean = {
- sym.isPrivateThis &&
- !sym.isParamAccessor &&
- !sym.owner.info.decls.exists(s => s.isGetter && s.accessed == sym)
- }
-
if (sym.isClassConstructor) {
- if (!sym.isPrivateThis) {
- reporter.error(sym.pos,
+ if (!sym.isAllOf(PrivateLocal)) {
+ report.error(
"Native JS classes may not have private constructors. " +
- "Use `private[this]` to declare an internal constructor.")
+ "Use `private[this]` to declare an internal constructor.",
+ sym)
}
- } else if (sym.isMethod || isFieldPrivateThis) {
- reporter.error(tree.pos,
+ } else if (!sym.is(ParamAccessor)) {
+ report.error(
"Native JS classes may not have private members. " +
- "Use a public member in a private facade instead.")
+ "Use a public member in a private facade instead.",
+ tree)
}
}
if (enclosingOwner is OwnerKind.JSNonNative) {
// Private methods cannot be overloaded
- if (sym.isMethod && isPrivateMaybeWithin(sym)) {
- val alts = sym.owner.info.member(sym.name).filter(_.isMethod)
+ if (sym.is(Method) && isPrivateMaybeWithin(sym)) {
+ val alts = sym.owner.info.memberBasedOnFlags(sym.name, required = Method)
if (alts.isOverloaded) {
- reporter.error(tree.pos,
- "Private methods in non-native JS classes cannot be " +
- "overloaded. Use different names instead.")
+ report.error(
+ "Private methods in non-native JS classes cannot be overloaded. Use different names instead.",
+ tree)
}
}
// private[Scope] methods must be final
- if (sym.isMethod && (sym.hasAccessBoundary && !sym.isProtected) &&
- !sym.isFinal && !sym.isClassConstructor) {
- reporter.error(tree.pos,
- "Qualified private members in non-native JS classes " +
- "must be final")
- }
+ if (!sym.isOneOf(Final | Protected) && sym.privateWithin.exists && !sym.isClassConstructor)
+ report.error("Qualified private members in non-native JS classes must be final", tree)
// Traits must be pure interfaces, except for js.undefined members
- if (sym.owner.isTrait && sym.isTerm && !sym.isConstructor) {
- if (sym.isMethod && isPrivateMaybeWithin(sym)) {
- reporter.error(tree.pos,
- "A non-native JS trait cannot contain private members")
- } else if (sym.isLazy) {
- reporter.error(tree.pos,
- "A non-native JS trait cannot contain lazy vals")
- } else if (!sym.isDeferred) {
- /* Tell the back-end not emit this thing. In fact, this only
+ if (sym.owner.is(Trait) && sym.isTerm && !sym.isConstructor) {
+ if (sym.is(Method) && isPrivateMaybeWithin(sym)) {
+ report.error("A non-native JS trait cannot contain private members", tree)
+ } else if (sym.is(Lazy)) {
+ report.error("A non-native JS trait cannot contain lazy vals", tree)
+ } else if (!sym.is(Deferred)) {
+ /* Tell the back-end not to emit this thing. In fact, this only
* matters for mixed-in members created from this member.
*/
- sym.addAnnotation(JSOptionalAnnotation)
+ sym.addAnnotation(jsdefn.JSOptionalAnnot)
- // For non-accessor methods, check that they do not have parens
- if (sym.isMethod && !sym.isAccessor) {
- sym.tpe match {
- case _: NullaryMethodType =>
- // ok
- case PolyType(_, _: NullaryMethodType) =>
- // ok
- case _ =>
- reporter.error(tree.rhs.pos,
- "In non-native JS traits, defs with parentheses " +
- "must be abstract.")
- }
- }
+ if (!sym.isSetter) {
+ // Check that methods do not have parens
+ if (sym.is(Method, butNot = Accessor) && sym.info.stripPoly.isInstanceOf[MethodType])
+ report.error("In non-native JS traits, defs with parentheses must be abstract.", tree.rhs)
- /* Check that the right-hand-side is `js.undefined`.
- *
- * On 2.12+, fields are created later than this phase, and getters
- * still hold the right-hand-side that we need to check (we
- * identify this case with `sym.accessed == NoSymbol`).
- * On 2.11 and before, however, the getter has already been
- * rewritten to read the field, so we must not check it.
- * In either case, setters must not be checked.
- */
- if (!sym.isAccessor || (sym.isGetter && sym.accessed == NoSymbol)) {
- // Check that the tree's body is `js.undefined`
+ // Check that the rhs is `js.undefined`
tree.rhs match {
- case sel: Select if sel.symbol == JSPackage_undefined =>
+ case sel: Select if sel.symbol == jsdefn.JSPackage_undefined =>
// ok
+ case Apply(Apply(TypeApply(fromTypeConstructorFun, _), (sel: Select) :: Nil), _)
+ if sel.symbol == jsdefn.JSPackage_undefined
+ && fromTypeConstructorFun.symbol == jsdefn.PseudoUnion_fromTypeConstructor =>
+ // ok: js.|.fromTypeConstructor(js.undefined)(...)
case _ =>
- reporter.error(tree.rhs.pos,
- "Members of non-native JS traits must either be " +
- "abstract, or their right-hand-side must be " +
- "`js.undefined`.")
+ report.error(
+ "Members of non-native JS traits must either be abstract, or their right-hand-side must be `js.undefined`.",
+ tree)
}
}
}
}
- }
-
- if (sym.isPrimaryConstructor || sym.isValueParameter ||
- sym.isParamWithDefault || sym.isAccessor ||
- sym.isParamAccessor || sym.isDeferred || sym.isSynthetic ||
- AllJSFunctionClasses.contains(sym.owner) ||
- (enclosingOwner is OwnerKind.JSNonNative)) {
- /* Ignore (i.e. allow) primary ctor, parameters, default parameter
- * getters, accessors, param accessors, abstract members, synthetic
- * methods (to avoid double errors with case classes, e.g. generated
- * copy method), js.Functions and js.ThisFunctions (they need abstract
- * methods for SAM treatment), and any member of a non-native JS
- * class/trait.
- */
- } else if (jsPrimitives.isJavaScriptPrimitive(sym)) {
- // No check for primitives. We trust our own standard library.
- } else if (sym.isConstructor) {
- // Force secondary ctor to have only a call to the primary ctor inside
- tree.rhs match {
- case Block(List(Apply(trg, _)), Literal(Constant(())))
- if trg.symbol.isPrimaryConstructor &&
- trg.symbol.owner == sym.owner =>
- // everything is fine here
- case _ =>
- reporter.error(tree.pos, "A secondary constructor of a class " +
- "extending js.Any may only call the primary constructor")
+ } else { // enclosingOwner isnt OwnerKind.JSNonNative
+ // Check that the rhs is valid
+ if (sym.isPrimaryConstructor || sym.isOneOf(Param | ParamAccessor | Deferred | Synthetic)
+ || sym.name.is(DefaultGetterName) || sym.isSetter) {
+ /* Ignore, i.e., allow:
+ * - primary constructor
+ * - all kinds of parameters
+ * - setters
+ * - default parameter getters (i.e., the default value of parameters)
+ * - abstract members
+ * - synthetic members (to avoid double errors with case classes, e.g. generated copy method)
+ */
+ } else if (sym.isConstructor) {
+ // Force secondary ctor to have only a call to the primary ctor inside
+ tree.rhs match {
+ case Block(List(Apply(trg, _)), Literal(Constant(())))
+ if trg.symbol.isPrimaryConstructor && trg.symbol.owner == sym.owner =>
+ // everything is fine here
+ case _ =>
+ report.error(
+ "A secondary constructor of a native JS class may only call the primary constructor",
+ tree.rhs)
+ }
+ } else {
+ // Check that the tree's rhs is exactly `= js.native`
+ checkRHSCallsJSNative(tree, "Concrete members of JS native types")
}
- } else {
- // Check that the tree's rhs is exactly `= js.native`
- checkRHSCallsJSNative(tree, "Concrete members of JS native types")
}
super.transform(tree)
}
- private def checkRHSCallsJSNative(tree: ValOrDefDef,
- longKindStr: String): Unit = {
+ private def checkRHSCallsJSNative(tree: ValOrDefDef, longKindStr: String)(using Context): Unit = {
// Check that the rhs is exactly `= js.native`
tree.rhs match {
- case sel: Select if sel.symbol == JSPackage_native =>
+ case sel: Select if sel.symbol == jsdefn.JSPackage_native =>
// ok
case _ =>
- val pos = if (tree.rhs != EmptyTree) tree.rhs.pos else tree.pos
- reporter.error(pos, s"$longKindStr may only call js.native.")
+ val pos = if (tree.rhs != EmptyTree) tree.rhs.srcPos else tree.srcPos
+ report.error(s"$longKindStr may only call js.native.", pos)
}
- // Warn if resultType is Nothing and not ascribed
- val sym = tree.symbol
- if (sym.tpe.resultType.typeSymbol == NothingClass &&
- tree.tpt.asInstanceOf[TypeTree].original == null) {
- val name = sym.name.decoded.trim
- reporter.warning(tree.pos,
- s"The type of $name got inferred as Nothing. " +
- "To suppress this warning, explicitly ascribe the type.")
- }
+ // Check that the resul type was explicitly specified
+ // (This is stronger than Scala 2, which only warns, and only if it was inferred as Nothing.)
+ if (tree.tpt.span.isSynthetic)
+ report.error(i"The type of ${tree.name} must be explicitly specified because it is JS native.", tree)
}
- private def checkJSNativeSpecificAnnotsOnNonJSNative(
- memberDef: MemberDef): Unit = {
- val sym = memberDef.symbol
-
- for (annot <- sym.annotations) {
+ private def checkJSNativeSpecificAnnotsOnNonJSNative(memberDef: MemberDef)(using Context): Unit = {
+ for (annot <- memberDef.symbol.annotations) {
annot.symbol match {
- case JSGlobalAnnotation =>
- reporter.error(annot.pos,
- "@JSGlobal can only be used on native JS definitions (with @js.native).")
- case JSImportAnnotation =>
- reporter.error(annot.pos,
- "@JSImport can only be used on native JS definitions (with @js.native).")
- case JSGlobalScopeAnnotation =>
- reporter.error(annot.pos,
- "@JSGlobalScope can only be used on native JS objects (with @js.native).")
+ case annotSym if annotSym == jsdefn.JSGlobalAnnot =>
+ report.error("@JSGlobal can only be used on native JS definitions (with @js.native).", annot.tree)
+ case annotSym if annotSym == jsdefn.JSImportAnnot =>
+ report.error("@JSImport can only be used on native JS definitions (with @js.native).", annot.tree)
+ case annotSym if annotSym == jsdefn.JSGlobalScopeAnnot =>
+ report.error("@JSGlobalScope can only be used on native JS objects (with @js.native).", annot.tree)
case _ =>
// ok
}
}
}
- private def checkJSNameAnnots(sym: Symbol): Unit = {
- for (annot <- sym.getAnnotation(JSNameAnnotation)) {
+ private def checkJSNameAnnots(sym: Symbol)(using Context): Unit = {
+ val allJSNameAnnots = sym.annotations.filter(_.symbol == jsdefn.JSNameAnnot).reverse
+
+ for (annot <- allJSNameAnnots.headOption) {
// Check everything about the first @JSName annotation
if (sym.isLocalToBlock || (enclosingOwner isnt OwnerKind.JSType)) {
- reporter.error(annot.pos,
- "@JSName can only be used on members of JS types.")
- } else if (sym.isTrait) {
- reporter.error(annot.pos,
- "@JSName cannot be used on traits.")
- } else if ((sym.isMethod || sym.isClass) && isPrivateMaybeWithin(sym)) {
- reporter.error(annot.pos,
- "@JSName cannot be used on private members.")
+ report.error("@JSName can only be used on members of JS types.", annot.tree)
+ } else if (sym.is(Trait)) {
+ report.error("@JSName cannot be used on traits.", annot.tree)
+ } else if (isPrivateMaybeWithin(sym)) {
+ report.error("@JSName cannot be used on private members.", annot.tree)
} else {
if (shouldCheckLiterals)
checkJSNameArgument(sym, annot)
}
// Check that there is at most one @JSName annotation.
- val allJSNameAnnots = sym.annotations.filter(_.symbol == JSNameAnnotation)
for (duplicate <- allJSNameAnnots.tail) {
- reporter.error(duplicate.pos,
- "A member can only have a single @JSName annotation.")
+ report.error("Duplicate @JSName annotation.", duplicate.tree)
}
}
}
- /** Checks that argument to @JSName on [[member]] is a literal.
+ /** Checks that the argument to `@JSName` annotations on `memberSym` is legal.
+ *
* Reports an error on each annotation where this is not the case.
+ * One one `@JSName` annotation is allowed, but that is handled somewhere else.
*/
- private def checkJSNameArgument(memberSym: Symbol, annot: AnnotationInfo): Unit = {
- val argTree = annot.args.head
- if (argTree.tpe.typeSymbol == StringClass) {
- if (!argTree.isInstanceOf[Literal]) {
- reporter.error(argTree.pos,
- "A string argument to JSName must be a literal string")
- }
+ private def checkJSNameArgument(memberSym: Symbol, annot: Annotation)(using Context): Unit = {
+ val argTree = annot.arguments.head
+ if (argTree.tpe.derivesFrom(defn.StringClass)) {
+ // We have a String. It must be a literal.
+ if (!annot.argumentConstantString(0).isDefined)
+ report.error("A String argument to JSName must be a literal string", argTree)
} else {
- // We have a js.Symbol
+ // We have a js.Symbol. It must be a stable reference.
val sym = argTree.symbol
- if (!sym.isStatic || !sym.isStable) {
- reporter.error(argTree.pos,
- "A js.Symbol argument to JSName must be a static, stable identifier")
- } else if ((enclosingOwner is OwnerKind.JSNonNative) &&
- sym.owner == memberSym.owner) {
- reporter.warning(argTree.pos,
- "This symbol is defined in the same object as the annotation's " +
- "target. This will cause a stackoverflow at runtime")
+ if (!sym.isStatic || !sym.isStableMember) {
+ report.error("A js.Symbol argument to JSName must be a static, stable identifier", argTree)
+ } else if ((enclosingOwner is OwnerKind.JSNonNative) && sym.owner == memberSym.owner) {
+ report.warning(
+ "This symbol is defined in the same object as the annotation's target. " +
+ "This will cause a stackoverflow at runtime",
+ argTree)
}
}
}
@@ -1179,86 +970,128 @@
* The symbol, which must be the module symbol for a module, not its
* module class symbol.
*/
- private def markExposedIfRequired(sym: Symbol): Unit = {
+ private def markExposedIfRequired(sym: Symbol)(using Context): Unit = {
def shouldBeExposed: Boolean = {
// it is a member of a non-native JS class
(enclosingOwner is OwnerKind.JSNonNative) && !sym.isLocalToBlock &&
- // it is a term member
- (sym.isModule || sym.isMethod) &&
+ // it is a term member, and it is not synthetic
+ sym.isOneOf(Module | Method, butNot = Synthetic) &&
// it is not private
!isPrivateMaybeWithin(sym) &&
- // it is not a kind of term member that we never expose
- !sym.isConstructor && !sym.isValueParameter && !sym.isParamWithDefault &&
- // it is not synthetic
- !sym.isSynthetic
+ // it is not a constructor
+ !sym.isConstructor //&& !sym.isValueParameter && !sym.isParamWithDefault
}
- if (shouldPrepareExports && shouldBeExposed) {
- sym.addAnnotation(ExposedJSMemberAnnot)
- /* For accessors, the field being accessed must also be exposed,
- * although it is private.
- *
- * #4089 Don't do this if `sym.accessed == NoSymbol`. This happens in
- * 2.12+, where fields are created later than this phase.
- */
- if (sym.isAccessor && sym.accessed != NoSymbol)
- sym.accessed.addAnnotation(ExposedJSMemberAnnot)
- }
+ if (shouldPrepareExports && shouldBeExposed)
+ sym.addAnnotation(jsdefn.ExposedJSMemberAnnot)
}
+ }
+}
+
+object PrepJSInterop {
+ val name: String = "prepjsinterop"
+
+ private final class OwnerKind private (private val baseKinds: Int) extends AnyVal {
+ import OwnerKind._
+
+ inline def isBaseKind: Boolean =
+ Integer.lowestOneBit(baseKinds) == baseKinds && baseKinds != 0 // exactly 1 bit on
+
+ // cannot be `inline` because it accesses the private constructor
+ @inline def |(that: OwnerKind): OwnerKind =
+ new OwnerKind(this.baseKinds | that.baseKinds)
+
+ inline def is(that: OwnerKind): Boolean =
+ (this.baseKinds & that.baseKinds) != 0
+
+ inline def isnt(that: OwnerKind): Boolean =
+ !this.is(that)
+ }
+
+ private object OwnerKind {
+ /** No owner, i.e., we are at the top-level. */
+ val None = new OwnerKind(0x00)
+
+ // Base kinds - those form a partition of all possible enclosing owners
+
+ /** A Scala class/trait. */
+ val ScalaClass = new OwnerKind(0x01)
+ /** A Scala object. */
+ val ScalaMod = new OwnerKind(0x02)
+ /** A native JS class/trait, which extends js.Any. */
+ val JSNativeClass = new OwnerKind(0x04)
+ /** A native JS object, which extends js.Any. */
+ val JSNativeMod = new OwnerKind(0x08)
+ /** A non-native JS class/trait. */
+ val JSClass = new OwnerKind(0x10)
+ /** A non-native JS object. */
+ val JSMod = new OwnerKind(0x20)
+ // Compound kinds
+
+ /** A Scala class, trait or object, i.e., anything not extending js.Any. */
+ val ScalaType = ScalaClass | ScalaMod
+
+ /** A native JS class/trait/object. */
+ val JSNative = JSNativeClass | JSNativeMod
+ /** A non-native JS class/trait/object. */
+ val JSNonNative = JSClass | JSMod
+ /** A JS type, i.e., something extending js.Any. */
+ val JSType = JSNative | JSNonNative
+
+ /** Any kind of class/trait, i.e., a Scala or JS class/trait. */
+ val AnyClass = ScalaClass | JSNativeClass | JSClass
}
- def isJSAny(sym: Symbol): Boolean =
- sym.isSubClass(JSAnyClass)
+ def isJSAny(sym: Symbol)(using Context): Boolean =
+ sym.isSubClass(jsdefn.JSAnyClass)
/** Checks that a setter has the right signature.
*
* Reports error messages otherwise.
*/
- def checkSetterSignature(sym: Symbol, pos: Position, exported: Boolean): Unit = {
+ def checkSetterSignature(sym: Symbol, pos: SrcPos, exported: Boolean)(using Context): Unit = {
val typeStr = if (exported) "Exported" else "JS"
- // Forbid setters with non-unit return type
- if (sym.tpe.resultType.typeSymbol != UnitClass) {
- reporter.error(pos, s"$typeStr setters must return Unit")
- }
+ val tpe = sym.info
- // Forbid setters with more than one argument
- sym.tpe.paramss match {
- case List(List(arg)) =>
+ // The result type must be Unit
+ if (!tpe.resultType.isRef(defn.UnitClass))
+ report.error(s"$typeStr setters must return Unit", pos)
+
+ // There must be exactly one non-varargs, non-default parameter
+ tpe.paramInfoss match {
+ case List(List(argInfo)) =>
// Arg list is OK. Do additional checks.
- if (isScalaRepeatedParamType(arg.tpe))
- reporter.error(pos, s"$typeStr setters may not have repeated params")
-
- if (arg.hasFlag(reflect.internal.Flags.DEFAULTPARAM))
- reporter.error(pos, s"$typeStr setters may not have default params")
+ if (tpe.isVarArgsMethod)
+ report.error(s"$typeStr setters may not have repeated params", pos)
+ if (sym.hasDefaultParams)
+ report.error(s"$typeStr setters may not have default params", pos)
case _ =>
- reporter.error(pos, s"$typeStr setters must have exactly one argument")
+ report.error(s"$typeStr setters must have exactly one argument", pos)
}
}
/** Tests whether the symbol has `private` in any form, either `private`,
* `private[this]` or `private[Enclosing]`.
*/
- def isPrivateMaybeWithin(sym: Symbol): Boolean =
- sym.isPrivate || (sym.hasAccessBoundary && !sym.isProtected)
+ def isPrivateMaybeWithin(sym: Symbol)(using Context): Boolean =
+ sym.is(Private) || (sym.privateWithin.exists && !sym.is(Protected))
/** Checks that the optional argument to an `@JSGlobal` annotation is a
* literal.
*
* Reports an error on the annotation if it is not the case.
*/
- private def checkJSGlobalLiteral(annot: AnnotationInfo): Unit = {
- if (annot.args.nonEmpty) {
- assert(annot.args.size == 1,
+ private def checkJSGlobalLiteral(annot: Annotation)(using Context): Unit = {
+ if (annot.arguments.nonEmpty) {
+ assert(annot.arguments.size == 1,
s"@JSGlobal annotation $annot has more than 1 argument")
- val argIsValid = annot.stringArg(0).isDefined
- if (!argIsValid) {
- reporter.error(annot.args.head.pos,
- "The argument to @JSGlobal must be a literal string.")
- }
+ val argIsValid = annot.argumentConstantString(0).isDefined
+ if (!argIsValid)
+ report.error("The argument to @JSGlobal must be a literal string.", annot.arguments.head)
}
}
@@ -1269,152 +1102,51 @@
*
* Reports an error on the annotation if it is not the case.
*/
- private def checkJSImportLiteral(annot: AnnotationInfo): Unit = {
- assert(annot.args.size == 2 || annot.args.size == 3,
- s"@JSImport annotation $annot does not have exactly 2 or 3 arguments")
-
- val firstArgIsValid = annot.stringArg(0).isDefined
- if (!firstArgIsValid) {
- reporter.error(annot.args.head.pos,
- "The first argument to @JSImport must be a literal string.")
- }
-
- val secondArgIsValid = {
- annot.stringArg(1).isDefined ||
- annot.args(1).symbol == JSImportNamespaceObject
- }
- if (!secondArgIsValid) {
- reporter.error(annot.args(1).pos,
- "The second argument to @JSImport must be literal string or the " +
- "JSImport.Namespace object.")
- }
-
- val thirdArgIsValid = annot.args.size < 3 || annot.stringArg(2).isDefined
- if (!thirdArgIsValid) {
- reporter.error(annot.args(2).pos,
- "The third argument to @JSImport, when present, must be a " +
- "literal string.")
- }
+ private def checkJSImportLiteral(annot: Annotation)(using Context): Unit = {
+ val args = annot.arguments
+ assert(args.size == 2 || args.size == 3,
+ i"@JSImport annotation $annot does not have exactly 2 or 3 arguments")
+
+ val firstArgIsValid = annot.argumentConstantString(0).isDefined
+ if (!firstArgIsValid)
+ report.error("The first argument to @JSImport must be a literal string.", args.head)
+
+ val secondArgIsValid = annot.argumentConstantString(1).isDefined || args(1).symbol == jsdefn.JSImportNamespaceModule
+ if (!secondArgIsValid)
+ report.error("The second argument to @JSImport must be literal string or the JSImport.Namespace object.", args(1))
+
+ val thirdArgIsValid = args.size < 3 || annot.argumentConstantString(2).isDefined
+ if (!thirdArgIsValid)
+ report.error("The third argument to @JSImport, when present, must be a literal string.", args(2))
}
- private abstract class ScalaEnumFctExtractors(methSym: Symbol) {
- private def resolve(ptpes: Symbol*) = {
- val res = methSym suchThat {
- _.tpe.params.map(_.tpe.typeSymbol) == ptpes.toList
- }
- assert(res != NoSymbol, s"no overload of $methSym for param types $ptpes")
- res
- }
-
- private val noArg = resolve()
- private val nameArg = resolve(StringClass)
- private val intArg = resolve(IntClass)
- private val fullMeth = resolve(IntClass, StringClass)
-
- /**
- * Extractor object for calls to the targeted symbol that do not have an
- * explicit name in the parameters
- *
- * Extracts:
- * - `sel: Select` where sel.symbol is targeted symbol (no arg)
- * - Apply(meth, List(param)) where meth.symbol is targeted symbol (i: Int)
- */
- object NoName {
- def unapply(t: Tree): Option[Option[Tree]] = t match {
- case sel: Select if sel.symbol == noArg =>
- Some(None)
- case Apply(meth, List(param)) if meth.symbol == intArg =>
- Some(Some(param))
- case _ =>
- None
- }
- }
+ private def checkAndGetJSNativeLoadingSpecAnnotOf(pos: SrcPos, sym: Symbol)(
+ using Context): Option[Annotation] = {
- object NullName {
- def unapply(tree: Tree): Boolean = tree match {
- case Apply(meth, List(Literal(Constant(null)))) =>
- meth.symbol == nameArg
- case Apply(meth, List(_, Literal(Constant(null)))) =>
- meth.symbol == fullMeth
- case _ => false
- }
- }
+ // Must not have @JSName
- }
+ for (annot <- sym.getAnnotation(jsdefn.JSNameAnnot))
+ report.error("@JSName can only be used on members of JS types.", annot.tree)
- private object ScalaEnumValue
- extends ScalaEnumFctExtractors(getMemberMethod(ScalaEnumClass, jsnme.Value))
+ // Must have exactly one JS native load spec annotation
- private object ScalaEnumVal
- extends ScalaEnumFctExtractors(getMemberClass(ScalaEnumClass, jsnme.Val).tpe.member(nme.CONSTRUCTOR))
+ val annots = sym.annotations.filter(annot => isJSNativeLoadingSpecAnnot(annot.symbol))
- /**
- * Construct a call to Enumeration.Value
- * @param thisSym ClassSymbol of enclosing class
- * @param nameOrig Symbol of ValDef where this call will be placed
- * (determines the string passed to Value)
- * @param intParam Optional tree with Int passed to Value
- * @return Typed tree with appropriate call to Value
- */
- private def ScalaEnumValName(
- thisSym: Symbol,
- nameOrig: Symbol,
- intParam: Option[Tree]) = {
-
- val defaultName = nameOrig.asTerm.getterName.encoded
-
-
- // Construct the following tree
- //
- // if (nextName != null && nextName.hasNext)
- // nextName.next()
- // else
- // <defaultName>
- //
- val nextNameTree = Select(This(thisSym), jsnme.nextName)
- val nullCompTree =
- Apply(Select(nextNameTree, nme.NE), Literal(Constant(null)) :: Nil)
- val hasNextTree = Select(nextNameTree, jsnme.hasNext)
- val condTree = Apply(Select(nullCompTree, nme.ZAND), hasNextTree :: Nil)
- val nameTree = If(condTree,
- Apply(Select(nextNameTree, jsnme.next), Nil),
- Literal(Constant(defaultName)))
- val params = intParam.toList :+ nameTree
-
- typer.typed {
- Apply(Select(This(thisSym), jsnme.Value), params)
- }
- }
-
- private def checkAndGetJSNativeLoadingSpecAnnotOf(
- pos: Position, sym: Symbol): Option[Annotation] = {
-
- for (annot <- sym.getAnnotation(JSNameAnnotation)) {
- reporter.error(annot.pos,
- "@JSName can only be used on members of JS types.")
- }
-
- val annots = sym.annotations.filter { annot =>
- JSNativeLoadingSpecAnnots.contains(annot.symbol)
- }
-
- val badAnnotCountMsg = if (sym.isModuleClass) {
- "Native JS objects must have exactly one annotation among " +
- "@JSGlobal, @JSImport and @JSGlobalScope."
- } else {
- "Native JS classes, vals and defs must have exactly one annotation " +
- "among @JSGlobal and @JSImport."
- }
+ val badAnnotCountMsg =
+ if (sym.is(Module)) "Native JS objects must have exactly one annotation among @JSGlobal, @JSImport and @JSGlobalScope."
+ else "Native JS classes, vals and defs must have exactly one annotation among @JSGlobal and @JSImport."
annots match {
case Nil =>
- reporter.error(pos, badAnnotCountMsg)
+ report.error(badAnnotCountMsg, pos)
None
-
- case result :: duplicates =>
+ case result :: Nil =>
+ Some(result)
+ case _ =>
+ // Annotations are stored in reverse order, which we re-reverse now
+ val result :: duplicates = annots.reverse
for (annot <- duplicates)
- reporter.error(annot.pos, badAnnotCountMsg)
-
+ report.error(badAnnotCountMsg, annot.tree)
Some(result)
}
}
@@ -1423,120 +1155,26 @@
* it's convenient for the purposes of PrepJSInterop. Actually @JSGlobalScope
* objects do not receive a JS loading spec in their IR.
*/
- private lazy val JSNativeLoadingSpecAnnots: Set[Symbol] = {
- Set(JSGlobalAnnotation, JSImportAnnotation, JSGlobalScopeAnnotation)
+ private def isJSNativeLoadingSpecAnnot(sym: Symbol)(using Context): Boolean = {
+ sym == jsdefn.JSGlobalAnnot
+ || sym == jsdefn.JSImportAnnot
+ || sym == jsdefn.JSGlobalScopeAnnot
}
- private lazy val ScalaEnumClass = getRequiredClass("scala.Enumeration")
-
- private def wasPublicBeforeTyper(sym: Symbol): Boolean =
- sym.hasAnnotation(WasPublicBeforeTyperClass)
-
- private def fixPublicBeforeTyper(ddef: DefDef): DefDef = {
- // This method assumes that isJSAny(ddef.symbol.owner) is true
- val sym = ddef.symbol
- val needsFix = {
- sym.isPrivate &&
- (wasPublicBeforeTyper(sym) ||
- (sym.isAccessor && wasPublicBeforeTyper(sym.accessed)))
- }
- if (needsFix) {
- sym.resetFlag(Flag.PRIVATE)
- treeCopy.DefDef(ddef, ddef.mods &~ Flag.PRIVATE, ddef.name, ddef.tparams,
- ddef.vparamss, ddef.tpt, ddef.rhs)
- } else {
- ddef
- }
- }
-
- private def checkInternalAnnotations(sym: Symbol): Unit = {
- /** Returns true iff it is a compiler annotations. This does not include
- * annotations inserted before the typer (such as `@WasPublicBeforeTyper`).
- */
- def isCompilerAnnotation(annotation: AnnotationInfo): Boolean = {
- annotation.symbol == ExposedJSMemberAnnot ||
- annotation.symbol == JSTypeAnnot ||
- annotation.symbol == JSOptionalAnnotation
+ private def checkInternalAnnotations(sym: Symbol)(using Context): Unit = {
+ /** Returns true iff it is a compiler annotations. */
+ def isCompilerAnnotation(annotation: Annotation): Boolean = {
+ annotation.symbol == jsdefn.ExposedJSMemberAnnot
+ || annotation.symbol == jsdefn.JSTypeAnnot
+ || annotation.symbol == jsdefn.JSOptionalAnnot
}
for (annotation <- sym.annotations) {
if (isCompilerAnnotation(annotation)) {
- reporter.error(annotation.pos,
- s"$annotation is for compiler internal use only. " +
- "Do not use it yourself.")
+ report.error(
+ i"@${annotation.symbol.fullName} is for compiler internal use only. Do not use it yourself.",
+ annotation.tree)
}
}
}
-
- private def moduleToModuleClass(sym: Symbol): Symbol =
- if (sym.isModule) sym.moduleClass
- else sym
-}
-
-object PrepJSInterop {
- private final class OwnerKind private (private val baseKinds: Int)
- extends AnyVal {
-
- import OwnerKind._
-
- @inline def isBaseKind: Boolean =
- Integer.lowestOneBit(baseKinds) == baseKinds && baseKinds != 0 // exactly 1 bit on
-
- @inline def |(that: OwnerKind): OwnerKind =
- new OwnerKind(this.baseKinds | that.baseKinds)
-
- @inline def is(that: OwnerKind): Boolean =
- (this.baseKinds & that.baseKinds) != 0
-
- @inline def isnt(that: OwnerKind): Boolean =
- !this.is(that)
- }
-
- private object OwnerKind {
- /** No owner, i.e., we are at the top-level. */
- val None = new OwnerKind(0x00)
-
- // Base kinds - those form a partition of all possible enclosing owners
-
- /** A Scala class/trait that does not extend Enumeration. */
- val NonEnumScalaClass = new OwnerKind(0x01)
- /** A Scala object that does not extend Enumeration. */
- val NonEnumScalaMod = new OwnerKind(0x02)
- /** A native JS class/trait, which extends js.Any. */
- val JSNativeClass = new OwnerKind(0x04)
- /** A native JS object, which extends js.Any. */
- val JSNativeMod = new OwnerKind(0x08)
- /** A non-native JS class/trait. */
- val JSClass = new OwnerKind(0x10)
- /** A non-native JS object. */
- val JSMod = new OwnerKind(0x20)
- /** A Scala class/trait that extends Enumeration. */
- val EnumClass = new OwnerKind(0x40)
- /** A Scala object that extends Enumeration. */
- val EnumMod = new OwnerKind(0x80)
- /** The Enumeration class itself. */
- val EnumImpl = new OwnerKind(0x100)
-
- // Compound kinds
-
- /** A Scala class/trait, possibly Enumeration-related. */
- val ScalaClass = NonEnumScalaClass | EnumClass | EnumImpl
- /** A Scala object, possibly Enumeration-related. */
- val ScalaMod = NonEnumScalaMod | EnumMod
- /** A Scala class, trait or object, i.e., anything not extending js.Any. */
- val ScalaThing = ScalaClass | ScalaMod
-
- /** A Scala class/trait/object extending Enumeration, but not Enumeration itself. */
- val Enum = EnumClass | EnumMod
-
- /** A native JS class/trait/object. */
- val JSNative = JSNativeClass | JSNativeMod
- /** A non-native JS class/trait/object. */
- val JSNonNative = JSClass | JSMod
- /** A JS type, i.e., something extending js.Any. */
- val JSType = JSNative | JSNonNative
-
- /** Any kind of class/trait, i.e., a Scala or JS class/trait. */
- val AnyClass = ScalaClass | JSNativeClass | JSClass
- }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment