@@ -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,21 +279,104 @@ 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) => i " its subpart ` $tp` is a top-level union type. "
354+ case GenericTupleType (args) if args.size <= Definitions .MaxTupleArity =>
355+ val tup = MirrorSource .tuple(args)
356+ if tup.equiv(msrc) then " "
357+ else i " a subpart reduces to the unrelated tuple ${tup.asClass}, expected ${msrc.asClass}"
358+ case tp : TypeProxy => whyNotAcceptableType(tp.underlying, msrc)
286359 case _ =>
287- if tp.classSymbol eq cls then " "
288- else i " a subpart reduces to the more precise ${tp.classSymbol}, expected $cls"
360+ mirrorSource(tp) match
361+ case Some (msrc2) =>
362+ if msrc2.equiv(msrc) then " "
363+ else i " a subpart reduces to the more precise ${msrc2.asClass}, expected ${msrc.asClass}"
364+ case _ => " ???" // caught early by initial `mirrorSource` that made `msrc`
289365
290- def makeProductMirror (cls : Symbol ): TreeWithErrors =
291- val accessors = cls.caseAccessors.filterNot(_.isAllOf(PrivateLocal ))
366+ /** widen TermRef to see if they are an alias to an enum singleton */
367+ def isEnumSingletonRef (tp : Type )(using Context ): Boolean = tp match
368+ case tp : TermRef =>
369+ val sym = tp.termSymbol
370+ sym.isEnumCase || (! tp.isOverloaded && isEnumSingletonRef(tp.underlying.widenExpr))
371+ case _ => false
372+
373+ def makeProductMirror (msrc : MirrorSource ): TreeWithErrors =
374+ val mirroredClass = msrc.asClass
375+ val accessors = mirroredClass.caseAccessors.filterNot(_.isAllOf(PrivateLocal ))
292376 val elemLabels = accessors.map(acc => ConstantType (Constant (acc.name.toString)))
293- val nestedPairs = TypeOps .nestedPairs(accessors.map(mirroredType.resultType.memberInfo(_).widenExpr))
377+ val nestedPairs = msrc match
378+ case MirrorSource .GenericTuple (_, args) => TypeOps .nestedPairs(args)
379+ case _ => TypeOps .nestedPairs(accessors.map(mirroredType.resultType.memberInfo(_).widenExpr))
294380 val (monoType, elemsType) = mirroredType match
295381 case mirroredType : HKTypeLambda =>
296382 (mkMirroredMonoType(mirroredType), mirroredType.derivedLambdaType(resType = nestedPairs))
@@ -300,22 +386,19 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
300386 checkRefinement(formal, tpnme.MirroredElemTypes , elemsType, span)
301387 checkRefinement(formal, tpnme.MirroredElemLabels , elemsLabels, span)
302388 val mirrorType =
303- mirrorCore(defn.Mirror_ProductClass , monoType, mirroredType, cls .name, formal)
389+ mirrorCore(defn.Mirror_ProductClass , monoType, mirroredType, mirroredClass .name, formal)
304390 .refinedWith(tpnme.MirroredElemTypes , TypeAlias (elemsType))
305391 .refinedWith(tpnme.MirroredElemLabels , TypeAlias (elemsLabels))
306392 val mirrorRef =
307- if cls.useCompanionAsProductMirror then companionPath(mirroredType, span)
308- else anonymousMirror(monoType, ExtendsProductMirror , span)
393+ if mirroredClass.useCompanionAsProductMirror then companionPath(mirroredType, span)
394+ else
395+ val arity = msrc match
396+ case MirrorSource .GenericTuple (arity, _) => Some (arity)
397+ case _ => None
398+ anonymousMirror(monoType, ExtendsProductMirror , arity, span)
309399 withNoErrors(mirrorRef.cast(mirrorType))
310400 end makeProductMirror
311401
312- /** widen TermRef to see if they are an alias to an enum singleton */
313- def isEnumSingletonRef (tp : Type )(using Context ): Boolean = tp match
314- case tp : TermRef =>
315- val sym = tp.termSymbol
316- sym.isEnumCase || (! tp.isOverloaded && isEnumSingletonRef(tp.underlying.widenExpr))
317- case _ => false
318-
319402 mirroredType match
320403 case AndType (tp1, tp2) =>
321404 orElse(productMirror(tp1, formal, span), productMirror(tp2, formal, span))
@@ -334,11 +417,17 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
334417 val mirrorType = mirrorCore(defn.Mirror_SingletonClass , mirroredType, mirroredType, singleton.name, formal)
335418 withNoErrors(singletonPath.cast(mirrorType))
336419 else
337- val acceptableMsg = whyNotAcceptableType(mirroredType, cls)
338- if acceptableMsg.isEmpty then
339- if cls.isGenericProduct then makeProductMirror(cls)
340- else withErrors(i " $cls is not a generic product because ${cls.whyNotGenericProduct}" )
341- else withErrors(i " type ` $mirroredType` is not a generic product because $acceptableMsg" )
420+ mirrorSource(mirroredType) match
421+ case Some (msrc) =>
422+ val acceptableMsg = whyNotAcceptableType(mirroredType, msrc)
423+ if acceptableMsg.isEmpty then
424+ if msrc.isGenericProd then
425+ makeProductMirror(msrc)
426+ else
427+ withErrors(i " ${msrc.asClass} is not a generic product because ${msrc.asClass.whyNotGenericProduct}" )
428+ else withErrors(i " type ` $mirroredType` is not a generic product because $acceptableMsg" )
429+ case None =>
430+ withErrors(i " type ` $mirroredType` does not reduce to a class or generic tuple type " )
342431 end productMirror
343432
344433 private def sumMirror (mirroredType : Type , formal : Type , span : Span )(using Context ): TreeWithErrors =
@@ -411,7 +500,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
411500 .refinedWith(tpnme.MirroredElemLabels , TypeAlias (TypeOps .nestedPairs(elemLabels)))
412501 val mirrorRef =
413502 if cls.useCompanionAsSumMirror then companionPath(mirroredType, span)
414- else anonymousMirror(monoType, ExtendsSumMirror , span)
503+ else anonymousMirror(monoType, ExtendsSumMirror , tupleArity = None , span)
415504 withNoErrors(mirrorRef.cast(mirrorType))
416505 else if acceptableMsg.nonEmpty then
417506 withErrors(i " type ` $mirroredType` is not a generic sum because $acceptableMsg" )
0 commit comments