Skip to content

Handle & and | types when computing tuple arity #15330

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/TypeErasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,7 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
private def erasePair(tp: Type)(using Context): Type = {
// NOTE: `tupleArity` does not consider TypeRef(EmptyTuple$) equivalent to EmptyTuple.type,
// we fix this for printers, but type erasure should be preserved.
val arity = tp.tupleArity()
val arity = tp.tupleArity
if (arity < 0) defn.ProductClass.typeRef
else if (arity <= Definitions.MaxTupleArity) defn.TupleType(arity).nn
else defn.TupleXXLClass.typeRef
Expand Down
20 changes: 11 additions & 9 deletions compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -215,15 +215,17 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {

def appliedText(tp: Type): Text = tp match
case tp @ AppliedType(tycon, args) =>
val cls = tycon.typeSymbol
if tycon.isRepeatedParam then toTextLocal(args.head) ~ "*"
else if defn.isFunctionClass(cls) then toTextFunction(args, cls.name.isContextFunction, cls.name.isErasedFunction)
else if tp.tupleArity(relaxEmptyTuple = true) >= 2 && !printDebug then toTextTuple(tp.tupleElementTypes)
else if isInfixType(tp) then
val l :: r :: Nil = args: @unchecked
val opName = tyconName(tycon)
toTextInfixType(tyconName(tycon), l, r) { simpleNameString(tycon.typeSymbol) }
else Str("")
tp.tupleElementTypes match
case Some(types) if types.size >= 2 && !printDebug => toTextTuple(types)
case _ =>
val cls = tycon.typeSymbol
if tycon.isRepeatedParam then toTextLocal(args.head) ~ "*"
else if defn.isFunctionClass(cls) then toTextFunction(args, cls.name.isContextFunction, cls.name.isErasedFunction)
else if isInfixType(tp) then
val l :: r :: Nil = args: @unchecked
val opName = tyconName(tycon)
toTextInfixType(tyconName(tycon), l, r) { simpleNameString(tycon.typeSymbol) }
else Str("")
case _ =>
Str("")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ object GenericSignatures {
case _ => jsig(elemtp)

case RefOrAppliedType(sym, pre, args) =>
if (sym == defn.PairClass && tp.tupleArity() > Definitions.MaxTupleArity)
if (sym == defn.PairClass && tp.tupleArity > Definitions.MaxTupleArity)
jsig(defn.TupleXXLClass.typeRef)
else if (isTypeParameterInSig(sym, sym0)) {
assert(!sym.isAliasType, "Unexpected alias type: " + sym)
Expand Down
41 changes: 22 additions & 19 deletions compiler/src/dotty/tools/dotc/transform/TypeUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,41 +51,44 @@ object TypeUtils {

/** The arity of this tuple type, which can be made up of EmptyTuple, TupleX and `*:` pairs,
* or -1 if this is not a tuple type.
*
* @param relaxEmptyTuple if true then TypeRef(EmptyTuple$) =:= EmptyTuple.type
*/
def tupleArity(relaxEmptyTuple: Boolean = false)(using Context): Int = self match {
Copy link
Member

@bishabosha bishabosha Jun 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edit: I think this will affect the RefinedPrinter which will no longer pretty print generic tuple types

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok it seems to work for refined printer I see you made the change, disregard all :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For other that may read this thread: the important thing here is that tupleElementTypes handles the EmptyTuple type alias but tupleArity does not (see comment on dealiasing)

def tupleArity(using Context): Int = self/*.dealias*/ match { // TODO: why does dealias cause a failure in tests/run-deep-subtype/Tuple-toArray.scala
case AppliedType(tycon, _ :: tl :: Nil) if tycon.isRef(defn.PairClass) =>
val arity = tl.tupleArity(relaxEmptyTuple)
val arity = tl.tupleArity
if (arity < 0) arity else arity + 1
case self: SingletonType =>
if self.termSymbol == defn.EmptyTupleModule then 0 else -1
case self: TypeRef if relaxEmptyTuple && self.classSymbol == defn.EmptyTupleModule.moduleClass =>
0
case self if defn.isTupleClass(self.classSymbol) =>
self.dealias.argInfos.length
case self: AndOrType =>
val arity1 = self.tp1.tupleArity
val arity2 = self.tp2.tupleArity
if arity1 == arity2 then arity1 else -1
case _ =>
-1
if defn.isTupleClass(self.classSymbol) then self.dealias.argInfos.length
else -1
}

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

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

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

Expand Down
14 changes: 8 additions & 6 deletions compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -358,13 +358,15 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
reduce(tp.underlying)
case tp: HKTypeLambda if tp.resultType.isInstanceOf[HKTypeLambda] =>
Left(i"its subpart `$tp` is not a supported kind (either `*` or `* -> *`)")
case tp @ AppliedType(tref: TypeRef, _)
if tref.symbol == defn.PairClass
&& tp.tupleArity(relaxEmptyTuple = true) > 0 =>
// avoid type aliases for tuples
Right(MirrorSource.GenericTuple(tp.tupleElementTypes))
case tp: TypeProxy =>
reduce(tp.underlying)
tp match
case tp @ AppliedType(tref: TypeRef, _) if tref.symbol == defn.PairClass =>
tp.tupleElementTypes match
case Some(types) =>
// avoid type aliases for tuples
Right(MirrorSource.GenericTuple(types))
case _ => reduce(tp.underlying)
case _ => reduce(tp.underlying)
case tp @ AndType(l, r) =>
for
lsrc <- reduce(l)
Expand Down
5 changes: 3 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2768,8 +2768,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
typed(desugar.smallTuple(tree).withSpan(tree.span), pt)
else {
val pts =
if (arity == pt.tupleArity()) pt.tupleElementTypes
else List.fill(arity)(defn.AnyType)
pt.tupleElementTypes match
case Some(types) if types.size == arity => types
case _ => List.fill(arity)(defn.AnyType)
val elems = tree.trees.lazyZip(pts).map(
if ctx.mode.is(Mode.Type) then typedType(_, _, mapPatternBounds = true)
else typed(_, _))
Expand Down
6 changes: 6 additions & 0 deletions tests/run/i15302a.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
inline def flatConcat2[A, B](a: A, b: B) =
b match
case bs: *:[bh, bt] => a *: bs

@main def Test =
val x: (Int, Int, Int) = flatConcat2(1, (2,3))
12 changes: 12 additions & 0 deletions tests/run/i15302b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@main def Test =
val tup2: Any = (1, 2)
val x1: (Int, Int) = tup2.asInstanceOf[(Int *: Int *: EmptyTuple) & (Int, Int)]
val x2: (Int, Int) = tup2.asInstanceOf[(Int *: Int *: Tuple) & (Int, Int)]
val x3: (Int, Int) = tup2.asInstanceOf[(Int *: Int *: EmptyTuple) | (Int, Int)]
val x4: Tuple = tup2.asInstanceOf[(Int *: Int *: Tuple) | (Int, Int)]

val tup3: Any = (1, 2, 3)
val x5: (Int, Int, Int) = tup3.asInstanceOf[Int *: ((Int *: Int *: EmptyTuple) & (Int, Int))]
val x6: (Int, Int, Int) = tup3.asInstanceOf[Int *: ((Int *: Int *: Tuple) & (Int, Int))]
val x7: (Int, Int, Int) = tup3.asInstanceOf[Int *: ((Int *: Int *: EmptyTuple) | (Int, Int))]
val x8: Tuple = tup3.asInstanceOf[Int *: ((Int *: Int *: Tuple) | (Int, Int))]