From 9fec009dc5ed901d1a1e0906d7e5d3ec02cfc599 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sat, 20 May 2017 17:37:55 +0200 Subject: [PATCH] Fix #2948: Remove js.use(x).as[T] --- .../main/scala/scala/scalajs/js/Using.scala | 19 - .../main/scala/scala/scalajs/js/package.scala | 103 --- .../scala/scalajs/macroimpls/Compat210.scala | 53 -- .../macroimpls/JSMemberSelection.scala | 26 - .../scala/scalajs/macroimpls/JSMembers.scala | 97 --- .../scalajs/macroimpls/UseAsMacros.scala | 355 ---------- project/Build.scala | 2 - .../scalajs/testsuite/library/UseAsTest.scala | 670 ------------------ 8 files changed, 1325 deletions(-) delete mode 100644 library/src/main/scala/scala/scalajs/js/Using.scala delete mode 100644 library/src/main/scala/scala/scalajs/macroimpls/Compat210.scala delete mode 100644 library/src/main/scala/scala/scalajs/macroimpls/JSMemberSelection.scala delete mode 100644 library/src/main/scala/scala/scalajs/macroimpls/JSMembers.scala delete mode 100644 library/src/main/scala/scala/scalajs/macroimpls/UseAsMacros.scala delete mode 100644 test-suite/js/src/test/scala/org/scalajs/testsuite/library/UseAsTest.scala diff --git a/library/src/main/scala/scala/scalajs/js/Using.scala b/library/src/main/scala/scala/scalajs/js/Using.scala deleted file mode 100644 index cc787ce067..0000000000 --- a/library/src/main/scala/scala/scalajs/js/Using.scala +++ /dev/null @@ -1,19 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ __ ____ Scala.js API ** -** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2015, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | |__/ /____/ ** -** |/____/ ** -\* */ - - -package scala.scalajs.js - -import scala.language.experimental.macros - -import scala.scalajs.macroimpls.UseAsMacros - -/** Helper for syntactic sugar of [[js.use]]. Only use in `js.use(x).as[T]` */ -final class Using[A] private[js] (val x: A) extends AnyVal { - def as[B <: Any]: B = macro UseAsMacros.as_impl[A, B] -} diff --git a/library/src/main/scala/scala/scalajs/js/package.scala b/library/src/main/scala/scala/scalajs/js/package.scala index 5ad71af18a..a3a5f47192 100644 --- a/library/src/main/scala/scala/scalajs/js/package.scala +++ b/library/src/main/scala/scala/scalajs/js/package.scala @@ -130,107 +130,4 @@ package object js { "because you tried to run Scala.js binaries on the JVM. Make sure you " + "are using the JVM version of the libraries.") - /** Allows to cast a value to a facade trait in a type-safe way. - * - * Use as follows: - * {{{ - * js.use(x).as[MyFacade] - * }}} - * - * Note that the method calls are only syntactic sugar. There is no overhead - * at runtime for such an operation. Using `use(x).as[T]` is strictly - * equivalent to `x.asInstanceOf[T]` if the compile time check does not fail. - * - * This method supports both Scala classes with exports and facade types - * which are structurally equivalent. - * - * == Examples == - * Given the following facade type: - * {{{ - * trait MyFacade extends js.Object { - * def foo(x: Int): String = js.native - * val bar: Int = js.native - * } - * }}} - * - * We show a couple of examples: - * {{{ - * class MyClass1 { - * @JSExport - * def foo(x: Int): String = x.toString - * - * @JSExport - * val bar: Int = 1 - * } - * - * val x1 = new MyClass1 - * js.use(x1).as[MyFacade] // OK - * }}} - * - * Note that JS conventions apply: The `val bar` can be implemented with a - * `def`. - * - * {{{ - * class MyClass2 { - * @JSExport - * def foo(x: Int): String = x.toString - * - * @JSExport - * def bar: Int = 1 // def instead of val - * } - * - * val x2 = new MyClass2 - * js.use(x2).as[MyFacade] // OK - * }}} - * - * Missing methods or methods with wrong types will cause a compile-time - * failure. - * - * {{{ - * class MyClass3 { - * @JSExport - * def foo(x: String): String = x.toString // wrong type signature - * - * // bar is missing - * } - * - * val x3 = new MyClass3 - * js.use(x2).as[MyFacade] // Fails: bar is missing and foo has wrong type - * }}} - * - * Methods must be exported, otherwise they are not taken into consideration. - * - * {{{ - * class MyClass4 { - * def foo(x: Int): String = x.toString - * - * @JSExport - * def bar: Int = 1 // def instead of val - * } - * - * val x4 = new MyClass4 - * js.use(x4).as[MyFacade] // Fails, foo is missing - * }}} - * - * Other facade types can also be used - * - * {{{ - * trait MyOtherFacade extends js.Object { - * def foo(x: Any): String = js.native - * val bar: Int = js.native - * def otherMethod(): Unit = js.native - * } - * - * val x5: MyOtherFacade = // ... - * js.use(x5).as[MyFacade] // OK - * }}} - * - * == Restrictions == - * - Facade types may only be traits and not have any class ancestors - * - Polymorphic methods are currently not supported - * - Facade types defining an apply method cannot used (this is a JavaScript - * restriction). - */ - def use[A](x: A): Using[A] = new Using[A](x) - } diff --git a/library/src/main/scala/scala/scalajs/macroimpls/Compat210.scala b/library/src/main/scala/scala/scalajs/macroimpls/Compat210.scala deleted file mode 100644 index 2d2e52457b..0000000000 --- a/library/src/main/scala/scala/scalajs/macroimpls/Compat210.scala +++ /dev/null @@ -1,53 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ __ ____ Scala.js API ** -** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2015, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | |__/ /____/ ** -** |/____/ ** -\* */ - - -package scala.scalajs.macroimpls - -@deprecated("Not actually deprecated, makes warnings go away", "") -private[macroimpls] object Compat210 { - object blackbox { // scalastyle:ignore - type Context = scala.reflect.macros.Context - } -} - -import Compat210._ - -@deprecated("Not actually deprecated, makes warnings go away", "") -private[macroimpls] trait Compat210Component { - // Import macros only here, otherwise we collide with the above - import scala.reflect.macros._ - import blackbox.Context - - val c: Context - - import c.universe._ - - implicit final class ContextCompat(self: c.type) { - def typecheck(tree: Tree): Tree = c.typeCheck(tree) - } - - implicit final class TypeCompat(self: Type) { - def dealias: Type = self.normalize - def decls: MemberScope = self.declarations - } - - implicit final class SymbolCompat(self: Symbol) { - def isConstructor: Boolean = self.isMethod && self.asMethod.isConstructor - def info: Type = self.typeSignature - } - - implicit final class AnnotationCompat(self: Annotation) { - def tree: Tree = { - // Taken from AnnotationInfos.scala (in 2.11.x) - // Assume that we only have scalaArgs - val ctorSelection = Select(New(TypeTree(self.tpe)), nme.CONSTRUCTOR) - c.typecheck(Apply(ctorSelection, self.scalaArgs)) - } - } -} diff --git a/library/src/main/scala/scala/scalajs/macroimpls/JSMemberSelection.scala b/library/src/main/scala/scala/scalajs/macroimpls/JSMemberSelection.scala deleted file mode 100644 index 1243021bc9..0000000000 --- a/library/src/main/scala/scala/scalajs/macroimpls/JSMemberSelection.scala +++ /dev/null @@ -1,26 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ __ ____ Scala.js API ** -** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2015, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | |__/ /____/ ** -** |/____/ ** -\* */ - - -package scala.scalajs.macroimpls - -/** Represents the way a member of a JS object is selected */ -private[macroimpls] sealed abstract class JSMemberSelection - -/** A member with statically known name */ -private[macroimpls] final case class JSNamedMember(name: String) - extends JSMemberSelection - -/** Calling the object */ -private[macroimpls] case object JSMemberCall extends JSMemberSelection - -/** Accessing via brackets (array-like access) */ -private[macroimpls] case object JSMemberBracketAccess extends JSMemberSelection - -/** Accessing and calling a member via brackets (with dynamic name) */ -private[macroimpls] case object JSMemberBracketCall extends JSMemberSelection diff --git a/library/src/main/scala/scala/scalajs/macroimpls/JSMembers.scala b/library/src/main/scala/scala/scalajs/macroimpls/JSMembers.scala deleted file mode 100644 index 61094160f1..0000000000 --- a/library/src/main/scala/scala/scalajs/macroimpls/JSMembers.scala +++ /dev/null @@ -1,97 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ __ ____ Scala.js API ** -** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2015, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | |__/ /____/ ** -** |/____/ ** -\* */ - - -package scala.scalajs.macroimpls - -import Compat210._ - -/** JSMember is an ADT more or less equivalent to Scala's MethodType - * that allows to distinguish setters and getters from methods. - * - * It also allows to check method conformance based on Scala.js' JavaScript - * calling conventions. - * - * Currently does not support polymorphic method types. - * - * @author Tobias Schlatter - */ -private[macroimpls] trait JSMembers { - // Import macros only here, otherwise we collide with Compat210._ - import scala.reflect.macros._ - import blackbox.Context - - val c: Context - - import c.universe._ - - sealed trait JSMember { - /** Whether this JSMember conforms to that JSMember */ - def conformsTo(that: JSMember): Boolean - - /** Create a display string of this member with a given name */ - def displayStr(name: String): String - } - - case class JSMethodParam(info: Type, isDefault: Boolean) { - def conformsTo(that: JSMethodParam): Boolean = - (!that.isDefault || this.isDefault) && that.info <:< this.info - - override def toString(): String = - if (isDefault) s"$info = ???" - else info.toString - } - - case class JSMethod(params: List[JSMethodParam], - resultType: Type) extends JSMember { - - def conformsTo(that: JSMember): Boolean = that match { - case JSMethod(thatParams, thatResultType) => - val (used, unused) = params.splitAt(thatParams.size) - - params.size >= thatParams.size && - resultType <:< thatResultType && - unused.forall(_.isDefault) && - (used zip thatParams).forall { case (x, y) => x.conformsTo(y) } - - case _ => - false - } - - def displayStr(name: String): String = - s"method $name(${params.mkString(", ")}): $resultType" - } - - case class JSGetter(tpe: Type) extends JSMember { - def conformsTo(that: JSMember): Boolean = that match { - case JSGetter(thatTpe) => tpe <:< thatTpe - case _ => false - } - - def displayStr(name: String): String = s"getter $name: $tpe" - } - - case class JSSetter(tpe: Type) extends JSMember { - def conformsTo(that: JSMember): Boolean = that match { - case JSSetter(thatTpe) => thatTpe <:< tpe - case _ => false - } - - def displayStr(name: String): String = s"setter $name: $tpe" - } - - /** Place holder for unsupported members. - * - * In source type position, these members can be ignored. - * In target type position, these members will trigger errors. - */ - case class UnsupportedMember(sym: Symbol, tpe: Type) extends JSMember { - def conformsTo(that: JSMember): Boolean = false - def displayStr(name: String): String = s"unsupported $name ($sym)" - } -} diff --git a/library/src/main/scala/scala/scalajs/macroimpls/UseAsMacros.scala b/library/src/main/scala/scala/scalajs/macroimpls/UseAsMacros.scala deleted file mode 100644 index 495e8c5089..0000000000 --- a/library/src/main/scala/scala/scalajs/macroimpls/UseAsMacros.scala +++ /dev/null @@ -1,355 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ __ ____ Scala.js API ** -** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2015, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | |__/ /____/ ** -** |/____/ ** -\* */ - - -package scala.scalajs.macroimpls - -import scala.annotation.tailrec -import scala.scalajs.js - -import Compat210._ - -/** Macros for `js.use(x).as[T]`. - * - * This implements a structural typechecker conforming to the JavaScript calling - * convention in Scala.js' export and facade system. - * - * @author Tobias Schlatter - */ -@deprecated("Not actually deprecated, makes warnings go away", "") -private[scalajs] object UseAsMacros { - // Import macros only here, otherwise we collide with Compat210._ - import scala.reflect.macros._ - import blackbox.Context - - def as_impl[A: c.WeakTypeTag, B <: js.Any: c.WeakTypeTag]( - c: Context { type PrefixType = js.Using[_] }): c.Expr[B] = { - (new Macros[c.type](c)).as[A, B] - } - - private class Macros[C <: Context { type PrefixType = js.Using[_] }](val c: C) - extends JSMembers with Compat210Component { - - import c.universe._ - - private val JSNameAnnotation = typeOf[js.annotation.JSName].typeSymbol - private val JSBracketAccessAnnotation = typeOf[js.annotation.JSBracketAccess].typeSymbol - private val JSBracketCallAnnotation = typeOf[js.annotation.JSBracketCall].typeSymbol - private val JSExportAnnotation = typeOf[js.annotation.JSExport].typeSymbol - private val JSExportAllAnnotation = typeOf[js.annotation.JSExportAll].typeSymbol - - /** Base classes that are allowed in a target type. - * These are also the classes whose methods do not need to be provided. - */ - private val JSObjectAncestors = typeOf[js.Object].baseClasses.toSet - - type JSMemberSet = Map[JSMemberSelection, List[JSMember]] - - def as[A: WeakTypeTag, B <: js.Any: WeakTypeTag]: Expr[B] = { - val trgTpe = verifyTargetType(weakTypeOf[B]) - val srcTpe = weakTypeOf[A] - - val srcSym = srcTpe.typeSymbol - - // Nothing and Null have everything - if (srcSym != definitions.NothingClass && - srcSym != definitions.NullClass) { - check(srcTpe, trgTpe) - } - - reify { c.prefix.splice.x.asInstanceOf[B] } - } - - /** Perform the actual structural typechecking. - * - * Checks if [[srcTpe]] conforms to [[trgTpe]]. Reports errors otherwise. - */ - private def check(srcTpe: Type, trgTpe: Type): Unit = { - val requiredMembers = rawJSMembers(trgTpe) - val isRawJSType = srcTpe <:< typeOf[js.Any] - - val definedMembers = - if (isRawJSType) rawJSMembers(srcTpe) - else exportedMembers(srcTpe) - - for { - (jsMemberSelection, jsMembers) <- requiredMembers - jsMember <- jsMembers - } { - // Fail for required unsupported members - jsMember match { - case UnsupportedMember(sym, tpe) => - val msg = tpe match { - case _: PolyType => - "Polymorphic methods are currently " + - s"not supported. Offending method: ${sym.fullName}" - - case _: ExistentialType => - "Methods with existential types are " + - s"not supported. Offending method: ${sym.fullName}. This is " + - "likely caused by an abstract type in the method signature" - - case _ => - sys.error("Unknown type in unsupported member. " + - "Report this as a bug.\n" + - s"Offending method: ${sym.fullName}\n" + - s"Offending type: ${showRaw(tpe)}") - } - - c.error(c.enclosingPosition, msg) - - case _ => - } - - val hasConformingMember = { - val overloads = definedMembers.getOrElse(jsMemberSelection, Nil) - overloads.exists(_.conformsTo(jsMember)) - } - - if (!hasConformingMember) { - // Error: A member is missing. Construct an informative error message - - def noSuchMember(memberName: String) = { - val membershipStr = if (isRawJSType) "have" else "export" - val memberStr = jsMember.displayStr(memberName) - s"$srcTpe does not $membershipStr a $memberStr." - } - - val errMsg = jsMemberSelection match { - case JSNamedMember(name) => - noSuchMember(name) - - case JSMemberCall if !isRawJSType => - s"$trgTpe defines an apply method. This cannot be implemented " + - "by any Scala exported type, since it would need to chain " + - "Function's prototype." - - case JSMemberBracketAccess if !isRawJSType => - s"$trgTpe defines a @JSMemberBracketAccess method. Existence " + - "of such a method cannot be statically checked for any " + - "Scala exported type." - - case JSMemberBracketCall if !isRawJSType => - s"$trgTpe defines a @JSMemberBracketCall method. Existence of " + - "such a method cannot be statically checked for any Scala " + - "exported type." - - case JSMemberCall => - noSuchMember("") + " (type is not callable)" - - case JSMemberBracketAccess => - noSuchMember("") + " (type doesn't support " + - "member selection via []). Add @JSBracketAccess to use a " + - "method for member selection." - - case JSMemberBracketCall => - noSuchMember("") + " (type doesn't support " + - "dynamically calling methods). Add @JSBracketCall to use a " + - "method for dynamic calls." - } - - c.error(c.enclosingPosition, errMsg) - } - } - } - - /** Members that a facade type defines */ - private def rawJSMembers(tpe: Type): JSMemberSet = { - - def isAPIMember(member: Symbol) = { - !JSObjectAncestors(member.owner) && - !member.isConstructor && - member.isMethod && - !member.asTerm.isParamWithDefault - } - - val tups = for { - member <- tpe.members - if isAPIMember(member) - } yield { - val memberMethod = member.asMethod - (jsMemberSelection(memberMethod), jsMemberFor(tpe, memberMethod)) - } - - // Group by member selection - for { - (selection, members) <- tups.groupBy(_._1) - } yield { - (selection, members.map(_._2).toList) - } - } - - /** Returns the way a member of a raw JS type is selected in JS */ - private def jsMemberSelection(sym: MethodSymbol): JSMemberSelection = { - val annots = memberAnnotations(sym) - - def hasAnnot(annot: Symbol) = annots.exists(annotIs(_, annot)) - - if (hasAnnot(JSBracketAccessAnnotation)) { - JSMemberBracketAccess - } else if (hasAnnot(JSBracketCallAnnotation)) { - JSMemberBracketCall - } else { - val optAnnot = annots.find(annotIs(_, JSNameAnnotation)) - val optName = optAnnot.flatMap(annotStringArg) - - optName.fold { - val name = defaultName(sym) - if (name == "apply") JSMemberCall - else JSNamedMember(name) - } { name => JSNamedMember(name) } - } - } - - /** Returns all exported members of a type */ - private def exportedMembers(tpe: Type): JSMemberSet = { - val exports = tpe.baseClasses.flatMap(exportedDecls(tpe, _)) - - // Group exports by name - for { - (name, elems) <- exports.groupBy(_._1) - } yield { - (JSNamedMember(name), elems.map(_._2)) - } - } - - /** All exported declarations of a class. - * (both @JSExportAll and @JSExport) - */ - private def exportedDecls(origTpe: Type, sym: Symbol) = { - require(sym.isClass) - - val exportAll = sym.annotations.exists(annotIs(_, JSExportAllAnnotation)) - - for { - decl <- sym.info.decls if decl.isMethod && !decl.isConstructor - name <- exportNames(decl.asMethod, exportAll) - } yield { - (name, jsMemberFor(origTpe, decl.asMethod)) - } - } - - /** Get the JS member for a method in [[origTpe]] */ - private def jsMemberFor(origTpe: Type, sym: MethodSymbol): JSMember = { - sym.info.asSeenFrom(origTpe, sym.owner) match { - case MethodType(List(param), resultType) - if resultType.typeSymbol == definitions.UnitClass && - sym.name.decodedName.toString.endsWith("_=") => - JSSetter(param.info) - - case NullaryMethodType(returnType) => - JSGetter(returnType) - - case info: MethodType => - @tailrec - def flatParams(tpe: Type, acc: List[JSMethodParam]): JSMethod = { - tpe match { - case MethodType(params, returnTpe) => - val ps = params map { p => - JSMethodParam(p.info, p.asTerm.isParamWithDefault) - } - flatParams(returnTpe, ps reverse_::: acc) - case tpe => - JSMethod(acc.reverse, tpe) - } - } - - flatParams(info, Nil) - - case tpe => - UnsupportedMember(sym, tpe) - } - } - - /** Names a method is exported to */ - private def exportNames(sym: MethodSymbol, exportAll: Boolean) = { - lazy val default = defaultName(sym) - - val explicitNames = for { - annot <- memberAnnotations(sym) - if annotIs(annot, JSExportAnnotation) - } yield { - annotStringArg(annot).getOrElse(default) - } - - if (exportAll && sym.isPublic) default :: explicitNames - else explicitNames - } - - /** Default JavaScript name of a method */ - private def defaultName(sym: MethodSymbol): String = - sym.name.decodedName.toString.stripSuffix("_=") - - /** Verifies that the given type is a class type or a refined type, - * has no class ancestors lower than js.Object and does only - * refine type members. - * @returns dealiased tpe - */ - private def verifyTargetType(tpe: Type): Type = { - tpe.dealias match { - case tpe @ TypeRef(_, sym0, _) if sym0.isClass => - val sym = sym0.asClass - - if (!sym.isTrait) - c.abort(c.enclosingPosition, "Only traits can be used with as") - - def allowedParent(sym: Symbol) = - sym.asClass.isTrait || JSObjectAncestors(sym) - - for (base <- sym.baseClasses if !allowedParent(base)) { - c.abort(c.enclosingPosition, s"Supertype ${base.fullName} of $sym " + - "is a class. Cannot be used with as.") - } - - tpe - - case tpe @ RefinedType(parents, decls) => - parents.foreach(verifyTargetType) - - for (decl <- decls if !decl.isType) { - c.abort(c.enclosingPosition, s"Refinement ${decl.name} " + - "is not a type. Only types may be refined with as.") - } - - tpe - - case tpe => - c.abort(c.enclosingPosition, "Only class types can be used with as") - } - } - - /** Annotations of a member symbol. - * Looks on accessed field if this is an accessor - */ - private def memberAnnotations(sym: MethodSymbol): List[Annotation] = { - val trgSym = - if (sym.isAccessor && sym.accessed != NoSymbol) sym.accessed - else sym - - // Force typeSignature to calculate annotations - trgSym.typeSignature - - trgSym.annotations - } - - /** Retrieve first argument to the annotation as literal string */ - private def annotStringArg(annot: Annotation): Option[String] = { - val args = annot.tree.children.tail - args match { - case List(Literal(Constant(s: String))) => Some(s) - case _ => None - } - } - - /** Checks if [[annot]] is of class [[clsSym]] */ - private def annotIs(annot: Annotation, clsSym: Symbol) = - annot.tree.tpe.typeSymbol == clsSym - - } - -} diff --git a/project/Build.scala b/project/Build.scala index 91c8b7d062..98e790043d 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1016,8 +1016,6 @@ object Build { exportJars := !isGeneratingEclipse, previousArtifactSetting, mimaBinaryIssueFilters ++= BinaryIncompatibilities.Library, - libraryDependencies += - "org.scala-lang" % "scala-reflect" % scalaVersion.value % "provided", scalaJSExternalCompileSettings, inConfig(Compile)(Seq( diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/library/UseAsTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/library/UseAsTest.scala deleted file mode 100644 index d213c8b1d3..0000000000 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/library/UseAsTest.scala +++ /dev/null @@ -1,670 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ __ ____ Scala.js Test Suite ** -** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2015, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** -** /____/\___/_/ |_/____/_/ | |__/ /____/ ** -** |/____/ ** -\* */ -package org.scalajs.testsuite.library - -import scala.scalajs.js -import scala.scalajs.js.annotation._ - -import org.scalajs.testsuite.Typechecking._ - -import org.junit.Assert._ -import org.junit.Test - -class UseAsScalaTypesTest { - import UseAsTest._ - - // js.use(x).as[T] - Scala Types - success cases - - @Test def should_support_basic_typechecking(): Unit = { - class A { - @JSExport - def m(a: Int, b: String): js.Object = ??? - } - - js.use(new A).as[JSBasic] - } - - @Test def should_support_covariance_in_return_types(): Unit = { - class A { - @JSExport - def m(a: Int, b: String): js.Array[Int] = ??? - } - - js.use(new A).as[JSBasic] - } - - @Test def should_support_contravariance_in_argument(): Unit = { - class A { - @JSExport - def m(a: Int, b: Any): js.Object = ??? - } - - js.use(new A).as[JSBasic] - } - - @Test def should_support_explicit_names_in_JSExports(): Unit = { - class A { - @JSExport("m") - def f(a: Int, b: String): js.Object = ??? - } - - js.use(new A).as[JSBasic] - } - - @Test def should_support_JSName(): Unit = { - class A { - @JSExport - def m(a: Int, b: String): js.Object = ??? - } - - class B { - @JSExport("m") - def bar(a: Int, b: String): js.Object = ??? - } - - js.use(new A).as[JSBasicJSName] - js.use(new B).as[JSBasicJSName] - } - - @Test def should_support_JSExportAll(): Unit = { - @JSExportAll - class A { - def m(a: Int, b: String): js.Object = ??? - } - - class B extends A - - js.use(new A).as[JSBasic] - js.use(new B).as[JSBasic] - } - - @Test def should_support_inherited_exports(): Unit = { - abstract class A { - @JSExport - def m(a: Int, b: String): js.Object - } - - class B extends A { - def m(a: Int, b: String): js.Object = ??? - } - - js.use(new B).as[JSBasic] - } - - @Test def should_support_JSExportAll_on_superclass(): Unit = { - @JSExportAll - abstract class A { - def m(a: Int, b: String): js.Object - } - - class B extends A { - def m(a: Int, b: String): js.Object = ??? - } - - js.use(new B).as[JSBasic] - } - - @Test def should_work_with_JSExportAll_with_an_apply_method(): Unit = { - @JSExportAll - class A { - @JSExport("apply") - @JSExport("bar") - def apply(x: Int): Int = x * 2 - } - - val a = js.use(new A).as[JSNamedApply] - - assertEquals(4, a(2)) - assertEquals(4, a.bar(2)) - } - - @Test def should_resolve_generics_in_JSRaw_types(): Unit = { - class A { - @JSExport - def arr: js.Array[Int] = ??? - } - - js.use(new A).as[JSGeneric[Int]] - js.use(new A).as[JSGenericInt] - } - - @Test def should_resolve_type_members_in_JSRaw_types(): Unit = { - class A { - @JSExport - def foo(x: Int): Int = ??? - } - - js.use(new A).as[JSTypeMember { type R = Int }] - } - - @Test def should_resolve_exports_with_class_level_type_parameter(): Unit = { - class A[T] { - @JSExport - def arr: js.Array[T] = ??? - } - - class B extends A[Int] - - js.use(new A[Int]).as[JSGeneric[Int]] - js.use(new B).as[JSGeneric[Int]] - } - - @Test def should_resolve_exports_with_type_member(): Unit = { - class A { - type T - - @JSExport - def arr: js.Array[T] = ??? - } - - class B extends A { - type T = Int - } - - js.use(new B).as[JSGeneric[Int]] - } - - @Test def should_resolve_overloading(): Unit = { - @JSExportAll - class A { - def m(a: Int, b: String): js.Object = ??? - def m(b: String): Int = ??? - - @JSExport("m") - def strangeName(a: Int): js.Object = ??? - } - - js.use(new A).as[JSOverload] - } - - @Test def should_support_vals_getters(): Unit = { - @JSExportAll - class A { - val a: Int = 1 - def b: String = ??? - // Test covariance as well - def c: js.Array[Int] = ??? - } - - js.use(new A).as[JSGetters] - } - - @Test def should_support_setters(): Unit = { - class A { - @JSExport("a") - def fooA_=(x: Int): Unit = ??? - - @JSExport - def b_=(x: String): Unit = ??? - - @JSExport("c_=") - def barC_=(x: js.Object): Unit = ??? - } - - js.use(new A).as[JSSetters] - } - - @Test def should_support_vars(): Unit = { - class A { - @JSExport - def a: Int = ??? - @JSExport - def a_=(x: Int): Unit = ??? - - @JSExport("b") - var fooB: String = _ - - @JSExport - var c: js.Object = _ - } - - js.use(new A).as[JSVars] - } - - @Test def should_support_abstract_members_class(): Unit = { - abstract class AbstractFieldsClass { - @JSExport - def a: Int - @JSExport - def a_=(x: Int): Unit - - @JSExport("b") - var fooB: String - - @JSExport - var c: js.Object - } - - js.use(null: AbstractFieldsClass).as[JSVars] - } - - @Test def should_support_abstract_members_trait(): Unit = { - trait AbstractFieldsTrait { - @JSExport - def a: Int - @JSExport - def a_=(x: Int): Unit - - @JSExport("b") - var fooB: String - - @JSExport - var c: js.Object - } - - js.use(null: AbstractFieldsTrait).as[JSVars] - } - - @Test def should_support_basic_default_arguments(): Unit = { - @JSExportAll - class A { - def sum4(a: Int, b: Int = 1, c: Int = 2, d: Int = 3): Int = a + b + c + d - def sum2(a: Int, b: Int = 1): Int = a + b - } - - js.use(new A).as[JSDefaultArgs] - } - - @Test def should_allow_additional_default_arguments_at_the_end_of_the_params(): Unit = { - class A { - @JSExport - def m(a: Int, b: String, c: Int = ???, d: String = ???): js.Object = ??? - } - - js.use(new A).as[JSBasic] - } - - @Test def should_support_repeated_parameter_lists(): Unit = { - @JSExportAll - class A { - def rep(a: Int, b: String*): Unit = ??? - def rep(a: Int*): Unit = ??? - } - - js.use(new A).as[JSRepeated] - } - - @Test def should_flatten_multi_parameter_lists_in_raw_JS_type(): Unit = { - @JSExportAll - class A { - def multi(a: Int, b: String): Int = ??? - } - - js.use(new A).as[JSMulti] - } - - @Test def should_flatten_multi_parameter_lists_in_exported_method(): Unit = { - @JSExportAll - class B { - def m(a: Int)(b: String): js.Object = ??? - } - - js.use(new B).as[JSBasic] - } - - @Test def should_support_anonymous_types(): Unit = { - js.use(new { @JSExport def m(a: Int, b: String): js.Object = ??? }).as[JSBasic] - } - - @Test def should_allow_Nothing(): Unit = { - if (false) { - js.use(???).as[JSBasic] - } - } - - @Test def should_allow_Null(): Unit = { - js.use(null).as[JSBasic] - } - - // js.use(x).as[T] - Raw JS Types - success cases - - @Test def should_support_basic_typechecking_raw_js(): Unit = { - js.use(null: JSBasic).as[JSBasicJSName] - js.use(null: JSBasicJSName).as[JSBasic] - } - - @Test def should_support_generics(): Unit = { - js.use(null: JSGeneric[Int]).as[JSGenericInt] - js.use(null: JSGenericInt).as[JSGeneric[Int]] - } - - @Test def should_support_JS_calls(): Unit = { - js.use(null: js.Function0[String]).as[JSApplyString] - } - - @Test def should_support_atJSBracketAccess(): Unit = { - js.use(new js.Array[Int](0)).as[JSBracketAccessInt] - } - - @Test def should_support_atJSBracketCall(): Unit = { - js.use(null: JSBracketCallInt1).as[JSBracketCallInt2] - } - - // js.use(x).as[T] - general failure cases - - @Test def fails_with_polymorphic_methods(): Unit = { - typeErrorWithMsg( - "js.use(new Object).as[JSPolyMethod]", - "Polymorphic methods are currently not supported. Offending " + - "method: org.scalajs.testsuite.library.UseAsTest.JSPolyMethod.poly") - } - - @Test def fails_with_non_type_refinements(): Unit = { - typeErrorWithMsg( - "js.use(???).as[JSBasic { def foo: Int }]", - "Refinement foo is not a type. Only types may be refined with as.") - } - - @Test def fails_with_non_trait(): Unit = { - typeErrorWithMsg( - "js.use(???).as[js.Date]", - "Only traits can be used with as") - } - - @Test def fails_with_class_parents(): Unit = { - typeErrorWithMsg( - "js.use(???).as[JSNonClassParent]", - "Supertype scala.scalajs.js.Date of trait JSNonClassParent is a " + - "class. Cannot be used with as.") - } - - @Test def fails_gracefully_with_existential_types_issue_1841(): Unit = { - typeErrorWithMsg( - "js.use(null: JSTypeMember).as[JSTypeMember]", - "Methods with existential types are not supported. Offending " + - "method: org.scalajs.testsuite.library.UseAsTest.JSTypeMember.foo. " + - "This is likely caused by an abstract type in the method signature") - } - - // js.use(x).as[T] - Scala Types - failure cases - - @Test def fails_with_apply_in_a_raw_JS_type(): Unit = { - typeErrorWithMsg( - "js.use(new Object).as[JSWithApply]", - "org.scalajs.testsuite.library.UseAsTest.JSWithApply defines an apply " + - "method. This cannot be implemented by any Scala exported type, " + - "since it would need to chain Function's prototype.") - } - - @Test def fails_with_atJSBracketAccess_in_a_raw_JS_type(): Unit = { - typeErrorWithMsg( - "js.use(new Object).as[JSWithBracketAccess]", - "org.scalajs.testsuite.library.UseAsTest.JSWithBracketAccess " + - "defines a @JSMemberBracketAccess method. Existence of such a " + - "method cannot be statically checked for any Scala exported type.") - } - - @Test def fails_with_atJSBracketCall_in_a_raw_JS_type(): Unit = { - typeErrorWithMsg( - "js.use(new Object).as[JSWithBracketCall]", - "org.scalajs.testsuite.library.UseAsTest.JSWithBracketCall defines " + - "a @JSMemberBracketCall method. Existence of such a method cannot " + - "be statically checked for any Scala exported type.") - } - - @Test def fails_with_a_missing_method_failure(): Unit = { - class A { - @JSExport - def e(a: Int, b: String): js.Object = ??? - } - - typeErrorWithMsg( - "js.use(new A).as[JSBasic]", - "A does not export a method m(Int, String): scala.scalajs.js.Object.") - } - - @Test def fails_with_a_missing_overload_failure(): Unit = { - class A { - @JSExport - def m(a: Int, b: String): js.Object = ??? - } - - typeErrorWithMsg( - "js.use(new A).as[JSOverload]", - "A does not export a method m(Int): scala.scalajs.js.Object.") - } - - @Test def fails_with_wrong_argument_types(): Unit = { - class A { - @JSExport - def m(a: String, b: Int): js.Object = ??? - } - - typeErrorWithMsg( - "js.use(new A).as[JSBasic]", - "A does not export a method m(Int, String): scala.scalajs.js.Object.") - } - - @Test def fails_with_wrong_return_types(): Unit = { - class A { - @JSExport - def m(a: Int, b: String): Any = ??? - } - - typeErrorWithMsg( - "js.use(new A).as[JSBasic]", - "A does not export a method m(Int, String): scala.scalajs.js.Object.") - } - - @Test def fails_with_a_missing_default_argument(): Unit = { - @JSExportAll - class A { - def sum4(a: Int, b: Int = 1, c: Int = 2, d: Int = 3): Int = a + b + c + d - def sum2(a: Int, b: Int): Int = a + b // should have default - } - - typeErrorWithMsg( - "js.use(new A).as[JSDefaultArgs]", - "A does not export a method sum2(Int, Int = ???): Int.") - } - - @Test def fails_with_a_mismatching_repeated_argument(): Unit = { - @JSExportAll - class A { - def rep(a: Int, b: String): Unit = ??? // should be repeated - def rep(a: Int*): Unit = ??? - } - - typeErrorWithMsg( - "js.use(new A).as[JSRepeated]", - "A does not export a method rep(Int, String*): Unit.") - - class B { - @JSExport - def m(a: Int, b: String*): js.Object = ??? // should not be repeated - } - - typeErrorWithMsg( - "js.use(new B).as[JSBasic]", - "B does not export a method m(Int, String): scala.scalajs.js.Object.") - } - - // js.use(x).as[T] - Raw JS Types - failure cases - - @Test def fails_with_a_missing_apply(): Unit = { - typeErrorWithMsg( - "js.use(new js.Object).as[JSWithApply]", - "scala.scalajs.js.Object does not have a method " + - "(String): Int. (type is not callable)") - } - - @Test def fails_with_a_missing_atJSBracketAccess(): Unit = { - typeErrorWithMsg( - "js.use(new js.Object).as[JSWithBracketAccess]", - "scala.scalajs.js.Object does not have a method " + - "(String): Int. (type doesn't support member " + - "selection via []). Add @JSBracketAccess to use a method for " + - "member selection.") - } - - @Test def fails_with_a_missing_atJSBracketCall(): Unit = { - typeErrorWithMsg( - "js.use(new js.Object).as[JSWithBracketCall]", - "scala.scalajs.js.Object does not have a method " + - "(String, String): Int. (type doesn't support " + - "dynamically calling methods). Add @JSBracketCall to use a method " + - "for dynamic calls.") - } - - @Test def fails_with_a_missing_method(): Unit = { - typeErrorWithMsg( - "js.use(new js.Object).as[JSBasic]", - "scala.scalajs.js.Object does not have a method " + - "m(Int, String): scala.scalajs.js.Object.") - } - - @Test def fails_with_a_missing_overload(): Unit = { - typeErrorWithMsg( - "js.use(null: JSBasic).as[JSOverload]", - "org.scalajs.testsuite.library.UseAsTest.JSBasic does not have a " + - "method m(Int): scala.scalajs.js.Object.") - } - - @Test def fails_with_wrongly_typed_generic(): Unit = { - typeErrorWithMsg( - "js.use(null: JSGeneric[Int]).as[JSGeneric[String]]", - "org.scalajs.testsuite.library.UseAsTest.JSGeneric[Int] does not " + - "have a getter arr: scala.scalajs.js.Array[String].") - } - -} - -object UseAsTest { - - @js.native - trait JSBasic extends js.Object { - def m(a: Int, b: String): js.Object = js.native - } - - @js.native - trait JSBasicJSName extends js.Object { - @JSName("m") - def foo(a: Int, b: String): js.Object = js.native - } - - @js.native - trait JSNamedApply extends js.Object { - @JSName("apply") - def apply(x: Int): Int = js.native - - def bar(x: Int): Int = js.native - } - - @js.native - trait JSGeneric[T] extends js.Object { - def arr: js.Array[T] = js.native - } - - @js.native - trait JSGenericInt extends JSGeneric[Int] - - @js.native - trait JSTypeMember extends js.Object { - type R - def foo(x: R): Int = js.native - } - - @js.native - trait JSOverload extends JSBasic { - def m(b: String): Int = js.native - def m(a: Int): js.Object = js.native - } - - @js.native - trait JSGetters extends js.Object { - def a: Int = js.native - val b: String = js.native - def c: js.Object = js.native - } - - @js.native - trait JSSetters extends js.Object { - def a_=(x: Int): Unit = js.native - - @JSName("b") - def fooJS_=(x: String): Unit = js.native - - @JSName("c_=") - def barJS_=(x: js.Array[Int]): Unit = js.native - } - - @js.native - trait JSVars extends js.Object { - var a: Int = js.native - def b: String = js.native - def b_=(x: String): Unit = js.native - - @JSName("c") - var fooJS: js.Object = js.native - } - - @js.native - trait JSDefaultArgs extends js.Object { - def sum4(a: Int, b: Int = ???, c: Int = ???, d: Int = ???): Int = js.native - def sum2(a: Int, b: Int = ???): Int = js.native - } - - @js.native - trait JSRepeated extends js.Object { - def rep(a: Int, b: String*): Unit = js.native - def rep(a: Int*): Unit = js.native - } - - @js.native - trait JSMulti extends js.Object { - def multi(a: Int)(b: String): Int = js.native - } - - @js.native - trait JSPolyMethod extends js.Object { - def poly[T](a: T): js.Array[T] = js.native - } - - @js.native - trait JSWithApply extends js.Object { - def apply(a: String): Int = js.native - } - - @js.native - trait JSWithBracketAccess extends js.Object { - @JSBracketAccess - def foo(a: String): Int = js.native - } - - @js.native - trait JSWithBracketCall extends js.Object { - @JSBracketCall - def foo(name: String, b: String): Int = js.native - } - - @js.native - trait JSNonClassParent extends js.Date - - @js.native - trait JSApplyString extends js.Object { - def apply(): String = js.native - } - - @js.native - trait JSBracketAccessInt extends js.Object { - @JSBracketAccess - def apply(x: Int): Int = js.native - } - - @js.native - trait JSBracketCallInt1 extends js.Object { - @JSBracketCall - def foo(method: String): Int = js.native - } - - @js.native - trait JSBracketCallInt2 extends js.Object { - @JSBracketCall - def bar(method: String): Int = js.native - } -}