Created
September 13, 2020 11:35
-
-
Save gzm0/8f515e48bb0918c56efab31cccbaaf17 to your computer and use it in GitHub Desktop.
Diff between https://github.com/scala-js/scala-js/blob/v1.2.0/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala / https://github.com/lampepfl/dotty/blob/442e205107efb4bad0cbf2ac905ade9b390410f1/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSInterop.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- 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