@@ -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
@@ -276,21 +279,104 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
276
279
case t => mapOver(t)
277
280
monoMap(mirroredType.resultType)
278
281
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
+
279
319
private def productMirror (mirroredType : Type , formal : Type , span : Span )(using Context ): TreeWithErrors =
280
320
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
282
351
case tp : HKTypeLambda if tp.resultType.isInstanceOf [HKTypeLambda ] =>
283
352
i " its subpart ` $tp` is not a supported kind (either `*` or `* -> *`) "
284
- case tp : TypeProxy => whyNotAcceptableType(tp.underlying, cls)
285
353
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)
286
359
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`
289
365
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 ))
292
376
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))
294
380
val (monoType, elemsType) = mirroredType match
295
381
case mirroredType : HKTypeLambda =>
296
382
(mkMirroredMonoType(mirroredType), mirroredType.derivedLambdaType(resType = nestedPairs))
@@ -300,22 +386,19 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
300
386
checkRefinement(formal, tpnme.MirroredElemTypes , elemsType, span)
301
387
checkRefinement(formal, tpnme.MirroredElemLabels , elemsLabels, span)
302
388
val mirrorType =
303
- mirrorCore(defn.Mirror_ProductClass , monoType, mirroredType, cls .name, formal)
389
+ mirrorCore(defn.Mirror_ProductClass , monoType, mirroredType, mirroredClass .name, formal)
304
390
.refinedWith(tpnme.MirroredElemTypes , TypeAlias (elemsType))
305
391
.refinedWith(tpnme.MirroredElemLabels , TypeAlias (elemsLabels))
306
392
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)
309
399
withNoErrors(mirrorRef.cast(mirrorType))
310
400
end makeProductMirror
311
401
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
-
319
402
mirroredType match
320
403
case AndType (tp1, tp2) =>
321
404
orElse(productMirror(tp1, formal, span), productMirror(tp2, formal, span))
@@ -334,11 +417,17 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
334
417
val mirrorType = mirrorCore(defn.Mirror_SingletonClass , mirroredType, mirroredType, singleton.name, formal)
335
418
withNoErrors(singletonPath.cast(mirrorType))
336
419
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 " )
342
431
end productMirror
343
432
344
433
private def sumMirror (mirroredType : Type , formal : Type , span : Span )(using Context ): TreeWithErrors =
@@ -411,7 +500,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
411
500
.refinedWith(tpnme.MirroredElemLabels , TypeAlias (TypeOps .nestedPairs(elemLabels)))
412
501
val mirrorRef =
413
502
if cls.useCompanionAsSumMirror then companionPath(mirroredType, span)
414
- else anonymousMirror(monoType, ExtendsSumMirror , span)
503
+ else anonymousMirror(monoType, ExtendsSumMirror , tupleArity = None , span)
415
504
withNoErrors(mirrorRef.cast(mirrorType))
416
505
else if acceptableMsg.nonEmpty then
417
506
withErrors(i " type ` $mirroredType` is not a generic sum because $acceptableMsg" )
0 commit comments