@@ -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,22 +279,105 @@ 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) =>
286
- Seq (tp1, tp2).map(whyNotAcceptableType(_, cls)).find(_.nonEmpty).getOrElse(" " )
287
- case _ =>
288
- if tp.classSymbol eq cls then " "
289
- else i " a subpart reduces to the more precise ${tp.classSymbol}, expected $cls"
354
+ Seq (tp1, tp2).map(whyNotAcceptableType(_, msrc)).find(_.nonEmpty).getOrElse(" " )
355
+ case GenericTupleType (args) if args.size <= Definitions .MaxTupleArity =>
356
+ val tup = MirrorSource .tuple(args)
357
+ if tup.equiv(msrc) then " "
358
+ else i " a subpart reduces to the unrelated tuple ${tup.asClass}, expected ${msrc.asClass}"
359
+ case tp : TypeProxy => whyNotAcceptableType(tp.underlying, msrc)
360
+ case _ =>
361
+ mirrorSource(tp) match
362
+ case Some (msrc2) =>
363
+ if msrc2.equiv(msrc) then " "
364
+ else i " a subpart reduces to the more precise ${msrc2.asClass}, expected ${msrc.asClass}"
365
+ case _ => " ???" // caught early by initial `mirrorSource` that made `msrc`
366
+
367
+ /** widen TermRef to see if they are an alias to an enum singleton */
368
+ def isEnumSingletonRef (tp : Type )(using Context ): Boolean = tp match
369
+ case tp : TermRef =>
370
+ val sym = tp.termSymbol
371
+ sym.isEnumCase || (! tp.isOverloaded && isEnumSingletonRef(tp.underlying.widenExpr))
372
+ case _ => false
290
373
291
- def makeProductMirror (cls : Symbol ): TreeWithErrors =
292
- val accessors = cls.caseAccessors.filterNot(_.isAllOf(PrivateLocal ))
374
+ def makeProductMirror (msrc : MirrorSource ): TreeWithErrors =
375
+ val mirroredClass = msrc.asClass
376
+ val accessors = mirroredClass.caseAccessors.filterNot(_.isAllOf(PrivateLocal ))
293
377
val elemLabels = accessors.map(acc => ConstantType (Constant (acc.name.toString)))
294
- val nestedPairs = TypeOps .nestedPairs(accessors.map(mirroredType.resultType.memberInfo(_).widenExpr))
378
+ val nestedPairs = msrc match
379
+ case MirrorSource .GenericTuple (_, args) => TypeOps .nestedPairs(args)
380
+ case _ => TypeOps .nestedPairs(accessors.map(mirroredType.resultType.memberInfo(_).widenExpr))
295
381
val (monoType, elemsType) = mirroredType match
296
382
case mirroredType : HKTypeLambda =>
297
383
(mkMirroredMonoType(mirroredType), mirroredType.derivedLambdaType(resType = nestedPairs))
@@ -301,22 +387,19 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
301
387
checkRefinement(formal, tpnme.MirroredElemTypes , elemsType, span)
302
388
checkRefinement(formal, tpnme.MirroredElemLabels , elemsLabels, span)
303
389
val mirrorType =
304
- mirrorCore(defn.Mirror_ProductClass , monoType, mirroredType, cls .name, formal)
390
+ mirrorCore(defn.Mirror_ProductClass , monoType, mirroredType, mirroredClass .name, formal)
305
391
.refinedWith(tpnme.MirroredElemTypes , TypeAlias (elemsType))
306
392
.refinedWith(tpnme.MirroredElemLabels , TypeAlias (elemsLabels))
307
393
val mirrorRef =
308
- if cls.useCompanionAsProductMirror then companionPath(mirroredType, span)
309
- else anonymousMirror(monoType, ExtendsProductMirror , span)
394
+ if mirroredClass.useCompanionAsProductMirror then companionPath(mirroredType, span)
395
+ else
396
+ val arity = msrc match
397
+ case MirrorSource .GenericTuple (arity, _) => Some (arity)
398
+ case _ => None
399
+ anonymousMirror(monoType, ExtendsProductMirror , arity, span)
310
400
withNoErrors(mirrorRef.cast(mirrorType))
311
401
end makeProductMirror
312
402
313
- /** widen TermRef to see if they are an alias to an enum singleton */
314
- def isEnumSingletonRef (tp : Type )(using Context ): Boolean = tp match
315
- case tp : TermRef =>
316
- val sym = tp.termSymbol
317
- sym.isEnumCase || (! tp.isOverloaded && isEnumSingletonRef(tp.underlying.widenExpr))
318
- case _ => false
319
-
320
403
mirroredType match
321
404
case AndType (tp1, tp2) =>
322
405
orElse(productMirror(tp1, formal, span), productMirror(tp2, formal, span))
@@ -335,11 +418,17 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
335
418
val mirrorType = mirrorCore(defn.Mirror_SingletonClass , mirroredType, mirroredType, singleton.name, formal)
336
419
withNoErrors(singletonPath.cast(mirrorType))
337
420
else
338
- val acceptableMsg = whyNotAcceptableType(mirroredType, cls)
339
- if acceptableMsg.isEmpty then
340
- if cls.isGenericProduct then makeProductMirror(cls)
341
- else withErrors(i " $cls is not a generic product because ${cls.whyNotGenericProduct}" )
342
- else withErrors(i " type $mirroredType is not a generic product because $acceptableMsg" )
421
+ mirrorSource(mirroredType) match
422
+ case Some (msrc) =>
423
+ val acceptableMsg = whyNotAcceptableType(mirroredType, msrc)
424
+ if acceptableMsg.isEmpty then
425
+ if msrc.isGenericProd then
426
+ makeProductMirror(msrc)
427
+ else
428
+ withErrors(i " $cls is not a generic product because ${msrc.asClass.whyNotGenericProduct}" )
429
+ else withErrors(i " type ` $mirroredType` is not a generic product because $acceptableMsg" )
430
+ case None =>
431
+ withErrors(i " type ` $mirroredType` does not reduce to a class or generic tuple type " )
343
432
end productMirror
344
433
345
434
private def sumMirror (mirroredType : Type , formal : Type , span : Span )(using Context ): TreeWithErrors =
@@ -413,7 +502,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
413
502
.refinedWith(tpnme.MirroredElemLabels , TypeAlias (TypeOps .nestedPairs(elemLabels)))
414
503
val mirrorRef =
415
504
if cls.useCompanionAsSumMirror then companionPath(mirroredType, span)
416
- else anonymousMirror(monoType, ExtendsSumMirror , span)
505
+ else anonymousMirror(monoType, ExtendsSumMirror , tupleArity = None , span)
417
506
withNoErrors(mirrorRef.cast(mirrorType))
418
507
else if acceptableMsg.nonEmpty then
419
508
withErrors(i " type $mirroredType is not a generic sum because $acceptableMsg" )
0 commit comments