diff --git a/community-build/community-projects/stdLib213 b/community-build/community-projects/stdLib213 index 5b4406c7de9a..09b05334175c 160000 --- a/community-build/community-projects/stdLib213 +++ b/community-build/community-projects/stdLib213 @@ -1 +1 @@ -Subproject commit 5b4406c7de9a7e671d8f533f7d4436a7a0586358 +Subproject commit 09b05334175ce5019f5169f3a3e5d4bbb341ea99 diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 5e0e711febe0..721c91118e4d 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -69,6 +69,7 @@ class Compiler { new CacheAliasImplicits, // Cache RHS of parameterless alias implicits new ByNameClosures, // Expand arguments to by-name parameters to closures new HoistSuperArgs, // Hoist complex arguments of supercalls to enclosing scope + new SpecializeApplyMethods, // Adds specialized methods to FunctionN new RefChecks) :: // Various checks mostly related to abstract members and overriding List(new ElimOpaque, // Turn opaque into normal aliases new TryCatchPatterns, // Compile cases in try/catch @@ -76,6 +77,7 @@ class Compiler { new sjs.ExplicitJSClasses, // Make all JS classes explicit (Scala.js only) new ExplicitOuter, // Add accessors to outer classes from nested ones. new ExplicitSelf, // Make references to non-trivial self types explicit as casts + new ElimByName, // Expand by-name parameter references new StringInterpolatorOpt) :: // Optimizes raw and s string interpolators by rewriting them to string concatentations List(new PruneErasedDefs, // Drop erased definitions from scopes and simplify erased expressions new InlinePatterns, // Remove placeholders of inlined patterns @@ -83,7 +85,7 @@ class Compiler { 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 LiftTry, // Put try expressions that might execute on non-empty stacks into their own methods new CollectNullableFields, // Collect fields that can be nulled out after use in lazy initialization new ElimOuterSelect, // Expand outer selections diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 016fc6b8487d..9ba7030b6292 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -784,8 +784,8 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => def tupleArgs(tree: Tree)(using Context): List[Tree] = tree match { case Block(Nil, expr) => tupleArgs(expr) case Inlined(_, Nil, expr) => tupleArgs(expr) - case Apply(fn, args) - if fn.symbol.name == nme.apply && + case Apply(fn: NameTree, args) + if fn.name == nme.apply && fn.symbol.owner.is(Module) && defn.isTupleClass(fn.symbol.owner.companionClass) => args case _ => Nil diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 637e136c8509..303d3dd32a65 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1209,7 +1209,11 @@ class Definitions { else funType(n) ).symbol.asClass - @tu lazy val Function0_apply: Symbol = FunctionClass(0).requiredMethod(nme.apply) + @tu lazy val Function0_apply: Symbol = Function0.requiredMethod(nme.apply) + + @tu lazy val Function0: Symbol = FunctionClass(0) + @tu lazy val Function1: Symbol = FunctionClass(1) + @tu lazy val Function2: Symbol = FunctionClass(2) def FunctionType(n: Int, isContextual: Boolean = false, isErased: Boolean = false)(using Context): TypeRef = FunctionClass(n, isContextual && !ctx.erasedTypes, isErased).typeRef @@ -1244,7 +1248,7 @@ class Definitions { def isBottomClassAfterErasure(cls: Symbol): Boolean = cls == NothingClass || cls == NullClass - /** Is a function class. + /** Is any function class where * - FunctionXXL * - FunctionN for N >= 0 * - ContextFunctionN for N >= 0 @@ -1253,6 +1257,11 @@ class Definitions { */ def isFunctionClass(cls: Symbol): Boolean = scalaClassName(cls).isFunction + /** Is a function class where + * - FunctionN for N >= 0 and N != XXL + */ + def isPlainFunctionClass(cls: Symbol) = isVarArityClass(cls, str.Function) + /** Is an context function class. * - ContextFunctionN for N >= 0 * - ErasedContextFunctionN for N > 0 @@ -1488,6 +1497,25 @@ class Definitions { false }) + @tu lazy val Function0SpecializedApplyNames: collection.Set[TermName] = + for r <- Function0SpecializedReturnTypes + yield nme.apply.specializedFunction(r, Nil).asTermName + + @tu lazy val Function1SpecializedApplyNames: collection.Set[TermName] = + for + r <- Function1SpecializedReturnTypes + t1 <- Function1SpecializedParamTypes + yield + nme.apply.specializedFunction(r, List(t1)).asTermName + + @tu lazy val Function2SpecializedApplyNames: collection.Set[TermName] = + for + r <- Function2SpecializedReturnTypes + t1 <- Function2SpecializedParamTypes + t2 <- Function2SpecializedParamTypes + yield + nme.apply.specializedFunction(r, List(t1, t2)).asTermName + def functionArity(tp: Type)(using Context): Int = tp.dropDependentRefinement.dealias.argInfos.length - 1 /** Return underlying context function type (i.e. instance of an ContextFunctionN class) diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index ed6e54ab9deb..6cfdedbe9a27 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -231,6 +231,11 @@ object NameOps { def isFunction: Boolean = (name eq tpnme.FunctionXXL) || checkedFunArity(functionSuffixStart) >= 0 + /** Is a function name + * - FunctionN for N >= 0 + */ + def isPlainFunction: Boolean = functionArity >= 0 + /** Is an context function name, i.e one of ContextFunctionN or ErasedContextFunctionN for N >= 0 */ def isContextFunction: Boolean = @@ -276,8 +281,10 @@ object NameOps { case nme.clone_ => nme.clone_ } + /** 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[Type], classTargsNames: List[Name], methodTargs: List[Type], methodTarsNames: List[Name])(using Context): N = { - 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)) @@ -286,6 +293,22 @@ 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: Type, args: List[Type])(using Context): Name = + val sb = new StringBuilder + sb.append(name.toString) + sb.append(nme.specializedTypeNames.prefix.toString) + sb.append(nme.specializedTypeNames.separator) + sb.append(defn.typeTag(ret).toString) + args.foreach { arg => sb.append(defn.typeTag(arg)) } + sb.append(nme.specializedTypeNames.suffix) + termName(sb.toString) + /** If name length exceeds allowable limit, replace part of it by hash */ def compactified(using Context): TermName = termName(compactify(name.toString)) diff --git a/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala b/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala index 374d938e5523..1b06394b1156 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala @@ -162,7 +162,7 @@ class ExpandSAMs extends MiniPhase { cpy.Block(tree)(pfDef :: Nil, New(pfSym.typeRef, Nil)) case _ => - val found = tpe.baseType(defn.FunctionClass(1)) + val found = tpe.baseType(defn.Function1) report.error(TypeMismatch(found, tpe), tree.srcPos) tree } diff --git a/compiler/src/dotty/tools/dotc/transform/FunctionXXLForwarders.scala b/compiler/src/dotty/tools/dotc/transform/FunctionXXLForwarders.scala index 25bb7a5ce28f..3ef0debb93e0 100644 --- a/compiler/src/dotty/tools/dotc/transform/FunctionXXLForwarders.scala +++ b/compiler/src/dotty/tools/dotc/transform/FunctionXXLForwarders.scala @@ -39,18 +39,20 @@ class FunctionXXLForwarders extends MiniPhase with IdentityDenotTransformer { ref(receiver.symbol).appliedToArgss(argss).cast(defn.ObjectType) } + if impl.symbol.owner.is(Trait) then return impl + val forwarders = for { - tree <- if (impl.symbol.owner.is(Trait)) Nil else impl.body - if tree.symbol.is(Method) && tree.symbol.name == nme.apply && - tree.symbol.signature.paramsSig.size > MaxImplementedFunctionArity && - tree.symbol.allOverriddenSymbols.exists(sym => defn.isXXLFunctionClass(sym.owner)) + (ddef: DefDef) <- impl.body + if ddef.name == nme.apply && ddef.symbol.is(Method) && + ddef.symbol.signature.paramsSig.size > MaxImplementedFunctionArity && + ddef.symbol.allOverriddenSymbols.exists(sym => defn.isXXLFunctionClass(sym.owner)) } yield { val xsType = defn.ArrayType.appliedTo(List(defn.ObjectType)) val methType = MethodType(List(nme.args))(_ => List(xsType), _ => defn.ObjectType) - val meth = newSymbol(tree.symbol.owner, nme.apply, Synthetic | Method, methType) - DefDef(meth, paramss => forwarderRhs(tree, paramss.head.head)) + val meth = newSymbol(ddef.symbol.owner, nme.apply, Synthetic | Method, methType) + DefDef(meth, paramss => forwarderRhs(ddef, paramss.head.head)) } cpy.Template(impl)(body = forwarders ::: impl.body) diff --git a/compiler/src/dotty/tools/dotc/transform/SpecializeApplyMethods.scala b/compiler/src/dotty/tools/dotc/transform/SpecializeApplyMethods.scala new file mode 100644 index 000000000000..ef825832c1cd --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/SpecializeApplyMethods.scala @@ -0,0 +1,118 @@ +package dotty.tools.dotc +package transform + +import ast.Trees._, ast.tpd, core._ +import Contexts._, Types._, Decorators._, Symbols._, DenotTransformers._ +import SymDenotations._, Scopes._, StdNames._, NameOps._, Names._ +import MegaPhase.MiniPhase + +import scala.collection.mutable + + +/** 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 SpecializeApplyMethods extends MiniPhase with InfoTransformer { + import ast.tpd._ + + val phaseName = "specializeApplyMethods" + + override def isEnabled(using Context): Boolean = + !ctx.settings.scalajs.value + + private def specApplySymbol(sym: Symbol, args: List[Type], ret: Type)(using Context): Symbol = { + val name = nme.apply.specializedFunction(ret, args) + // Create the symbol at the next phase, so that it is a valid member of the + // corresponding function for all valid periods of its SymDenotations. + // Otherwise, the valid period will offset by 1, which causes a stale symbol + // in compiling stdlib. + atNextPhase(newSymbol(sym, name, Flags.Method, MethodType(args, ret))) + } + + private inline def specFun0(inline op: Type => Unit)(using Context): Unit = { + for (r <- defn.Function0SpecializedReturnTypes) do + op(r) + } + + private inline def specFun1(inline op: (Type, Type) => Unit)(using Context): Unit = { + for + r <- defn.Function1SpecializedReturnTypes + t1 <- defn.Function1SpecializedParamTypes + do + op(t1, r) + } + + private inline def specFun2(inline op: (Type, Type, Type) => Unit)(using Context): Unit = { + for + r <- defn.Function2SpecializedReturnTypes + t1 <- defn.Function2SpecializedParamTypes + t2 <- defn.Function2SpecializedParamTypes + do + op(t1, t2, r) + } + + override def infoMayChange(sym: Symbol)(using Context) = + sym == defn.Function0 + || sym == defn.Function1 + || sym == defn.Function2 + + /** Add symbols for specialized methods to FunctionN */ + override def transformInfo(tp: Type, sym: Symbol)(using Context) = tp match { + case tp: ClassInfo => + if sym == defn.Function0 then + val scope = tp.decls.cloneScope + specFun0 { r => scope.enter(specApplySymbol(sym, Nil, r)) } + tp.derivedClassInfo(decls = scope) + + else if sym == defn.Function1 then + val scope = tp.decls.cloneScope + specFun1 { (t1, r) => scope.enter(specApplySymbol(sym, t1 :: Nil, r)) } + tp.derivedClassInfo(decls = scope) + + else if sym == defn.Function2 then + val scope = tp.decls.cloneScope + specFun2 { (t1, t2, r) => scope.enter(specApplySymbol(sym, t1 :: t2 :: Nil, r)) } + tp.derivedClassInfo(decls = scope) + + else tp + + case _ => tp + } + + /** Create bridge methods for FunctionN with specialized applys */ + override def transformTemplate(tree: Template)(using Context) = { + val cls = tree.symbol.owner.asClass + + def synthesizeApply(names: collection.Set[TermName]): Tree = { + val applyBuf = new mutable.ListBuffer[DefDef] + names.foreach { name => + val applySym = cls.info.decls.lookup(name) + val ddef = DefDef( + applySym.asTerm, + { vparamss => + This(cls) + .select(nme.apply) + .appliedToArgss(vparamss) + .ensureConforms(applySym.info.finalResultType) + } + ) + applyBuf += ddef + } + cpy.Template(tree)(body = tree.body ++ applyBuf) + } + + if cls == defn.Function0 then + synthesizeApply(defn.Function0SpecializedApplyNames) + else if cls == defn.Function1 then + synthesizeApply(defn.Function1SpecializedApplyNames) + else if cls == defn.Function2 then + synthesizeApply(defn.Function2SpecializedApplyNames) + else + tree + } +} 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..781f854383cd --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala @@ -0,0 +1,104 @@ +package dotty.tools.dotc +package transform + +import ast.Trees._, ast.tpd, core._ +import Contexts._, 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 { + import ast.tpd._ + val phaseName = "specializeFunctions" + override def runsAfter = Set(ElimByName.name) + + override def isEnabled(using Context): Boolean = + !ctx.settings.scalajs.value + + /** Create forwarders from the generic applys to the specialized ones. + */ + override def transformDefDef(ddef: DefDef)(using Context) = { + if ddef.name != nme.apply + || ddef.vparamss.length != 1 + || ddef.vparamss.head.length > 2 + || !ctx.owner.isClass + then + return ddef + + val sym = ddef.symbol + val cls = ctx.owner.asClass + + var specName: Name = null + + def isSpecializable = { + val paramTypes = ddef.vparamss.head.map(_.symbol.info) + val retType = sym.info.finalResultType + specName = nme.apply.specializedFunction(retType, paramTypes) + defn.isSpecializableFunction(cls, paramTypes, retType) + } + + if (sym.is(Flags.Deferred) || !isSpecializable) return ddef + + val specializedApply = newSymbol( + cls, + specName, + sym.flags | Flags.Synthetic, + sym.info + ).entered + + val specializedDecl = + DefDef(specializedApply.asTerm, vparamss => { + ddef.rhs + .changeOwner(ddef.symbol, specializedApply) + .subst(ddef.vparamss.head.map(_.symbol), vparamss.head.map(_.symbol)) + }) + + // create a forwarding to the specialized apply + val args = ddef.vparamss.head.map(vparam => ref(vparam.symbol)) + val rhs = This(cls).select(specializedApply).appliedToArgs(args) + val ddef1 = cpy.DefDef(ddef)(rhs = rhs) + Thicket(ddef1, specializedDecl) + } + + /** Dispatch to specialized `apply`s in user code when available */ + override def transformApply(tree: Apply)(using Context) = + tree match { + case Apply(fun: NameTree, args) if fun.name == nme.apply && args.size <= 3 => + val argTypes = fun.tpe.widen.firstParamTypes.map(_.widenSingleton.dealias) + val retType = tree.tpe.widenSingleton.dealias + val isSpecializable = + defn.isSpecializableFunction( + fun.symbol.owner.asClass, + argTypes, + retType + ) + + if (!isSpecializable || argTypes.exists(_.isInstanceOf[ExprType])) return tree + + val specializedApply = nme.apply.specializedFunction(retType, argTypes) + 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) + + case _ => tree + } + + private def derivesFromFn012(cls: ClassSymbol)(using Context): Boolean = + cls.baseClasses.exists { p => + p == defn.Function0 || p == defn.Function1 || p == defn.Function2 + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala b/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala index 867c3329d944..dab1d8f25ca2 100644 --- a/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala +++ b/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala @@ -47,7 +47,7 @@ abstract class TransformByNameApply extends MiniPhase { thisPhase: DenotTransfor ref(defn.cbnArg).appliedToType(argType).appliedTo(arg).withSpan(arg.span) arg match { case Apply(Select(qual, nme.apply), Nil) - if qual.tpe.derivesFrom(defn.FunctionClass(0)) && (isPureExpr(qual) || qual.symbol.isAllOf(Inline | Param)) => + if qual.tpe.derivesFrom(defn.Function0) && (isPureExpr(qual) || qual.symbol.isAllOf(Inline | Param)) => wrap(qual) case _ => if (isByNameRef(arg) || arg.symbol == defn.cbnArg) arg diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index ee9095e693f3..03a81f42745b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -174,7 +174,7 @@ object Implicits: // We keep the old behavior under -source 3.0-migration. val isFunctionInS2 = migrateTo3 - && tpw.derivesFrom(defn.FunctionClass(1)) + && tpw.derivesFrom(defn.Function1) && ref.symbol != defn.Predef_conforms val isImplicitConversion = tpw.derivesFrom(defn.ConversionClass) // An implementation of <:< counts as a view diff --git a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTest.scala b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTest.scala index f6887fc3d459..2ceeec681295 100644 --- a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTest.scala +++ b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTest.scala @@ -21,6 +21,8 @@ import scala.tools.asm.{ClassWriter, ClassReader} import scala.tools.asm.tree._ import java.io.{File => JFile, InputStream} +import org.junit.Assert._ + trait DottyBytecodeTest { import AsmNode._ import ASMConverters._ @@ -74,6 +76,14 @@ trait DottyBytecodeTest { cn } + /** Finds a class with `cls` as name in `dir`, throws if it can't find it */ + def findClass(cls: String, dir: AbstractFile) = { + 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}'") @@ -212,6 +222,52 @@ trait DottyBytecodeTest { 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) + } + + protected def hasInvokeStatic(method: MethodNode): Boolean = { + val ins = instructionsFromMethod(method) + ins.exists { + case Invoke(op, owner, name, desc, itf) => + op == 184 + case _ => false + } + } + } object DottyBytecodeTest { extension [T](l: List[T]) def stringLines = l.mkString("\n") 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..5d159622f5ea --- /dev/null +++ b/compiler/test/dotty/tools/dotc/transform/SpecializeFunctionsTests.scala @@ -0,0 +1,253 @@ +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" => + assert(!hasInvokeStatic(m)) // should not call super specialized method + 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 notSpecializeAbstractMethod = { + val source = + """|trait Vector extends (Int=>Int) { + | override def apply(i: Int): Int + |}""".stripMargin + + checkBCode(source) { dir => + val apps = + findClass("Vector", dir).methods.asScala.collect { + case m if m.name == "apply$mcII$sp" => m + case m if m.name == "apply" => m + } + .map(_.name) + .toList + + assert( + apps.length == 1, + s"Wrong number of specialized applys, actual length: ${apps.length} - $apps" + ) + } + } + + @Test def noBoxingSpecFunction0 = { + implicit val source: String = + """|class 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 = + """|class 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 = + """|class 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 = + """|class 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 = + """|class 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 = + """|class 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 = + """|class 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 = + """|class 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 = + """|class 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 = + """|class 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 = + """|class Test { + | def fn(x: => Int): Int = x + | fn(2) + |}""".stripMargin + + checkBCode(source) { dir => + assertNoBoxing("fn", findClass("Test", dir).methods) + } + } +} diff --git a/tests/run/t2857.scala b/tests/run/t2857.scala index c523227c4f6c..37c342048333 100644 --- a/tests/run/t2857.scala +++ b/tests/run/t2857.scala @@ -5,5 +5,3 @@ object Test extends App { m.removeBinding(6, "Foo") println(m.contains(6)) } - -