@@ -223,16 +223,19 @@ 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 ], span : Span )(using Context ) =
226+ private def anonymousMirror (monoType : Type , attachment : Property .StickyKey [Unit ], tupleArity : Option [ Int ], span : Span )(using Context ) =
227227 if ctx.isAfterTyper then ctx.compilationUnit.needsMirrorSupport = true
228228 val monoTypeDef = untpd.TypeDef (tpnme.MirroredMonoType , untpd.TypeTree (monoType))
229- val newImpl = untpd.Template (
229+ var newImpl = untpd.Template (
230230 constr = untpd.emptyConstructor,
231231 parents = untpd.TypeTree (defn.ObjectType ) :: 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+ }
236239 typer.typed(untpd.New (newImpl).withSpan(span))
237240
238241 /** The mirror type
@@ -286,6 +289,17 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
286289 private [Synthesizer ] enum MirrorSource :
287290 case ClassSymbol (cls : Symbol )
288291 case Singleton (src : Symbol , tref : TermRef )
292+ case GenericTuple (tps : List [Type ])
293+
294+ /** Tests that both sides are tuples of the same arity */
295+ infix def sameTuple (that : MirrorSource )(using Context ): Boolean =
296+ def arity (msrc : MirrorSource ): Int = msrc match
297+ case GenericTuple (tps) => tps.size
298+ case ClassSymbol (cls) if defn.isTupleClass(cls) => cls.typeParams.size // tested in tests/pos/i13859.scala
299+ case _ => - 1
300+ def equivalent (n : Int , m : Int ) =
301+ n == m && n > 0
302+ equivalent(arity(this ), arity(that))
289303
290304 /** A comparison that chooses the most specific MirrorSource, this is guided by what is necessary for
291305 * `Mirror.Product.fromProduct`. i.e. its result type should be compatible with the erasure of `mirroredType`.
@@ -296,10 +310,15 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
296310 case (ClassSymbol (cls1), ClassSymbol (cls2)) => cls1.isSubClass(cls2)
297311 case (Singleton (src1, _), Singleton (src2, _)) => src1 eq src2
298312 case (_ : ClassSymbol , _ : Singleton ) => false
313+ case _ => this sameTuple that
299314
300315 def show (using Context ): String = this match
301316 case ClassSymbol (cls) => i " $cls"
302317 case Singleton (src, _) => i " $src"
318+ case GenericTuple (tps) =>
319+ val arity = tps.size
320+ if arity <= Definitions .MaxTupleArity then s " class Tuple $arity"
321+ else s " trait Tuple { def size: $arity } "
303322
304323 private [Synthesizer ] object MirrorSource :
305324
@@ -339,6 +358,11 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
339358 reduce(tp.underlying)
340359 case tp : HKTypeLambda if tp.resultType.isInstanceOf [HKTypeLambda ] =>
341360 Left (i " its subpart ` $tp` is not a supported kind (either `*` or `* -> *`) " )
361+ case tp @ AppliedType (tref : TypeRef , _)
362+ if tref.symbol == defn.PairClass
363+ && tp.tupleArity(relaxEmptyTuple = true ) > 0 =>
364+ // avoid type aliases for tuples
365+ Right (MirrorSource .GenericTuple (tp.tupleElementTypes))
342366 case tp : TypeProxy =>
343367 reduce(tp.underlying)
344368 case tp @ AndType (l, r) =>
@@ -361,10 +385,12 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
361385
362386 private def productMirror (mirroredType : Type , formal : Type , span : Span )(using Context ): TreeWithErrors =
363387
364- def makeProductMirror (cls : Symbol ): TreeWithErrors =
388+ def makeProductMirror (cls : Symbol , tps : Option [ List [ Type ]] ): TreeWithErrors =
365389 val accessors = cls.caseAccessors.filterNot(_.isAllOf(PrivateLocal ))
366390 val elemLabels = accessors.map(acc => ConstantType (Constant (acc.name.toString)))
367- val nestedPairs = TypeOps .nestedPairs(accessors.map(mirroredType.resultType.memberInfo(_).widenExpr))
391+ val nestedPairs =
392+ val elems = tps.getOrElse(accessors.map(mirroredType.resultType.memberInfo(_).widenExpr))
393+ TypeOps .nestedPairs(elems)
368394 val (monoType, elemsType) = mirroredType match
369395 case mirroredType : HKTypeLambda =>
370396 (mkMirroredMonoType(mirroredType), mirroredType.derivedLambdaType(resType = nestedPairs))
@@ -380,7 +406,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
380406 }
381407 val mirrorRef =
382408 if cls.useCompanionAsProductMirror then companionPath(mirroredType, span)
383- else anonymousMirror(monoType, ExtendsProductMirror , span)
409+ else anonymousMirror(monoType, ExtendsProductMirror , tps.map(_.size), span)
384410 withNoErrors(mirrorRef.cast(mirrorType))
385411 end makeProductMirror
386412
@@ -400,8 +426,15 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
400426 mirrorCore(defn.Mirror_SingletonClass , mirroredType, mirroredType, singleton.name)
401427 }
402428 withNoErrors(singletonPath.cast(mirrorType))
429+ case MirrorSource .GenericTuple (tps) =>
430+ val maxArity = Definitions .MaxTupleArity
431+ val arity = tps.size
432+ if tps.size <= maxArity then makeProductMirror(defn.TupleType (arity).nn.classSymbol, Some (tps))
433+ else
434+ val reason = s " it reduces to a tuple with arity $arity, expected arity <= $maxArity"
435+ withErrors(i " ${defn.PairClass } is not a generic product because $reason" )
403436 case MirrorSource .ClassSymbol (cls) =>
404- if cls.isGenericProduct then makeProductMirror(cls)
437+ if cls.isGenericProduct then makeProductMirror(cls, None )
405438 else withErrors(i " $cls is not a generic product because ${cls.whyNotGenericProduct}" )
406439 case Left (msg) =>
407440 withErrors(i " type ` $mirroredType` is not a generic product because $msg" )
@@ -412,6 +445,10 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
412445 val (acceptableMsg, cls) = MirrorSource .reduce(mirroredType) match
413446 case Right (MirrorSource .Singleton (_, tp)) => (i " its subpart ` $tp` is a term reference " , NoSymbol )
414447 case Right (MirrorSource .ClassSymbol (cls)) => (" " , cls)
448+ case Right (MirrorSource .GenericTuple (tps)) =>
449+ val arity = tps.size
450+ val cls = if arity <= Definitions .MaxTupleArity then defn.TupleType (arity).nn.classSymbol else defn.PairClass
451+ (" " , cls)
415452 case Left (msg) => (msg, NoSymbol )
416453
417454 val clsIsGenericSum = cls.isGenericSum
@@ -471,7 +508,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
471508 }
472509 val mirrorRef =
473510 if cls.useCompanionAsSumMirror then companionPath(mirroredType, span)
474- else anonymousMirror(monoType, ExtendsSumMirror , span)
511+ else anonymousMirror(monoType, ExtendsSumMirror , None , span)
475512 withNoErrors(mirrorRef.cast(mirrorType))
476513 else if acceptableMsg.nonEmpty then
477514 withErrors(i " type ` $mirroredType` is not a generic sum because $acceptableMsg" )
0 commit comments