From dca3ea16745f273dd9d4ec5c23d40ea37c00830c Mon Sep 17 00:00:00 2001 From: Lluc Santamaria Riba Date: Sat, 13 Dec 2025 12:01:48 +0100 Subject: [PATCH] Fix inconsistent typeSize for TupleN vs nested pairs Previously, `typeSize` reported different values for standard `TupleN` types (e.g., `(A, B)`) compared to their equivalent recursive pair encodings (e.g., `A *: B *: EmptyTuple`). This discrepancy occurred because `TupleN` is a flat `AppliedType`, while the nested encoding forms a deeper tree structure. This patch modifies `TypeSizeAccumulator` to canonicalize `TupleN` types into their recursive `*:` representation before calculating the size. This ensures that the size metric is consistent regardless of whether the tuple is represented syntactically or structurally. This change is verified by a new unit test in `TypesTest`, which confirms that both `Tuple3[Int, Boolean, Double]` and its recursive equivalent `Int *: Boolean *: Double *: EmptyTuple` now yield identical `typeSize` values. Fixes #24730 --- compiler/src/dotty/tools/dotc/core/Types.scala | 10 +++++++++- .../test/dotty/tools/dotc/core/TypesTest.scala | 18 ++++++++++++++++++ .../tools/dotc/typer/DivergenceChecker.scala | 4 ++-- 3 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 compiler/test/dotty/tools/dotc/core/TypesTest.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index dc54d14b0d4b..39c8f3cc4f3b 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -7124,7 +7124,15 @@ object Types extends TypeUtils { class TypeSizeAccumulator(using Context) extends TypeAccumulator[Int] { var seen = util.HashSet[Type](initialCapacity = 8) def apply(n: Int, tp: Type): Int = - tp match { + + val canonicTp = tp match + case AppliedType(_, args) if defn.isTupleNType(tp) => + args.foldRight(defn.EmptyTupleClass.typeRef: Type) { (elemType, acc) => + defn.PairClass.typeRef.appliedTo(elemType, acc) + } + case _ => tp + + canonicTp match { case tp: AppliedType => val tpNorm = tp.tryNormalize if tpNorm.exists then apply(n, tpNorm) diff --git a/compiler/test/dotty/tools/dotc/core/TypesTest.scala b/compiler/test/dotty/tools/dotc/core/TypesTest.scala new file mode 100644 index 000000000000..00336dca2b2c --- /dev/null +++ b/compiler/test/dotty/tools/dotc/core/TypesTest.scala @@ -0,0 +1,18 @@ +package dotty.tools.dotc.core + +import dotty.tools.DottyTest +import dotty.tools.dotc.core.Symbols.defn +import dotty.tools.dotc.core.TypeOps + +import org.junit.Test +import org.junit.Assert.assertEquals + +class TypesTest extends DottyTest: + + @Test def tuple3TypeSize = + val tpe = defn.TupleType(3).nn.appliedTo(List(defn.IntType, defn.BooleanType, defn.DoubleType)) + assertEquals(3, tpe.typeSize) + + @Test def tuple3ConsTypeSize = + val tpe = TypeOps.nestedPairs(List(defn.IntType, defn.BooleanType, defn.DoubleType)) + assertEquals(3, tpe.typeSize) \ No newline at end of file diff --git a/compiler/test/dotty/tools/dotc/typer/DivergenceChecker.scala b/compiler/test/dotty/tools/dotc/typer/DivergenceChecker.scala index 520b3d2de57e..da3482a43d27 100644 --- a/compiler/test/dotty/tools/dotc/typer/DivergenceChecker.scala +++ b/compiler/test/dotty/tools/dotc/typer/DivergenceChecker.scala @@ -50,8 +50,8 @@ class DivergenceCheckerTests extends DottyTest { 1, 1, 1, - 3, - 5 + 4, + 6 ) tpes.lazyZip(expectedSizes).lazyZip(expectedCoveringSets).foreach {