diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index e318332826f6..8a70a583e0af 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -60,6 +60,7 @@ class Compiler { new LiftTry, // Put try expressions that might execute on non-empty stacks into their own methods new HoistSuperArgs, // Hoist complex arguments of supercalls to enclosing scope new ClassOf, // Expand `Predef.classOf` calls. + new SpecializedApplyMethods, // Adds specialized methods to FunctionN new RefChecks), // Various checks mostly related to abstract members and overriding List(new TryCatchPatterns, // Compile cases in try/catch new PatternMatcher, // Compile pattern matches @@ -67,13 +68,14 @@ class Compiler { new ExplicitSelf, // Make references to non-trivial self types explicit as casts new ShortcutImplicits, // Allow implicit functions without creating closures new CrossCastAnd, // Normalize selections involving intersection types. - new Splitter), // Expand selections involving union types into conditionals + new Splitter, // Expand selections involving union types into conditionals + new ElimByName), // Expand by-name parameter references List(new PhantomArgLift, // Extracts the evaluation of phantom arguments placing them before the call. new VCInlineMethods, // Inlines calls to value class methods new SeqLiterals, // Express vararg arguments as arrays new InterceptedMethods, // Special handling of `==`, `|=`, `getClass` methods new Getters, // Replace non-private vals and vars with getter defs (fields are added later) - new ElimByName, // Expand by-name parameter references + new SpecializeFunctions, // Specialized Function{0,1,2} by replacing super with specialized super new ElimOuterSelect, // Expand outer selections new AugmentScala2Traits, // Expand traits defined in Scala 2.x to simulate old-style rewritings new ResolveSuper, // Implement super accessors and add forwarders to trait methods diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 3ecf650f6c89..a50053507cc7 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -801,12 +801,17 @@ class Definitions { def isBottomType(tp: Type) = tp.derivesFrom(NothingClass) || tp.derivesFrom(NullClass) - /** Is a function class. + /** Is any function class that satisfies: * - FunctionN for N >= 0 * - ImplicitFunctionN for N > 0 */ def isFunctionClass(cls: Symbol) = scalaClassName(cls).isFunction + /** Is a function class where + * - FunctionN for N >= 0 + */ + def isPlainFunctionClass(cls: Symbol) = scalaClassName(cls).isPlainFunction + /** Is an implicit function class. * - ImplicitFunctionN for N > 0 */ @@ -922,28 +927,33 @@ class Definitions { } // Specialized type parameters defined for scala.Function{0,1,2}. - private lazy val Function1SpecializedParams: collection.Set[Type] = + lazy val Function1SpecializedParams: collection.Set[Type] = Set(IntType, LongType, FloatType, DoubleType) - private lazy val Function2SpecializedParams: collection.Set[Type] = + lazy val Function2SpecializedParams: collection.Set[Type] = Set(IntType, LongType, DoubleType) - private lazy val Function0SpecializedReturns: collection.Set[Type] = + lazy val Function0SpecializedReturns: collection.Set[Type] = ScalaNumericValueTypeList.toSet[Type] + UnitType + BooleanType - private lazy val Function1SpecializedReturns: collection.Set[Type] = + lazy val Function1SpecializedReturns: collection.Set[Type] = Set(UnitType, BooleanType, IntType, FloatType, LongType, DoubleType) - private lazy val Function2SpecializedReturns: collection.Set[Type] = + lazy val Function2SpecializedReturns: collection.Set[Type] = Function1SpecializedReturns def isSpecializableFunction(cls: ClassSymbol, paramTypes: List[Type], retType: Type)(implicit ctx: Context) = - isFunctionClass(cls) && (paramTypes match { + cls.derivesFrom(FunctionClass(paramTypes.length)) && (paramTypes match { case Nil => - Function0SpecializedReturns.contains(retType) + val specializedReturns = Function0SpecializedReturns.map(_.typeSymbol) + specializedReturns.contains(retType.typeSymbol) case List(paramType0) => - Function1SpecializedParams.contains(paramType0) && - Function1SpecializedReturns.contains(retType) + val specializedParams = Function1SpecializedParams.map(_.typeSymbol) + lazy val specializedReturns = Function1SpecializedReturns.map(_.typeSymbol) + specializedParams.contains(paramType0.typeSymbol) && + specializedReturns.contains(retType.typeSymbol) case List(paramType0, paramType1) => - Function2SpecializedParams.contains(paramType0) && - Function2SpecializedParams.contains(paramType1) && - Function2SpecializedReturns.contains(retType) + val specializedParams = Function2SpecializedParams.map(_.typeSymbol) + lazy val specializedReturns = Function2SpecializedReturns.map(_.typeSymbol) + specializedParams.contains(paramType0.typeSymbol) && + specializedParams.contains(paramType1.typeSymbol) && + specializedReturns.contains(retType.typeSymbol) case _ => false }) @@ -987,9 +997,9 @@ class Definitions { lazy val ScalaNumericValueTypeList = List( ByteType, ShortType, CharType, IntType, LongType, FloatType, DoubleType) - private lazy val ScalaNumericValueTypes: collection.Set[TypeRef] = ScalaNumericValueTypeList.toSet - private lazy val ScalaValueTypes: collection.Set[TypeRef] = ScalaNumericValueTypes + UnitType + BooleanType - private lazy val ScalaBoxedTypes = ScalaValueTypes map (t => boxedTypes(t.name)) + lazy val ScalaNumericValueTypes: collection.Set[TypeRef] = ScalaNumericValueTypeList.toSet + lazy val ScalaValueTypes: collection.Set[TypeRef] = ScalaNumericValueTypes + UnitType + BooleanType + lazy val ScalaBoxedTypes = ScalaValueTypes map (t => boxedTypes(t.name)) val ScalaNumericValueClasses = new PerRun[collection.Set[Symbol]](implicit ctx => ScalaNumericValueTypes.map(_.symbol)) val ScalaValueClasses = new PerRun[collection.Set[Symbol]](implicit ctx => ScalaValueTypes.map(_.symbol)) diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 4e568861e401..c8e00391bf7e 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -177,13 +177,17 @@ object NameOps { if (n == 0) -1 else n } - /** Is a function name + /** Is any function name that satisfies * - FunctionN for N >= 0 * - ImplicitFunctionN for N >= 1 - * - false otherwise */ def isFunction: Boolean = functionArity >= 0 + /** Is a function name + * - FunctionN for N >= 0 + */ + def isPlainFunction: Boolean = functionArityFor(str.Function) >= 0 + /** Is a implicit function name * - ImplicitFunctionN for N >= 1 * - false otherwise @@ -227,8 +231,10 @@ object NameOps { case nme.clone_ => nme.clone_ } - def specializedFor(classTargs: List[Types.Type], classTargsNames: List[Name], methodTargs: List[Types.Type], methodTarsNames: List[Name])(implicit ctx: Context): name.ThisName = { - + /** This method is to be used on **type parameters** from a class, since + * this method does sorting based on their names + */ + def specializedFor(classTargs: List[Types.Type], classTargsNames: List[Name], methodTargs: List[Types.Type], methodTarsNames: List[Name])(implicit ctx: Context): Name = { val methodTags: Seq[Name] = (methodTargs zip methodTarsNames).sortBy(_._2).map(x => defn.typeTag(x._1)) val classTags: Seq[Name] = (classTargs zip classTargsNames).sortBy(_._2).map(x => defn.typeTag(x._1)) @@ -237,6 +243,17 @@ object NameOps { classTags.fold(nme.EMPTY)(_ ++ _) ++ nme.specializedTypeNames.suffix) } + /** Use for specializing function names ONLY and use it if you are **not** + * creating specialized name from type parameters. The order of names will + * be: + * + * `<...>` + */ + def specializedFunction(ret: Types.Type, args: List[Types.Type])(implicit ctx: Context): Name = + name ++ nme.specializedTypeNames.prefix ++ + nme.specializedTypeNames.separator ++ defn.typeTag(ret) ++ + args.map(defn.typeTag).fold(nme.EMPTY)(_ ++ _) ++ nme.specializedTypeNames.suffix + /** If name length exceeds allowable limit, replace part of it by hash */ def compactified(implicit ctx: Context): TermName = termName(compactify(name.toString)) diff --git a/compiler/src/dotty/tools/dotc/transform/ElimByName.scala b/compiler/src/dotty/tools/dotc/transform/ElimByName.scala index 36c22f2e2068..758bf8c260f9 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimByName.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimByName.scala @@ -42,9 +42,6 @@ class ElimByName extends TransformByNameApply with InfoTransformer { override def phaseName: String = "elimByName" - override def runsAfterGroupsOf = Set(classOf[Splitter]) - // I got errors running this phase in an earlier group, but I did not track them down. - /** Map `tree` to `tree.apply()` is `ftree` was of ExprType and becomes now a function */ private def applyIfFunction(tree: Tree, ftree: Tree)(implicit ctx: Context) = if (isByNameRef(ftree)) diff --git a/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala b/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala new file mode 100644 index 000000000000..f931339066b5 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala @@ -0,0 +1,156 @@ +package dotty.tools.dotc +package transform + +import ast.Trees._, ast.tpd, core._ +import Contexts.Context, Types._, Decorators._, Symbols._, DenotTransformers._ +import SymDenotations._, Scopes._, StdNames._, NameOps._, Names._ +import MegaPhase.MiniPhase + +import scala.collection.mutable + +/** Specializes classes that inherit from `FunctionN` where there exists a + * specialized form. + */ +class SpecializeFunctions extends MiniPhase with InfoTransformer { + import ast.tpd._ + val phaseName = "specializeFunctions" + override def runsAfter = Set(classOf[ElimByName]) + + private val jFunction = "scala.compat.java8.JFunction".toTermName + + /** Transforms the type to include decls for specialized applys */ + override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context) = tp match { + case tp: ClassInfo if !sym.is(Flags.Package) && (tp.decls ne EmptyScope) && derivesFromFn012(sym) => + var newApplys = Map.empty[Name, Symbol] + + var arity = 0 + while (arity < 3) { + val func = defn.FunctionClass(arity) + if (tp.derivesFrom(func)) { + val typeParams = tp.cls.typeRef.baseType(func).argInfos + val isSpecializable = + defn.isSpecializableFunction( + sym.asClass, + typeParams.init, + typeParams.last + ) + + if (isSpecializable && tp.decls.lookup(nme.apply).exists) { + val interface = specInterface(typeParams) + val specializedMethodName = nme.apply.specializedFunction(typeParams.last, typeParams.init) + newApplys += (specializedMethodName -> interface) + } + } + arity += 1 + } + + def newDecls = + newApplys.toList.map { case (name, interface) => + ctx.newSymbol( + sym, + name, + Flags.Override | Flags.Method | Flags.Synthetic, + interface.info.decls.lookup(name).info + ) + } + .foldLeft(tp.decls.cloneScope) { + (scope, sym) => scope.enter(sym); scope + } + + if (newApplys.isEmpty) tp + else tp.derivedClassInfo(decls = newDecls) + + case _ => tp + } + + /** Transforms the `Template` of the classes to contain forwarders from the + * generic applys to the specialized ones. Also inserts the specialized applys + * in the template body. + */ + override def transformTemplate(tree: Template)(implicit ctx: Context) = { + val cls = tree.symbol.enclosingClass.asClass + if (derivesFromFn012(cls)) { + val applyBuf = new mutable.ListBuffer[Tree] + val newBody = tree.body.mapConserve { + case dt: DefDef if dt.name == nme.apply && dt.vparamss.length == 1 => + val typeParams = dt.vparamss.head.map(_.symbol.info) + val retType = dt.tpe.widen.finalResultType + + val specName = specializedName(nme.apply, typeParams :+ retType) + val specializedApply = cls.info.decls.lookup(specName) + if (specializedApply.exists) { + val apply = specializedApply.asTerm + val specializedDecl = + polyDefDef(apply, trefs => vrefss => { + dt.rhs + .changeOwner(dt.symbol, apply) + .subst(dt.vparamss.flatten.map(_.symbol), vrefss.flatten.map(_.symbol)) + }) + applyBuf += specializedDecl + + // create a forwarding to the specialized apply + cpy.DefDef(dt)(rhs = { + tpd + .ref(apply) + .appliedToArgs(dt.vparamss.head.map(vparam => ref(vparam.symbol))) + }) + } else dt + + case x => x + } + + cpy.Template(tree)( + body = applyBuf.toList ::: newBody + ) + } else tree + } + + /** Dispatch to specialized `apply`s in user code when available */ + override def transformApply(tree: Apply)(implicit ctx: Context) = + tree match { + case Apply(fun, args) + if fun.symbol.name == nme.apply && + fun.symbol.owner.derivesFrom(defn.FunctionClass(args.length)) + => + val params = (fun.tpe.widen.firstParamTypes :+ tree.tpe).map(_.widenSingleton.dealias) + val isSpecializable = + defn.isSpecializableFunction( + fun.symbol.owner.asClass, + params.init, + params.last) + + if (isSpecializable && !params.exists(_.isInstanceOf[ExprType])) { + val specializedApply = specializedName(nme.apply, params) + val newSel = fun match { + case Select(qual, _) => + qual.select(specializedApply) + case _ => + (fun.tpe: @unchecked) match { + case TermRef(prefix: ThisType, name) => + tpd.This(prefix.cls).select(specializedApply) + case TermRef(prefix: NamedType, name) => + tpd.ref(prefix).select(specializedApply) + } + } + + newSel.appliedToArgs(args) + } + else tree + + case _ => tree + } + + private def specializedName(name: Name, args: List[Type])(implicit ctx: Context) = + name.specializedFunction(args.last, args.init) + + private def functionName(typeParams: List[Type])(implicit ctx: Context) = + jFunction ++ (typeParams.length - 1).toString + + private def specInterface(typeParams: List[Type])(implicit ctx: Context) = + ctx.getClassIfDefined(functionName(typeParams).specializedFunction(typeParams.last, typeParams.init)) + + private def derivesFromFn012(sym: Symbol)(implicit ctx: Context): Boolean = + sym.derivesFrom(defn.FunctionClass(0)) || + sym.derivesFrom(defn.FunctionClass(1)) || + sym.derivesFrom(defn.FunctionClass(2)) +} diff --git a/compiler/src/dotty/tools/dotc/transform/SpecializedApplyMethods.scala b/compiler/src/dotty/tools/dotc/transform/SpecializedApplyMethods.scala new file mode 100644 index 000000000000..19c7abacb5fb --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/SpecializedApplyMethods.scala @@ -0,0 +1,96 @@ +package dotty.tools.dotc +package transform + +import ast.Trees._, ast.tpd, core._ +import Contexts.Context, Types._, Decorators._, Symbols._, DenotTransformers._ +import SymDenotations._, Scopes._, StdNames._, NameOps._, Names._ +import MegaPhase.MiniPhase + +/** This phase synthesizes specialized methods for FunctionN, this is done + * since there are no scala signatures in the bytecode for the specialized + * methods. + * + * We know which specializations exist for the different arities, therefore we + * can hardcode them. This should, however be removed once we're using a + * different standard library. + */ +class SpecializedApplyMethods extends MiniPhase with InfoTransformer { + import ast.tpd._ + + val phaseName = "specializedApplyMethods" + + private[this] var func0Applys: collection.Set[Symbol] = _ + private[this] var func1Applys: collection.Set[Symbol] = _ + private[this] var func2Applys: collection.Set[Symbol] = _ + private[this] var func0: Symbol = _ + private[this] var func1: Symbol = _ + private[this] var func2: Symbol = _ + + private def init()(implicit ctx: Context): Unit = if (func0Applys eq null) { + val definitions = ctx.definitions + import definitions._ + + def specApply(sym: Symbol, args: List[Type], ret: Type)(implicit ctx: Context): Symbol = { + val name = nme.apply.specializedFunction(ret, args) + ctx.newSymbol(sym, name, Flags.Method, MethodType(args, ret)) + } + + func0 = FunctionClass(0) + func0Applys = for (r <- defn.Function0SpecializedReturns) yield specApply(func0, Nil, r) + + func1 = FunctionClass(1) + func1Applys = for { + r <- defn.Function1SpecializedReturns + t1 <- defn.Function1SpecializedParams + } yield specApply(func1, List(t1), r) + + func2 = FunctionClass(2) + func2Applys = for { + r <- Function2SpecializedReturns + t1 <- Function2SpecializedParams + t2 <- Function2SpecializedReturns + } yield specApply(func2, List(t1, t2), r) + } + + /** Add symbols for specialized methods to FunctionN */ + override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context) = tp match { + case tp: ClassInfo if defn.isPlainFunctionClass(sym) => { + init() + val newDecls = sym.name.functionArity match { + case 0 => func0Applys.foldLeft(tp.decls.cloneScope) { + (decls, sym) => decls.enter(sym); decls + } + case 1 => func1Applys.foldLeft(tp.decls.cloneScope) { + (decls, sym) => decls.enter(sym); decls + } + case 2 => func2Applys.foldLeft(tp.decls.cloneScope) { + (decls, sym) => decls.enter(sym); decls + } + case _ => tp.decls + } + + tp.derivedClassInfo(decls = newDecls) + } + case _ => tp + } + + /** Create bridge methods for FunctionN with specialized applys */ + override def transformTemplate(tree: Template)(implicit ctx: Context) = { + val owner = tree.symbol.owner + val additionalSymbols = + if (owner eq func0) func0Applys + else if (owner eq func1) func1Applys + else if (owner eq func2) func2Applys + else Nil + + if (additionalSymbols eq Nil) tree + else cpy.Template(tree)(body = tree.body ++ additionalSymbols.map { apply => + DefDef(apply.asTerm, { vparamss => + This(owner.asClass) + .select(nme.apply) + .appliedToArgss(vparamss) + .ensureConforms(apply.info.finalResultType) + }) + }) + } +} diff --git a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTest.scala b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTest.scala index 4542db0d54a0..5923f1c7695c 100644 --- a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTest.scala +++ b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTest.scala @@ -17,6 +17,8 @@ import scala.tools.asm.{ClassWriter, ClassReader} import scala.tools.asm.tree._ import java.io.{File => JFile, InputStream} +import org.junit.Assert._ + class TestGenBCode(val outDir: String) extends GenBCode { override def phaseName: String = "testGenBCode" val virtualDir = new Directory(outDir, None) @@ -87,6 +89,14 @@ trait DottyBytecodeTest extends DottyTest { cn } + /** Finds a class with `cls` as name in `dir`, throws if it can't find it */ + def findClass(cls: String, dir: Directory) = { + val clsIn = dir.lookupName(s"$cls.class", directory = false).input + val clsNode = loadClassNode(clsIn) + assert(clsNode.name == cls, s"inspecting wrong class: ${clsNode.name}") + clsNode + } + protected def getMethod(classNode: ClassNode, name: String): MethodNode = classNode.methods.asScala.find(_.name == name) getOrElse sys.error(s"Didn't find method '$name' in class '${classNode.name}'") @@ -203,4 +213,41 @@ trait DottyBytecodeTest extends DottyTest { s"Wrong number of null checks ($actualChecks), expected: $expectedChecks" ) } + + def assertBoxing(nodeName: String, methods: java.lang.Iterable[MethodNode])(implicit source: String): Unit = + methods.asScala.find(_.name == nodeName) + .map { node => + val (ins, boxed) = boxingInstructions(node) + if (!boxed) fail("No boxing in:\n" + boxingError(ins, source)) + } + .getOrElse(fail("Could not find constructor for object `Test`")) + + private def boxingError(ins: List[_], source: String) = + s"""|---------------------------------- + |${ins.mkString("\n")} + |---------------------------------- + |From code: + |$source + |----------------------------------""".stripMargin + + + protected def assertNoBoxing(nodeName: String, methods: java.lang.Iterable[MethodNode])(implicit source: String): Unit = + methods.asScala.find(_.name == nodeName) + .map { node => + val (ins, boxed) = boxingInstructions(node) + if (boxed) fail(boxingError(ins, source)) + } + .getOrElse(fail("Could not find constructor for object `Test`")) + + protected def boxingInstructions(method: MethodNode): (List[_], Boolean) = { + val ins = instructionsFromMethod(method) + val boxed = ins.exists { + case Invoke(op, owner, name, desc, itf) => + owner.toLowerCase.contains("box") || name.toLowerCase.contains("box") + case _ => false + } + + (ins, boxed) + } + } diff --git a/compiler/test/dotty/tools/dotc/transform/SpecializeFunctionsTests.scala b/compiler/test/dotty/tools/dotc/transform/SpecializeFunctionsTests.scala new file mode 100644 index 000000000000..503d5198aae4 --- /dev/null +++ b/compiler/test/dotty/tools/dotc/transform/SpecializeFunctionsTests.scala @@ -0,0 +1,229 @@ +package dotty.tools +package dotc +package transform + +import org.junit.Assert._ +import org.junit.Test + +import dotty.tools.backend.jvm.DottyBytecodeTest + +class SpecializeFunctionsTests extends DottyBytecodeTest { + + import dotty.tools.backend.jvm.ASMConverters._ + import dotty.tools.backend.jvm.AsmNode._ + import scala.collection.JavaConverters._ + import scala.tools.asm.tree.MethodNode + + @Test def specializeParentIntToInt = { + val source = """ + |class Foo extends Function1[Int, Int] { + | def apply(i: Int) = i + |} + """.stripMargin + + checkBCode(source) { dir => + val applys = + findClass("Foo", dir).methods.asScala.collect { + case m if m.name == "apply$mcII$sp" => m + case m if m.name == "apply" => m + } + .map(_.name) + .toList + + assert( + // there should be two "apply", one generic and the one overwritten and + // then the specialized one + applys.length == 3, + s"Wrong number of specialized applys, actual length: ${applys.length} $applys" + ) + assert(applys.contains("apply"), "Foo did not contain `apply` forwarder method") + assert(applys.contains("apply$mcII$sp"), "Foo did not contain specialized apply") + } + } + + @Test def specializeFunction2Applys = { + val source = + """|class Func2 extends Function2[Int, Int, Int] { + | def apply(i: Int, j: Int): Int = i + j + |}""".stripMargin + + checkBCode(source) { dir => + val apps = + findClass("Func2", dir).methods.asScala.collect { + case m if m.name == "apply$mcIII$sp" => m + case m if m.name == "apply" => m + } + .map(_.name) + .toList + + assert( + apps.length == 3, + s"Wrong number of specialized applys, actual length: ${apps.length} - $apps" + ) + assert(apps.contains("apply"), "Func2 did not contain `apply` forwarder method") + assert(apps.contains("apply$mcIII$sp"), "Func2 did not contain specialized apply") + } + } + + @Test def noBoxingSpecFunction0 = { + implicit val source: String = + """|object Test { + | class Func0 extends Function0[Int] { + | def apply() = 1337 + | } + | + | (new Func0: Function0[Int])() + |}""".stripMargin + + checkBCode(source) { dir => + assertNoBoxing("", findClass("Test$", dir).methods) + } + } + + @Test def boxingFunction1 = { + implicit val source: String = + """|object Test { + | class Func1 extends Function1[Char, Int] { + | def apply(c: Char) = c.toInt + | } + | + | (new Func1: Function1[Char, Int])('c') + |}""".stripMargin + + checkBCode(source) { dir => + // No specialization for Function1[Char, Int] + assertBoxing("", findClass("Test$", dir).methods) + } + } + + @Test def noBoxingSpecFunction1 = { + implicit val source: String = + """|object Test { + | class Func1 extends Function1[Int, Int] { + | def apply(i: Int) = i + 1 + | } + | + | (new Func1: Function1[Int, Int])(1) + |}""".stripMargin + + checkBCode(source) { dir => + assertNoBoxing("", findClass("Test$", dir).methods) + } + } + + @Test def noBoxingSpecFunction2 = { + implicit val source: String = + """|object Test { + | class Func2 extends Function2[Int, Int, Int] { + | def apply(i: Int, j: Int) = i + j + | } + | + | (new Func2: Function2[Int, Int, Int])(1300, 37) + |}""".stripMargin + + checkBCode(source) { dir => + assertNoBoxing("", findClass("Test$", dir).methods) + } + } + + @Test def boxingFunction2 = { + implicit val source: String = + """|object Test { + | class Func2 extends Function2[Char, Char, Char] { + | def apply(c1: Char, c2: Char) = c1 + | } + | + | (new Func2: Function2[Char, Char, Char])('c', 'd') + |}""".stripMargin + + checkBCode(source) { dir => + // No specialization for Function2[Char, Char, Char] + assertBoxing("", findClass("Test$", dir).methods) + } + } + + @Test def multipleParentsNoBoxing = { + implicit val source: String = + """|object Test { + | class Func01 extends Function0[Int] with Function1[Int, Int] { + | def apply(): Int = 0 + | def apply(x: Int): Int = x + | } + | (new Func01: Function0[Int])() + | (new Func01: Function1[Int, Int])(1) + |}""".stripMargin + + checkBCode(source) { dir => + assertNoBoxing("", findClass("Test$", dir).methods) + } + } + + @Test def multipleLevelInheritanceNoBoxing = { + implicit val source: String = + """|object Test { + | class Func1[T](fn: T => Int) extends Function1[T, Int] { + | def apply(x: T): Int = fn(x) + | } + | class Fn extends Func1(identity[Int]) + | (new Fn: Function1[Int, Int])(123) + |}""".stripMargin + + checkBCode(source) { dir => + assertNoBoxing("", findClass("Test$", dir).methods) + } + } + + @Test def lambdaNoBoxing1 = { + implicit val source: String = + """|object Test { + | val fn = (x: Int) => x + 1 + | fn(2) + |}""".stripMargin + + checkBCode(source) { dir => + assertNoBoxing("", findClass("Test$", dir).methods) + } + } + + @Test def lambdaNoBoxing2 = { + implicit val source: String = + """|object Test { + | def fn[T, U, V](op0: T => U, op1: U => V): T => V = (x: T) => op1(op0(x)) + | val f0: Int => Double = _.toDouble + | val f1: Double => Int = _.toInt + | val id = fn(f0, f1) + | id(2) + |}""".stripMargin + + checkBCode(source) { dir => + assertNoBoxing("", findClass("Test$", dir).methods) + } + } + + @Test def classWithFieldBoxing = { + implicit val source: String = + """|object Test { + | class Func0[T](x: T) extends Function0[T] { + | def apply(): T = x + | } + | (new Func0(2): Function0[Int])() + |}""".stripMargin + + checkBCode(source) { dir => + // Boxing happens because of the field of `Func0`. + assertBoxing("", findClass("Test$", dir).methods) + } + } + + @Test def passByNameNoBoxing = { + implicit val source: String = + """|object Test { + | def fn(x: => Int): Int = x + | fn(2) + |}""".stripMargin + + checkBCode(source) { dir => + assertNoBoxing("fn", findClass("Test$", dir).methods) + } + } +}