Skip to content

Commit 2836fbc

Browse files
Merge pull request #15330 from dotty-staging/fix-15302
Handle & and | types when computing tuple arity
2 parents 47fa175 + ea6ee13 commit 2836fbc

File tree

8 files changed

+64
-38
lines changed

8 files changed

+64
-38
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -691,7 +691,7 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
691691
private def erasePair(tp: Type)(using Context): Type = {
692692
// NOTE: `tupleArity` does not consider TypeRef(EmptyTuple$) equivalent to EmptyTuple.type,
693693
// we fix this for printers, but type erasure should be preserved.
694-
val arity = tp.tupleArity()
694+
val arity = tp.tupleArity
695695
if (arity < 0) defn.ProductClass.typeRef
696696
else if (arity <= Definitions.MaxTupleArity) defn.TupleType(arity).nn
697697
else defn.TupleXXLClass.typeRef

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

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -215,15 +215,17 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
215215

216216
def appliedText(tp: Type): Text = tp match
217217
case tp @ AppliedType(tycon, args) =>
218-
val cls = tycon.typeSymbol
219-
if tycon.isRepeatedParam then toTextLocal(args.head) ~ "*"
220-
else if defn.isFunctionClass(cls) then toTextFunction(args, cls.name.isContextFunction, cls.name.isErasedFunction)
221-
else if tp.tupleArity(relaxEmptyTuple = true) >= 2 && !printDebug then toTextTuple(tp.tupleElementTypes)
222-
else if isInfixType(tp) then
223-
val l :: r :: Nil = args: @unchecked
224-
val opName = tyconName(tycon)
225-
toTextInfixType(tyconName(tycon), l, r) { simpleNameString(tycon.typeSymbol) }
226-
else Str("")
218+
tp.tupleElementTypes match
219+
case Some(types) if types.size >= 2 && !printDebug => toTextTuple(types)
220+
case _ =>
221+
val cls = tycon.typeSymbol
222+
if tycon.isRepeatedParam then toTextLocal(args.head) ~ "*"
223+
else if defn.isFunctionClass(cls) then toTextFunction(args, cls.name.isContextFunction, cls.name.isErasedFunction)
224+
else if isInfixType(tp) then
225+
val l :: r :: Nil = args: @unchecked
226+
val opName = tyconName(tycon)
227+
toTextInfixType(tyconName(tycon), l, r) { simpleNameString(tycon.typeSymbol) }
228+
else Str("")
227229
case _ =>
228230
Str("")
229231

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: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -51,41 +51,44 @@ 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
5654
*/
57-
def tupleArity(relaxEmptyTuple: Boolean = false)(using Context): Int = self match {
55+
def tupleArity(using Context): Int = self/*.dealias*/ match { // TODO: why does dealias cause a failure in tests/run-deep-subtype/Tuple-toArray.scala
5856
case AppliedType(tycon, _ :: tl :: Nil) if tycon.isRef(defn.PairClass) =>
59-
val arity = tl.tupleArity(relaxEmptyTuple)
57+
val arity = tl.tupleArity
6058
if (arity < 0) arity else arity + 1
6159
case self: SingletonType =>
6260
if self.termSymbol == defn.EmptyTupleModule then 0 else -1
63-
case self: TypeRef if relaxEmptyTuple && self.classSymbol == defn.EmptyTupleModule.moduleClass =>
64-
0
65-
case self if defn.isTupleClass(self.classSymbol) =>
66-
self.dealias.argInfos.length
61+
case self: AndOrType =>
62+
val arity1 = self.tp1.tupleArity
63+
val arity2 = self.tp2.tupleArity
64+
if arity1 == arity2 then arity1 else -1
6765
case _ =>
68-
-1
66+
if defn.isTupleClass(self.classSymbol) then self.dealias.argInfos.length
67+
else -1
6968
}
7069

7170
/** The element types of this tuple type, which can be made up of EmptyTuple, TupleX and `*:` pairs */
72-
def tupleElementTypes(using Context): List[Type] = self match {
71+
def tupleElementTypes(using Context): Option[List[Type]] = self.dealias match {
7372
case AppliedType(tycon, hd :: tl :: Nil) if tycon.isRef(defn.PairClass) =>
74-
hd :: tl.tupleElementTypes
73+
tl.tupleElementTypes.map(hd :: _)
7574
case self: SingletonType =>
76-
assert(self.termSymbol == defn.EmptyTupleModule, "not a tuple")
77-
Nil
78-
case self: TypeRef if self.classSymbol == defn.EmptyTupleModule.moduleClass =>
79-
Nil
80-
case self if defn.isTupleClass(self.classSymbol) =>
81-
self.dealias.argInfos
75+
if self.termSymbol == defn.EmptyTupleModule then Some(Nil) else None
76+
case AndType(tp1, tp2) =>
77+
// We assume that we have the following property:
78+
// (T1, T2, ..., Tn) & (U1, U2, ..., Un) = (T1 & U1, T2 & U2, ..., Tn & Un)
79+
tp1.tupleElementTypes.zip(tp2.tupleElementTypes).map { case (t1, t2) => t1 & t2 }
80+
case OrType(tp1, tp2) =>
81+
None // We can't combine the type of two tuples
8282
case _ =>
83-
throw new AssertionError("not a tuple")
83+
if defn.isTupleClass(self.classSymbol) then Some(self.dealias.argInfos)
84+
else None
8485
}
8586

8687
/** The `*:` equivalent of an instance of a Tuple class */
8788
def toNestedPairs(using Context): Type =
88-
TypeOps.nestedPairs(tupleElementTypes)
89+
tupleElementTypes match
90+
case Some(types) => TypeOps.nestedPairs(types)
91+
case None => throw new AssertionError("not a tuple")
8992

9093
def refinedWith(name: Name, info: Type)(using Context) = RefinedType(self, name, info)
9194

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -355,13 +355,15 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
355355
reduce(tp.underlying)
356356
case tp: HKTypeLambda if tp.resultType.isInstanceOf[HKTypeLambda] =>
357357
Left(i"its subpart `$tp` is not a supported kind (either `*` or `* -> *`)")
358-
case tp @ AppliedType(tref: TypeRef, _)
359-
if tref.symbol == defn.PairClass
360-
&& tp.tupleArity(relaxEmptyTuple = true) > 0 =>
361-
// avoid type aliases for tuples
362-
Right(MirrorSource.GenericTuple(tp.tupleElementTypes))
363358
case tp: TypeProxy =>
364-
reduce(tp.underlying)
359+
tp match
360+
case tp @ AppliedType(tref: TypeRef, _) if tref.symbol == defn.PairClass =>
361+
tp.tupleElementTypes match
362+
case Some(types) =>
363+
// avoid type aliases for tuples
364+
Right(MirrorSource.GenericTuple(types))
365+
case _ => reduce(tp.underlying)
366+
case _ => reduce(tp.underlying)
365367
case tp @ AndType(l, r) =>
366368
for
367369
lsrc <- reduce(l)

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2768,8 +2768,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
27682768
typed(desugar.smallTuple(tree).withSpan(tree.span), pt)
27692769
else {
27702770
val pts =
2771-
if (arity == pt.tupleArity()) pt.tupleElementTypes
2772-
else List.fill(arity)(defn.AnyType)
2771+
pt.tupleElementTypes match
2772+
case Some(types) if types.size == arity => types
2773+
case _ => List.fill(arity)(defn.AnyType)
27732774
val elems = tree.trees.lazyZip(pts).map(
27742775
if ctx.mode.is(Mode.Type) then typedType(_, _, mapPatternBounds = true)
27752776
else typed(_, _))

tests/run/i15302a.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
inline def flatConcat2[A, B](a: A, b: B) =
2+
b match
3+
case bs: *:[bh, bt] => a *: bs
4+
5+
@main def Test =
6+
val x: (Int, Int, Int) = flatConcat2(1, (2,3))

tests/run/i15302b.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
@main def Test =
2+
val tup2: Any = (1, 2)
3+
val x1: (Int, Int) = tup2.asInstanceOf[(Int *: Int *: EmptyTuple) & (Int, Int)]
4+
val x2: (Int, Int) = tup2.asInstanceOf[(Int *: Int *: Tuple) & (Int, Int)]
5+
val x3: (Int, Int) = tup2.asInstanceOf[(Int *: Int *: EmptyTuple) | (Int, Int)]
6+
val x4: Tuple = tup2.asInstanceOf[(Int *: Int *: Tuple) | (Int, Int)]
7+
8+
val tup3: Any = (1, 2, 3)
9+
val x5: (Int, Int, Int) = tup3.asInstanceOf[Int *: ((Int *: Int *: EmptyTuple) & (Int, Int))]
10+
val x6: (Int, Int, Int) = tup3.asInstanceOf[Int *: ((Int *: Int *: Tuple) & (Int, Int))]
11+
val x7: (Int, Int, Int) = tup3.asInstanceOf[Int *: ((Int *: Int *: EmptyTuple) | (Int, Int))]
12+
val x8: Tuple = tup3.asInstanceOf[Int *: ((Int *: Int *: Tuple) | (Int, Int))]

0 commit comments

Comments
 (0)