Skip to content

Commit 47fa175

Browse files
authored
Merge pull request #15404 from dotty-staging/add-runtime-tuplemirror
Add runtime.TupleMirror to avoid anonymous classes for mirrors of tuples
2 parents 3f92ce9 + 368673d commit 47fa175

File tree

6 files changed

+49
-21
lines changed

6 files changed

+49
-21
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,8 @@ class Definitions {
915915

916916
def TupleXXL_fromIterator(using Context): Symbol = TupleXXLModule.requiredMethod("fromIterator")
917917

918+
@tu lazy val RuntimeTupleMirrorTypeRef: TypeRef = requiredClassRef("scala.runtime.TupleMirror")
919+
918920
@tu lazy val RuntimeTuplesModule: Symbol = requiredModule("scala.runtime.Tuples")
919921
@tu lazy val RuntimeTuplesModuleClass: Symbol = RuntimeTuplesModule.moduleClass
920922
lazy val RuntimeTuples_consIterator: Symbol = RuntimeTuplesModule.requiredMethod("consIterator")

compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,6 @@ object SyntheticMembers {
2626

2727
/** Attachment recording that an anonymous class should extend Mirror.Sum */
2828
val ExtendsSumMirror: Property.StickyKey[Unit] = new Property.StickyKey
29-
30-
/** Attachment recording that an anonymous class (with the ExtendsProductMirror attachment)
31-
* should implement its `fromProduct` method in terms of the runtime class corresponding
32-
* to a tuple with that arity.
33-
*/
34-
val GenericTupleArity: Property.StickyKey[Int] = new Property.StickyKey
3529
}
3630

3731
/** Synthetic method implementations for case classes, case objects,
@@ -607,11 +601,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
607601
else if (impl.removeAttachment(ExtendsSingletonMirror).isDefined)
608602
makeSingletonMirror()
609603
else if (impl.removeAttachment(ExtendsProductMirror).isDefined)
610-
val tupleArity = impl.removeAttachment(GenericTupleArity)
611-
val cls = tupleArity match
612-
case Some(n) => defn.TupleType(n).nn.classSymbol
613-
case _ => monoType.typeRef.dealias.classSymbol
614-
makeProductMirror(cls)
604+
makeProductMirror(monoType.typeRef.dealias.classSymbol)
615605
else if (impl.removeAttachment(ExtendsSumMirror).isDefined)
616606
makeSumMirror(monoType.typeRef.dealias.classSymbol)
617607

compiler/src/dotty/tools/dotc/typer/Synthesizer.scala

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -223,19 +223,16 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
223223
/** Create an anonymous class `new Object { type MirroredMonoType = ... }`
224224
* and mark it with given attachment so that it is made into a mirror at PostTyper.
225225
*/
226-
private def anonymousMirror(monoType: Type, attachment: Property.StickyKey[Unit], tupleArity: Option[Int], span: Span)(using Context) =
226+
private def anonymousMirror(monoType: Type, attachment: Property.StickyKey[Unit], span: Span)(using Context) =
227227
if ctx.isAfterTyper then ctx.compilationUnit.needsMirrorSupport = true
228228
val monoTypeDef = untpd.TypeDef(tpnme.MirroredMonoType, untpd.TypeTree(monoType))
229-
var newImpl = untpd.Template(
229+
val newImpl = untpd.Template(
230230
constr = untpd.emptyConstructor,
231231
parents = untpd.TypeTree(defn.ObjectType) :: untpd.TypeTree(defn.JavaSerializableClass.typeRef) :: Nil,
232232
derived = Nil,
233233
self = EmptyValDef,
234234
body = monoTypeDef :: Nil
235235
).withAttachment(attachment, ())
236-
tupleArity.foreach { n =>
237-
newImpl = newImpl.withAttachment(GenericTupleArity, n)
238-
}
239236
typer.typed(untpd.New(newImpl).withSpan(span))
240237

241238
/** The mirror type
@@ -385,12 +382,17 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
385382

386383
private def productMirror(mirroredType: Type, formal: Type, span: Span)(using Context): TreeWithErrors =
387384

385+
/** `new scala.runtime.TupleMirror(arity)`
386+
* using TupleMirror avoids generating anonymous classes for tuple mirrors.
387+
*/
388+
def newTupleMirror(arity: Int): Tree =
389+
New(defn.RuntimeTupleMirrorTypeRef, Literal(Constant(arity)) :: Nil)
390+
388391
def makeProductMirror(cls: Symbol, tps: Option[List[Type]]): TreeWithErrors =
389392
val accessors = cls.caseAccessors.filterNot(_.isAllOf(PrivateLocal))
390393
val elemLabels = accessors.map(acc => ConstantType(Constant(acc.name.toString)))
391-
val nestedPairs =
392-
val elems = tps.getOrElse(accessors.map(mirroredType.resultType.memberInfo(_).widenExpr))
393-
TypeOps.nestedPairs(elems)
394+
val typeElems = tps.getOrElse(accessors.map(mirroredType.resultType.memberInfo(_).widenExpr))
395+
val nestedPairs = TypeOps.nestedPairs(typeElems)
394396
val (monoType, elemsType) = mirroredType match
395397
case mirroredType: HKTypeLambda =>
396398
(mkMirroredMonoType(mirroredType), mirroredType.derivedLambdaType(resType = nestedPairs))
@@ -406,7 +408,8 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
406408
}
407409
val mirrorRef =
408410
if cls.useCompanionAsProductMirror then companionPath(mirroredType, span)
409-
else anonymousMirror(monoType, ExtendsProductMirror, tps.map(_.size), span)
411+
else if defn.isTupleClass(cls) then newTupleMirror(typeElems.size) // TODO: cls == defn.PairClass when > 22
412+
else anonymousMirror(monoType, ExtendsProductMirror, span)
410413
withNoErrors(mirrorRef.cast(mirrorType))
411414
end makeProductMirror
412415

@@ -508,7 +511,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
508511
}
509512
val mirrorRef =
510513
if cls.useCompanionAsSumMirror then companionPath(mirroredType, span)
511-
else anonymousMirror(monoType, ExtendsSumMirror, None, span)
514+
else anonymousMirror(monoType, ExtendsSumMirror, span)
512515
withNoErrors(mirrorRef.cast(mirrorType))
513516
else if acceptableMsg.nonEmpty then
514517
withErrors(i"type `$mirroredType` is not a generic sum because $acceptableMsg")
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package scala.runtime
2+
3+
/** A concrete subclass of `scala.deriving.Mirror.Product`, enabling reduction of bytecode size.
4+
* as we do not need to synthesize an anonymous Mirror class at every callsite.
5+
*/
6+
final class TupleMirror(arity: Int) extends scala.deriving.Mirror.Product with Serializable:
7+
assert(arity >= 0) // technically could be used for EmptyTuple also, but it has its own singleton mirror.
8+
9+
override type MirroredMonoType <: Tuple
10+
11+
final def fromProduct(product: Product): MirroredMonoType =
12+
if product.productArity != arity then
13+
throw IllegalArgumentException(s"expected Product with $arity elements, got ${product.productArity}")
14+
runtime.Tuples.fromProduct(product).asInstanceOf[MirroredMonoType]

tests/run/i15399.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Expected failure when pass Tuple3 to TupleMirror(2):
2+
- expected Product with 2 elements, got 3
3+
Expected failure when pass Tuple1 to TupleMirror(2):
4+
- expected Product with 2 elements, got 1

tests/run/i15399.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
@main def Test =
2+
val tup2Mirror = summon[scala.deriving.Mirror.Of[(Int, Int)]]
3+
try
4+
val tup2a: (Int, Int) = tup2Mirror.fromProduct((1, 2, 3)) // fails silently and creates (1, 2)
5+
println(tup2a) // should be unreachable
6+
catch case err: IllegalArgumentException =>
7+
println("Expected failure when pass Tuple3 to TupleMirror(2):")
8+
println(s"- ${err.getMessage}")
9+
10+
try
11+
val tup2b: (Int, Int) = tup2Mirror.fromProduct(Tuple(1)) // crashes with index out of bounds
12+
println(tup2b) // should be unreachable
13+
catch case err: IllegalArgumentException =>
14+
println("Expected failure when pass Tuple1 to TupleMirror(2):")
15+
println(s"- ${err.getMessage}")

0 commit comments

Comments
 (0)