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 2bf72ec588b8..cf9fcbabf536 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeUtils.scala @@ -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 { + 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) 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(_, _)) 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))]