diff options
Diffstat (limited to 'examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/PrepJSInterop.scala')
-rw-r--r-- | examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/PrepJSInterop.scala | 621 |
1 files changed, 0 insertions, 621 deletions
diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/PrepJSInterop.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/PrepJSInterop.scala deleted file mode 100644 index 437576a..0000000 --- a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/PrepJSInterop.scala +++ /dev/null @@ -1,621 +0,0 @@ -/* Scala.js compiler - * Copyright 2013 LAMP/EPFL - * @author Tobias Schlatter - */ - -package scala.scalajs.compiler - -import scala.tools.nsc -import nsc._ - -import scala.collection.immutable.ListMap -import scala.collection.mutable - -/** Prepares classes extending js.Any for JavaScript interop - * - * 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. - * - * @author Tobias Schlatter - */ -abstract class PrepJSInterop extends plugins.PluginComponent - with PrepJSExports - with transform.Transform - with Compat210Component { - val jsAddons: JSGlobalAddons { - val global: PrepJSInterop.this.global.type - } - - val scalaJSOpts: ScalaJSOptions - - import global._ - import jsAddons._ - import definitions._ - import rootMirror._ - import jsDefinitions._ - - val phaseName = "jsinterop" - - override def newPhase(p: nsc.Phase) = new JSInteropPhase(p) - class JSInteropPhase(prev: nsc.Phase) extends Phase(prev) { - override def name = phaseName - override def description = "Prepare ASTs for JavaScript interop" - override def run(): Unit = { - jsPrimitives.initPrepJSPrimitives() - super.run() - } - } - - override protected def newTransformer(unit: CompilationUnit) = - new JSInteropTransformer(unit) - - private object jsnme { - val hasNext = newTermName("hasNext") - val next = newTermName("next") - val nextName = newTermName("nextName") - val x = newTermName("x") - val Value = newTermName("Value") - val Val = newTermName("Val") - } - - private object jstpnme { - val scala_ = newTypeName("scala") // not defined in 2.10's tpnme - } - - class JSInteropTransformer(unit: CompilationUnit) extends Transformer { - - // Force evaluation of JSDynamicLiteral: Strangely, we are unable to find - // nested objects in the JSCode phase (probably after flatten). - // Therefore we force the symbol of js.Dynamic.literal here in order to - // have access to it in JSCode. - JSDynamicLiteral - - var inJSAnyMod = false - var inJSAnyCls = false - var inScalaCls = false - /** are we inside a subclass of scala.Enumeration */ - var inScalaEnum = false - /** are we inside the implementation of scala.Enumeration? */ - var inEnumImpl = false - - def jsAnyClassOnly = !inJSAnyCls && allowJSAny - def allowImplDef = !inJSAnyCls && !inJSAnyMod - def allowJSAny = !inScalaCls - def inJSAny = inJSAnyMod || inJSAnyCls - - /** DefDefs in class templates that export methods to JavaScript */ - val exporters = mutable.Map.empty[Symbol, mutable.ListBuffer[Tree]] - - override def transform(tree: Tree): Tree = postTransform { tree match { - // Catch special case of ClassDef in ModuleDef - case cldef: ClassDef if jsAnyClassOnly && isJSAny(cldef) => - transformJSAny(cldef) - - // Catch forbidden implDefs - case idef: ImplDef if !allowImplDef => - reporter.error(idef.pos, "Traits, classes and objects extending " + - "js.Any may not have inner traits, classes or objects") - super.transform(tree) - - // Handle js.Anys - case idef: ImplDef if isJSAny(idef) => - transformJSAny(idef) - - // Catch the definition of scala.Enumeration itself - case cldef: ClassDef if cldef.symbol == ScalaEnumClass => - enterEnumImpl { super.transform(cldef) } - - // Catch Scala Enumerations to transform calls to scala.Enumeration.Value - case cldef: ClassDef if isScalaEnum(cldef) => - enterScalaCls { - enterScalaEnum { - super.transform(cldef) - } - } - case idef: ImplDef if isScalaEnum(idef) => - enterScalaEnum { super.transform(idef) } - - // Catch (Scala) ClassDefs to forbid js.Anys - case cldef: ClassDef => - enterScalaCls { super.transform(cldef) } - - // Catch ValorDefDef in js.Any - case vddef: ValOrDefDef if inJSAny => - transformValOrDefDefInRawJSType(vddef) - - // Catch ValDefs in enumerations with simple calls to Value - case ValDef(mods, name, tpt, ScalaEnumValue.NoName(optPar)) if inScalaEnum => - val nrhs = ScalaEnumValName(tree.symbol.owner, tree.symbol, optPar) - treeCopy.ValDef(tree, mods, name, transform(tpt), nrhs) - - // Catch Select on Enumeration.Value we couldn't transform but need to - // we ignore the implementation of scala.Enumeration itself - case ScalaEnumValue.NoName(_) if !inEnumImpl => - 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 !inEnumImpl => - 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 !inEnumImpl => - 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 !inEnumImpl => - 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) - - // 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] - // - // If we encounter such a tree, depending on the plugin options, we fail - // here or silently fix those calls. - case TypeApply( - classOfTree @ Select(Select(This(jstpnme.scala_), nme.Predef), nme.classOf), - List(tpeArg)) => - 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 - } - - // Exporter generation - case ddef: DefDef => - // Generate exporters for this ddef if required - exporters.getOrElseUpdate(ddef.symbol.owner, - mutable.ListBuffer.empty) ++= genExportMember(ddef) - - super.transform(tree) - - // Module export sanity check (export generated in JSCode phase) - case modDef: ModuleDef => - val sym = modDef.symbol - - def condErr(msg: String) = { - for (exp <- jsInterop.exportsOf(sym)) { - reporter.error(exp.pos, msg) - } - } - - if (!hasLegalExportVisibility(sym)) - condErr("You may only export public and protected objects") - else if (sym.isLocalToBlock) - condErr("You may not export a local object") - else if (!sym.owner.hasPackageFlag) - condErr("You may not export a nested object") - - super.transform(modDef) - - // Fix for issue with calls to js.Dynamic.x() - // Rewrite (obj: js.Dynamic).x(...) to obj.applyDynamic("x")(...) - case Select(Select(trg, jsnme.x), nme.apply) if isJSDynamic(trg) => - val newTree = atPos(tree.pos) { - Apply( - Select(super.transform(trg), newTermName("applyDynamic")), - List(Literal(Constant("x"))) - ) - } - typer.typed(newTree, Mode.FUNmode, tree.tpe) - - - // Fix for issue with calls to js.Dynamic.x() - // Rewrite (obj: js.Dynamic).x to obj.selectDynamic("x") - case Select(trg, jsnme.x) if isJSDynamic(trg) => - val newTree = atPos(tree.pos) { - Apply( - Select(super.transform(trg), newTermName("selectDynamic")), - List(Literal(Constant("x"))) - ) - } - typer.typed(newTree, Mode.FUNmode, tree.tpe) - - case _ => super.transform(tree) - } } - - private def postTransform(tree: Tree) = tree match { - case Template(parents, self, body) => - val clsSym = tree.symbol.owner - val exports = exporters.get(clsSym).toIterable.flatten - // Add exports to the template - treeCopy.Template(tree, parents, self, body ++ exports) - - case memDef: MemberDef => - val sym = memDef.symbol - if (sym.isLocalToBlock && !sym.owner.isCaseApplyOrUnapply) { - // We exclude case class apply (and unapply) to work around SI-8826 - for (exp <- jsInterop.exportsOf(sym)) { - val msg = { - val base = "You may not export a local definition" - if (sym.owner.isPrimaryConstructor) - base + ". To export a (case) class field, use the " + - "meta-annotation scala.annotation.meta.field like this: " + - "@(JSExport @field)." - else - base - } - reporter.error(exp.pos, msg) - } - } - memDef - - case _ => tree - } - - /** - * Performs checks and rewrites specific to classes / objects extending - * js.Any - */ - private def transformJSAny(implDef: ImplDef) = { - val sym = implDef.symbol - - lazy val badParent = sym.info.parents.find(t => !(t <:< JSAnyClass.tpe)) - val inScalaJSJSPackage = - sym.enclosingPackage == ScalaJSJSPackage || - sym.enclosingPackage == ScalaJSJSPrimPackage - - implDef match { - // Check that we do not have a case modifier - case _ if implDef.mods.hasFlag(Flag.CASE) => - reporter.error(implDef.pos, "Classes and objects extending " + - "js.Any may not have a case modifier") - - // Check that we do not extends a trait that does not extends js.Any - case _ if !inScalaJSJSPackage && !badParent.isEmpty && - !isJSLambda(sym) => - val badName = badParent.get.typeSymbol.fullName - reporter.error(implDef.pos, s"${sym.nameString} extends ${badName} " + - "which does not extend js.Any.") - - // Check that we are not an anonymous class - case cldef: ClassDef - if cldef.symbol.isAnonymousClass && !isJSLambda(sym) => - reporter.error(implDef.pos, "Anonymous classes may not " + - "extend js.Any") - - // Check if we may have a js.Any here - case cldef: ClassDef if !allowJSAny && !jsAnyClassOnly && - !isJSLambda(sym) => - reporter.error(implDef.pos, "Classes extending js.Any may not be " + - "defined inside a class or trait") - - case _: ModuleDef if !allowJSAny => - reporter.error(implDef.pos, "Objects extending js.Any may not be " + - "defined inside a class or trait") - - case _ if sym.isLocalToBlock && !isJSLambda(sym) => - reporter.error(implDef.pos, "Local classes and objects may not " + - "extend js.Any") - - // Check that this is not a class extending js.GlobalScope - case _: ClassDef if isJSGlobalScope(implDef) && - implDef.symbol != JSGlobalScopeClass => - reporter.error(implDef.pos, "Only objects may extend js.GlobalScope") - - case _ => - // We cannot use sym directly, since the symbol - // of a module is not its type's symbol but the value it declares - val tSym = sym.tpe.typeSymbol - - tSym.setAnnotations(rawJSAnnot :: sym.annotations) - - } - - if (implDef.isInstanceOf[ModuleDef]) - enterJSAnyMod { super.transform(implDef) } - else - enterJSAnyCls { super.transform(implDef) } - } - - /** Verify a ValOrDefDef inside a js.Any */ - private def transformValOrDefDefInRawJSType(tree: ValOrDefDef) = { - val sym = tree.symbol - - val exports = jsInterop.exportsOf(sym) - - if (exports.nonEmpty) { - val memType = if (sym.isConstructor) "constructor" else "method" - reporter.error(exports.head.pos, - s"You may not export a $memType of a subclass of js.Any") - } - - if (isNonJSScalaSetter(sym)) { - // Forbid setters with non-unit return type - reporter.error(tree.pos, "Setters that do not return Unit are " + - "not allowed in types extending js.Any") - } - - if (sym.hasAnnotation(NativeAttr)) { - // Native methods are not allowed - reporter.error(tree.pos, "Methods in a js.Any may not be @native") - } - - for { - annot <- sym.getAnnotation(JSNameAnnotation) - if annot.stringArg(0).isEmpty - } { - reporter.error(annot.pos, - "The argument to JSName must be a literal string") - } - - if (sym.isPrimaryConstructor || sym.isValueParameter || - sym.isParamWithDefault || sym.isAccessor && !sym.isDeferred || - sym.isParamAccessor || sym.isSynthetic || - AllJSFunctionClasses.contains(sym.owner)) { - /* Ignore (i.e. allow) primary ctor, parameters, default parameter - * getters, accessors, param accessors, synthetic methods (to avoid - * double errors with case classes, e.g. generated copy method) and - * js.Functions and js.ThisFunctions (they need abstract methods for SAM - * treatment. - */ - } else if (jsPrimitives.isJavaScriptPrimitive(sym)) { - // Force rhs of a primitive to be `sys.error("stub")` except for the - // js.native primitive which displays an elaborate error message - if (sym != JSPackage_native) { - tree.rhs match { - case Apply(trg, Literal(Constant("stub")) :: Nil) - if trg.symbol == definitions.Sys_error => - case _ => - reporter.error(tree.pos, - "The body of a primitive must be `sys.error(\"stub\")`.") - } - } - } 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 { - // Check that the tree's body is either empty or calls js.native - tree.rhs match { - case sel: Select if sel.symbol == JSPackage_native => - case _ => - val pos = if (tree.rhs != EmptyTree) tree.rhs.pos else tree.pos - reporter.warning(pos, "Members of traits, classes and objects " + - "extending js.Any may only contain members that call js.native. " + - "This will be enforced in 1.0.") - } - - if (sym.tpe.resultType.typeSymbol == NothingClass && - tree.tpt.asInstanceOf[TypeTree].original == null) { - // Warn if resultType is Nothing and not ascribed - 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.") - } - } - - super.transform(tree) - } - - private def enterJSAnyCls[T](body: =>T) = { - val old = inJSAnyCls - inJSAnyCls = true - val res = body - inJSAnyCls = old - res - } - - private def enterJSAnyMod[T](body: =>T) = { - val old = inJSAnyMod - inJSAnyMod = true - val res = body - inJSAnyMod = old - res - } - - private def enterScalaCls[T](body: =>T) = { - val old = inScalaCls - inScalaCls = true - val res = body - inScalaCls = old - res - } - - private def enterScalaEnum[T](body: =>T) = { - val old = inScalaEnum - inScalaEnum = true - val res = body - inScalaEnum = old - res - } - - private def enterEnumImpl[T](body: =>T) = { - val old = inEnumImpl - inEnumImpl = true - val res = body - inEnumImpl = old - res - } - - } - - def isJSAny(sym: Symbol): Boolean = - sym.tpe.typeSymbol isSubClass JSAnyClass - - private def isJSAny(implDef: ImplDef): Boolean = isJSAny(implDef.symbol) - - private def isJSGlobalScope(implDef: ImplDef) = - implDef.symbol.tpe.typeSymbol isSubClass JSGlobalScopeClass - - private def isJSLambda(sym: Symbol) = sym.isAnonymousClass && - AllJSFunctionClasses.exists(sym.tpe.typeSymbol isSubClass _) - - private def isScalaEnum(implDef: ImplDef) = - implDef.symbol.tpe.typeSymbol isSubClass ScalaEnumClass - - private def isJSDynamic(tree: Tree) = tree.tpe.typeSymbol == JSDynamicClass - - /** - * is this symbol a setter that has a non-unit return type - * - * these setters don't make sense in JS (in JS, assignment returns - * the assigned value) and are therefore not allowed in facade types - */ - private def isNonJSScalaSetter(sym: Symbol) = nme.isSetterName(sym.name) && { - sym.tpe.paramss match { - case List(List(arg)) => - !isScalaRepeatedParamType(arg.tpe) && - sym.tpe.resultType.typeSymbol != UnitClass - case _ => false - } - } - - trait ScalaEnumFctExtractors { - protected val methSym: Symbol - - protected def resolve(ptpes: Symbol*) = { - val res = methSym suchThat { - _.tpe.params.map(_.tpe.typeSymbol) == ptpes.toList - } - assert(res != NoSymbol) - res - } - - protected val noArg = resolve() - protected val nameArg = resolve(StringClass) - protected val intArg = resolve(IntClass) - protected 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) = 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 - } - } - - object NullName { - def unapply(tree: Tree) = tree match { - case Apply(meth, List(Literal(Constant(null)))) => - meth.symbol == nameArg - case Apply(meth, List(_, Literal(Constant(null)))) => - meth.symbol == fullMeth - case _ => false - } - } - - } - - private object ScalaEnumValue extends { - protected val methSym = getMemberMethod(ScalaEnumClass, jsnme.Value) - } with ScalaEnumFctExtractors - - private object ScalaEnumVal extends { - protected val methSym = { - val valSym = getMemberClass(ScalaEnumClass, jsnme.Val) - valSym.tpe.member(nme.CONSTRUCTOR) - } - } with ScalaEnumFctExtractors - - /** - * 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 rawJSAnnot = - Annotation(RawJSTypeAnnot.tpe, List.empty, ListMap.empty) - - private lazy val ScalaEnumClass = getRequiredClass("scala.Enumeration") - - /** checks if the primary constructor of the ClassDef `cldef` does not - * take any arguments - */ - private def primCtorNoArg(cldef: ClassDef) = - getPrimCtor(cldef.symbol.tpe).map(_.paramss == List(List())).getOrElse(true) - - /** return the MethodSymbol of the primary constructor of the given type - * if it exists - */ - private def getPrimCtor(tpe: Type) = - tpe.declaration(nme.CONSTRUCTOR).alternatives.collectFirst { - case ctor: MethodSymbol if ctor.isPrimaryConstructor => ctor - } - -} |