@@ -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
@@ -276,22 +279,105 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
276279 case t => mapOver(t)
277280 monoMap(mirroredType.resultType)
278281
282+ private [Synthesizer ] enum MirrorSource :
283+ case ClassSymbol (cls : Symbol )
284+ case GenericTuple (tuplArity : Int , tpArgs : List [Type ])
285+
286+ def isGenericTuple : Boolean = this .isInstanceOf [GenericTuple ]
287+
288+ /** tuple arity, works for TupleN classes and generic tuples */
289+ final def arity (using Context ): Int = this match
290+ case GenericTuple (arity, _) => arity
291+ case ClassSymbol (cls) if defn.isTupleClass(cls) => cls.typeParams.length
292+ case _ => - 1
293+
294+ def equiv (that : MirrorSource )(using Context ): Boolean = (this .arity, that.arity) match
295+ case (n, m) if n > 0 || m > 0 =>
296+ // we shortcut when at least one was a tuple.
297+ // This protects us from comparing classes for two TupleXXL with different arities.
298+ n == m
299+ case _ => this .asClass eq that.asClass // class equality otherwise
300+
301+ def isSub (that : MirrorSource )(using Context ): Boolean = (this .arity, that.arity) match
302+ case (n, m) if n > 0 || m > 0 =>
303+ // we shortcut when at least one was a tuple.
304+ // This protects us from comparing classes for two TupleXXL with different arities.
305+ n == m
306+ case _ => this .asClass isSubClass that.asClass
307+
308+ def asClass (using Context ): Symbol = this match
309+ case ClassSymbol (cls) => cls
310+ case GenericTuple (arity, _) =>
311+ if arity <= Definitions .MaxTupleArity then defn.TupleType (arity).nn.classSymbol
312+ else defn.TupleXXLClass
313+
314+ object MirrorSource :
315+ def tuple (tps : List [Type ]): MirrorSource .GenericTuple = MirrorSource .GenericTuple (tps.size, tps)
316+
317+ end MirrorSource
318+
279319 private def productMirror (mirroredType : Type , formal : Type , span : Span )(using Context ): TreeWithErrors =
280320
281- def whyNotAcceptableType (tp : Type , cls : Symbol ): String = tp match
321+ extension (msrc : MirrorSource ) def isGenericProd (using Context ) =
322+ msrc.isGenericTuple || msrc.asClass.isGenericProduct
323+
324+ /** Follows `classSymbol`, but instead reduces to a proxy of a generic tuple (or a scala.TupleN class).
325+ *
326+ * Does not need to consider AndType, as that is already stripped.
327+ */
328+ def tupleProxy (tp : Type )(using Context ): Option [MirrorSource ] = tp match
329+ case tp : TypeRef => if tp.symbol.isClass then None else tupleProxy(tp.superType)
330+ case GenericTupleType (args) => Some (MirrorSource .tuple(args))
331+ case tp : TypeProxy =>
332+ tupleProxy(tp.underlying)
333+ case tp : OrType =>
334+ if tp.tp1.hasClassSymbol(defn.NothingClass ) then
335+ tupleProxy(tp.tp2)
336+ else if tp.tp2.hasClassSymbol(defn.NothingClass ) then
337+ tupleProxy(tp.tp1)
338+ else tupleProxy(tp.join)
339+ case _ =>
340+ None
341+
342+ def mirrorSource (tp : Type )(using Context ): Option [MirrorSource ] =
343+ val fromClass = tp.classSymbol
344+ if fromClass.exists then // test if it could be reduced to a generic tuple
345+ if fromClass.isSubClass(defn.TupleClass ) && ! defn.isTupleClass(fromClass) then tupleProxy(tp)
346+ else Some (MirrorSource .ClassSymbol (fromClass))
347+ else None
348+
349+ /** do all parts match the class symbol? */
350+ def whyNotAcceptableType (tp : Type , msrc : MirrorSource ): String = tp match
282351 case tp : HKTypeLambda if tp.resultType.isInstanceOf [HKTypeLambda ] =>
283352 i " its subpart $tp is not a supported kind (either `*` or `* -> *`) "
284- case tp : TypeProxy => whyNotAcceptableType(tp.underlying, cls)
285353 case OrType (tp1, tp2) =>
286- Seq (tp1, tp2).map(whyNotAcceptableType(_, cls)).find(_.nonEmpty).getOrElse(" " )
287- case _ =>
288- if tp.classSymbol eq cls then " "
289- else i " a subpart reduces to the more precise ${tp.classSymbol}, expected $cls"
354+ Seq (tp1, tp2).map(whyNotAcceptableType(_, msrc)).find(_.nonEmpty).getOrElse(" " )
355+ case GenericTupleType (args) if args.size <= Definitions .MaxTupleArity =>
356+ val tup = MirrorSource .tuple(args)
357+ if tup.equiv(msrc) then " "
358+ else i " a subpart reduces to the unrelated tuple ${tup.asClass}, expected ${msrc.asClass}"
359+ case tp : TypeProxy => whyNotAcceptableType(tp.underlying, msrc)
360+ case _ =>
361+ mirrorSource(tp) match
362+ case Some (msrc2) =>
363+ if msrc2.equiv(msrc) then " "
364+ else i " a subpart reduces to the more precise ${msrc2.asClass}, expected ${msrc.asClass}"
365+ case _ => " ???" // caught early by initial `mirrorSource` that made `msrc`
366+
367+ /** widen TermRef to see if they are an alias to an enum singleton */
368+ def isEnumSingletonRef (tp : Type )(using Context ): Boolean = tp match
369+ case tp : TermRef =>
370+ val sym = tp.termSymbol
371+ sym.isEnumCase || (! tp.isOverloaded && isEnumSingletonRef(tp.underlying.widenExpr))
372+ case _ => false
290373
291- def makeProductMirror (cls : Symbol ): TreeWithErrors =
292- val accessors = cls.caseAccessors.filterNot(_.isAllOf(PrivateLocal ))
374+ def makeProductMirror (msrc : MirrorSource ): TreeWithErrors =
375+ val mirroredClass = msrc.asClass
376+ val accessors = mirroredClass.caseAccessors.filterNot(_.isAllOf(PrivateLocal ))
293377 val elemLabels = accessors.map(acc => ConstantType (Constant (acc.name.toString)))
294- val nestedPairs = TypeOps .nestedPairs(accessors.map(mirroredType.resultType.memberInfo(_).widenExpr))
378+ val nestedPairs = msrc match
379+ case MirrorSource .GenericTuple (_, args) => TypeOps .nestedPairs(args)
380+ case _ => TypeOps .nestedPairs(accessors.map(mirroredType.resultType.memberInfo(_).widenExpr))
295381 val (monoType, elemsType) = mirroredType match
296382 case mirroredType : HKTypeLambda =>
297383 (mkMirroredMonoType(mirroredType), mirroredType.derivedLambdaType(resType = nestedPairs))
@@ -301,22 +387,19 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
301387 checkRefinement(formal, tpnme.MirroredElemTypes , elemsType, span)
302388 checkRefinement(formal, tpnme.MirroredElemLabels , elemsLabels, span)
303389 val mirrorType =
304- mirrorCore(defn.Mirror_ProductClass , monoType, mirroredType, cls .name, formal)
390+ mirrorCore(defn.Mirror_ProductClass , monoType, mirroredType, mirroredClass .name, formal)
305391 .refinedWith(tpnme.MirroredElemTypes , TypeAlias (elemsType))
306392 .refinedWith(tpnme.MirroredElemLabels , TypeAlias (elemsLabels))
307393 val mirrorRef =
308- if cls.useCompanionAsProductMirror then companionPath(mirroredType, span)
309- else anonymousMirror(monoType, ExtendsProductMirror , span)
394+ if mirroredClass.useCompanionAsProductMirror then companionPath(mirroredType, span)
395+ else
396+ val arity = msrc match
397+ case MirrorSource .GenericTuple (arity, _) => Some (arity)
398+ case _ => None
399+ anonymousMirror(monoType, ExtendsProductMirror , arity, span)
310400 withNoErrors(mirrorRef.cast(mirrorType))
311401 end makeProductMirror
312402
313- /** widen TermRef to see if they are an alias to an enum singleton */
314- def isEnumSingletonRef (tp : Type )(using Context ): Boolean = tp match
315- case tp : TermRef =>
316- val sym = tp.termSymbol
317- sym.isEnumCase || (! tp.isOverloaded && isEnumSingletonRef(tp.underlying.widenExpr))
318- case _ => false
319-
320403 mirroredType match
321404 case AndType (tp1, tp2) =>
322405 orElse(productMirror(tp1, formal, span), productMirror(tp2, formal, span))
@@ -335,11 +418,17 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
335418 val mirrorType = mirrorCore(defn.Mirror_SingletonClass , mirroredType, mirroredType, singleton.name, formal)
336419 withNoErrors(singletonPath.cast(mirrorType))
337420 else
338- val acceptableMsg = whyNotAcceptableType(mirroredType, cls)
339- if acceptableMsg.isEmpty then
340- if cls.isGenericProduct then makeProductMirror(cls)
341- else withErrors(i " $cls is not a generic product because ${cls.whyNotGenericProduct}" )
342- else withErrors(i " type $mirroredType is not a generic product because $acceptableMsg" )
421+ mirrorSource(mirroredType) match
422+ case Some (msrc) =>
423+ val acceptableMsg = whyNotAcceptableType(mirroredType, msrc)
424+ if acceptableMsg.isEmpty then
425+ if msrc.isGenericProd then
426+ makeProductMirror(msrc)
427+ else
428+ withErrors(i " $cls is not a generic product because ${msrc.asClass.whyNotGenericProduct}" )
429+ else withErrors(i " type ` $mirroredType` is not a generic product because $acceptableMsg" )
430+ case None =>
431+ withErrors(i " type ` $mirroredType` does not reduce to a class or generic tuple type " )
343432 end productMirror
344433
345434 private def sumMirror (mirroredType : Type , formal : Type , span : Span )(using Context ): TreeWithErrors =
@@ -413,7 +502,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
413502 .refinedWith(tpnme.MirroredElemLabels , TypeAlias (TypeOps .nestedPairs(elemLabels)))
414503 val mirrorRef =
415504 if cls.useCompanionAsSumMirror then companionPath(mirroredType, span)
416- else anonymousMirror(monoType, ExtendsSumMirror , span)
505+ else anonymousMirror(monoType, ExtendsSumMirror , tupleArity = None , span)
417506 withNoErrors(mirrorRef.cast(mirrorType))
418507 else if acceptableMsg.nonEmpty then
419508 withErrors(i " type $mirroredType is not a generic sum because $acceptableMsg" )
0 commit comments