diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index d41c57ab116e..ee13c2574d97 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -96,6 +96,7 @@ class Compiler { new InterceptedMethods, // Special handling of `==`, `|=`, `getClass` methods new Getters, // Replace non-private vals and vars with getter defs (fields are added later) new SpecializeFunctions, // Specialized Function{0,1,2} by replacing super with specialized super + new SpecializeTuples, // Specializes Tuples by replacing tuple construction and selection trees 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/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index a489590249ad..871ee481c279 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -282,8 +282,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint private def printTree(last: PrintedTree)(using Context): PrintedTree = { val unit = ctx.compilationUnit - val prevPhase = ctx.phase.prev // can be a mini-phase - val fusedPhase = ctx.base.fusedContaining(prevPhase) + val fusedPhase = ctx.phase.prevMega val echoHeader = f"[[syntax trees at end of $fusedPhase%25s]] // ${unit.source}" val tree = if ctx.isAfterTyper then unit.tpdTree else unit.untpdTree val treeString = tree.show(using ctx.withProperty(XprintMode, Some(()))) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index e09b7ca98955..80cdade9ad50 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1329,6 +1329,12 @@ class Definitions { @tu lazy val TupleType: Array[TypeRef | Null] = mkArityArray("scala.Tuple", MaxTupleArity, 1) + def isSpecializedTuple(cls: Symbol)(using Context): Boolean = + cls.isClass && TupleSpecializedClasses.exists(tupleCls => cls.name.isSpecializedNameOf(tupleCls.name)) + + def SpecializedTuple(base: Symbol, args: List[Type])(using Context): Symbol = + base.owner.requiredClass(base.name.specializedName(args)) + private class FunType(prefix: String): private var classRefs: Array[TypeRef | Null] = new Array(22) def apply(n: Int): TypeRef = @@ -1587,6 +1593,20 @@ class Definitions { def isFunctionType(tp: Type)(using Context): Boolean = isNonRefinedFunction(tp.dropDependentRefinement) + private def withSpecMethods(cls: ClassSymbol, bases: List[Name], paramTypes: Set[TypeRef]) = + for base <- bases; tp <- paramTypes do + cls.enter(newSymbol(cls, base.specializedName(List(tp)), Method, ExprType(tp))) + cls + + @tu lazy val Tuple1: ClassSymbol = withSpecMethods(requiredClass("scala.Tuple1"), List(nme._1), Tuple1SpecializedParamTypes) + @tu lazy val Tuple2: ClassSymbol = withSpecMethods(requiredClass("scala.Tuple2"), List(nme._1, nme._2), Tuple2SpecializedParamTypes) + + @tu lazy val TupleSpecializedClasses: Set[Symbol] = Set(Tuple1, Tuple2) + @tu lazy val Tuple1SpecializedParamTypes: Set[TypeRef] = Set(IntType, LongType, DoubleType) + @tu lazy val Tuple2SpecializedParamTypes: Set[TypeRef] = Set(IntType, LongType, DoubleType, CharType, BooleanType) + @tu lazy val Tuple1SpecializedParamClasses: PerRun[Set[Symbol]] = new PerRun(Tuple1SpecializedParamTypes.map(_.symbol)) + @tu lazy val Tuple2SpecializedParamClasses: PerRun[Set[Symbol]] = new PerRun(Tuple2SpecializedParamTypes.map(_.symbol)) + // Specialized type parameters defined for scala.Function{0,1,2}. @tu lazy val Function1SpecializedParamTypes: collection.Set[TypeRef] = Set(IntType, LongType, FloatType, DoubleType) @@ -1610,6 +1630,13 @@ class Definitions { @tu lazy val Function2SpecializedReturnClasses: PerRun[collection.Set[Symbol]] = new PerRun(Function2SpecializedReturnTypes.map(_.symbol)) + def isSpecializableTuple(base: Symbol, args: List[Type])(using Context): Boolean = + args.length <= 2 && base.isClass && TupleSpecializedClasses.exists(base.asClass.derivesFrom) && args.match + case List(x) => Tuple1SpecializedParamClasses().contains(x.classSymbol) + case List(x, y) => Tuple2SpecializedParamClasses().contains(x.classSymbol) && Tuple2SpecializedParamClasses().contains(y.classSymbol) + case _ => false + && base.owner.denot.info.member(base.name.specializedName(args)).exists // when dotc compiles the stdlib there are no specialised classes + def isSpecializableFunction(cls: ClassSymbol, paramTypes: List[Type], retType: Type)(using Context): Boolean = paramTypes.length <= 2 && (cls.derivesFrom(FunctionClass(paramTypes.length)) || isByNameFunctionClass(cls)) diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index a19f35a9cd9a..3c88917d29c9 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -8,6 +8,7 @@ import scala.io.Codec import Int.MaxValue import Names._, StdNames._, Contexts._, Symbols._, Flags._, NameKinds._, Types._ import util.Chars.{isOperatorPart, digit2int} +import Decorators.* import Definitions._ import nme._ @@ -278,6 +279,29 @@ object NameOps { classTags.fold(nme.EMPTY)(_ ++ _) ++ nme.specializedTypeNames.suffix) } + /** Determines if the current name is the specialized name of the given base name. + * For example `typeName("Tuple2$mcII$sp").isSpecializedNameOf(tpnme.Tuple2) == true` + */ + def isSpecializedNameOf(base: N)(using Context): Boolean = + var i = 0 + inline def nextString(str: String) = name.startsWith(str, i) && { i += str.length; true } + nextString(base.toString) + && nextString(nme.specializedTypeNames.prefix.toString) + && nextString(nme.specializedTypeNames.separator.toString) + && name.endsWith(nme.specializedTypeNames.suffix.toString) + + /** Returns the name of the class specialised to the provided types, + * in the given order. Used for the specialized tuple classes. + */ + def specializedName(args: List[Type])(using Context): N = + val sb = new StringBuilder + sb.append(name.toString) + sb.append(nme.specializedTypeNames.prefix.toString) + sb.append(nme.specializedTypeNames.separator) + args.foreach { arg => sb.append(defn.typeTag(arg)) } + sb.append(nme.specializedTypeNames.suffix) + likeSpacedN(termName(sb.toString)) + /** 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: diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index b72515b518a6..aeb0d2697a0b 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -402,6 +402,9 @@ object Phases { final def prev: Phase = if (id > FirstPhaseId) myBase.phases(start - 1) else NoPhase + final def prevMega(using Context): Phase = + ctx.base.fusedContaining(ctx.phase.prev) + final def next: Phase = if (hasNext) myBase.phases(end + 1) else NoPhase diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index e1a54325af03..1cf85c0f495e 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -92,6 +92,10 @@ object SymDenotations { if (myFlags.is(Trait)) NoInitsInterface & bodyFlags // no parents are initialized from a trait else NoInits & bodyFlags & parentFlags) + final def setStableConstructor()(using Context): Unit = + val ctorStable = if myFlags.is(Trait) then myFlags.is(NoInits) else isNoInitsRealClass + if ctorStable then primaryConstructor.setFlag(StableRealizable) + def isCurrent(fs: FlagSet)(using Context): Boolean = def knownFlags(info: Type): FlagSet = info match case _: SymbolLoader | _: ModuleCompleter => FromStartFlags diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index 8550ffb5fd8e..0a24bc355fe8 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -1017,7 +1017,10 @@ class ClassfileParser( else return unpickleTASTY(bytes) } - if (scan(tpnme.ScalaATTR) && !scalaUnpickleWhitelist.contains(classRoot.name)) + if scan(tpnme.ScalaATTR) && !scalaUnpickleWhitelist.contains(classRoot.name) + && !(classRoot.name.startsWith("Tuple") && classRoot.name.endsWith("$sp")) + && !(classRoot.name.startsWith("Product") && classRoot.name.endsWith("$sp")) + then // To understand the situation, it's helpful to know that: // - Scalac emits the `ScalaSig` attribute for classfiles with pickled information // and the `Scala` attribute for everything else. diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index dbd3e15dcc2d..9499e5ebf41e 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -617,7 +617,9 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas // we need the checkNonCyclic call to insert LazyRefs for F-bounded cycles else if (!denot.is(Param)) tp1.translateFromRepeated(toArray = false) else tp1 - if (denot.isConstructor) addConstructorTypeParams(denot) + if (denot.isConstructor) + denot.owner.setStableConstructor() + addConstructorTypeParams(denot) if (atEnd) assert(!denot.symbol.isSuperAccessor, denot) else { diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index e3f81adeee8a..671a954c5180 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -33,9 +33,16 @@ object Formatting { object ShowAny extends Show[Any]: def show(x: Any): Shown = x - class ShowImplicits2: + class ShowImplicits3: given Show[Product] = ShowAny + class ShowImplicits2 extends ShowImplicits3: + given Show[ParamInfo] with + def show(x: ParamInfo) = x match + case x: Symbol => Show[x.type].show(x) + case x: LambdaParam => Show[x.type].show(x) + case _ => ShowAny + class ShowImplicits1 extends ShowImplicits2: given Show[ImplicitRef] = ShowAny given Show[Names.Designator] = ShowAny @@ -99,6 +106,8 @@ object Formatting { val sep = StringContext.processEscapes(rawsep) if (rest.nonEmpty) (arg.map(showArg).mkString(sep), rest.tail) else (arg, suffix) + case arg: Seq[?] => + (arg.map(showArg).mkString("[", ", ", "]"), suffix) case _ => (showArg(arg), suffix) } diff --git a/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala b/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala new file mode 100644 index 000000000000..0ee1ffad9239 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala @@ -0,0 +1,53 @@ +package dotty.tools +package 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 typer.Inliner.isElideableExpr + +/** Specializes Tuples by replacing tuple construction and selection trees. + * + * Specifically: + * 1. Replaces `(1, 1)` (which is `Tuple2.apply[Int, Int](1, 1)`) and + * `new Tuple2[Int, Int](1, 1)` with `new Tuple2$mcII$sp(1, 1)`. + * 2. Replaces `(_: Tuple2[Int, Int])._1` with `(_: Tuple2[Int, Int])._1$mcI$sp` + */ +class SpecializeTuples extends MiniPhase: + import tpd.* + + override def phaseName: String = SpecializeTuples.name + override def description: String = SpecializeTuples.description + override def isEnabled(using Context): Boolean = !ctx.settings.scalajs.value + + override def transformApply(tree: Apply)(using Context): Tree = tree match + case Apply(TypeApply(fun: NameTree, targs), args) + if fun.symbol.name == nme.apply && fun.symbol.exists && defn.isSpecializableTuple(fun.symbol.owner.companionClass, targs.map(_.tpe)) + && isElideableExpr(tree) + => + cpy.Apply(tree)(Select(New(defn.SpecializedTuple(fun.symbol.owner.companionClass, targs.map(_.tpe)).typeRef), nme.CONSTRUCTOR), args).withType(tree.tpe) + case Apply(TypeApply(fun: NameTree, targs), args) + if fun.symbol.name == nme.CONSTRUCTOR && fun.symbol.exists && defn.isSpecializableTuple(fun.symbol.owner, targs.map(_.tpe)) + && isElideableExpr(tree) + => + cpy.Apply(tree)(Select(New(defn.SpecializedTuple(fun.symbol.owner, targs.map(_.tpe)).typeRef), nme.CONSTRUCTOR), args).withType(tree.tpe) + case _ => tree + end transformApply + + override def transformSelect(tree: Select)(using Context): Tree = tree match + case Select(qual, nme._1) if isAppliedSpecializableTuple(qual.tpe.widen) => + Select(qual, nme._1.specializedName(qual.tpe.widen.argInfos.slice(0, 1))) + case Select(qual, nme._2) if isAppliedSpecializableTuple(qual.tpe.widen) => + Select(qual, nme._2.specializedName(qual.tpe.widen.argInfos.slice(1, 2))) + case _ => tree + + private def isAppliedSpecializableTuple(tp: Type)(using Context) = tp match + case AppliedType(tycon, args) => defn.isSpecializableTuple(tycon.classSymbol, args) + case _ => false +end SpecializeTuples + +object SpecializeTuples: + val name: String = "specializeTuples" + val description: String = "replaces tuple construction and selection trees" diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 3bd3050ee8f1..0a97048743d6 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -91,7 +91,7 @@ class TreeChecker extends Phase with SymTransformer { if (ctx.phaseId <= erasurePhase.id) { val initial = symd.initial assert(symd == initial || symd.signature == initial.signature, - i"""Signature of ${sym.showLocated} changed at phase ${ctx.base.fusedContaining(ctx.phase.prev)} + i"""Signature of ${sym.showLocated} changed at phase ${ctx.phase.prevMega} |Initial info: ${initial.info} |Initial sig : ${initial.signature} |Current info: ${symd.info} @@ -122,8 +122,7 @@ class TreeChecker extends Phase with SymTransformer { } def check(phasesToRun: Seq[Phase], ctx: Context): Tree = { - val prevPhase = ctx.phase.prev // can be a mini-phase - val fusedPhase = ctx.base.fusedContaining(prevPhase) + val fusedPhase = ctx.phase.prevMega(using ctx) report.echo(s"checking ${ctx.compilationUnit} after phase ${fusedPhase}")(using ctx) inContext(ctx) { @@ -145,7 +144,7 @@ class TreeChecker extends Phase with SymTransformer { catch { case NonFatal(ex) => //TODO CHECK. Check that we are bootstrapped inContext(checkingCtx) { - println(i"*** error while checking ${ctx.compilationUnit} after phase ${ctx.phase.prev} ***") + println(i"*** error while checking ${ctx.compilationUnit} after phase ${ctx.phase.prevMega(using ctx)} ***") } throw ex } @@ -422,6 +421,13 @@ class TreeChecker extends Phase with SymTransformer { assert(tree.qual.typeOpt.isInstanceOf[ThisType], i"expect prefix of Super to be This, actual = ${tree.qual}") super.typedSuper(tree, pt) + override def typedApply(tree: untpd.Apply, pt: Type)(using Context): Tree = tree match + case Apply(Select(qual, nme.CONSTRUCTOR), _) + if !ctx.phase.erasedTypes + && defn.isSpecializedTuple(qual.typeOpt.typeSymbol) => + promote(tree) // e.g. `new Tuple2$mcII$sp(7, 8)` should keep its `(7, 8)` type instead of `Tuple2$mcII$sp` + case _ => super.typedApply(tree, pt) + override def typedTyped(tree: untpd.Typed, pt: Type)(using Context): Tree = val tpt1 = checkSimpleKinded(typedType(tree.tpt)) val expr1 = tree.expr match diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index d097ed8dcc4e..d24282a6688e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -411,6 +411,70 @@ object Inliner { } + /** Very similar to TreeInfo.isPureExpr, but with the following inliner-only exceptions: + * - synthetic case class apply methods, when the case class constructor is empty, are + * elideable but not pure. Elsewhere, accessing the apply method might cause the initialization + * of a containing object so they are merely idempotent. + */ + object isElideableExpr { + def isStatElideable(tree: Tree)(using Context): Boolean = unsplice(tree) match { + case EmptyTree + | TypeDef(_, _) + | Import(_, _) + | DefDef(_, _, _, _) => + true + case vdef @ ValDef(_, _, _) => + if (vdef.symbol.flags is Mutable) false else apply(vdef.rhs) + case _ => + false + } + + def apply(tree: Tree)(using Context): Boolean = unsplice(tree) match { + case EmptyTree + | This(_) + | Super(_, _) + | Literal(_) => + true + case Ident(_) => + isPureRef(tree) || tree.symbol.isAllOf(Inline | Param) + case Select(qual, _) => + if (tree.symbol.is(Erased)) true + else isPureRef(tree) && apply(qual) + case New(_) | Closure(_, _, _) => + true + case TypeApply(fn, _) => + if (fn.symbol.is(Erased) || fn.symbol == defn.QuotedTypeModule_of) true else apply(fn) + case Apply(fn, args) => + val isCaseClassApply = { + val cls = tree.tpe.classSymbol + val meth = fn.symbol + meth.name == nme.apply && + meth.flags.is(Synthetic) && + meth.owner.linkedClass.is(Case) && + cls.isNoInitsRealClass && + funPart(fn).match + case Select(qual, _) => qual.symbol.is(Synthetic) // e.g: disallow `{ ..; Foo }.apply(..)` + case meth @ Ident(_) => meth.symbol.is(Synthetic) // e.g: allow `import Foo.{ apply => foo }; foo(..)` + case _ => false + } + if isPureApply(tree, fn) then + apply(fn) && args.forall(apply) + else if (isCaseClassApply) + args.forall(apply) + else if (fn.symbol.is(Erased)) true + else false + case Typed(expr, _) => + apply(expr) + case Block(stats, expr) => + apply(expr) && stats.forall(isStatElideable) + case Inlined(_, bindings, expr) => + apply(expr) && bindings.forall(isStatElideable) + case NamedArg(_, expr) => + apply(expr) + case _ => + false + } + } } /** Produces an inlined version of `call` via its `inlined` method. @@ -691,67 +755,6 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { || tpe.cls.is(Package) || tpe.cls.isStaticOwner && !(tpe.cls.seesOpaques && inlinedMethod.isContainedIn(tpe.cls)) - /** Very similar to TreeInfo.isPureExpr, but with the following inliner-only exceptions: - * - synthetic case class apply methods, when the case class constructor is empty, are - * elideable but not pure. Elsewhere, accessing the apply method might cause the initialization - * of a containing object so they are merely idempotent. - */ - object isElideableExpr { - def isStatElideable(tree: Tree)(using Context): Boolean = unsplice(tree) match { - case EmptyTree - | TypeDef(_, _) - | Import(_, _) - | DefDef(_, _, _, _) => - true - case vdef @ ValDef(_, _, _) => - if (vdef.symbol.flags is Mutable) false else apply(vdef.rhs) - case _ => - false - } - - def apply(tree: Tree): Boolean = unsplice(tree) match { - case EmptyTree - | This(_) - | Super(_, _) - | Literal(_) => - true - case Ident(_) => - isPureRef(tree) || tree.symbol.isAllOf(Inline | Param) - case Select(qual, _) => - if (tree.symbol.is(Erased)) true - else isPureRef(tree) && apply(qual) - case New(_) | Closure(_, _, _) => - true - case TypeApply(fn, _) => - if (fn.symbol.is(Erased) || fn.symbol == defn.QuotedTypeModule_of) true else apply(fn) - case Apply(fn, args) => - val isCaseClassApply = { - val cls = tree.tpe.classSymbol - val meth = fn.symbol - meth.name == nme.apply && - meth.flags.is(Synthetic) && - meth.owner.linkedClass.is(Case) && - cls.isNoInitsRealClass - } - if isPureApply(tree, fn) then - apply(fn) && args.forall(apply) - else if (isCaseClassApply) - args.forall(apply) - else if (fn.symbol.is(Erased)) true - else false - case Typed(expr, _) => - apply(expr) - case Block(stats, expr) => - apply(expr) && stats.forall(isStatElideable) - case Inlined(_, bindings, expr) => - apply(expr) && bindings.forall(isStatElideable) - case NamedArg(_, expr) => - apply(expr) - case _ => - false - } - } - /** Populate `thisProxy` and `paramProxy` as follows: * * 1a. If given type refers to a static this, thisProxy binds it to corresponding global reference, diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 2b85d442e959..d2d782df2c80 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1442,10 +1442,7 @@ class Namer { typer: Typer => cls.baseClasses.foreach(_.invalidateBaseTypeCache()) // we might have looked before and found nothing cls.invalidateMemberCaches() // we might have checked for a member when parents were not known yet. cls.setNoInitsFlags(parentsKind(parents), untpd.bodyKind(rest)) - val ctorStable = - if cls.is(Trait) then cls.is(NoInits) - else cls.isNoInitsRealClass - if ctorStable then cls.primaryConstructor.setFlag(StableRealizable) + cls.setStableConstructor() processExports(using localCtx) defn.patchStdLibClass(cls) addConstructorProxies(cls) diff --git a/compiler/test/dotty/tools/dotc/transform/SpecializeTuplesTests.scala b/compiler/test/dotty/tools/dotc/transform/SpecializeTuplesTests.scala new file mode 100644 index 000000000000..cd894dab86b1 --- /dev/null +++ b/compiler/test/dotty/tools/dotc/transform/SpecializeTuplesTests.scala @@ -0,0 +1,56 @@ +package dotty.tools +package dotc +package transform + +import org.junit.Test + +import dotty.tools.backend.jvm.DottyBytecodeTest + +import scala.jdk.CollectionConverters.* + +class SpecializeTuplesTests extends DottyBytecodeTest { + @Test def noBoxing = { + given source: String = + """|class Test { + | def foo: (Int, Int) = (1, 1) + | def bar: Int = foo._1 + |}""".stripMargin + + checkBCode(source) { dir => + val methods = findClass("Test", dir).methods.nn + assertNoBoxing("foo", methods) + assertNoBoxing("bar", methods) + } + } + + @Test def boxing = { + // Pick a tuple type that isn't specialised. + given source: String = + """|class Test { + | def t: (Boolean, Byte, Short, Char, Int, Long, Float, Double, AnyRef) = (true, 8, 16, 'c', 32, 64L, 32.0f, 64.0, this) + | def _1 = t._1 + | def _2 = t._2 + | def _3 = t._3 + | def _4 = t._4 + | def _5 = t._5 + | def _6 = t._6 + | def _7 = t._7 + | def _8 = t._8 + | def _9 = t._9 + |}""".stripMargin + + checkBCode(source) { dir => + val methods = findClass("Test", dir).methods.nn + assertBoxing("t", methods) + assertBoxing("_1", methods) + assertBoxing("_2", methods) + assertBoxing("_3", methods) + assertBoxing("_4", methods) + assertBoxing("_5", methods) + assertBoxing("_6", methods) + assertBoxing("_7", methods) + assertBoxing("_8", methods) + assertNoBoxing("_9", methods) + } + } +} diff --git a/tests/pos/i11114.scala b/tests/pos/i11114.scala new file mode 100644 index 000000000000..0c67da39acda --- /dev/null +++ b/tests/pos/i11114.scala @@ -0,0 +1,4 @@ +class Foo { + val x: (Int, Int) = (1, 1) + val y: Int = x._1 +} diff --git a/tests/run/i11114.check b/tests/run/i11114.check new file mode 100644 index 000000000000..e65f776de56e --- /dev/null +++ b/tests/run/i11114.check @@ -0,0 +1,7 @@ +class scala.Tuple2$mcII$sp +class scala.Tuple2$mcII$sp +class scala.Tuple2$mcII$sp +class scala.Tuple2$mcII$sp +class scala.Tuple2$mcII$sp +initialise +class scala.Tuple2 diff --git a/tests/run/i11114.scala b/tests/run/i11114.scala new file mode 100644 index 000000000000..c401e2b99869 --- /dev/null +++ b/tests/run/i11114.scala @@ -0,0 +1,12 @@ +@main def Test() = + println((1, 1).getClass) + println(Tuple2.apply(1, 1).getClass) + println(new Tuple2(1, 1).getClass) + + import Tuple2.apply + println(apply(1, 4).getClass) + + import Tuple2.{ apply => t2 } + println(t2(1, 5).getClass) + + println({ println("initialise"); Tuple2 }.apply(1, 6).getClass)