@@ -223,16 +223,19 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
223
223
/** Create an anonymous class `new Object { type MirroredMonoType = ... }`
224
224
* and mark it with given attachment so that it is made into a mirror at PostTyper.
225
225
*/
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 ) =
227
227
if ctx.isAfterTyper then ctx.compilationUnit.needsMirrorSupport = true
228
228
val monoTypeDef = untpd.TypeDef (tpnme.MirroredMonoType , untpd.TypeTree (monoType))
229
- val newImpl = untpd.Template (
229
+ var newImpl = untpd.Template (
230
230
constr = untpd.emptyConstructor,
231
231
parents = untpd.TypeTree (defn.ObjectType ) :: Nil ,
232
232
derived = Nil ,
233
233
self = EmptyValDef ,
234
234
body = monoTypeDef :: Nil
235
235
).withAttachment(attachment, ())
236
+ tupleArity.foreach { n =>
237
+ newImpl = newImpl.withAttachment(GenericTupleArity , n)
238
+ }
236
239
typer.typed(untpd.New (newImpl).withSpan(span))
237
240
238
241
/** The mirror type
@@ -279,6 +282,20 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
279
282
private [Synthesizer ] enum MirrorSource :
280
283
case ClassSymbol (cls : Symbol )
281
284
case Singleton (src : Symbol , tref : TermRef )
285
+ case GenericTuple (tuplArity : Int , tpArgs : List [Type ])
286
+
287
+ def whyNotGenericProd (using Context ): Option [String ] = this match
288
+ case GenericTuple (arity, _) =>
289
+ val maxArity = Definitions .MaxTupleArity
290
+ if arity <= maxArity then None
291
+ else Some (i " it reduces to tuple with arity $arity, expected arity <= $maxArity" )
292
+ case ClassSymbol (cls) => if cls.isGenericProduct then None else Some (cls.whyNotGenericProduct)
293
+ case _ => None
294
+
295
+ /** tuple arity, works for TupleN classes and generic tuples */
296
+ final def arity (using Context ): Int = this match
297
+ case GenericTuple (arity, _) => arity
298
+ case _ => - 1
282
299
283
300
/** A comparison that chooses the most specific MirrorSource, this is guided by what is necessary for
284
301
* `Mirror.Product.fromProduct`. i.e. its result type should be compatible with the erasure of `mirroredType`.
@@ -289,12 +306,29 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
289
306
case (ClassSymbol (cls1), ClassSymbol (cls2)) => cls1.isSubClass(cls2)
290
307
case (Singleton (src1, _), Singleton (src2, _)) => src1 eq src2
291
308
case (_ : ClassSymbol , _ : Singleton ) => false
309
+ case _ => (this .arity, that.arity) match
310
+ case (n, m) if n > 0 || m > 0 =>
311
+ // we shortcut when at least one was a tuple.
312
+ // This protects us from comparing classes for two TupleXXL with different arities.
313
+ n == m
314
+ case _ => false
315
+
316
+ def asClass (using Context ): Symbol = this match
317
+ case ClassSymbol (cls) => cls
318
+ case Singleton (src, _) => src.info.classSymbol
319
+ case GenericTuple (arity, _) =>
320
+ if arity <= Definitions .MaxTupleArity then defn.TupleType (arity).nn.classSymbol
321
+ else defn.PairClass
292
322
293
323
def show (using Context ): String = this match
294
324
case ClassSymbol (cls) => i " $cls"
295
325
case Singleton (src, _) => i " $src"
326
+ case GenericTuple (arity, _) =>
327
+ if arity <= Definitions .MaxTupleArity then i " class Tuple $arity"
328
+ else i " trait Tuple { def size: $arity } "
296
329
297
330
private [Synthesizer ] object MirrorSource :
331
+ type WithArity = ClassSymbol | GenericTuple
298
332
299
333
/** Reduces a mirroredType to either its most specific ClassSymbol,
300
334
* or a TermRef to a singleton value. These are
@@ -332,6 +366,9 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
332
366
reduce(tp.underlying)
333
367
case tp : HKTypeLambda if tp.resultType.isInstanceOf [HKTypeLambda ] =>
334
368
Left (i " its subpart ` $tp` is not a supported kind (either `*` or `* -> *`) " )
369
+ case tp @ GenericTupleType (args) =>
370
+ val arity = args.size
371
+ Right (MirrorSource .GenericTuple (arity, args))
335
372
case tp : TypeProxy =>
336
373
reduce(tp.underlying)
337
374
case tp @ AndType (l, r) =>
@@ -354,10 +391,22 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
354
391
355
392
private def productMirror (mirroredType : Type , formal : Type , span : Span )(using Context ): TreeWithErrors =
356
393
357
- def makeProductMirror (cls : Symbol ): TreeWithErrors =
358
- val accessors = cls.caseAccessors.filterNot(_.isAllOf(PrivateLocal ))
394
+ /** widen TermRef to see if they are an alias to an enum singleton */
395
+ def isEnumSingletonRef (tp : Type )(using Context ): Boolean = tp match
396
+ case tp : TermRef =>
397
+ val sym = tp.termSymbol
398
+ sym.isEnumCase || (! tp.isOverloaded && isEnumSingletonRef(tp.underlying.widenExpr))
399
+ case _ => false
400
+
401
+ def makeProductMirror (msrc : MirrorSource .WithArity ): TreeWithErrors =
402
+ val mirroredClass = msrc.asClass
403
+ val accessors = mirroredClass.caseAccessors.filterNot(_.isAllOf(PrivateLocal ))
359
404
val elemLabels = accessors.map(acc => ConstantType (Constant (acc.name.toString)))
360
- val nestedPairs = TypeOps .nestedPairs(accessors.map(mirroredType.resultType.memberInfo(_).widenExpr))
405
+ val (arity, nestedPairs) = msrc match
406
+ case MirrorSource .GenericTuple (arity, args) =>
407
+ (Some (arity), TypeOps .nestedPairs(args))
408
+ case MirrorSource .ClassSymbol (cls) =>
409
+ (None , TypeOps .nestedPairs(accessors.map(mirroredType.resultType.memberInfo(_).widenExpr)))
361
410
val (monoType, elemsType) = mirroredType match
362
411
case mirroredType : HKTypeLambda =>
363
412
(mkMirroredMonoType(mirroredType), mirroredType.derivedLambdaType(resType = nestedPairs))
@@ -367,12 +416,12 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
367
416
checkRefinement(formal, tpnme.MirroredElemTypes , elemsType, span)
368
417
checkRefinement(formal, tpnme.MirroredElemLabels , elemsLabels, span)
369
418
val mirrorType =
370
- mirrorCore(defn.Mirror_ProductClass , monoType, mirroredType, cls .name, formal)
419
+ mirrorCore(defn.Mirror_ProductClass , monoType, mirroredType, mirroredClass .name, formal)
371
420
.refinedWith(tpnme.MirroredElemTypes , TypeAlias (elemsType))
372
421
.refinedWith(tpnme.MirroredElemLabels , TypeAlias (elemsLabels))
373
422
val mirrorRef =
374
- if cls .useCompanionAsProductMirror then companionPath(mirroredType, span)
375
- else anonymousMirror(monoType, ExtendsProductMirror , span)
423
+ if mirroredClass .useCompanionAsProductMirror then companionPath(mirroredType, span)
424
+ else anonymousMirror(monoType, ExtendsProductMirror , arity, span)
376
425
withNoErrors(mirrorRef.cast(mirrorType))
377
426
end makeProductMirror
378
427
@@ -389,9 +438,10 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
389
438
else
390
439
val mirrorType = mirrorCore(defn.Mirror_SingletonClass , mirroredType, mirroredType, singleton.name, formal)
391
440
withNoErrors(singletonPath.cast(mirrorType))
392
- case MirrorSource .ClassSymbol (cls) =>
393
- if cls.isGenericProduct then makeProductMirror(cls)
394
- else withErrors(i " $cls is not a generic product because ${cls.whyNotGenericProduct}" )
441
+ case msrc : MirrorSource .WithArity =>
442
+ msrc.whyNotGenericProd match
443
+ case Some (err) => withErrors(i " ${msrc.asClass} is not a generic product because $err" )
444
+ case _ => makeProductMirror(msrc)
395
445
case Left (msg) =>
396
446
withErrors(i " type ` $mirroredType` is not a generic product because $msg" )
397
447
end productMirror
@@ -400,7 +450,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
400
450
401
451
val (acceptableMsg, cls) = MirrorSource .reduce(mirroredType) match
402
452
case Right (MirrorSource .Singleton (_, tp)) => (i " its subpart ` $tp` is a term reference " , NoSymbol )
403
- case Right (MirrorSource . ClassSymbol (cls)) => (" " , cls )
453
+ case Right (msrc) => (" " , msrc.asClass )
404
454
case Left (msg) => (msg, NoSymbol )
405
455
406
456
val clsIsGenericSum = cls.isGenericSum
@@ -457,7 +507,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
457
507
.refinedWith(tpnme.MirroredElemLabels , TypeAlias (TypeOps .nestedPairs(elemLabels)))
458
508
val mirrorRef =
459
509
if cls.useCompanionAsSumMirror then companionPath(mirroredType, span)
460
- else anonymousMirror(monoType, ExtendsSumMirror , span)
510
+ else anonymousMirror(monoType, ExtendsSumMirror , tupleArity = None , span)
461
511
withNoErrors(mirrorRef.cast(mirrorType))
462
512
else if acceptableMsg.nonEmpty then
463
513
withErrors(i " type ` $mirroredType` is not a generic sum because $acceptableMsg" )
0 commit comments