diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala index 0db9662d6eee..3007ea2b05cc 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala @@ -512,9 +512,16 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { case ClazzTag => val tp = toTypeKind(const.typeValue) - // classOf[Int] is transformed to Integer.TYPE by ClassOf - assert(!tp.isPrimitive, s"expected class type in classOf[T], found primitive type $tp") - mnode.visitLdcInsn(tp.toASMType) + if tp.isPrimitive then + val boxedClass = boxedClassOfPrimitive(tp.asPrimitiveBType) + mnode.visitFieldInsn( + asm.Opcodes.GETSTATIC, + boxedClass.internalName, + "TYPE", // field name + jlClassRef.descriptor + ) + else + mnode.visitLdcInsn(tp.toASMType) case EnumTag => val sym = const.symbolValue diff --git a/compiler/src/dotty/tools/backend/jvm/CoreBTypes.scala b/compiler/src/dotty/tools/backend/jvm/CoreBTypes.scala index fae4e5d82bb8..e15744b995c1 100644 --- a/compiler/src/dotty/tools/backend/jvm/CoreBTypes.scala +++ b/compiler/src/dotty/tools/backend/jvm/CoreBTypes.scala @@ -124,6 +124,7 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: DottyBackendInterface]](val bTyp lazy val jlStringBuilderRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.StringBuilder]) lazy val jlStringBufferRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.StringBuffer]) lazy val jlCharSequenceRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.CharSequence]) + lazy val jlClassRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Class[_]]) lazy val ThrowableReference : ClassBType = classBTypeFromSymbol(defn.ThrowableClass) lazy val jlCloneableReference : ClassBType = classBTypeFromSymbol(defn.JavaCloneableClass) // java/lang/Cloneable lazy val jlNPEReference : ClassBType = classBTypeFromSymbol(defn.NullPointerExceptionClass) // java/lang/NullPointerException @@ -248,6 +249,7 @@ final class CoreBTypesProxy[BTFS <: BTypesFromSymbols[_ <: DottyBackendInterface def jlStringBuilderRef : ClassBType = _coreBTypes.jlStringBuilderRef def jlStringBufferRef : ClassBType = _coreBTypes.jlStringBufferRef def jlCharSequenceRef : ClassBType = _coreBTypes.jlCharSequenceRef + def jlClassRef : ClassBType = _coreBTypes.jlClassRef def ThrowableReference : ClassBType = _coreBTypes.ThrowableReference def jlCloneableReference : ClassBType = _coreBTypes.jlCloneableReference def jlNPEReference : ClassBType = _coreBTypes.jlNPEReference diff --git a/compiler/src/dotty/tools/backend/jvm/scalaPrimitives.scala b/compiler/src/dotty/tools/backend/jvm/scalaPrimitives.scala index 018108834cf6..8b21afd8377a 100644 --- a/compiler/src/dotty/tools/backend/jvm/scalaPrimitives.scala +++ b/compiler/src/dotty/tools/backend/jvm/scalaPrimitives.scala @@ -397,6 +397,9 @@ class DottyPrimitives(ictx: Context) { primitives.toMap } + def isPrimitive(sym: Symbol): Boolean = + primitives.contains(sym) + def isPrimitive(fun: Tree): Boolean = given Context = ictx primitives.contains(fun.symbol) @@ -407,4 +410,3 @@ class DottyPrimitives(ictx: Context) { case _ => true }) } - diff --git a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala index 27ea62881e0f..843860b4fd0e 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala @@ -791,9 +791,9 @@ class JSCodeGen()(using genCtx: Context) { toIRType(param.info), mutable = false, rest = false) } - /*if (primitives.isPrimitive(sym)) { + if (primitives.isPrimitive(sym)) { None - } else*/ if (sym.is(Deferred)) { + } else if (sym.is(Deferred)) { Some(js.MethodDef(js.MemberFlags.empty, methodName, originalName, jsParams, toIRType(patchedResultType(sym)), None)( OptimizerHints.empty, None)) @@ -2815,9 +2815,181 @@ class JSCodeGen()(using genCtx: Context) { js.ForIn(objVarDef.ref, keyVarIdent, NoOriginalName, { js.JSFunctionApply(fVarDef.ref, List(keyVarRef)) })) + + case REFLECT_SELECTABLE_SELECTDYN => + // scala.reflect.Selectable.selectDynamic + genReflectiveCall(tree, isSelectDynamic = true) + case REFLECT_SELECTABLE_APPLYDYN => + // scala.reflect.Selectable.applyDynamic + genReflectiveCall(tree, isSelectDynamic = false) } } + /** Gen the SJSIR for a reflective call. + * + * Reflective calls are calls to a structural type field or method that + * involve a reflective Selectable. They look like the following in source + * code: + * {{{ + * import scala.reflect.Selectable.reflectiveSelectable + * + * type Structural = { + * val foo: Int + * def bar(x: Int, y: String): String + * } + * + * val structural: Structural = new { + * val foo: Int = 5 + * def bar(x: Int, y: String): String = x.toString + y + * } + * + * structural.foo + * structural.bar(6, "hello") + * }}} + * + * After expansion by the Scala 3 rules for structural member selections and + * calls, they look like + * + * {{{ + * reflectiveSelectable(structural).selectDynamic("foo") + * reflectiveSelectable(structural).applyDynamic("bar", + * ClassTag(classOf[Int]), ClassTag(classOf[String]) + * )( + * 6, "hello" + * ) + * }}} + * + * and eventually reach the back-end as + * + * {{{ + * reflectiveSelectable(structural).selectDynamic("foo") // same as above + * reflectiveSelectable(structural).applyDynamic("bar", + * wrapRefArray([ ClassTag(classOf[Int]), ClassTag(classOf[String]) : ClassTag ] + * )( + * genericWrapArray([ Int.box(6), "hello" : Object ]) + * ) + * }}} + * + * If we use the deprecated `import scala.language.reflectiveCalls`, the + * wrapper for the receiver `structural` are the following instead: + * + * {{{ + * reflectiveSelectableFromLangReflectiveCalls(structural)( + * using scala.languageFeature.reflectiveCalls) + * }}} + * + * (in which case we don't care about the contextual argument). + * + * In SJSIR, they must be encoded as follows: + * + * {{{ + * structural.foo;R() + * structural.bar;I;Ljava.lang.String;R( + * Int.box(6).asInstanceOf[int], + * "hello".asInstanceOf[java.lang.String] + * ) + * }}} + * + * This means that we must deconstruct the elaborated calls to recover: + * + * - the original receiver `structural` + * - the method name as a compile-time string `foo` or `bar` + * - the `tp: Type`s that have been wrapped in `ClassTag(classOf[tp])`, as a + * compile-time List[Type], from which we'll derive `jstpe.Type`s for the + * `asInstanceOf`s and `jstpe.TypeRef`s for the `MethodName.reflectiveProxy` + * - the actual arguments as a compile-time `List[Tree]` + * + * Virtually all of the code in `genReflectiveCall` deals with recovering + * those elements. Constructing the IR Tree is the easy part after that. + */ + private def genReflectiveCall(tree: Apply, isSelectDynamic: Boolean): js.Tree = { + implicit val pos = tree.span + val Apply(fun @ Select(receiver0, _), args) = tree + + /* Extract the real receiver, which is the first argument to one of the + * implicit conversions scala.reflect.Selectable.reflectiveSelectable or + * scala.Selectable.reflectiveSelectableFromLangReflectiveCalls. + */ + val receiver = receiver0 match { + case Apply(fun1, receiver :: _) + if fun1.symbol == jsdefn.ReflectSelectable_reflectiveSelectable || + fun1.symbol == jsdefn.Selectable_reflectiveSelectableFromLangReflectiveCalls => + genExpr(receiver) + + case _ => + report.error( + "The receiver of Selectable.selectDynamic or Selectable.applyDynamic " + + "must be a call to the (implicit) method scala.reflect.Selectable.reflectiveSelectable. " + + "Other uses are not supported in Scala.js.", + tree.sourcePos) + js.Undefined() + } + + // Extract the method name as a String + val methodNameStr = args.head match { + case Literal(Constants.Constant(name: String)) => + name + case _ => + report.error( + "The method name given to Selectable.selectDynamic or Selectable.applyDynamic " + + "must be a literal string. " + + "Other uses are not supported in Scala.js.", + args.head.sourcePos) + "erroneous" + } + + val (formalParamTypeRefs, actualArgs) = if (isSelectDynamic) { + (Nil, Nil) + } else { + // Extract the param type refs and actual args from the 2nd and 3rd argument to applyDynamic + args.tail match { + case WrapArray(classTagsArray: JavaSeqLiteral) :: WrapArray(actualArgsAnyArray: JavaSeqLiteral) :: Nil => + // Extract jstpe.Type's and jstpe.TypeRef's from the ClassTag.apply(_) trees + val formalParamTypesAndTypeRefs = classTagsArray.elems.map { + // ClassTag.apply(classOf[tp]) -> tp + case Apply(fun, Literal(const) :: Nil) + if fun.symbol == defn.ClassTagModule_apply && const.tag == Constants.ClazzTag => + toIRTypeAndTypeRef(const.typeValue) + // ClassTag.SpecialType -> erasure(SepecialType.typeRef) (e.g., ClassTag.Any -> Object) + case Apply(Select(classTagModule, name), Nil) + if classTagModule.symbol == defn.ClassTagModule && + defn.SpecialClassTagClasses.exists(_.name == name.toTypeName) => + toIRTypeAndTypeRef(TypeErasure.erasure( + defn.SpecialClassTagClasses.find(_.name == name.toTypeName).get.typeRef)) + // Anything else is invalid + case classTag => + report.error( + "The ClassTags passed to Selectable.applyDynamic must be " + + "literal ClassTag(classOf[T]) expressions " + + "(typically compiler-generated). " + + "Other uses are not supported in Scala.js.", + classTag.sourcePos) + (jstpe.AnyType, jstpe.ClassRef(jsNames.ObjectClass)) + } + + // Gen the actual args, downcasting them to the formal param types + val actualArgs = actualArgsAnyArray.elems.zip(formalParamTypesAndTypeRefs).map { + (actualArgAny, formalParamTypeAndTypeRef) => + val genActualArgAny = genExpr(actualArgAny) + js.AsInstanceOf(genActualArgAny, formalParamTypeAndTypeRef._1)(genActualArgAny.pos) + } + + (formalParamTypesAndTypeRefs.map(_._2), actualArgs) + + case _ => + report.error( + "Passing the varargs of Selectable.applyDynamic with `: _*` " + + "is not supported in Scala.js.", + tree.sourcePos) + (Nil, Nil) + } + } + + val methodName = MethodName.reflectiveProxy(methodNameStr, formalParamTypeRefs) + + js.Apply(js.ApplyFlags.empty, receiver, js.MethodIdent(methodName), actualArgs)(jstpe.AnyType) + } + /** Gen actual actual arguments to Scala method call. * Returns a list of the transformed arguments. * @@ -2992,8 +3164,9 @@ class JSCodeGen()(using genCtx: Context) { lazy val isWrapArray: Set[Symbol] = { val names0 = defn.ScalaValueClasses().map(sym => nme.wrapXArray(sym.name)) val names1 = names0 ++ Set(nme.wrapRefArray, nme.genericWrapArray) - val names2 = names1.map(defn.ScalaPredefModule.requiredMethod(_)) - names2.toSet + val symsInPredef = names1.map(defn.ScalaPredefModule.requiredMethod(_)) + val symsInScalaRunTime = names1.map(defn.ScalaRuntimeModule.requiredMethod(_)) + (symsInPredef ++ symsInScalaRunTime).toSet } def unapply(tree: Apply): Option[Tree] = tree match { diff --git a/compiler/src/dotty/tools/backend/sjs/JSDefinitions.scala b/compiler/src/dotty/tools/backend/sjs/JSDefinitions.scala index 21a5e67c45c6..6c15c7c72976 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSDefinitions.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSDefinitions.scala @@ -173,6 +173,23 @@ final class JSDefinitions()(using Context) { @threadUnsafe lazy val Reflect_registerInstantiatableClassR = ReflectModule.requiredMethodRef("registerInstantiatableClass") def Reflect_registerInstantiatableClass(using Context) = Reflect_registerInstantiatableClassR.symbol + @threadUnsafe lazy val ReflectSelectableType: TypeRef = requiredClassRef("scala.reflect.Selectable") + def ReflectSelectableClass(using Context) = ReflectSelectableType.symbol.asClass + @threadUnsafe lazy val ReflectSelectable_selectDynamicR = ReflectSelectableClass.requiredMethodRef("selectDynamic") + def ReflectSelectable_selectDynamic(using Context) = ReflectSelectable_selectDynamicR.symbol + @threadUnsafe lazy val ReflectSelectable_applyDynamicR = ReflectSelectableClass.requiredMethodRef("applyDynamic") + def ReflectSelectable_applyDynamic(using Context) = ReflectSelectable_applyDynamicR.symbol + + @threadUnsafe lazy val ReflectSelectableModuleRef = requiredModuleRef("scala.reflect.Selectable") + def ReflectSelectableModule(using Context) = ReflectSelectableModuleRef.symbol + @threadUnsafe lazy val ReflectSelectable_reflectiveSelectableR = ReflectSelectableModule.requiredMethodRef("reflectiveSelectable") + def ReflectSelectable_reflectiveSelectable(using Context) = ReflectSelectable_reflectiveSelectableR.symbol + + @threadUnsafe lazy val SelectableModuleRef = requiredModuleRef("scala.Selectable") + def SelectableModule(using Context) = SelectableModuleRef.symbol + @threadUnsafe lazy val Selectable_reflectiveSelectableFromLangReflectiveCallsR = SelectableModule.requiredMethodRef("reflectiveSelectableFromLangReflectiveCalls") + def Selectable_reflectiveSelectableFromLangReflectiveCalls(using Context) = Selectable_reflectiveSelectableFromLangReflectiveCallsR.symbol + private var allRefClassesCache: Set[Symbol] = _ def allRefClasses(using Context): Set[Symbol] = { if (allRefClassesCache == null) { diff --git a/compiler/src/dotty/tools/backend/sjs/JSEncoding.scala b/compiler/src/dotty/tools/backend/sjs/JSEncoding.scala index a097c4ca5a8b..ca6092983451 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSEncoding.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSEncoding.scala @@ -253,8 +253,15 @@ object JSEncoding { } } - def toIRType(tp: Type)(using Context): jstpe.Type = { + def toIRTypeAndTypeRef(tp: Type)(using Context): (jstpe.Type, jstpe.TypeRef) = { val typeRefInternal = toTypeRefInternal(tp) + (toIRTypeInternal(typeRefInternal), typeRefInternal._1) + } + + def toIRType(tp: Type)(using Context): jstpe.Type = + toIRTypeInternal(toTypeRefInternal(tp)) + + private def toIRTypeInternal(typeRefInternal: (jstpe.TypeRef, Symbol))(using Context): jstpe.Type = { typeRefInternal._1 match { case jstpe.PrimRef(irTpe) => irTpe diff --git a/compiler/src/dotty/tools/backend/sjs/JSPrimitives.scala b/compiler/src/dotty/tools/backend/sjs/JSPrimitives.scala index 56b3ca9da32e..5169628b5d55 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSPrimitives.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSPrimitives.scala @@ -40,7 +40,10 @@ object JSPrimitives { final val THROW = DEBUGGER + 1 - final val LastJSPrimitiveCode = THROW + final val REFLECT_SELECTABLE_SELECTDYN = THROW + 1 // scala.reflect.Selectable.selectDynamic + final val REFLECT_SELECTABLE_APPLYDYN = REFLECT_SELECTABLE_SELECTDYN + 1 // scala.reflect.Selectable.applyDynamic + + final val LastJSPrimitiveCode = REFLECT_SELECTABLE_APPLYDYN def isJSPrimitive(code: Int): Boolean = code >= FirstJSPrimitiveCode && code <= LastJSPrimitiveCode @@ -59,6 +62,9 @@ class JSPrimitives(ictx: Context) extends DottyPrimitives(ictx) { override def getPrimitive(app: Apply, tpe: Type)(using Context): Int = jsPrimitives.getOrElse(app.fun.symbol, super.getPrimitive(app, tpe)) + override def isPrimitive(sym: Symbol): Boolean = + jsPrimitives.contains(sym) || super.isPrimitive(sym) + override def isPrimitive(fun: Tree): Boolean = jsPrimitives.contains(fun.symbol(using ictx)) || super.isPrimitive(fun) @@ -109,6 +115,9 @@ class JSPrimitives(ictx: Context) extends DottyPrimitives(ictx) { addPrimitive(defn.throwMethod, THROW) + addPrimitive(jsdefn.ReflectSelectable_selectDynamic, REFLECT_SELECTABLE_SELECTDYN) + addPrimitive(jsdefn.ReflectSelectable_applyDynamic, REFLECT_SELECTABLE_APPLYDYN) + primitives.toMap } diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index b7748e975e28..e48d89f842ea 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -1155,20 +1155,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { /** A tree that corresponds to `Predef.classOf[$tp]` in source */ def clsOf(tp: Type)(using Context): Tree = - if ctx.erasedTypes then - def TYPE(module: TermSymbol) = ref(module).select(nme.TYPE_) - defn.scalaClassName(tp) match - case tpnme.Boolean => TYPE(defn.BoxedBooleanModule) - case tpnme.Byte => TYPE(defn.BoxedByteModule) - case tpnme.Short => TYPE(defn.BoxedShortModule) - case tpnme.Char => TYPE(defn.BoxedCharModule) - case tpnme.Int => TYPE(defn.BoxedIntModule) - case tpnme.Long => TYPE(defn.BoxedLongModule) - case tpnme.Float => TYPE(defn.BoxedFloatModule) - case tpnme.Double => TYPE(defn.BoxedDoubleModule) - case tpnme.Unit => TYPE(defn.BoxedUnitModule) - case _ => - Literal(Constant(TypeErasure.erasure(tp))) + if ctx.erasedTypes && !tp.isRef(defn.UnitClass) then + Literal(Constant(TypeErasure.erasure(tp))) else Literal(Constant(tp)) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 1f7b51bf78e7..b6e1c69af584 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1164,6 +1164,8 @@ class Definitions { @tu lazy val NotRuntimeClasses: Set[Symbol] = Set(AnyClass, AnyValClass, NullClass, NothingClass) + @tu lazy val SpecialClassTagClasses: Set[Symbol] = Set(UnitClass, AnyClass, AnyValClass) + /** Classes that are known not to have an initializer irrespective of * whether NoInits is set. Note: FunctionXXLClass is in this set * because if it is compiled by Scala2, it does not get a NoInit flag. diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index cf434da334e2..569c26b3aaaa 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -36,10 +36,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): val sym = tp.typeSymbol val classTag = ref(defn.ClassTagModule) val tag = - if sym == defn.UnitClass - || sym == defn.AnyClass - || sym == defn.AnyValClass - then + if defn.SpecialClassTagClasses.contains(sym) then classTag.select(sym.name.toTermName) else classTag.select(nme.apply).appliedToType(tp).appliedTo(clsOf(erasure(tp))) @@ -425,4 +422,4 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): EmptyTree recur(specialHandlers) -end Synthesizer \ No newline at end of file +end Synthesizer diff --git a/project/Build.scala b/project/Build.scala index 0580d71c121d..b45b7a0eaa6b 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1059,7 +1059,7 @@ object Build { ++ (dir / "shared/src/test/scala/org/scalajs/testsuite/niocharset" ** (("*.scala": FileFilter) -- "BaseCharsetTest.scala" -- "Latin1Test.scala" -- "USASCIITest.scala" -- "UTF16Test.scala" -- "UTF8Test.scala")).get ++ (dir / "shared/src/test/scala/org/scalajs/testsuite/scalalib" ** (("*.scala": FileFilter) -- "ArrayBuilderTest.scala" -- "ClassTagTest.scala" -- "EnumerationTest.scala" -- "SymbolTest.scala")).get ++ (dir / "shared/src/test/require-sam" ** "*.scala").get - ++ (dir / "shared/src/test/require-jdk8/org/scalajs/testsuite/compiler" ** (("*.scala": FileFilter) -- "DefaultMethodsTest.scala")).get + ++ (dir / "shared/src/test/require-jdk8/org/scalajs/testsuite/compiler" ** "*.scala").get ++ (dir / "shared/src/test/require-jdk8/org/scalajs/testsuite/javalib/lang" ** "*.scala").get ++ (dir / "shared/src/test/require-jdk8/org/scalajs/testsuite/javalib/util" ** (("*.scala": FileFilter) -- "CollectionsOnCopyOnWriteArrayListTestOnJDK8.scala")).get ++ (dir / "shared/src/test/require-jdk7/org/scalajs/testsuite/javalib/io" ** "*.scala").get diff --git a/tests/sjs-junit/test/org/scalajs/testsuite/compiler/ReflectiveCallTestScala3.scala b/tests/sjs-junit/test/org/scalajs/testsuite/compiler/ReflectiveCallTestScala3.scala new file mode 100644 index 000000000000..ddf46d4ce971 --- /dev/null +++ b/tests/sjs-junit/test/org/scalajs/testsuite/compiler/ReflectiveCallTestScala3.scala @@ -0,0 +1,175 @@ +package org.scalajs.testsuite.compiler + +/* Most of this file is a copy of `tests/run/structural.scala`, adapted to work + * under JUnit for Scala.js. + * There are more tests at the end of the file that are not in run/structural.scala. + */ + +import scala.reflect.Selectable.reflectiveSelectable + +import org.junit.Assert._ +import org.junit.Test + +object ReflectiveCallTestScala3 { + class C { type S = String; type I } + class D extends C { type I = Int } + + type Foo = { + def sel0: Int + def sel1: Int => Int + def fun0(x: Int): Int + + def fun1(x: Int)(y: Int): Int + def fun2(x: Int): Int => Int + def fun3(a1: Int, a2: Int, a3: Int) + (a4: Int, a5: Int, a6: Int) + (a7: Int, a8: Int, a9: Int): Int + + def fun4(implicit x: Int): Int + def fun5(x: Int)(implicit y: Int): Int + + def fun6(x: C, y: x.S): Int + def fun7(x: C, y: x.I): Int + def fun8(y: C): y.S + def fun9(y: C): y.I + } + + class Foo1 { + def sel0: Int = 1 + def sel1: Int => Int = x => x + def fun0(x: Int): Int = x + + def fun1(x: Int)(y: Int): Int = x + y + def fun2(x: Int): Int => Int = y => x * y + def fun3(a1: Int, a2: Int, a3: Int) + (a4: Int, a5: Int, a6: Int) + (a7: Int, a8: Int, a9: Int): Int = -1 + + def fun4(implicit x: Int): Int = x + def fun5(x: Int)(implicit y: Int): Int = x + y + + def fun6(x: C, y: x.S): Int = 1 + def fun7(x: C, y: x.I): Int = 2 + def fun8(y: C): y.S = "Hello" + def fun9(y: C): y.I = 1.asInstanceOf[y.I] + } + + class Foo2 extends scala.Selectable { + def sel0: Int = 1 + def sel1: Int => Int = x => x + def fun0(x: Int): Int = x + + def fun1(x: Int)(y: Int): Int = x + y + def fun2(x: Int): Int => Int = y => x * y + def fun3(a1: Int, a2: Int, a3: Int) + (a4: Int, a5: Int, a6: Int) + (a7: Int, a8: Int, a9: Int): Int = -1 + + def fun4(implicit x: Int): Int = x + def fun5(x: Int)(implicit y: Int): Int = x + y + + def fun6(x: C, y: x.S): Int = 1 + def fun7(x: C, y: x.I): Int = 2 + def fun8(y: C): y.S = "Hello" + def fun9(y: C): y.I = 1.asInstanceOf[y.I] + } + + def basic(x: Foo): Unit ={ + assert(x.sel0 == 1) + assert(x.sel1(2) == 2) + assert(x.fun0(3) == 3) + + val f = x.sel1 + assert(f(3) == 3) + } + + def currying(x: Foo): Unit = { + assert(x.fun1(1)(2) == 3) + assert(x.fun2(1)(2) == 2) + assert(x.fun3(1, 2, 3)(4, 5, 6)(7, 8, 9) == -1) + } + + def etaExpansion(x: Foo): Unit = { + val f0 = x.fun0(_) + assert(f0(2) == 2) + + val f1 = x.fun0 _ + assert(f1(2) == 2) + + val f2 = x.fun1(1)(_) + assert(f2(2) == 3) + + val f3 = x.fun1(1) _ + assert(f3(2) == 3) + + val f4 = x.fun1(1) + assert(f4(2) == 3) + } + + def implicits(x: Foo) = { + implicit val y = 2 + assert(x.fun4 == 2) + assert(x.fun5(1) == 3) + } + + // Limited support for dependant methods + def dependent(x: Foo) = { + val y = new D + + assert(x.fun6(y, "Hello") == 1) + // assert(x.fun7(y, 1) == 2) // error: No ClassTag available for x.I + + val s = x.fun8(y) + assert((s: String) == "Hello") + + // val i = x.fun9(y) // error: rejected (blows up in pickler if not rejected) + // assert((i: String) == "Hello") // error: Type mismatch: found: y.S(i); required: String + } +} + +class ReflectiveCallTestScala3 { + import ReflectiveCallTestScala3._ + + @Test def testBasic1(): Unit = basic(new Foo1) + @Test def testCurrying1(): Unit = currying(new Foo1) + @Test def testEtaExpansion1(): Unit = etaExpansion(new Foo1) + @Test def testImplicits1(): Unit = implicits(new Foo1) + @Test def testDependent1(): Unit = dependent(new Foo1) + + @Test def testBasic2(): Unit = basic(new Foo2) + @Test def testCurrying2(): Unit = currying(new Foo2) + @Test def testEtaExpansion2(): Unit = etaExpansion(new Foo2) + @Test def testImplicits2(): Unit = implicits(new Foo2) + @Test def testDependent2(): Unit = dependent(new Foo2) + + @Test def testAllSpecialTypes(): Unit = { + // Test types that have special ClassTags + type Foo = { + def foo(unit: Unit, any: Any, anyVal: AnyVal, anyRef: AnyRef, obj: Object): String + + def nullMeth(nul: Null): Any + def nothingMeth(nothing: Nothing): Any + } + + class FooImpl { + def foo(unit: Unit, any: Any, anyVal: AnyVal, anyRef: AnyRef, obj: Object): String = + s"$unit $any $anyVal $anyRef $obj" + + def nullMeth(nul: Null): Any = "" + nul + def nothingMeth(nothing: Nothing): Any = ??? + } + + val foo: Foo = new FooImpl + assertEquals("undefined any-string 5 Some(5) None", + foo.foo((), "any-string", 5, Some[Int](5), None)) + + // The following calls do not compile in Dotty because + // No ClassTag available for Null/Nothing + + //assertEquals("null", foo.nullMeth(null)) + //// Make sure that a call to nothingMeth can link + //if (Math.random() > 2) // always false + // foo.nothingMeth(???) + } + +}