Skip to content

Commit 7299938

Browse files
committed
cleaner way to extract class or tuple proxy
1 parent 0e09603 commit 7299938

File tree

8 files changed

+121
-53
lines changed

8 files changed

+121
-53
lines changed

compiler/src/dotty/tools/dotc/core/TypeErasure.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -689,7 +689,9 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
689689
}
690690

691691
private def erasePair(tp: Type)(using Context): Type = {
692-
val arity = tp.tupleArity
692+
// NOTE: `tupleArity` does not consider TypeRef(EmptyTuple$) equivalent to EmptyTuple.type,
693+
// we fix this for printers, but type erasure should be preserved.
694+
val arity = tp.tupleArity()
693695
if (arity < 0) defn.ProductClass.typeRef
694696
else if (arity <= Definitions.MaxTupleArity) defn.TupleType(arity).nn
695697
else defn.TupleXXLClass.typeRef

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,23 @@ import scala.annotation.internal.sharable
4040
import scala.annotation.threadUnsafe
4141

4242
import dotty.tools.dotc.transform.SymUtils._
43+
import dotty.tools.dotc.transform.TypeUtils.*
4344

4445
object Types {
4546

4647
@sharable private var nextId = 0
4748

4849
implicit def eqType: CanEqual[Type, Type] = CanEqual.derived
4950

51+
object GenericTupleType:
52+
def unapply(tp: Type)(using Context): Option[List[Type]] = tp match
53+
case tp @ AppliedType(r: TypeRef, _) if r.isRef(defn.PairClass) && tp.tupleArity(relaxEmptyTuple = true) > 0 =>
54+
Some(tp.tupleElementTypes)
55+
case AppliedType(_: TypeRef, args) if defn.isTupleNType(tp) =>
56+
Some(args)
57+
case _ =>
58+
None
59+
5060
/** Main class representing types.
5161
*
5262
* The principal subclasses and sub-objects are as follows:

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
218218
val cls = tycon.typeSymbol
219219
if tycon.isRepeatedParam then toTextLocal(args.head) ~ "*"
220220
else if defn.isFunctionClass(cls) then toTextFunction(args, cls.name.isContextFunction, cls.name.isErasedFunction)
221-
else if tp.tupleArity >= 2 && !printDebug then toTextTuple(tp.tupleElementTypes)
221+
else if tp.tupleArity(relaxEmptyTuple = true) >= 2 && !printDebug then toTextTuple(tp.tupleElementTypes)
222222
else if isInfixType(tp) then
223223
val l :: r :: Nil = args: @unchecked
224224
val opName = tyconName(tycon)

compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ object GenericSignatures {
248248
case _ => jsig(elemtp)
249249

250250
case RefOrAppliedType(sym, pre, args) =>
251-
if (sym == defn.PairClass && tp.tupleArity > Definitions.MaxTupleArity)
251+
if (sym == defn.PairClass && tp.tupleArity() > Definitions.MaxTupleArity)
252252
jsig(defn.TupleXXLClass.typeRef)
253253
else if (isTypeParameterInSig(sym, sym0)) {
254254
assert(!sym.isAliasType, "Unexpected alias type: " + sym)

compiler/src/dotty/tools/dotc/transform/TypeUtils.scala

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,16 @@ object TypeUtils {
5151

5252
/** The arity of this tuple type, which can be made up of EmptyTuple, TupleX and `*:` pairs,
5353
* or -1 if this is not a tuple type.
54+
*
55+
* @param relaxEmptyTuple if true then TypeRef(EmptyTuple$) =:= EmptyTuple.type
5456
*/
55-
def tupleArity(using Context): Int = self match {
57+
def tupleArity(relaxEmptyTuple: Boolean = false)(using Context): Int = self match {
5658
case AppliedType(tycon, _ :: tl :: Nil) if tycon.isRef(defn.PairClass) =>
57-
val arity = tl.tupleArity
59+
val arity = tl.tupleArity(relaxEmptyTuple)
5860
if (arity < 0) arity else arity + 1
5961
case self: SingletonType =>
6062
if self.termSymbol == defn.EmptyTupleModule then 0 else -1
61-
case self: TypeRef if self.classSymbol == defn.EmptyTupleModule.moduleClass =>
63+
case self: TypeRef if relaxEmptyTuple && self.classSymbol == defn.EmptyTupleModule.moduleClass =>
6264
0
6365
case self if defn.isTupleClass(self.classSymbol) =>
6466
self.dealias.argInfos.length

compiler/src/dotty/tools/dotc/typer/Synthesizer.scala

Lines changed: 96 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -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 =

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2766,7 +2766,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
27662766
typed(desugar.smallTuple(tree).withSpan(tree.span), pt)
27672767
else {
27682768
val pts =
2769-
if (arity == pt.tupleArity) pt.tupleElementTypes
2769+
if (arity == pt.tupleArity()) pt.tupleElementTypes
27702770
else List.fill(arity)(defn.AnyType)
27712771
val elems = tree.trees.lazyZip(pts).map(
27722772
if ctx.mode.is(Mode.Type) then typedType(_, _, mapPatternBounds = true)

tests/neg/i14127a.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@ import scala.deriving.Mirror
22

33
// mixing arities is not supported
44

5-
val mT23 = summon[Mirror.Of[(Int *: Int *: EmptyTuple) | (Int *: Int *: Int *: EmptyTuple)]] // error
5+
val mT2Or2a = summon[Mirror.Of[(Int *: Int *: EmptyTuple) | (Int *: Int *: EmptyTuple)]] // ok, same arity
6+
val mT2Or2b = summon[Mirror.Of[(Int *: Int *: EmptyTuple) | Tuple2[Int, Int]]] // ok, same arity
7+
val mT2Or2c = summon[Mirror.Of[Tuple2[Int, Int] | (Int *: Int *: EmptyTuple)]] // ok, same arity
8+
val mT2Or3 = summon[Mirror.Of[(Int *: Int *: EmptyTuple) | (Int *: Int *: Int *: EmptyTuple)]] // error

0 commit comments

Comments
 (0)