@@ -279,36 +279,81 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
279279 case t => mapOver(t)
280280 monoMap(mirroredType.resultType)
281281
282- private def productMirror (mirroredType : Type , formal : Type , span : Span )(using Context ): TreeWithErrors =
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
283318
284- var isSafeGenericTuple = Option .empty[( Symbol , List [ Type ])]
319+ private def productMirror ( mirroredType : Type , formal : Type , span : Span )( using Context ) : TreeWithErrors =
285320
286- /** do all parts match the class symbol? Or can we extract a generic tuple type out? */
287- def acceptable (tp : Type , cls : Symbol ): Boolean =
288- var genericTupleParts = List .empty[(Symbol , List [Type ])]
321+ extension (msrc : MirrorSource ) def isGenericProd (using Context ) =
322+ msrc.isGenericTuple || msrc.asClass.isGenericProduct && canAccessCtor(msrc.asClass)
289323
290- def acceptableGenericTuple (tp : AppliedType ): Boolean =
291- val tupleArgs = tp.tupleElementTypes
292- val arity = tupleArgs.size
293- val isOk = arity <= Definitions .MaxTupleArity
294- if isOk then
295- genericTupleParts ::= {
296- val cls = defn.TupleType (arity).nn.classSymbol
297- (cls, tupleArgs)
298- }
299- isOk
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
300341
301- def inner (tp : Type , cls : Symbol ): Boolean = tp match
302- case tp : HKTypeLambda if tp.resultType. isInstanceOf [ HKTypeLambda ] => false
303- case tp @ AppliedType ( cons : TypeRef , _) if cons.isRef(defn. PairClass ) => acceptableGenericTuple(tp)
304- case tp : TypeProxy => inner (tp.underlying, cls )
305- case OrType (tp1, tp2) => inner(tp1, cls) && inner(tp2, cls )
306- case _ => tp.classSymbol eq cls
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
307348
308- val classPartsMatch = inner(tp, cls)
309- classPartsMatch && genericTupleParts.map((cls, _) => cls).distinct.sizeIs <= 1 &&
310- { isSafeGenericTuple = genericTupleParts.headOption ; true }
311- end acceptable
349+ /** do all parts match the class symbol? */
350+ def acceptable (tp : Type , msrc : MirrorSource ): Boolean = tp match
351+ case tp : HKTypeLambda if tp.resultType.isInstanceOf [HKTypeLambda ] => false
352+ case OrType (tp1, tp2) => acceptable(tp1, msrc) && acceptable(tp2, msrc)
353+ case GenericTupleType (args) if args.size <= Definitions .MaxTupleArity =>
354+ MirrorSource .tuple(args).equiv(msrc)
355+ case tp : TypeProxy => acceptable(tp.underlying, msrc)
356+ case _ => mirrorSource(tp).exists(_.equiv(msrc))
312357
313358 /** for a case class, if it will have an anonymous mirror,
314359 * check that its constructor can be accessed
@@ -326,13 +371,13 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
326371 def genAnonyousMirror (cls : Symbol ): Boolean =
327372 cls.is(Scala2x ) || cls.linkedClass.is(Case )
328373
329- def makeProductMirror (cls : Symbol ): TreeWithErrors =
330- val mirroredClass = isSafeGenericTuple.fold(cls)((cls, _) => cls)
374+ def makeProductMirror (msrc : MirrorSource ): TreeWithErrors =
375+ val mirroredClass = msrc.asClass
331376 val accessors = mirroredClass.caseAccessors.filterNot(_.isAllOf(PrivateLocal ))
332377 val elemLabels = accessors.map(acc => ConstantType (Constant (acc.name.toString)))
333- val nestedPairs = isSafeGenericTuple.map((_, tps) => TypeOps .nestedPairs(tps)).getOrElse {
334- TypeOps .nestedPairs(accessors.map(mirroredType.resultType.memberInfo(_).widenExpr) )
335- }
378+ val nestedPairs = msrc match
379+ case MirrorSource . GenericTuple (_, args) => TypeOps .nestedPairs(args )
380+ case _ => TypeOps .nestedPairs(accessors.map(mirroredType.resultType.memberInfo(_).widenExpr))
336381 val (monoType, elemsType) = mirroredType match
337382 case mirroredType : HKTypeLambda =>
338383 (mkMirroredMonoType(mirroredType), mirroredType.derivedLambdaType(resType = nestedPairs))
@@ -342,25 +387,30 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
342387 checkRefinement(formal, tpnme.MirroredElemTypes , elemsType, span)
343388 checkRefinement(formal, tpnme.MirroredElemLabels , elemsLabels, span)
344389 val mirrorType =
345- mirrorCore(defn.Mirror_ProductClass , monoType, mirroredType, cls .name, formal)
390+ mirrorCore(defn.Mirror_ProductClass , monoType, mirroredType, mirroredClass .name, formal)
346391 .refinedWith(tpnme.MirroredElemTypes , TypeAlias (elemsType))
347392 .refinedWith(tpnme.MirroredElemLabels , TypeAlias (elemsLabels))
348393 val mirrorRef =
349394 if genAnonyousMirror(mirroredClass) then
350- anonymousMirror(monoType, ExtendsProductMirror , isSafeGenericTuple.map(_(1 ).size), span)
395+ val arity = msrc match
396+ case MirrorSource .GenericTuple (arity, _) => Some (arity)
397+ case _ => None
398+ anonymousMirror(monoType, ExtendsProductMirror , arity, span)
351399 else companionPath(mirroredType, span)
352400 withNoErrors(mirrorRef.cast(mirrorType))
353401 end makeProductMirror
354402
355- def getError (cls : Symbol ): String =
403+ def getError (msrc : MirrorSource ): String =
356404 val reason =
357- if ! cls.isGenericProduct then
358- i " because ${cls.whyNotGenericProduct}"
359- else if ! canAccessCtor(cls) then
360- i " because the constructor of $cls is innaccessible from the calling scope. "
405+ if ! msrc.isGenericTuple then
406+ if ! msrc.asClass.isGenericProduct then
407+ i " because ${msrc.asClass.whyNotGenericProduct}"
408+ else if ! canAccessCtor(msrc.asClass) then
409+ i " because the constructor of ${msrc.asClass} is innaccessible from the calling scope. "
410+ else " "
361411 else
362412 " "
363- i " $cls is not a generic product $reason"
413+ i " ${msrc.asClass} is not a generic product $reason"
364414 end getError
365415
366416 mirroredType match
@@ -378,13 +428,14 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
378428 val mirrorType = mirrorCore(defn.Mirror_SingletonClass , mirroredType, mirroredType, module.name, formal)
379429 withNoErrors(modulePath.cast(mirrorType))
380430 else
381- val cls = mirroredType.classSymbol
382- if acceptable(mirroredType, cls)
383- && isSafeGenericTuple.isDefined || (cls.isGenericProduct && canAccessCtor(cls))
384- then
385- makeProductMirror(cls)
386- else
387- (EmptyTree , List (getError(cls)))
431+ mirrorSource(mirroredType) match
432+ case Some (msrc) =>
433+ if acceptable(mirroredType, msrc) && msrc.isGenericProd then
434+ makeProductMirror(msrc)
435+ else
436+ (EmptyTree , List (getError(msrc)))
437+ case None =>
438+ (EmptyTree , List (i " ${mirroredType.show} does not reduce to a class or generic tuple type " ))
388439 end productMirror
389440
390441 private def sumMirror (mirroredType : Type , formal : Type , span : Span )(using Context ): TreeWithErrors =
0 commit comments