From c87bd056f5010778bc0e0952dd3e0c2e679b6dc2 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 3 May 2022 12:30:59 +0100 Subject: [PATCH 01/15] Formatting: Auto-show lists --- compiler/src/dotty/tools/dotc/printing/Formatting.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index e3f81adeee8a..71cc9cc078d2 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -99,6 +99,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) } From 20edebedf3c03a9fd9cd14620f3fd0a0bbe9da65 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 28 Apr 2022 13:48:58 +0100 Subject: [PATCH 02/15] Add Show[ParamInfo] Requires pushing Show[Showable] down a notch --- compiler/src/dotty/tools/dotc/printing/Formatting.scala | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index 71cc9cc078d2..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 From eeca5f1f4730971285b239178a9ab9fb4039e5a7 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 29 Apr 2022 11:26:44 +0100 Subject: [PATCH 03/15] Fix incorrect use of phase.prev, introduce prevMega --- compiler/src/dotty/tools/dotc/Run.scala | 3 +-- compiler/src/dotty/tools/dotc/core/Phases.scala | 3 +++ compiler/src/dotty/tools/dotc/transform/TreeChecker.scala | 7 +++---- 3 files changed, 7 insertions(+), 6 deletions(-) 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/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/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 3bd3050ee8f1..7a6ededc80fd 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 } From ad0c52b9c990b446bcc6d27f6c4608c7543a7835 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 28 Apr 2022 11:26:00 +0100 Subject: [PATCH 04/15] Implement Tuple specialisation Of both construction (both `new` and `apply`) and of selection (`_1$mcI$sp`). --- compiler/src/dotty/tools/dotc/Compiler.scala | 1 + .../dotty/tools/dotc/core/Definitions.scala | 22 +++++++++ .../src/dotty/tools/dotc/core/NameOps.scala | 9 ++++ .../dotc/core/classfile/ClassfileParser.scala | 5 +- .../dotc/transform/SpecializeTuples.scala | 48 +++++++++++++++++++ tests/pos/i11114.scala | 4 ++ tests/run/i11114.check | 3 ++ tests/run/i11114.scala | 4 ++ 8 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala create mode 100644 tests/pos/i11114.scala create mode 100644 tests/run/i11114.check create mode 100644 tests/run/i11114.scala 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/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index e09b7ca98955..b9332ed19f9a 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1329,6 +1329,8 @@ class Definitions { @tu lazy val TupleType: Array[TypeRef | Null] = mkArityArray("scala.Tuple", MaxTupleArity, 1) + def SpecialisedTuple(base: Symbol, args: List[Type]): 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 +1589,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 +1626,12 @@ 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.typeSymbol) + case List(x, y) => Tuple2SpecializedParamClasses().contains(x.typeSymbol) && Tuple2SpecializedParamClasses().contains(y.typeSymbol) + case _ => false + 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..23292b307233 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -278,6 +278,15 @@ object NameOps { classTags.fold(nme.EMPTY)(_ ++ _) ++ nme.specializedTypeNames.suffix) } + 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/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/transform/SpecializeTuples.scala b/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala new file mode 100644 index 000000000000..acc19fc3bfdd --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala @@ -0,0 +1,48 @@ +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 + +/** 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.name == nme.apply && fun.symbol.exists && defn.isSpecializableTuple(fun.symbol.owner.companionClass, targs.map(_.tpe)) => + Apply(Select(New(defn.SpecialisedTuple(fun.symbol.owner.companionClass, targs.map(_.tpe)).typeRef), nme.CONSTRUCTOR), args) + case Apply(TypeApply(fun: NameTree, targs), args) + if fun.name == nme.CONSTRUCTOR && fun.symbol.exists && defn.isSpecializableTuple(fun.symbol.owner, targs.map(_.tpe)) => + Apply(Select(New(defn.SpecialisedTuple(fun.symbol.owner, targs.map(_.tpe)).typeRef), nme.CONSTRUCTOR), args) + case _ => tree + end transformApply + + override def transformSelect(tree: Select)(using Context): Tree = tree match + case Select(qual, nme._1) if qual.tpe.widen.match + case AppliedType(tycon, args) => defn.isSpecializableTuple(tycon.classSymbol, args) + case _ => false + => Select(qual, nme._1.specializedName(qual.tpe.widen.argInfos.slice(0, 1))) + case Select(qual, nme._2) if qual.tpe.widen.match + case AppliedType(tycon, args) => defn.isSpecializableTuple(tycon.classSymbol, args) + case _ => false + => Select(qual, nme._2.specializedName(qual.tpe.widen.argInfos.slice(1, 2))) + case _ => tree +end SpecializeTuples + +object SpecializeTuples: + val name: String = "specializeTuples" + val description: String = "replaces tuple construction and selection trees" 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..eb1085b259de --- /dev/null +++ b/tests/run/i11114.check @@ -0,0 +1,3 @@ +class scala.Tuple2$mcII$sp +class scala.Tuple2$mcII$sp +class scala.Tuple2$mcII$sp diff --git a/tests/run/i11114.scala b/tests/run/i11114.scala new file mode 100644 index 000000000000..f448509ef4c6 --- /dev/null +++ b/tests/run/i11114.scala @@ -0,0 +1,4 @@ +@main def Test() = + println((1, 1).getClass) + println(Tuple2.apply(1, 1).getClass) + println(new Tuple2(1, 1).getClass) From e6042a56bdf17b706f25b9e50849905ad86f56e2 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 4 May 2022 11:35:35 +0100 Subject: [PATCH 05/15] Workaround TreeChecker & specialised tuples --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 3 +++ compiler/src/dotty/tools/dotc/core/NameOps.scala | 10 ++++++++++ .../dotty/tools/dotc/transform/SpecializeTuples.scala | 4 ++-- .../src/dotty/tools/dotc/transform/TreeChecker.scala | 7 +++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index b9332ed19f9a..1109c96f9762 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1329,6 +1329,9 @@ class Definitions { @tu lazy val TupleType: Array[TypeRef | Null] = mkArityArray("scala.Tuple", MaxTupleArity, 1) + def isSpecializedTuple(cls: Symbol): Boolean = + TupleSpecializedClasses.exists(tupleCls => cls.name.isSpecializedNameOf(tupleCls.name)) + def SpecialisedTuple(base: Symbol, args: List[Type]): Symbol = base.owner.requiredClass(base.name.specializedName(args)) private class FunType(prefix: String): diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 23292b307233..ca0f8c1e3f02 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -278,6 +278,16 @@ object NameOps { classTags.fold(nme.EMPTY)(_ ++ _) ++ nme.specializedTypeNames.suffix) } + def isSpecializedNameOf(base: N)(using Context): Boolean = + import Decorators.* + val sb = new StringBuilder + sb.append(base.toString) + sb.append(nme.specializedTypeNames.prefix.toString) + sb.append(nme.specializedTypeNames.separator) + val prefix = sb.toString() + val suffix = nme.specializedTypeNames.suffix.toString + name.startsWith(prefix) && name.endsWith(suffix) + def specializedName(args: List[Type])(using Context): N = val sb = new StringBuilder sb.append(name.toString) diff --git a/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala b/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala index acc19fc3bfdd..5091e13ef70e 100644 --- a/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala +++ b/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala @@ -24,10 +24,10 @@ class SpecializeTuples extends MiniPhase: override def transformApply(tree: Apply)(using Context): Tree = tree match case Apply(TypeApply(fun: NameTree, targs), args) if fun.name == nme.apply && fun.symbol.exists && defn.isSpecializableTuple(fun.symbol.owner.companionClass, targs.map(_.tpe)) => - Apply(Select(New(defn.SpecialisedTuple(fun.symbol.owner.companionClass, targs.map(_.tpe)).typeRef), nme.CONSTRUCTOR), args) + Apply(Select(New(defn.SpecialisedTuple(fun.symbol.owner.companionClass, targs.map(_.tpe)).typeRef), nme.CONSTRUCTOR), args).withType(tree.tpe) case Apply(TypeApply(fun: NameTree, targs), args) if fun.name == nme.CONSTRUCTOR && fun.symbol.exists && defn.isSpecializableTuple(fun.symbol.owner, targs.map(_.tpe)) => - Apply(Select(New(defn.SpecialisedTuple(fun.symbol.owner, targs.map(_.tpe)).typeRef), nme.CONSTRUCTOR), args) + Apply(Select(New(defn.SpecialisedTuple(fun.symbol.owner, targs.map(_.tpe)).typeRef), nme.CONSTRUCTOR), args).withType(tree.tpe) case _ => tree end transformApply diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 7a6ededc80fd..0a97048743d6 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -421,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 From 49e9d679aa75b6b76a538d27caf57b8da5ca5fd0 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 4 May 2022 15:06:34 +0100 Subject: [PATCH 06/15] Avoid crashing when compiling stdLib213 package scala does not have a member class Tuple2$mcCI$sp while compiling /__w/dotty/dotty/community-build/community-projects/stdLib213/src/library/scala/AnyValCompanion.scala, ... [error] dotty.tools.dotc.core.TypeError: package scala does not have a member class Tuple2$mcCI$sp [error] at dotty.tools.dotc.core.Denotations$Denotation.requiredSymbol(Denotations.scala:305) [error] at dotty.tools.dotc.core.Denotations$Denotation.requiredClass(Denotations.scala:340) [error] at dotty.tools.dotc.core.Definitions.SpecialisedTuple(Definitions.scala:1335) [error] at dotty.tools.dotc.transform.SpecializeTuples.transformApply(SpecializeTuples.scala:27) --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 1109c96f9762..62568efba3e3 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1634,6 +1634,7 @@ class Definitions { case List(x) => Tuple1SpecializedParamClasses().contains(x.typeSymbol) case List(x, y) => Tuple2SpecializedParamClasses().contains(x.typeSymbol) && Tuple2SpecializedParamClasses().contains(y.typeSymbol) case _ => false + && base.owner.denot.info.member(base.name.specializedName(args)).disambiguate(_.isClass).exists def isSpecializableFunction(cls: ClassSymbol, paramTypes: List[Type], retType: Type)(using Context): Boolean = paramTypes.length <= 2 From 23e4361afb7c3fba41df9b690737a03bf5cd75ed Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 16 May 2022 12:36:06 +0100 Subject: [PATCH 07/15] Dedupe/refactor in SpecializeTuples --- .../tools/dotc/transform/SpecializeTuples.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala b/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala index 5091e13ef70e..236b7e11f4a6 100644 --- a/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala +++ b/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala @@ -24,23 +24,23 @@ class SpecializeTuples extends MiniPhase: override def transformApply(tree: Apply)(using Context): Tree = tree match case Apply(TypeApply(fun: NameTree, targs), args) if fun.name == nme.apply && fun.symbol.exists && defn.isSpecializableTuple(fun.symbol.owner.companionClass, targs.map(_.tpe)) => - Apply(Select(New(defn.SpecialisedTuple(fun.symbol.owner.companionClass, targs.map(_.tpe)).typeRef), nme.CONSTRUCTOR), args).withType(tree.tpe) + cpy.Apply(tree)(Select(New(defn.SpecialisedTuple(fun.symbol.owner.companionClass, targs.map(_.tpe)).typeRef), nme.CONSTRUCTOR), args).withType(tree.tpe) case Apply(TypeApply(fun: NameTree, targs), args) if fun.name == nme.CONSTRUCTOR && fun.symbol.exists && defn.isSpecializableTuple(fun.symbol.owner, targs.map(_.tpe)) => - Apply(Select(New(defn.SpecialisedTuple(fun.symbol.owner, targs.map(_.tpe)).typeRef), nme.CONSTRUCTOR), args).withType(tree.tpe) + cpy.Apply(tree)(Select(New(defn.SpecialisedTuple(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 qual.tpe.widen.match - case AppliedType(tycon, args) => defn.isSpecializableTuple(tycon.classSymbol, args) - case _ => false + 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 qual.tpe.widen.match - case AppliedType(tycon, args) => defn.isSpecializableTuple(tycon.classSymbol, args) - case _ => false + 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: From 1d2c6415034bf4f99b0eff0e8d18bd188412d760 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 16 May 2022 15:54:01 +0100 Subject: [PATCH 08/15] Add a "no-boxing" test for specialised tuples --- .../dotc/transform/SpecializeTuples.scala | 8 +-- .../transform/SpecializeTuplesTests.scala | 55 +++++++++++++++++++ 2 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 compiler/test/dotty/tools/dotc/transform/SpecializeTuplesTests.scala diff --git a/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala b/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala index 236b7e11f4a6..22651e5e88c1 100644 --- a/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala +++ b/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala @@ -32,10 +32,10 @@ class SpecializeTuples extends MiniPhase: 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 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 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..939be5f6258c --- /dev/null +++ b/compiler/test/dotty/tools/dotc/transform/SpecializeTuplesTests.scala @@ -0,0 +1,55 @@ +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 => + assertNoBoxing("foo", findClass("Test", dir).methods) + assertNoBoxing("bar", findClass("Test", dir).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 + 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) + } + } +} From 434f7a7a924bafda2ff03a859494694495d9a08a Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 17 May 2022 12:11:19 +0100 Subject: [PATCH 09/15] Match the original apply method name in tuple specialisation ... not the new name from the import. --- .../src/dotty/tools/dotc/transform/SpecializeTuples.scala | 4 ++-- tests/run/i11114.check | 2 ++ tests/run/i11114.scala | 6 ++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala b/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala index 22651e5e88c1..3367442d2914 100644 --- a/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala +++ b/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala @@ -23,10 +23,10 @@ class SpecializeTuples extends MiniPhase: override def transformApply(tree: Apply)(using Context): Tree = tree match case Apply(TypeApply(fun: NameTree, targs), args) - if fun.name == nme.apply && fun.symbol.exists && defn.isSpecializableTuple(fun.symbol.owner.companionClass, targs.map(_.tpe)) => + if fun.symbol.name == nme.apply && fun.symbol.exists && defn.isSpecializableTuple(fun.symbol.owner.companionClass, targs.map(_.tpe)) => cpy.Apply(tree)(Select(New(defn.SpecialisedTuple(fun.symbol.owner.companionClass, targs.map(_.tpe)).typeRef), nme.CONSTRUCTOR), args).withType(tree.tpe) case Apply(TypeApply(fun: NameTree, targs), args) - if fun.name == nme.CONSTRUCTOR && fun.symbol.exists && defn.isSpecializableTuple(fun.symbol.owner, targs.map(_.tpe)) => + if fun.symbol.name == nme.CONSTRUCTOR && fun.symbol.exists && defn.isSpecializableTuple(fun.symbol.owner, targs.map(_.tpe)) => cpy.Apply(tree)(Select(New(defn.SpecialisedTuple(fun.symbol.owner, targs.map(_.tpe)).typeRef), nme.CONSTRUCTOR), args).withType(tree.tpe) case _ => tree end transformApply diff --git a/tests/run/i11114.check b/tests/run/i11114.check index eb1085b259de..a991eaee3628 100644 --- a/tests/run/i11114.check +++ b/tests/run/i11114.check @@ -1,3 +1,5 @@ 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 diff --git a/tests/run/i11114.scala b/tests/run/i11114.scala index f448509ef4c6..bb4209a0e0e2 100644 --- a/tests/run/i11114.scala +++ b/tests/run/i11114.scala @@ -2,3 +2,9 @@ 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) From 573a0c53bd14d46f9e756f32b8e9af79985d9fcb Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 17 May 2022 12:12:58 +0100 Subject: [PATCH 10/15] Expose, tweak & use isElideableExpr in tuple specialisation Handle the contrived example constructing a non-elideable construction of a tuple. Requires exposing isElideableExpr, tweaking its "isCaseClassApply" (to disallow block select qualifiers - while still allowing bare ident applies), also fixing the StableRealizable flag on tuple constructors so that isPureApply is true for those, so they can elideable too. --- .../tools/dotc/core/SymDenotations.scala | 4 + .../core/unpickleScala2/Scala2Unpickler.scala | 4 +- .../dotc/transform/SpecializeTuples.scala | 9 +- .../src/dotty/tools/dotc/typer/Inliner.scala | 125 +++++++++--------- .../src/dotty/tools/dotc/typer/Namer.scala | 5 +- tests/run/i11114.check | 2 + tests/run/i11114.scala | 2 + 7 files changed, 83 insertions(+), 68 deletions(-) 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/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/transform/SpecializeTuples.scala b/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala index 3367442d2914..16e9bbe8bec4 100644 --- a/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala +++ b/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala @@ -6,6 +6,7 @@ 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. * @@ -23,10 +24,14 @@ class SpecializeTuples extends MiniPhase: 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)) => + 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.SpecialisedTuple(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)) => + 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.SpecialisedTuple(fun.symbol.owner, targs.map(_.tpe)).typeRef), nme.CONSTRUCTOR), args).withType(tree.tpe) case _ => tree end transformApply 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/tests/run/i11114.check b/tests/run/i11114.check index a991eaee3628..e65f776de56e 100644 --- a/tests/run/i11114.check +++ b/tests/run/i11114.check @@ -3,3 +3,5 @@ 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 index bb4209a0e0e2..c401e2b99869 100644 --- a/tests/run/i11114.scala +++ b/tests/run/i11114.scala @@ -8,3 +8,5 @@ import Tuple2.{ apply => t2 } println(t2(1, 5).getClass) + + println({ println("initialise"); Tuple2 }.apply(1, 6).getClass) From 599b8f3f3ff479ca233a5f80732cc02b89b99457 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 18 May 2022 09:27:15 +0100 Subject: [PATCH 11/15] Switch isSpecializableTuple to classSymbol --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 62568efba3e3..8cdf8ddb2982 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1631,8 +1631,8 @@ class Definitions { 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.typeSymbol) - case List(x, y) => Tuple2SpecializedParamClasses().contains(x.typeSymbol) && Tuple2SpecializedParamClasses().contains(y.typeSymbol) + 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)).disambiguate(_.isClass).exists From fb11113f8f15b3f6f3e0ccdd6bdfb4482e2249d9 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 19 May 2022 11:17:15 +0100 Subject: [PATCH 12/15] Fix defn.isSpecializedTuple/SpecialisedTuple with Context --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 8cdf8ddb2982..84703ec391d3 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1329,10 +1329,11 @@ class Definitions { @tu lazy val TupleType: Array[TypeRef | Null] = mkArityArray("scala.Tuple", MaxTupleArity, 1) - def isSpecializedTuple(cls: Symbol): Boolean = + def isSpecializedTuple(cls: Symbol)(using Context): Boolean = TupleSpecializedClasses.exists(tupleCls => cls.name.isSpecializedNameOf(tupleCls.name)) - def SpecialisedTuple(base: Symbol, args: List[Type]): Symbol = base.owner.requiredClass(base.name.specializedName(args)) + def SpecialisedTuple(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) @@ -1634,7 +1635,6 @@ class Definitions { 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)).disambiguate(_.isClass).exists def isSpecializableFunction(cls: ClassSymbol, paramTypes: List[Type], retType: Type)(using Context): Boolean = paramTypes.length <= 2 From 02ff089e4a5355ec3dc5194b78ecb671c16bb6e2 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 19 May 2022 11:40:53 +0100 Subject: [PATCH 13/15] Fix bootstrapped SpecializeTuplesTests --- .../dotty/tools/dotc/transform/SpecializeTuplesTests.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/transform/SpecializeTuplesTests.scala b/compiler/test/dotty/tools/dotc/transform/SpecializeTuplesTests.scala index 939be5f6258c..cd894dab86b1 100644 --- a/compiler/test/dotty/tools/dotc/transform/SpecializeTuplesTests.scala +++ b/compiler/test/dotty/tools/dotc/transform/SpecializeTuplesTests.scala @@ -17,8 +17,9 @@ class SpecializeTuplesTests extends DottyBytecodeTest { |}""".stripMargin checkBCode(source) { dir => - assertNoBoxing("foo", findClass("Test", dir).methods) - assertNoBoxing("bar", findClass("Test", dir).methods) + val methods = findClass("Test", dir).methods.nn + assertNoBoxing("foo", methods) + assertNoBoxing("bar", methods) } } @@ -39,7 +40,7 @@ class SpecializeTuplesTests extends DottyBytecodeTest { |}""".stripMargin checkBCode(source) { dir => - val methods = findClass("Test", dir).methods + val methods = findClass("Test", dir).methods.nn assertBoxing("t", methods) assertBoxing("_1", methods) assertBoxing("_2", methods) From 112419995893586ffb2d761958b000441a801a5b Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 20 May 2022 12:20:21 +0100 Subject: [PATCH 14/15] Add back isSpecializableTuple exclusion ... and leave a comment now that I understand it. --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 84703ec391d3..5373fb49f414 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1635,6 +1635,7 @@ class Definitions { 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 From 4c0ab7b95f8f815a20489fefeee9c4d8d167c420 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 30 May 2022 10:15:35 +0100 Subject: [PATCH 15/15] Optimise isSpecializedNameOf & misc docs/renames --- .../dotty/tools/dotc/core/Definitions.scala | 4 ++-- .../src/dotty/tools/dotc/core/NameOps.scala | 23 +++++++++++-------- .../dotc/transform/SpecializeTuples.scala | 4 ++-- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 5373fb49f414..80cdade9ad50 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1330,9 +1330,9 @@ class Definitions { @tu lazy val TupleType: Array[TypeRef | Null] = mkArityArray("scala.Tuple", MaxTupleArity, 1) def isSpecializedTuple(cls: Symbol)(using Context): Boolean = - TupleSpecializedClasses.exists(tupleCls => cls.name.isSpecializedNameOf(tupleCls.name)) + cls.isClass && TupleSpecializedClasses.exists(tupleCls => cls.name.isSpecializedNameOf(tupleCls.name)) - def SpecialisedTuple(base: Symbol, args: List[Type])(using Context): Symbol = + def SpecializedTuple(base: Symbol, args: List[Type])(using Context): Symbol = base.owner.requiredClass(base.name.specializedName(args)) private class FunType(prefix: String): diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index ca0f8c1e3f02..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,16 +279,20 @@ 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 = - import Decorators.* - val sb = new StringBuilder - sb.append(base.toString) - sb.append(nme.specializedTypeNames.prefix.toString) - sb.append(nme.specializedTypeNames.separator) - val prefix = sb.toString() - val suffix = nme.specializedTypeNames.suffix.toString - name.startsWith(prefix) && name.endsWith(suffix) - + 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) diff --git a/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala b/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala index 16e9bbe8bec4..0ee1ffad9239 100644 --- a/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala +++ b/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala @@ -27,12 +27,12 @@ class SpecializeTuples extends MiniPhase: 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.SpecialisedTuple(fun.symbol.owner.companionClass, targs.map(_.tpe)).typeRef), nme.CONSTRUCTOR), args).withType(tree.tpe) + 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.SpecialisedTuple(fun.symbol.owner, targs.map(_.tpe)).typeRef), nme.CONSTRUCTOR), args).withType(tree.tpe) + cpy.Apply(tree)(Select(New(defn.SpecializedTuple(fun.symbol.owner, targs.map(_.tpe)).typeRef), nme.CONSTRUCTOR), args).withType(tree.tpe) case _ => tree end transformApply