Skip to content

Commit 3cc35fc

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

File tree

7 files changed

+111
-53
lines changed

7 files changed

+111
-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: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,44 @@ 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(ref: TypeRef, _)
54+
if ref.isRef(defn.PairClass) && tp.tupleArity(relaxEmptyTuple = true) > 0 =>
55+
Some(tp.tupleElementTypes)
56+
case _ => None
57+
58+
enum ClassOrTuple:
59+
case ClassSymbol(cls: Symbol)
60+
case GenericTuple(arity: Int, tpArgs: List[Type])
61+
case NoClass
62+
63+
def isGenericTuple: Boolean = this.isInstanceOf[GenericTuple]
64+
65+
private var _asClass: Symbol | Null = null
66+
67+
def asClass(using Context): Symbol =
68+
val local = _asClass
69+
if local == null then
70+
val res = this match
71+
case ClassSymbol(cls) => cls
72+
case GenericTuple(arity, _) =>
73+
if arity <= Definitions.MaxTupleArity then defn.TupleType(arity).nn.classSymbol
74+
else defn.TupleXXLClass
75+
case NoClass => NoSymbol
76+
_asClass = res
77+
res
78+
else local
79+
end ClassOrTuple
80+
5081
/** Main class representing types.
5182
*
5283
* The principal subclasses and sub-objects are as follows:
@@ -491,6 +522,8 @@ object Types {
491522
/** The least class or trait of which this type is a subtype or parameterized
492523
* instance, or NoSymbol if none exists (either because this type is not a
493524
* value type, or because superclasses are ambiguous).
525+
*
526+
* If modified, update [[underlyingClassOrTuple]] as well.
494527
*/
495528
final def classSymbol(using Context): Symbol = this match
496529
case tp: TypeRef =>
@@ -524,6 +557,42 @@ object Types {
524557
case _ =>
525558
NoSymbol
526559

560+
/** Follows [[classSymbol]], but also escapes generic tuples to a proxy representing their class and type arguments.
561+
*/
562+
final def underlyingClassOrTuple(using Context): ClassOrTuple = this match
563+
case tp: TypeRef =>
564+
val sym = tp.symbol
565+
if (sym.isClass) ClassOrTuple.ClassSymbol(sym) else tp.superType.underlyingClassOrTuple
566+
case GenericTupleType(args) =>
567+
ClassOrTuple.GenericTuple(args.size, args)
568+
case tp: TypeProxy =>
569+
tp.underlying.underlyingClassOrTuple
570+
case tp: ClassInfo =>
571+
ClassOrTuple.ClassSymbol(tp.cls)
572+
case AndType(l, r) =>
573+
val lsym = l.underlyingClassOrTuple
574+
val rsym = r.underlyingClassOrTuple
575+
if (lsym.asClass isSubClass rsym.asClass) lsym
576+
else if (rsym.asClass isSubClass lsym.asClass) rsym
577+
else ClassOrTuple.NoClass
578+
case tp: OrType =>
579+
if tp.tp1.hasClassSymbol(defn.NothingClass) then
580+
tp.tp2.underlyingClassOrTuple
581+
else if tp.tp2.hasClassSymbol(defn.NothingClass) then
582+
tp.tp1.underlyingClassOrTuple
583+
else
584+
def tp1Null = tp.tp1.hasClassSymbol(defn.NullClass)
585+
def tp2Null = tp.tp2.hasClassSymbol(defn.NullClass)
586+
if ctx.erasedTypes && (tp1Null || tp2Null) then
587+
val otherSide = if tp1Null then tp.tp2.underlyingClassOrTuple else tp.tp1.underlyingClassOrTuple
588+
if otherSide.asClass.isValueClass then ClassOrTuple.ClassSymbol(defn.AnyClass) else otherSide
589+
else
590+
tp.join.underlyingClassOrTuple
591+
case _: JavaArrayType =>
592+
ClassOrTuple.ClassSymbol(defn.ArrayClass)
593+
case _ =>
594+
ClassOrTuple.NoClass
595+
527596
/** The least (wrt <:<) set of symbols satisfying the `include` predicate of which this type is a subtype
528597
*/
529598
final def parentSymbols(include: Symbol => Boolean)(using Context): List[Symbol] = this match {

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()
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: 31 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -281,34 +281,16 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
281281

282282
private def productMirror(mirroredType: Type, formal: Type, span: Span)(using Context): TreeWithErrors =
283283

284-
var isSafeGenericTuple = Option.empty[(Symbol, List[Type])]
285-
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])]
289-
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
300-
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
284+
extension (clsOrTuple: ClassOrTuple) def isGenericProd(using Context) =
285+
clsOrTuple.isGenericTuple || clsOrTuple.asClass.isGenericProduct && canAccessCtor(clsOrTuple.asClass)
307286

308-
val classPartsMatch = inner(tp, cls)
309-
classPartsMatch && genericTupleParts.map((cls, _) => cls).distinct.sizeIs <= 1 &&
310-
{ isSafeGenericTuple = genericTupleParts.headOption ; true }
311-
end acceptable
287+
/** do all parts match the class symbol? */
288+
def acceptable(tp: Type, clsOrTuple: ClassOrTuple): Boolean = tp match
289+
case tp: HKTypeLambda if tp.resultType.isInstanceOf[HKTypeLambda] => false
290+
case OrType(tp1, tp2) => acceptable(tp1, clsOrTuple) && acceptable(tp2, clsOrTuple)
291+
case GenericTupleType(args) => args.size <= Definitions.MaxTupleArity
292+
case tp: TypeProxy => acceptable(tp.underlying, clsOrTuple)
293+
case _ => tp.underlyingClassOrTuple.asClass eq clsOrTuple.asClass
312294

313295
/** for a case class, if it will have an anonymous mirror,
314296
* check that its constructor can be accessed
@@ -326,13 +308,13 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
326308
def genAnonyousMirror(cls: Symbol): Boolean =
327309
cls.is(Scala2x) || cls.linkedClass.is(Case)
328310

329-
def makeProductMirror(cls: Symbol): TreeWithErrors =
330-
val mirroredClass = isSafeGenericTuple.fold(cls)((cls, _) => cls)
311+
def makeProductMirror(clsOrTuple: ClassOrTuple): TreeWithErrors =
312+
val mirroredClass = clsOrTuple.asClass
331313
val accessors = mirroredClass.caseAccessors.filterNot(_.isAllOf(PrivateLocal))
332314
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-
}
315+
val nestedPairs = clsOrTuple match
316+
case ClassOrTuple.GenericTuple(_, args) => TypeOps.nestedPairs(args)
317+
case _ => TypeOps.nestedPairs(accessors.map(mirroredType.resultType.memberInfo(_).widenExpr))
336318
val (monoType, elemsType) = mirroredType match
337319
case mirroredType: HKTypeLambda =>
338320
(mkMirroredMonoType(mirroredType), mirroredType.derivedLambdaType(resType = nestedPairs))
@@ -342,25 +324,30 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
342324
checkRefinement(formal, tpnme.MirroredElemTypes, elemsType, span)
343325
checkRefinement(formal, tpnme.MirroredElemLabels, elemsLabels, span)
344326
val mirrorType =
345-
mirrorCore(defn.Mirror_ProductClass, monoType, mirroredType, cls.name, formal)
327+
mirrorCore(defn.Mirror_ProductClass, monoType, mirroredType, mirroredClass.name, formal)
346328
.refinedWith(tpnme.MirroredElemTypes, TypeAlias(elemsType))
347329
.refinedWith(tpnme.MirroredElemLabels, TypeAlias(elemsLabels))
348330
val mirrorRef =
349331
if genAnonyousMirror(mirroredClass) then
350-
anonymousMirror(monoType, ExtendsProductMirror, isSafeGenericTuple.map(_(1).size), span)
332+
val arity = clsOrTuple match
333+
case ClassOrTuple.GenericTuple(arity, _) => Some(arity)
334+
case _ => None
335+
anonymousMirror(monoType, ExtendsProductMirror, arity, span)
351336
else companionPath(mirroredType, span)
352337
withNoErrors(mirrorRef.cast(mirrorType))
353338
end makeProductMirror
354339

355-
def getError(cls: Symbol): String =
340+
def getError(clsOrTuple: ClassOrTuple): String =
356341
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."
342+
if !clsOrTuple.isGenericTuple then
343+
if !clsOrTuple.asClass.isGenericProduct then
344+
i"because ${clsOrTuple.asClass.whyNotGenericProduct}"
345+
else if !canAccessCtor(clsOrTuple.asClass) then
346+
i"because the constructor of ${clsOrTuple.asClass} is innaccessible from the calling scope."
347+
else ""
361348
else
362349
""
363-
i"$cls is not a generic product $reason"
350+
i"${clsOrTuple.asClass} is not a generic product $reason"
364351
end getError
365352

366353
mirroredType match
@@ -378,13 +365,11 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
378365
val mirrorType = mirrorCore(defn.Mirror_SingletonClass, mirroredType, mirroredType, module.name, formal)
379366
withNoErrors(modulePath.cast(mirrorType))
380367
else
381-
val cls = mirroredType.classSymbol
382-
if acceptable(mirroredType, cls)
383-
&& isSafeGenericTuple.isDefined || (cls.isGenericProduct && canAccessCtor(cls))
384-
then
385-
makeProductMirror(cls)
368+
val clsOrTuple = mirroredType.underlyingClassOrTuple
369+
if acceptable(mirroredType, clsOrTuple) && clsOrTuple.isGenericProd then
370+
makeProductMirror(clsOrTuple)
386371
else
387-
(EmptyTree, List(getError(cls)))
372+
(EmptyTree, List(getError(clsOrTuple)))
388373
end productMirror
389374

390375
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)

0 commit comments

Comments
 (0)