From 2917667fa69679930437e1d7294ce589a8993875 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 30 May 2022 16:56:42 +0200 Subject: [PATCH 1/2] Handle & and | types when computing tuple arity Fixes #15302 --- .../src/dotty/tools/dotc/transform/TypeUtils.scala | 9 ++++++--- tests/run/i15302a.scala | 6 ++++++ tests/run/i15302b.scala | 12 ++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 tests/run/i15302a.scala create mode 100644 tests/run/i15302b.scala diff --git a/compiler/src/dotty/tools/dotc/transform/TypeUtils.scala b/compiler/src/dotty/tools/dotc/transform/TypeUtils.scala index 2bf72ec588b8..23c62d39721e 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeUtils.scala @@ -62,10 +62,13 @@ object TypeUtils { 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(relaxEmptyTuple) + val arity2 = self.tp2.tupleArity(relaxEmptyTuple) + 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 */ diff --git a/tests/run/i15302a.scala b/tests/run/i15302a.scala new file mode 100644 index 000000000000..a723c8a7a8fa --- /dev/null +++ b/tests/run/i15302a.scala @@ -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)) diff --git a/tests/run/i15302b.scala b/tests/run/i15302b.scala new file mode 100644 index 000000000000..42e119bf4e04 --- /dev/null +++ b/tests/run/i15302b.scala @@ -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))] From ea6ee136783d76be014c6d829caf92161d4e20a2 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 2 Jun 2022 10:36:20 +0200 Subject: [PATCH 2/2] Fix conflict between tupleArity and tupleElementTypes There are cases where we know the tuple arity but cannot know the flat element types of the tuple. For example `(A, B) | (C, D)` has arity 2 but we cannot create a single tuple containing these elements. In tupleArity we are missing a dealias. We need to investigate this further. At first glance it seems that the signature of `Tuple.apply(x)` is erased to `(Object): Product` and not `(Object): Tuple1`. This would have been caused be the missing dealias. Unfortunately, if this is the case we might not be able to fix the erasure without breaking binary compatibility of the standard library. --- .../dotty/tools/dotc/core/TypeErasure.scala | 2 +- .../tools/dotc/printing/RefinedPrinter.scala | 20 ++++++----- .../dotc/transform/GenericSignatures.scala | 2 +- .../tools/dotc/transform/TypeUtils.scala | 36 +++++++++---------- .../dotty/tools/dotc/typer/Synthesizer.scala | 14 ++++---- .../src/dotty/tools/dotc/typer/Typer.scala | 5 +-- 6 files changed, 42 insertions(+), 37 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 87a829e51519..cb9874070456 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 9a9bc345ee69..b74e78ba591b 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -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("") diff --git a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala index 6a01c9dc64f9..9a6ab233e239 100644 --- a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala +++ b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala @@ -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) diff --git a/compiler/src/dotty/tools/dotc/transform/TypeUtils.scala b/compiler/src/dotty/tools/dotc/transform/TypeUtils.scala index 23c62d39721e..cf9fcbabf536 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeUtils.scala @@ -51,20 +51,16 @@ 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 { + 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: AndOrType => - val arity1 = self.tp1.tupleArity(relaxEmptyTuple) - val arity2 = self.tp2.tupleArity(relaxEmptyTuple) + val arity1 = self.tp1.tupleArity + val arity2 = self.tp2.tupleArity if arity1 == arity2 then arity1 else -1 case _ => if defn.isTupleClass(self.classSymbol) then self.dealias.argInfos.length @@ -72,23 +68,27 @@ object TypeUtils { } /** 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) diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index 0701534a08c6..0899a7d9a26b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -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) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index bdc6a03f6949..38825688ddb9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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(_, _))