diff --git a/compiler/src/dotty/tools/dotc/ast/CheckTrees.scala.disabled b/compiler/src/dotty/tools/dotc/ast/CheckTrees.scala.disabled index 6eddf64fff15..6bf7530faf24 100644 --- a/compiler/src/dotty/tools/dotc/ast/CheckTrees.scala.disabled +++ b/compiler/src/dotty/tools/dotc/ast/CheckTrees.scala.disabled @@ -217,7 +217,7 @@ object CheckTrees { optionArg.argTypesHi match { case Nil => optionArg :: Nil - case tupleArgs if defn.isTupleType(optionArg) => + case tupleArgs if defn.isTupleNType(optionArg) => tupleArgs } case _ => diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index e5701db53d56..0816ee23889a 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1462,7 +1462,11 @@ class Definitions { def isPolymorphicAfterErasure(sym: Symbol): Boolean = (sym eq Any_isInstanceOf) || (sym eq Any_asInstanceOf) || (sym eq Object_synchronized) - def isTupleType(tp: Type)(using Context): Boolean = { + /** Is this type a `TupleN` type? + * + * @return true if the dealiased type of `tp` is `TupleN[T1, T2, ..., Tn]` + */ + def isTupleNType(tp: Type)(using Context): Boolean = { val arity = tp.dealias.argInfos.length arity <= MaxTupleArity && TupleType(arity) != null && tp.isRef(TupleType(arity).symbol) } diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index e46165ac3db3..0fbb829f1446 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -1180,7 +1180,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling compareLower(info2, tyconIsTypeRef = true) case info2: ClassInfo => tycon2.name.startsWith("Tuple") && - defn.isTupleType(tp2) && recur(tp1, tp2.toNestedPairs) || + defn.isTupleNType(tp2) && recur(tp1, tp2.toNestedPairs) || tryBaseType(info2.cls) case _ => fourthTry @@ -2620,9 +2620,9 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling fullyInstantiated(tp2) && !tp1.classSymbols.exists(_.derivesFrom(tp2.symbol)) case (tp1: TypeRef, tp2: TermRef) if isEnumValue(tp2) => fullyInstantiated(tp1) && !tp2.classSymbols.exists(_.derivesFrom(tp1.symbol)) - case (tp1: Type, tp2: Type) if defn.isTupleType(tp1) => + case (tp1: Type, tp2: Type) if defn.isTupleNType(tp1) => provablyDisjoint(tp1.toNestedPairs, tp2) - case (tp1: Type, tp2: Type) if defn.isTupleType(tp2) => + case (tp1: Type, tp2: Type) if defn.isTupleNType(tp2) => provablyDisjoint(tp1, tp2.toNestedPairs) case (tp1: TypeProxy, tp2: TypeProxy) => provablyDisjoint(tp1.superType, tp2) || provablyDisjoint(tp1, tp2.superType) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 0bf825a6baa0..759ce471772a 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -147,7 +147,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { changePrec(GlobalPrec) { val argStr: Text = if args.length == 2 - && !defn.isTupleType(args.head) + && !defn.isTupleNType(args.head) && !isGiven && !isErased then atPrec(InfixPrec) { argText(args.head) } diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 708944434015..9be85cdd34f5 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -768,7 +768,7 @@ class SpaceEngine(using Context) extends SpaceLogic { val sym = tp.classSymbol - if (ctx.definitions.isTupleType(tp)) + if (ctx.definitions.isTupleNType(tp)) params(tp).map(_ => "_").mkString("(", ", ", ")") else if (scalaListType.isRef(sym)) if (flattenList) "_*" else "_: List" @@ -782,7 +782,7 @@ class SpaceEngine(using Context) extends SpaceLogic { else if (decomposed) "_: " + showType(tp, showTypeArgs = true) else "_" case Prod(tp, fun, params) => - if (ctx.definitions.isTupleType(tp)) + if (ctx.definitions.isTupleNType(tp)) "(" + params.map(doShow(_)).mkString(", ") + ")" else if (tp.isRef(scalaConsType.symbol)) if (flattenList) params.map(doShow(_, flattenList)).filter(_.nonEmpty).mkString(", ") diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 2f34caf0e2f7..ab7187f6f4d4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1363,7 +1363,7 @@ trait Applications extends Compatibility { for (argType <- argTypes) assert(!isBounds(argType), unapplyApp.tpe.show) val bunchedArgs = argTypes match { case argType :: Nil => - if (args.lengthCompare(1) > 0 && Feature.autoTuplingEnabled && defn.isTupleType(argType)) untpd.Tuple(args) :: Nil + if (args.lengthCompare(1) > 0 && Feature.autoTuplingEnabled && defn.isTupleNType(argType)) untpd.Tuple(args) :: Nil else args case _ => args } diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 11d5ab92b1fd..5b6deb7bc526 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -1738,6 +1738,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler val tpNoRefinement = self.dropDependentRefinement tpNoRefinement != self && dotc.core.Symbols.defn.isNonRefinedFunction(tpNoRefinement) + def isTupleN: Boolean = + dotc.core.Symbols.defn.isTupleNType(self) def select(sym: Symbol): TypeRepr = self.select(sym) def appliedTo(targ: TypeRepr): TypeRepr = dotc.core.Types.decorateTypeApplications(self).appliedTo(targ) diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index e6f229676d34..ce957a1a93b3 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -2526,6 +2526,12 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => */ def isDependentFunctionType: Boolean + /** Is this type a `TupleN` type? + * + * @return true if the dealiased type of `self` is `TupleN[T1, T2, ..., Tn]` + */ + def isTupleN: Boolean + /** The type , reduced if possible */ def select(sym: Symbol): TypeRepr diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 697f0bca662b..d4d837e271a8 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -18,5 +18,6 @@ object MiMaFilters { exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule.TypedOrTestTypeTest"), exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule.TypedOrTest"), exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule.TypedOrTestMethods"), + exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TypeReprMethods.isTupleN"), ) } diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/InkuireSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/InkuireSupport.scala index 2b34ba633b0e..a5103c914732 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/InkuireSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/InkuireSupport.scala @@ -150,7 +150,7 @@ trait InkuireSupport: params = typeList.init.map(p => Inkuire.Contravariance(inner(p, vars))) :+ Inkuire.Covariance(inner(typeList.last, vars)), itid = Some(Inkuire.ITID(s"${name}scala.${name}//[]", isParsed = false)) ) - else if t.isTupleType then + else if t.isTupleN then val name = s"Tuple${typeList.size}" Inkuire.Type( name = Inkuire.TypeName(name), diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala index 689c66a200ee..735d9c74d733 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala @@ -6,16 +6,9 @@ import scala.quoted._ object SyntheticsSupport: extension (using Quotes)(t: reflect.TypeRepr) - def isTupleType: Boolean = t.hackIsTupleType(t) def isCompiletimeAppliedType: Boolean = t.hackIsCompiletimeAppliedType(t) - private def hackIsTupleType(rtpe: reflect.TypeRepr): Boolean = - import dotty.tools.dotc - given ctx: dotc.core.Contexts.Context = quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx - val tpe = rtpe.asInstanceOf[dotc.core.Types.Type] - ctx.definitions.isTupleType(tpe) - private def hackIsCompiletimeAppliedType(rtpe: reflect.TypeRepr): Boolean = import dotty.tools.dotc given ctx: dotc.core.Contexts.Context = quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index ed79da0e83b8..91b4eb71b06b 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -187,7 +187,7 @@ trait TypesSupport: partOfSignature ++ texts(" => ") ++ inner(rtpe) case args => texts("(") ++ commas(args.init.map(inner)) ++ texts(") => ") ++ inner(args.last) - else if t.isTupleType then + else if t.isTupleN then typeList match case Nil => Nil diff --git a/tests/run-macros/TypeRepr-isTupleN/Macro_1.scala b/tests/run-macros/TypeRepr-isTupleN/Macro_1.scala new file mode 100644 index 000000000000..37f8c7e32dab --- /dev/null +++ b/tests/run-macros/TypeRepr-isTupleN/Macro_1.scala @@ -0,0 +1,8 @@ +import scala.quoted.* + +inline def isTupleN[T]: Boolean = ${ isTupleNImpl[T] } + +private def isTupleNImpl[T: Type](using Quotes): Expr[Boolean] = { + import quotes.reflect.* + Expr(TypeRepr.of[T].isTupleN) +} diff --git a/tests/run-macros/TypeRepr-isTupleN/Test_2.scala b/tests/run-macros/TypeRepr-isTupleN/Test_2.scala new file mode 100644 index 000000000000..12e15d567b78 --- /dev/null +++ b/tests/run-macros/TypeRepr-isTupleN/Test_2.scala @@ -0,0 +1,38 @@ +@main def Test = { + assert(isTupleN[Tuple1[Int]]) + assert(isTupleN[(Int, Int)]) + assert(isTupleN[(Int, Int, Int)]) + assert(isTupleN[(Int, Int, Int, Int)]) + assert(isTupleN[(Int, Int, Int, Int, Int)]) + assert(isTupleN[(Int, Int, Int, Int, Int, Int)]) + assert(isTupleN[(Int, Int, Int, Int, Int, Int, Int)]) + assert(isTupleN[(Int, Int, Int, Int, Int, Int, Int, Int)]) + assert(isTupleN[(Int, Int, Int, Int, Int, Int, Int, Int, Int)]) + assert(isTupleN[(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)]) + assert(isTupleN[(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)]) + assert(isTupleN[(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)]) + assert(isTupleN[(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)]) + assert(isTupleN[(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)]) + assert(isTupleN[(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)]) + assert(isTupleN[(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)]) + assert(isTupleN[(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)]) + assert(isTupleN[(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)]) + assert(isTupleN[(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)]) + assert(isTupleN[(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)]) + assert(isTupleN[(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)]) + assert(isTupleN[(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)]) + + type Tup = (Int, Int) + assert(isTupleN[Tup]) + + assert(!isTupleN[(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)]) // No tuple 23 + assert(!isTupleN[Tuple]) + assert(!isTupleN[EmptyTuple]) + assert(!isTupleN[NonEmptyTuple]) + assert(!isTupleN[Int *: Tuple]) + + assert(!isTupleN[Any]) + assert(!isTupleN[Int]) + assert(!isTupleN[Object]) + assert(!isTupleN[Nothing]) +}