Skip to content

Add runtime.TupleMirror to avoid anonymous classes for mirrors of tuples #15404

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 9, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
12 changes: 1 addition & 11 deletions compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)

Expand Down
26 changes: 16 additions & 10 deletions compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))
Expand All @@ -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

Expand Down Expand Up @@ -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")
Expand Down
16 changes: 16 additions & 0 deletions library/src/scala/runtime/TupleMirror.scala
Original file line number Diff line number Diff line change
@@ -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"<tuple-mirror@${Integer.toHexString(hashCode).nn.take(6)}>"
4 changes: 4 additions & 0 deletions tests/run/i15399.check
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions tests/run/i15399.scala
Original file line number Diff line number Diff line change
@@ -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}")