From 1732445da14b4443e3c062d090439939035179d6 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 8 Jun 2022 19:43:34 +0200 Subject: [PATCH 1/4] add runtime.TupleMirror Co-authored-by: Jamie Thompson Co-authored-by: Nicolas Stucki --- library/src/scala/runtime/TupleMirror.scala | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 library/src/scala/runtime/TupleMirror.scala diff --git a/library/src/scala/runtime/TupleMirror.scala b/library/src/scala/runtime/TupleMirror.scala new file mode 100644 index 000000000000..f33d20392a0b --- /dev/null +++ b/library/src/scala/runtime/TupleMirror.scala @@ -0,0 +1,16 @@ +package scala.runtime + +/** A concrete subclass of `scala.deriving.Mirror.Product`, enabling reduction of bytecode size. + * as we do not need to synthesize an anonymous Mirror class at every callsite. + */ +final class TupleMirror(arity: Int) extends scala.deriving.Mirror.Product with Serializable: + assert(arity > 0) // EmptyTuple is not a valid `MirroredType` for TupleMirror + + override type MirroredMonoType <: NonEmptyTuple + + final def fromProduct(product: Product): MirroredMonoType = + if product.productArity != arity then + throw IllegalArgumentException(s"expected Product with $arity elements, got ${product.productArity}") + runtime.Tuples.fromProduct(product).asInstanceOf[MirroredMonoType] + + override final def toString: String = s"" From d1f75557bff673b5308e686dc4e6b620ef1833d0 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 8 Jun 2022 19:46:06 +0200 Subject: [PATCH 2/4] use TupleMirror for tuple mirrors --- .../dotty/tools/dotc/core/Definitions.scala | 2 ++ .../dotc/transform/SyntheticMembers.scala | 12 +-------- .../dotty/tools/dotc/typer/Synthesizer.scala | 26 ++++++++++++------- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 32c48bb46253..7d1591cb4f80 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -915,6 +915,8 @@ class Definitions { def TupleXXL_fromIterator(using Context): Symbol = TupleXXLModule.requiredMethod("fromIterator") + @tu lazy val RuntimeTupleMirrorTypeRef: TypeRef = requiredClassRef("scala.runtime.TupleMirror") + @tu lazy val RuntimeTuplesModule: Symbol = requiredModule("scala.runtime.Tuples") @tu lazy val RuntimeTuplesModuleClass: Symbol = RuntimeTuplesModule.moduleClass lazy val RuntimeTuples_consIterator: Symbol = RuntimeTuplesModule.requiredMethod("consIterator") diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index e0e6870d82d8..faccb79f3c9a 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -26,12 +26,6 @@ object SyntheticMembers { /** Attachment recording that an anonymous class should extend Mirror.Sum */ val ExtendsSumMirror: Property.StickyKey[Unit] = new Property.StickyKey - - /** Attachment recording that an anonymous class (with the ExtendsProductMirror attachment) - * should implement its `fromProduct` method in terms of the runtime class corresponding - * to a tuple with that arity. - */ - val GenericTupleArity: Property.StickyKey[Int] = new Property.StickyKey } /** Synthetic method implementations for case classes, case objects, @@ -607,11 +601,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) { else if (impl.removeAttachment(ExtendsSingletonMirror).isDefined) makeSingletonMirror() else if (impl.removeAttachment(ExtendsProductMirror).isDefined) - val tupleArity = impl.removeAttachment(GenericTupleArity) - val cls = tupleArity match - case Some(n) => defn.TupleType(n).nn.classSymbol - case _ => monoType.typeRef.dealias.classSymbol - makeProductMirror(cls) + makeProductMirror(monoType.typeRef.dealias.classSymbol) else if (impl.removeAttachment(ExtendsSumMirror).isDefined) makeSumMirror(monoType.typeRef.dealias.classSymbol) diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index 549a33efb4e7..4e67a4bfec1d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -223,19 +223,16 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): /** Create an anonymous class `new Object { type MirroredMonoType = ... }` * and mark it with given attachment so that it is made into a mirror at PostTyper. */ - private def anonymousMirror(monoType: Type, attachment: Property.StickyKey[Unit], tupleArity: Option[Int], span: Span)(using Context) = + private def anonymousMirror(monoType: Type, attachment: Property.StickyKey[Unit], span: Span)(using Context) = if ctx.isAfterTyper then ctx.compilationUnit.needsMirrorSupport = true val monoTypeDef = untpd.TypeDef(tpnme.MirroredMonoType, untpd.TypeTree(monoType)) - var newImpl = untpd.Template( + val newImpl = untpd.Template( constr = untpd.emptyConstructor, parents = untpd.TypeTree(defn.ObjectType) :: Nil, derived = Nil, self = EmptyValDef, body = monoTypeDef :: Nil ).withAttachment(attachment, ()) - tupleArity.foreach { n => - newImpl = newImpl.withAttachment(GenericTupleArity, n) - } typer.typed(untpd.New(newImpl).withSpan(span)) /** The mirror type @@ -385,12 +382,17 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): private def productMirror(mirroredType: Type, formal: Type, span: Span)(using Context): TreeWithErrors = + /** `new scala.runtime.TupleMirror(arity)` + * using TupleMirror avoids generating anonymous classes for tuple mirrors. + */ + def newTupleMirror(arity: Int): Tree = + New(defn.RuntimeTupleMirrorTypeRef, Literal(Constant(arity)) :: Nil) + def makeProductMirror(cls: Symbol, tps: Option[List[Type]]): TreeWithErrors = val accessors = cls.caseAccessors.filterNot(_.isAllOf(PrivateLocal)) val elemLabels = accessors.map(acc => ConstantType(Constant(acc.name.toString))) - val nestedPairs = - val elems = tps.getOrElse(accessors.map(mirroredType.resultType.memberInfo(_).widenExpr)) - TypeOps.nestedPairs(elems) + val typeElems = tps.getOrElse(accessors.map(mirroredType.resultType.memberInfo(_).widenExpr)) + val nestedPairs = TypeOps.nestedPairs(typeElems) val (monoType, elemsType) = mirroredType match case mirroredType: HKTypeLambda => (mkMirroredMonoType(mirroredType), mirroredType.derivedLambdaType(resType = nestedPairs)) @@ -406,7 +408,11 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): } val mirrorRef = if cls.useCompanionAsProductMirror then companionPath(mirroredType, span) - else anonymousMirror(monoType, ExtendsProductMirror, tps.map(_.size), span) + else + if defn.isTupleClass(cls) then // add `|| cls == defn.PairClass` when we support TupleXXL + newTupleMirror(arity = typeElems.size) + else + anonymousMirror(monoType, ExtendsProductMirror, span) withNoErrors(mirrorRef.cast(mirrorType)) end makeProductMirror @@ -508,7 +514,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): } val mirrorRef = if cls.useCompanionAsSumMirror then companionPath(mirroredType, span) - else anonymousMirror(monoType, ExtendsSumMirror, None, span) + else anonymousMirror(monoType, ExtendsSumMirror, span) withNoErrors(mirrorRef.cast(mirrorType)) else if acceptableMsg.nonEmpty then withErrors(i"type `$mirroredType` is not a generic sum because $acceptableMsg") From 2ff227b1b98c268eeea616391c5ed4624501c220 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 8 Jun 2022 20:02:55 +0200 Subject: [PATCH 3/4] add test --- tests/run/i15399.check | 4 ++++ tests/run/i15399.scala | 15 +++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 tests/run/i15399.check create mode 100644 tests/run/i15399.scala diff --git a/tests/run/i15399.check b/tests/run/i15399.check new file mode 100644 index 000000000000..832ce7263b47 --- /dev/null +++ b/tests/run/i15399.check @@ -0,0 +1,4 @@ +Expected failure when pass Tuple3 to TupleMirror(2): +- expected Product with 2 elements, got 3 +Expected failure when pass Tuple1 to TupleMirror(2): +- expected Product with 2 elements, got 1 diff --git a/tests/run/i15399.scala b/tests/run/i15399.scala new file mode 100644 index 000000000000..27b27e7f1ae8 --- /dev/null +++ b/tests/run/i15399.scala @@ -0,0 +1,15 @@ +@main def Test = + val tup2Mirror = summon[scala.deriving.Mirror.Of[(Int, Int)]] + try + val tup2a: (Int, Int) = tup2Mirror.fromProduct((1, 2, 3)) // fails silently and creates (1, 2) + println(tup2a) // should be unreachable + catch case err: IllegalArgumentException => + println("Expected failure when pass Tuple3 to TupleMirror(2):") + println(s"- ${err.getMessage}") + + try + val tup2b: (Int, Int) = tup2Mirror.fromProduct(Tuple(1)) // crashes with index out of bounds + println(tup2b) // should be unreachable + catch case err: IllegalArgumentException => + println("Expected failure when pass Tuple1 to TupleMirror(2):") + println(s"- ${err.getMessage}") From 368673ddec9d04ef7bbef59a26f12121fe50689f Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Thu, 9 Jun 2022 10:27:47 +0200 Subject: [PATCH 4/4] address review comments --- compiler/src/dotty/tools/dotc/typer/Synthesizer.scala | 7 ++----- library/src/scala/runtime/TupleMirror.scala | 6 ++---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index 4e67a4bfec1d..868af8200662 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -408,11 +408,8 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): } val mirrorRef = if cls.useCompanionAsProductMirror then companionPath(mirroredType, span) - else - if defn.isTupleClass(cls) then // add `|| cls == defn.PairClass` when we support TupleXXL - newTupleMirror(arity = typeElems.size) - else - anonymousMirror(monoType, ExtendsProductMirror, span) + else if defn.isTupleClass(cls) then newTupleMirror(typeElems.size) // TODO: cls == defn.PairClass when > 22 + else anonymousMirror(monoType, ExtendsProductMirror, span) withNoErrors(mirrorRef.cast(mirrorType)) end makeProductMirror diff --git a/library/src/scala/runtime/TupleMirror.scala b/library/src/scala/runtime/TupleMirror.scala index f33d20392a0b..e6981df54134 100644 --- a/library/src/scala/runtime/TupleMirror.scala +++ b/library/src/scala/runtime/TupleMirror.scala @@ -4,13 +4,11 @@ package scala.runtime * as we do not need to synthesize an anonymous Mirror class at every callsite. */ final class TupleMirror(arity: Int) extends scala.deriving.Mirror.Product with Serializable: - assert(arity > 0) // EmptyTuple is not a valid `MirroredType` for TupleMirror + assert(arity >= 0) // technically could be used for EmptyTuple also, but it has its own singleton mirror. - override type MirroredMonoType <: NonEmptyTuple + override type MirroredMonoType <: Tuple final def fromProduct(product: Product): MirroredMonoType = if product.productArity != arity then throw IllegalArgumentException(s"expected Product with $arity elements, got ${product.productArity}") runtime.Tuples.fromProduct(product).asInstanceOf[MirroredMonoType] - - override final def toString: String = s""