@@ -279,36 +279,81 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
279
279
case t => mapOver(t)
280
280
monoMap(mirroredType.resultType)
281
281
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
283
318
284
- var isSafeGenericTuple = Option .empty[( Symbol , List [ Type ])]
319
+ private def productMirror ( mirroredType : Type , formal : Type , span : Span )( using Context ) : TreeWithErrors =
285
320
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)
289
323
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
300
341
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
307
348
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))
312
357
313
358
/** for a case class, if it will have an anonymous mirror,
314
359
* check that its constructor can be accessed
@@ -326,13 +371,13 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
326
371
def genAnonyousMirror (cls : Symbol ): Boolean =
327
372
cls.is(Scala2x ) || cls.linkedClass.is(Case )
328
373
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
331
376
val accessors = mirroredClass.caseAccessors.filterNot(_.isAllOf(PrivateLocal ))
332
377
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))
336
381
val (monoType, elemsType) = mirroredType match
337
382
case mirroredType : HKTypeLambda =>
338
383
(mkMirroredMonoType(mirroredType), mirroredType.derivedLambdaType(resType = nestedPairs))
@@ -342,25 +387,30 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
342
387
checkRefinement(formal, tpnme.MirroredElemTypes , elemsType, span)
343
388
checkRefinement(formal, tpnme.MirroredElemLabels , elemsLabels, span)
344
389
val mirrorType =
345
- mirrorCore(defn.Mirror_ProductClass , monoType, mirroredType, cls .name, formal)
390
+ mirrorCore(defn.Mirror_ProductClass , monoType, mirroredType, mirroredClass .name, formal)
346
391
.refinedWith(tpnme.MirroredElemTypes , TypeAlias (elemsType))
347
392
.refinedWith(tpnme.MirroredElemLabels , TypeAlias (elemsLabels))
348
393
val mirrorRef =
349
394
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)
351
399
else companionPath(mirroredType, span)
352
400
withNoErrors(mirrorRef.cast(mirrorType))
353
401
end makeProductMirror
354
402
355
- def getError (cls : Symbol ): String =
403
+ def getError (msrc : MirrorSource ): String =
356
404
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 " "
361
411
else
362
412
" "
363
- i " $cls is not a generic product $reason"
413
+ i " ${msrc.asClass} is not a generic product $reason"
364
414
end getError
365
415
366
416
mirroredType match
@@ -378,13 +428,14 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
378
428
val mirrorType = mirrorCore(defn.Mirror_SingletonClass , mirroredType, mirroredType, module.name, formal)
379
429
withNoErrors(modulePath.cast(mirrorType))
380
430
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 " ))
388
439
end productMirror
389
440
390
441
private def sumMirror (mirroredType : Type , formal : Type , span : Span )(using Context ): TreeWithErrors =
0 commit comments