dotty.tools.dotc.transform.sjs.PrepJSInterop.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scala3-compiler_3 Show documentation
Show all versions of scala3-compiler_3 Show documentation
scala3-compiler-bootstrapped
package dotty.tools.dotc
package transform
package sjs
import scala.collection.mutable
import ast.tpd
import core.*
import typer.Checking
import util.SrcPos
import Annotations.*
import Constants.*
import Contexts.*
import Decorators.*
import DenotTransformers.*
import Flags.*
import NameKinds.{DefaultGetterName, ModuleClassName}
import NameOps.*
import StdNames.*
import Symbols.*
import Types.*
import JSSymUtils.*
import dotty.tools.sjs.ir.Trees.JSGlobalRef
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 is the equivalent of `PrepJSInterop` in Scala 2, minus the handling
* of `scala.Enumeration`.
*
* 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.
*/
class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisPhase =>
import PrepJSInterop.*
import tpd.*
override def phaseName: String = PrepJSInterop.name
override def description: String = PrepJSInterop.description
override def isEnabled(using Context): Boolean =
ctx.settings.scalajs.value
override def changesMembers: Boolean = true // the phase adds export forwarders
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
/** Cumulative kinds of all enclosing owners. */
private var allEnclosingOwners: OwnerKind = OwnerKind.None
/** Nicer syntax for `allEnclosingOwners is kind`. */
private def anyEnclosingOwner: OwnerKind = allEnclosingOwners
private def enterOwner[A](kind: OwnerKind)(body: => A): A = {
require(kind.isBaseKind, kind)
val oldEnclosingOwner = enclosingOwner
val oldAllEnclosingOwners = allEnclosingOwners
enclosingOwner = kind
allEnclosingOwners |= kind
try {
body
} finally {
enclosingOwner = oldEnclosingOwner
allEnclosingOwners = oldAllEnclosingOwners
}
}
private var dynamicImportEnclosingClasses: Set[Symbol] = Set.empty
private def enterDynamicImportEnclosingClass[A](cls: Symbol)(body: => A): A = {
val saved = dynamicImportEnclosingClasses
dynamicImportEnclosingClasses = saved + cls
try
body
finally
dynamicImportEnclosingClasses = saved
}
private def hasImplicitThisPrefixToDynamicImportEnclosingClass(tpe: Type)(using Context): Boolean =
tpe match
case tpe: ThisType => dynamicImportEnclosingClasses.contains(tpe.cls)
case TermRef(prefix, _) => hasImplicitThisPrefixToDynamicImportEnclosingClass(prefix)
case _ => false
end hasImplicitThisPrefixToDynamicImportEnclosingClass
/** DefDefs in class templates that export methods to JavaScript */
private val exporters = mutable.Map.empty[Symbol, mutable.ListBuffer[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.
* We still need to mark exposed if required, since that needs to be done
* on the module symbol, not its module class.
*/
markExposedIfRequired(tree.symbol)
super.transform(tree)
case tree: MemberDef => transformMemberDef(tree)
case tree: Template => transformTemplate(tree)
case _ => transformStatOrExpr(tree)
}
}
private def transformMemberDef(tree: MemberDef)(using Context): Tree = {
val sym = tree.symbol
checkInternalAnnotations(sym)
stripJSAnnotsOnExported(sym)
/* Checks related to @js.native:
* - if @js.native, verify that it is allowed in this context, and if
* yes, compute and store the JS native load spec
* - 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.getAnnotation(jsdefn.JSNativeAnnot) match {
case Some(annot) =>
checkJSNativeDefinition(tree, annot.tree, sym)
true
case None =>
checkJSNativeSpecificAnnotsOnNonJSNative(tree)
false
}
checkJSNameAnnots(sym)
constFoldJSExportTopLevelAndStaticAnnotations(sym)
markExposedIfRequired(tree.symbol)
tree match {
case tree: TypeDef if tree.isClassDef =>
val exports = genExport(sym)
if (exports.nonEmpty)
exporters.getOrElseUpdate(sym.owner, mutable.ListBuffer.empty) ++= exports
if (isJSAny(sym))
transformJSClassDef(tree)
else
transformScalaClassDef(tree)
case _: TypeDef =>
super.transform(tree)
case tree: ValOrDefDef =>
// Prepare exports
val exports = genExport(sym)
if (exports.nonEmpty) {
val target = if (sym.isConstructor) sym.owner.owner else sym.owner
exporters.getOrElseUpdate(target, mutable.ListBuffer.empty) ++= exports
}
if (sym.isLocalToBlock)
super.transform(tree)
else if (isJSNative)
transformJSNativeValOrDefDef(tree)
else if (enclosingOwner is OwnerKind.JSType)
transformValOrDefDefInJSType(tree)
else
transformScalaValOrDefDef(tree)
}
}
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) {
/* We have to allow synthetic companion objects here, as they get
* generated when a nested native JS class has default arguments in
* its constructor (see #1891).
*/
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 == jsdefn.PseudoUnionClass)
sym.addAnnotation(jsdefn.JSTypeAnnot)
val kind = if (sym.isSubClass(jsdefn.scalaEnumeration.EnumerationClass)) {
if (sym.is(Module)) OwnerKind.EnumMod
else if (sym == jsdefn.scalaEnumeration.EnumerationClass) OwnerKind.EnumImpl
else OwnerKind.EnumClass
} else {
if (sym.is(Module)) OwnerKind.NonEnumScalaMod
else OwnerKind.NonEnumScalaClass
}
enterOwner(kind) {
super.transform(tree)
}
}
private def transformTemplate(tree: Template)(using Context): Template = {
// First, recursively transform the template
val transformedTree = super.transform(tree).asInstanceOf[Template]
val clsSym = ctx.owner
// Check that @JSExportStatic fields come first
if (clsSym.is(ModuleClass)) { // quick check to avoid useless work
var foundStatOrNonStaticVal: Boolean = false
for (tree <- transformedTree.body) {
tree match {
case vd: ValDef if vd.symbol.hasAnnotation(jsdefn.JSExportStaticAnnot) =>
if (foundStatOrNonStaticVal) {
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.is(Lazy) =>
foundStatOrNonStaticVal = true
case _: MemberDef =>
case _ =>
foundStatOrNonStaticVal = true
}
}
}
// Add exports to the template, if there are any
exporters.get(clsSym).fold {
transformedTree
} { exports =>
assert(exports.nonEmpty, s"found empty exporters for $clsSym" )
checkNoDoubleDeclaration(clsSym)
cpy.Template(transformedTree)(
transformedTree.constr,
transformedTree.parents,
Nil,
transformedTree.self,
transformedTree.body ::: exports.toList
)
}
}
private def transformStatOrExpr(tree: Tree)(using Context): Tree = {
tree match {
case Closure(env, call, functionalInterface) =>
val tpeSym = functionalInterface.tpe.typeSymbol
if (tpeSym.isJSType) {
def reportError(reasonAndExplanation: String): Unit = {
report.error(
em"Using an anonymous function as a SAM for the JavaScript type ${
tpeSym.fullName
} is not allowed because $reasonAndExplanation",
tree)
}
if (!tpeSym.is(Trait) || tpeSym.asClass.superClass != jsdefn.JSFunctionClass) {
reportError(
"it is not a trait extending js.Function. " +
"Use an anonymous class instead.")
} else if (tpeSym.hasAnnotation(jsdefn.JSNativeAnnot)) {
reportError(
"it is a native JS type. " +
"It is not possible to directly implement it.")
} else if (!tree.tpe.possibleSamMethods.exists(_.symbol.hasJSCallCallingConvention)) {
reportError(
"its single abstract method is not named `apply`. " +
"Use an anonymous class instead.")
}
}
super.transform(tree)
// Validate js.constructorOf[T]
case TypeApply(ctorOfTree, List(tpeArg))
if ctorOfTree.symbol == jsdefn.JSPackage_constructorOf =>
validateJSConstructorOf(tree, tpeArg)
super.transform(tree)
/* Rewrite js.ConstructorTag.materialize[T] into
* runtime.newConstructorTag[T](js.constructorOf[T])
*/
case TypeApply(ctorOfTree, List(tpeArg))
if ctorOfTree.symbol == jsdefn.JSConstructorTag_materialize =>
validateJSConstructorOf(tree, tpeArg)
val ctorOf = ref(jsdefn.JSPackage_constructorOf).appliedToTypeTree(tpeArg)
ref(jsdefn.Runtime_newConstructorTag).appliedToType(tpeArg.tpe).appliedTo(ctorOf)
/* Rewrite js.dynamicImport[T](body) into
*
* runtime.dynamicImport[T](
* new DynamicImportThunk { def apply(): Any = body }
* )
*/
case Apply(TypeApply(fun, List(tpeArg)), List(body))
if fun.symbol == jsdefn.JSPackage_dynamicImport =>
val span = tree.span
val currentOwner = ctx.owner
assert(currentOwner.isTerm, s"unexpected owner: $currentOwner at ${tree.sourcePos}")
val enclosingClass = currentOwner.enclosingClass
// new DynamicImportThunk { def apply(): Any = body }
val dynamicImportThunkAnonClass = AnonClass(currentOwner, List(jsdefn.DynamicImportThunkType), span) { cls =>
val applySym = newSymbol(cls, nme.apply, Method, MethodType(Nil, Nil, defn.AnyType), coord = span).entered
val transformedBody = enterDynamicImportEnclosingClass(enclosingClass) {
transform(body)
}
val newBody = transformedBody.changeOwnerAfter(currentOwner, applySym, thisPhase)
val applyDefDef = DefDef(applySym, newBody)
List(applyDefDef)
}
// runtime.DynamicImport[A](new ...)
ref(jsdefn.Runtime_dynamicImport)
.appliedToTypeTree(tpeArg)
.appliedTo(dynamicImportThunkAnonClass)
// #17344 Make `ThisType`-based references to enclosing classes of `js.dynamicImport` explicit
case tree: Ident if hasImplicitThisPrefixToDynamicImportEnclosingClass(tree.tpe) =>
def rec(tpe: Type): Tree = (tpe: @unchecked) match // exhaustive because of the `if ... =>`
case tpe: ThisType => This(tpe.cls)
case tpe @ TermRef(prefix, _) => rec(prefix).select(tpe.symbol)
rec(tree.tpe).withSpan(tree.span)
// Compile-time errors and warnings for js.Dynamic.literal
case Apply(Apply(fun, nameArgs), args)
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")
report.error(em"js.Dynamic.literal does not have a method named $s", tree)
case _ =>
report.error(em"js.Dynamic.literal.${tree.symbol.name} may not be called directly", tree)
}
// TODO Warn for known duplicate property names
super.transform(tree)
// Warnings for scala.Enumeration.Value that could not be transformed
case _:Ident | _:Select | _:Apply if jsdefn.scalaEnumeration.isValueMethodNoName(tree.symbol) =>
report.warning(
"Could not transform call to scala.Enumeration.Value.\n" +
"The resulting program is unlikely to function properly as this operation requires reflection.",
tree)
super.transform(tree)
// Warnings for scala.Enumeration.Value with a `null` name
case Apply(_, args) if jsdefn.scalaEnumeration.isValueMethodName(tree.symbol) && isNullLiteral(args.last) =>
report.warning(
"Passing null as name to scala.Enumeration.Value requires reflection at run-time.\n" +
"The resulting program is unlikely to function properly.",
tree)
super.transform(tree)
// Warnings for scala.Enumeration.Val without name
case _: Apply if jsdefn.scalaEnumeration.isValCtorNoName(tree.symbol) =>
report.warning(
"Calls to the non-string constructors of scala.Enumeration.Val require reflection at run-time.\n" +
"The resulting program is unlikely to function properly.",
tree)
super.transform(tree)
// Warnings for scala.Enumeration.Val with a `null` name
case Apply(_, args) if jsdefn.scalaEnumeration.isValCtorName(tree.symbol) && isNullLiteral(args.last) =>
report.warning(
"Passing null as name to a constructor of scala.Enumeration.Val requires reflection at run-time.\n" +
"The resulting program is unlikely to function properly.",
tree)
super.transform(tree)
case _: Export =>
if enclosingOwner is OwnerKind.JSNative then
report.error("Native JS traits, classes and objects cannot contain exported definitions.", tree)
else if enclosingOwner is OwnerKind.JSTrait then
report.error("Non-native JS traits cannot contain exported definitions.", tree)
super.transform(tree)
case _ =>
super.transform(tree)
}
}
private def isNullLiteral(tree: Tree): Boolean = tree match
case Literal(Constant(null)) => true
case _ => false
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(em"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 transformJSClassDef(classDef: TypeDef)(using Context): Tree = {
val sym = classDef.symbol.asClass
val isJSNative = sym.hasAnnotation(jsdefn.JSNativeAnnot)
sym.addAnnotation(jsdefn.JSTypeAnnot)
// Forbid @EnableReflectiveInstantiation on JS types
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.isPackageObject)
report.error("Package objects may not extend js.Any.", classDef)
// Check that we do 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 (parentSym <- sym.parentSyms) {
parentSym match {
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.is(Trait) && parentSym.hasAnnotation(jsdefn.JSNativeAnnot)) {
report.error(
"Non-native JS types cannot directly extend native JS traits.",
classDef)
}
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 scala-js/scala-js#1385.
*/
case parentSym =>
/* This is a Scala class or trait other than AnyRef and Dynamic,
* which is never valid.
*/
report.error(
em"${sym.name} extends ${parentSym.fullName} which does not extend js.Any.",
classDef)
}
}
// Checks for non-native JS stuff
if (!isJSNative) {
// It cannot be in a native JS class or trait
if (enclosingOwner is OwnerKind.JSNativeClass) {
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.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.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
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
}
// Some utils inspired by RefChecks
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"
val ccStr = s" called from JS as '${sym.jsCallingConvention.displayName}'"
i"${if (showLocation) sym1.showLocated else sym1}$infoStr$ccStr"
}
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(
em"""error overriding ${infoStringWithLocation(overridden)};
| ${infoString(overriding)} $msg""",
errorPos)
}
// Check for overrides with different JS names - issue scala-js/scala-js#1983
if (overriding.jsCallingConvention != overridden.jsCallingConvention)
emitOverrideError("has a different JS calling convention")
/* 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.is(ModuleClass)) OwnerKind.JSMod
else if (sym.is(Trait)) OwnerKind.JSTrait
else OwnerKind.JSNonTraitClass
} else {
if (sym.is(ModuleClass)) OwnerKind.JSNativeMod
else OwnerKind.JSNativeClass
}
}
enterOwner(kind) {
super.transform(classDef)
}
}
private def checkJSNativeDefinition(treePos: SrcPos, annotPos: SrcPos, sym: Symbol)(using Context): Unit = {
// Check if we may have a JS native here
if (sym.isLocalToBlock) {
report.error("@js.native is not allowed on local definitions", annotPos)
} else if (!sym.isClass && (anyEnclosingOwner is (OwnerKind.ScalaClass | OwnerKind.JSType))) {
report.error("@js.native vals and defs can only appear in static Scala objects", annotPos)
} else if (sym.isClass && !isJSAny(sym)) {
report.error("Classes, traits and objects not extending js.Any may not have an @js.native annotation", annotPos)
} else if (anyEnclosingOwner is OwnerKind.ScalaClass) {
report.error("Scala traits and classes may not have native JS members", annotPos)
} else if (enclosingOwner is OwnerKind.JSNonNative) {
report.error("non-native JS classes, traits and objects may not have native JS members", annotPos)
} else {
// The symbol can be annotated with @js.native. Now check its JS native loading spec.
if (sym.is(Trait)) {
for (annot <- sym.annotations) {
val annotSym = annot.symbol
if (isJSNativeLoadingSpecAnnot(annotSym))
report.error(em"Traits may not have an @${annotSym.name} annotation.", annot.tree)
}
} else {
checkJSNativeLoadSpecOf(treePos, sym)
}
}
}
private def checkJSNativeLoadSpecOf(pos: SrcPos, sym: Symbol)(using Context): Unit = {
def checkGlobalRefName(globalRef: String): Unit = {
if (!JSGlobalRef.isValidJSGlobalRefName(globalRef))
report.error(em"The name of a JS global variable must be a valid JS identifier (got '$globalRef')", pos)
}
if (enclosingOwner is OwnerKind.JSNative) {
/* We cannot get here for @js.native vals and defs. That would mean we
* have an @js.native val/def inside a JavaScript type, which is not
* allowed and already caught in checkJSNativeDefinition().
*/
assert(sym.isClass,
s"undetected @js.native val or def ${sym.fullName} inside JS type at $pos")
for (annot <- sym.annotations) {
val annotSym = annot.symbol
if (isJSNativeLoadingSpecAnnot(annotSym))
report.error(em"Nested JS classes and objects cannot have an @${annotSym.name} annotation.", annot.tree)
}
if (sym.owner.isStaticOwner) {
for (annot <- sym.annotations) {
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)
}
}
if (sym.owner.hasAnnotation(jsdefn.JSGlobalScopeAnnot)) {
val jsName = sym.jsName match {
case JSName.Literal(jsName) =>
checkGlobalRefName(jsName)
case JSName.Computed(_) =>
() // compile error above or in `checkJSNameArgument`
}
}
}
} else {
def checkGlobalRefPath(pathName: String): Unit = {
val dotIndex = pathName.indexOf('.')
val globalRef =
if (dotIndex < 0) pathName
else pathName.substring(0, dotIndex).nn
checkGlobalRefName(globalRef)
}
checkAndGetJSNativeLoadingSpecAnnotOf(pos, sym) match {
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)
}
case Some(annot) if annot.symbol == jsdefn.JSGlobalAnnot =>
checkJSGlobalLiteral(annot)
val pathName = annot.argumentConstantString(0).getOrElse {
val symTermName = sym.name.exclude(NameKinds.ModuleClassName).toTermName
if (symTermName == nme.apply) {
report.error(
"Native JS definitions named 'apply' must have an explicit name in @JSGlobal",
annot.tree)
} else if (symTermName.isSetterName) {
report.error(
"Native JS definitions with a name ending in '_=' must have an explicit name in @JSGlobal",
annot.tree)
}
sym.defaultJSName
}
checkGlobalRefPath(pathName)
case Some(annot) if annot.symbol == jsdefn.JSImportAnnot =>
checkJSImportLiteral(annot)
if (annot.arguments.sizeIs < 2) {
val symTermName = sym.name.exclude(NameKinds.ModuleClassName).toTermName
if (symTermName == nme.apply) {
report.error(
"Native JS definitions named 'apply' must have an explicit name in @JSImport",
annot.tree)
} else if (symTermName.isSetterName) {
report.error(
"Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport",
annot.tree)
}
}
annot.argumentConstantString(2).foreach { globalPathName =>
checkGlobalRefPath(globalPathName)
}
case _ =>
// We already emitted an error in checkAndGetJSNativeLoadingSpecAnnotOf
()
}
}
}
/** Transforms a non-`@js.native` ValDef or DefDef in a Scala class. */
private def transformScalaValOrDefDef(tree: ValOrDefDef)(using Context): Tree = {
tree match {
// Catch ValDefs in enumerations with simple calls to Value
case vd: ValDef
if (enclosingOwner is OwnerKind.Enum) && jsdefn.scalaEnumeration.isValueMethodNoName(vd.rhs.symbol) =>
val enumDefn = jsdefn.scalaEnumeration
// Extract the Int argument if it is present
val optIntArg = vd.rhs match {
case _:Select | _:Ident => None
case Apply(_, intArg :: Nil) => Some(intArg)
}
val defaultName = vd.name.getterName.encode.toString
/* Construct the following tree
*
* if (nextName != null && nextName.hasNext)
* nextName.next()
* else
*
*/
val thisClass = vd.symbol.owner.asClass
val nextNameTree = This(thisClass).select(enumDefn.Enumeration_nextName)
val nullCompTree = nextNameTree.select(nme.NE).appliedTo(Literal(Constant(null)))
val hasNextTree = nextNameTree.select(enumDefn.hasNext)
val condTree = nullCompTree.select(nme.ZAND).appliedTo(hasNextTree)
val nameTree = If(condTree, nextNameTree.select(enumDefn.next).appliedToNone, Literal(Constant(defaultName)))
val newRhs = optIntArg match {
case None =>
This(thisClass).select(enumDefn.Enumeration_Value_StringArg).appliedTo(nameTree)
case Some(intArg) =>
This(thisClass).select(enumDefn.Enumeration_Value_IntStringArg).appliedTo(intArg, nameTree)
}
cpy.ValDef(vd)(rhs = newRhs)
case _ =>
super.transform(tree)
}
}
/** Verify a ValOrDefDef that is annotated with `@js.native`. */
private def transformJSNativeValOrDefDef(tree: ValOrDefDef)(using Context): ValOrDefDef = {
val sym = tree.symbol
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))
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(em"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)(using Context): Tree = {
val sym = tree.symbol
assert(!sym.isLocalToBlock, i"$tree at ${tree.span}")
sym.name match {
case nme.apply if !sym.hasAnnotation(jsdefn.JSNameAnnot) && (!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)
case nme.equals_ if sym.info.matches(defn.Any_equals.info) =>
report.error(
"error overriding method equals(that: Any): Boolean in a JS class;\n" +
" method equals(that: Any): Boolean is considered final in trait js.Any;\n" +
" if you want to define a method named \"equals\" in JavaScript, use a different name and add @JSName(\"equals\").",
sym)
case nme.hashCode_ if sym.info.matches(defn.Any_hashCode.info) =>
report.error(
"error overriding method hashCode(): Int in a JS class;\n" +
" method hashCode(): Int is considered final in trait js.Any;\n" +
" if you want to define a method named \"hashCode\" in JavaScript, use a different name and add @JSName(\"hashCode\").",
sym)
case _ =>
}
if (sym.isJSSetter)
checkSetterSignature(sym, tree, exported = false)
if (enclosingOwner is OwnerKind.JSNonNative) {
JSCallingConvention.of(sym) match {
case JSCallingConvention.Property(_) => // checked above
case JSCallingConvention.Method(_) => // no checks needed
case JSCallingConvention.Call if !sym.is(Deferred) =>
report.error("A non-native JS class cannot declare a concrete method named `apply` without `@JSName`", tree)
case JSCallingConvention.Call => // if sym.isDeferred
/* Allow an abstract `def apply` only if the owner is a plausible
* JS function SAM trait.
*/
val owner = sym.owner
val isPlausibleJSFunctionType = {
owner.is(Trait) &&
owner.asClass.superClass == jsdefn.JSFunctionClass &&
owner.typeRef.possibleSamMethods.map(_.symbol) == Seq(sym) &&
!sym.info.isInstanceOf[PolyType]
}
if (!isPlausibleJSFunctionType) {
report.error(
"A non-native JS type can only declare an abstract method named `apply` without `@JSName` " +
"if it is the SAM of a trait that extends js.Function",
tree)
}
case JSCallingConvention.BracketAccess =>
report.error("@JSBracketAccess is not allowed in non-native JS classes", tree)
case JSCallingConvention.BracketCall =>
report.error("@JSBracketCall is not allowed in non-native JS classes", tree)
case JSCallingConvention.UnaryOp(_) =>
report.error("A non-native JS class cannot declare a method named like a unary operation without `@JSName`", tree)
case JSCallingConvention.BinaryOp(_) =>
report.error("A non-native JS class cannot declare a method named like a binary operation without `@JSName`", tree)
}
} else {
def checkNoDefaultOrRepeated(subject: String) = {
if (sym.info.paramInfoss.flatten.exists(_.isRepeatedParam))
report.error(s"$subject may not have repeated parameters", tree)
if (sym.hasDefaultParams)
report.error(s"$subject may not have default parameters", tree)
}
JSCallingConvention.of(sym) match {
case JSCallingConvention.Property(_) => // checked above
case JSCallingConvention.Method(_) => // no checks needed
case JSCallingConvention.Call => // no checks needed
case JSCallingConvention.UnaryOp(_) => // no checks needed
case JSCallingConvention.BinaryOp(_) =>
checkNoDefaultOrRepeated("methods representing binary operations")
case JSCallingConvention.BracketAccess =>
val paramCount = sym.info.paramNamess.map(_.size).sum
if (paramCount != 1 && paramCount != 2)
report.error("@JSBracketAccess methods must have one or two parameters", tree)
else if (paramCount == 2 && !sym.info.finalResultType.isRef(defn.UnitClass))
report.error("@JSBracketAccess methods with two parameters must return Unit", tree)
checkNoDefaultOrRepeated("@JSBracketAccess methods")
case JSCallingConvention.BracketCall =>
// JS bracket calls must have at least one non-repeated parameter
sym.info.stripPoly match {
case mt: MethodType if mt.paramInfos.nonEmpty && !mt.paramInfos.head.isRepeatedParam =>
// ok
case _ =>
report.error("@JSBracketCall methods must have at least one non-repeated parameter", tree)
}
}
}
if (sym.hasAnnotation(defn.NativeAnnot)) {
// Native methods are not allowed
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)) {
if (sym.isClassConstructor) {
if (!sym.isAllOf(PrivateLocal)) {
report.error(
"Native JS classes may not have private constructors. " +
"Use `private[this]` to declare an internal constructor.",
sym)
}
} else if (!sym.is(ParamAccessor)) {
report.error(
"Native JS classes may not have private members. " +
"Use a public member in a private facade instead.",
tree)
}
}
if (enclosingOwner is OwnerKind.JSNonNative) {
// Private methods cannot be overloaded
if (sym.is(Method) && isPrivateMaybeWithin(sym)) {
val alts = sym.owner.info.memberBasedOnFlags(sym.name, required = Method)
if (alts.isOverloaded) {
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.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.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(ParamAccessor)) {
// #12621
report.error("A non-native JS trait cannot have constructor parameters", 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(jsdefn.JSOptionalAnnot)
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 rhs is `js.undefined`
tree.rhs match {
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 _ =>
report.error(
"Members of non-native JS traits must either be abstract, or their right-hand-side must be `js.undefined`.",
tree)
}
}
}
}
} 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")
}
}
super.transform(tree)
}
/** Removes annotations from exported definitions (e.g. `export foo.bar`):
* - `js.native`
* - `js.annotation.*`
*/
private def stripJSAnnotsOnExported(sym: Symbol)(using Context): Unit =
if !sym.is(Exported) then
return // only remove annotations from exported definitions
val JSNativeAnnot = jsdefn.JSNativeAnnot
val JSAnnotPackage = jsdefn.JSAnnotPackage
extension (sym: Symbol) def isJSAnnot =
(sym eq JSNativeAnnot) || (sym.owner eq JSAnnotPackage)
val newAnnots = sym.annotations.filterConserve(!_.symbol.isJSAnnot)
if newAnnots ne sym.annotations then
sym.annotations = newAnnots
end stripJSAnnotsOnExported
private def checkRHSCallsJSNative(tree: ValOrDefDef, longKindStr: String)(using Context): Unit = {
if tree.symbol.is(Exported) then
return // we already report an error that exports are not allowed here, this prevents extra errors.
// Check that the rhs is exactly `= js.native`
tree.rhs match {
case sel: Select if sel.symbol == jsdefn.JSPackage_native =>
// ok
case rhs: Ident if rhs.symbol == jsdefn.JSPackage_native =>
// ok
case _ =>
val pos = if (tree.rhs != EmptyTree) tree.rhs.srcPos else tree.srcPos
report.error(s"$longKindStr may only call js.native.", pos)
}
// Check that the result type was explicitly specified
// (This is stronger than Scala 2, which only warns, and only if it was inferred as Nothing.)
if (tree.tpt.isInstanceOf[InferredTypeTree])
report.error(em"The type of ${tree.name} must be explicitly specified because it is JS native.", tree)
}
private def checkJSNativeSpecificAnnotsOnNonJSNative(memberDef: MemberDef)(using Context): Unit = {
for (annot <- memberDef.symbol.annotations) {
annot.symbol match {
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)(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))
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
checkJSNameArgument(sym, annot)
// Check that there is at most one @JSName annotation.
for (duplicate <- allJSNameAnnots.tail)
report.error("Duplicate @JSName annotation.", duplicate.tree)
}
}
/** 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: 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. It must be a stable reference.
val sym = argTree.symbol
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)
}
}
}
/** Constant-folds arguments to `@JSExportTopLevel` and `@JSExportStatic`.
*
* Unlike scalac, dotc does not constant-fold expressions in annotations.
* Our back-end needs to have access to the arguments to those two
* annotations as literal strings, so we specifically constant-fold them
* here.
*/
private def constFoldJSExportTopLevelAndStaticAnnotations(sym: Symbol)(using Context): Unit = {
val annots = sym.annotations
val newAnnots = annots.mapConserve { annot =>
if (annot.symbol == jsdefn.JSExportTopLevelAnnot || annot.symbol == jsdefn.JSExportStaticAnnot) {
annot.tree match {
case app @ Apply(fun, args) =>
val newArgs = args.mapConserve { arg =>
arg match {
case _: Literal =>
arg
case _ =>
arg.tpe.widenTermRefExpr.normalized match {
case ConstantType(c) => Literal(c).withSpan(arg.span)
case _ => arg // PrepJSExports will emit an error for those cases
}
}
}
if (newArgs eq args)
annot
else
Annotation(cpy.Apply(app)(fun, newArgs))
case _ =>
annot
}
} else {
annot
}
}
if (newAnnots ne annots)
sym.annotations = newAnnots
}
/** Mark the symbol as exposed if it is a non-private term member of a
* non-native JS class.
*
* @param sym
* The symbol, which must be the module symbol for a module, not its
* module class symbol.
*/
private def markExposedIfRequired(sym: Symbol)(using Context): Unit = {
val shouldBeExposed: Boolean = {
// it is a term member
sym.isTerm &&
// it is a member of a non-native JS class
(enclosingOwner is OwnerKind.JSNonNative) && !sym.isLocalToBlock &&
// it is not synthetic
!sym.isOneOf(Synthetic) &&
// it is not private
!isPrivateMaybeWithin(sym) &&
// it is not a constructor
!sym.isConstructor &&
// it is not a default getter
!sym.name.is(DefaultGetterName)
}
if (shouldBeExposed)
sym.addAnnotation(jsdefn.ExposedJSMemberAnnot)
}
}
}
object PrepJSInterop {
val name: String = "prepjsinterop"
val description: String = "additional checks and transformations for Scala.js"
private final class OwnerKind private (private val baseKinds: Int) extends AnyVal {
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 NonEnumScalaClass = new OwnerKind(0x01)
/** A Scala object. */
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 (not a trait). */
val JSNonTraitClass = new OwnerKind(0x10)
/** A non-native JS trait. */
val JSTrait = new OwnerKind(0x20)
/** A non-native JS object. */
val JSMod = new OwnerKind(0x40)
/** A Scala class/trait that extends Enumeration. */
val EnumClass = new OwnerKind(0x80)
/** A Scala object that extends Enumeration. */
val EnumMod = new OwnerKind(0x100)
/** The Enumeration class itself. */
val EnumImpl = new OwnerKind(0x200)
// 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 ScalaType = 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 = JSNonTraitClass | JSTrait | 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 | JSNonTraitClass | JSTrait
}
/** Tests if the symbol extend `js.Any`.
*
* This is different from `sym.isJSType` because it returns `false` for the
* pseudo-union type.
*/
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: SrcPos, exported: Boolean)(using Context): Unit = {
val typeStr = if (exported) "Exported" else "JS"
val tpe = sym.info
// 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 (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 _ =>
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)(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: 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.argumentConstantString(0).isDefined
if (!argIsValid)
report.error("The argument to @JSGlobal must be a literal string.", annot.arguments.head)
}
}
/** Checks that arguments to an `@JSImport` annotation are literals.
*
* The second argument can also be the singleton `JSImport.Namespace`
* object.
*
* Reports an error on the annotation if it is not the case.
*/
private def checkJSImportLiteral(annot: Annotation)(using Context): Unit = {
val args = annot.arguments
val argCount = args.size
assert(argCount >= 1 && argCount <= 3,
i"@JSImport annotation $annot does not have between 1 and 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 = argCount < 2 || 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 = argCount < 3 || annot.argumentConstantString(2).isDefined
if (!thirdArgIsValid)
report.error("The third argument to @JSImport, when present, must be a literal string.", args(2))
}
private def checkAndGetJSNativeLoadingSpecAnnotOf(pos: SrcPos, sym: Symbol)(
using Context): Option[Annotation] = {
// Must not have @JSName
for (annot <- sym.getAnnotation(jsdefn.JSNameAnnot))
report.error("@JSName can only be used on members of JS types.", annot.tree)
// Must have exactly one JS native load spec annotation
val annots = sym.annotations.filter(annot => isJSNativeLoadingSpecAnnot(annot.symbol))
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 =>
report.error(badAnnotCountMsg, pos)
None
case result :: Nil =>
Some(result)
case _ =>
// Annotations are stored in reverse order, which we re-reverse now
val result :: duplicates = annots.reverse: @unchecked
for (annot <- duplicates)
report.error(badAnnotCountMsg, annot.tree)
Some(result)
}
}
/* Note that we consider @JSGlobalScope as a JS native loading spec because
* it's convenient for the purposes of PrepJSInterop. Actually @JSGlobalScope
* objects do not receive a JS loading spec in their IR.
*/
private def isJSNativeLoadingSpecAnnot(sym: Symbol)(using Context): Boolean = {
sym == jsdefn.JSGlobalAnnot
|| sym == jsdefn.JSImportAnnot
|| sym == jsdefn.JSGlobalScopeAnnot
}
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)) {
report.error(
em"@${annotation.symbol.fullName} is for compiler internal use only. Do not use it yourself.",
annotation.tree)
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy