From 745cd20bb6a3619fd29bf3dcf2ce290a69a1849a Mon Sep 17 00:00:00 2001 From: Emil Ejbyfeldt Date: Thu, 23 May 2024 08:19:30 +0200 Subject: [PATCH 1/4] Remove seen from TypeSizeAccumulator This fixes #15692 and does not seem to break an existing compilation tests. The problem with seen when it comes #15692 is that seen means that type that has repeated types will get a lower size and this incorrectly triggers the divergence check since some of the steps involved less repeated types. The seen logic was introduced in #6329 and the motivation was to deal with F-bounds. Since not tests fail it not clear if this logic is still needed to deal with F-bounds? If it is still needed we can add a test and instead of removing the seen logic we can make it track only types the appear as a bound and could cause infinite recursion instead of tracking all. [Cherry-picked c2ef180d351c7e1e759b9199c6a6df73cdbba584] --- .../src/dotty/tools/dotc/core/Types.scala | 31 ++++++++----------- tests/pos/i15692.scala | 24 ++++++++++++++ 2 files changed, 37 insertions(+), 18 deletions(-) create mode 100644 tests/pos/i15692.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index f0ca272cce36..fc00147c81e7 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -6585,25 +6585,20 @@ 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 = - if seen.contains(tp) then n - else { - seen += tp - tp match { - case tp: AppliedType => - val tpNorm = tp.tryNormalize - if tpNorm.exists then apply(n, tpNorm) - else foldOver(n + 1, tp) - case tp: RefinedType => - foldOver(n + 1, tp) - case tp: TypeRef if tp.info.isTypeAlias => - apply(n, tp.superType) - case tp: TypeParamRef => - apply(n, TypeComparer.bounds(tp)) - case _ => - foldOver(n, tp) - } + tp match { + case tp: AppliedType => + val tpNorm = tp.tryNormalize + if tpNorm.exists then apply(n, tpNorm) + else foldOver(n + 1, tp) + case tp: RefinedType => + foldOver(n + 1, tp) + case tp: TypeRef if tp.info.isTypeAlias => + apply(n, tp.superType) + case tp: TypeParamRef => + apply(n, TypeComparer.bounds(tp)) + case _ => + foldOver(n, tp) } } diff --git a/tests/pos/i15692.scala b/tests/pos/i15692.scala new file mode 100644 index 000000000000..11b69e3bd377 --- /dev/null +++ b/tests/pos/i15692.scala @@ -0,0 +1,24 @@ +sealed trait Nat +sealed trait Succ[Prev <: Nat] extends Nat +sealed trait Zero extends Nat + +class Sum[M <: Nat, N <: Nat] { + type Out <: Nat +} + +object Sum { + type Aux[M <: Nat, N <: Nat, R <: Nat] = Sum[M, N] { type Out = R } + + implicit def sum0[N <: Nat]: Sum.Aux[Zero, N, N] = new Sum[Zero, N] { type Out = N } + implicit def sum1[M <: Nat, N <: Nat, R <: Nat](implicit sum: Sum.Aux[M, Succ[N], R]): Sum.Aux[Succ[M], N, R] = + new Sum[Succ[M], N] { type Out = R } +} + +object Test { + def main(args: Array[String]): Unit = { + type _3 = Succ[Succ[Succ[Zero]]] + type _5 = Succ[Succ[_3]] + + implicitly[Sum[_3, _5]] + } +} From 56d572fc4ce9a36bf6b249ed0bca26828ecec383 Mon Sep 17 00:00:00 2001 From: Emil Ejbyfeldt Date: Sat, 5 Jul 2025 21:59:37 +0200 Subject: [PATCH 2/4] Do seen check only for LazyRef Also adds test case that stackoverflows without any seen check. [Cherry-picked 008d58e42a7143af670cb238105148f2ae200236] --- compiler/src/dotty/tools/dotc/core/Types.scala | 4 ++++ tests/neg/i15692.scala | 5 +++++ tests/pos/i15692.scala | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 tests/neg/i15692.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index fc00147c81e7..d400335e7f83 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -6585,7 +6585,9 @@ 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 = + seen += tp tp match { case tp: AppliedType => val tpNorm = tp.tryNormalize @@ -6597,6 +6599,8 @@ object Types extends TypeUtils { apply(n, tp.superType) case tp: TypeParamRef => apply(n, TypeComparer.bounds(tp)) + case tp: LazyRef if seen.contains(tp) => + n case _ => foldOver(n, tp) } diff --git a/tests/neg/i15692.scala b/tests/neg/i15692.scala new file mode 100644 index 000000000000..0cb163426691 --- /dev/null +++ b/tests/neg/i15692.scala @@ -0,0 +1,5 @@ +trait TC[X] +object TC { + given [T, S <: TC[S]](using TC[S]): TC[T] = ??? + summon[TC[Int]] // error +} diff --git a/tests/pos/i15692.scala b/tests/pos/i15692.scala index 11b69e3bd377..99eddcd33d71 100644 --- a/tests/pos/i15692.scala +++ b/tests/pos/i15692.scala @@ -9,7 +9,7 @@ class Sum[M <: Nat, N <: Nat] { object Sum { type Aux[M <: Nat, N <: Nat, R <: Nat] = Sum[M, N] { type Out = R } - implicit def sum0[N <: Nat]: Sum.Aux[Zero, N, N] = new Sum[Zero, N] { type Out = N } + implicit def sum0[N <: Nat]: Sum.Aux[Zero, N, N] = new Sum[Zero, N] { type Out = N } implicit def sum1[M <: Nat, N <: Nat, R <: Nat](implicit sum: Sum.Aux[M, Succ[N], R]): Sum.Aux[Succ[M], N, R] = new Sum[Succ[M], N] { type Out = R } } From 84b3f1e333bc5fd97873983c6594fece7bc09856 Mon Sep 17 00:00:00 2001 From: Emil Ejbyfeldt Date: Tue, 16 Sep 2025 15:18:26 +0200 Subject: [PATCH 3/4] Only track types in from LazyRef [Cherry-picked 8a0bbdf1ce04add1d864f52f2a88c001509bd0bd] --- compiler/src/dotty/tools/dotc/core/Types.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index d400335e7f83..abdf7c82bbfd 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -6587,7 +6587,6 @@ 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 = - seen += tp tp match { case tp: AppliedType => val tpNorm = tp.tryNormalize @@ -6599,8 +6598,11 @@ object Types extends TypeUtils { apply(n, tp.superType) case tp: TypeParamRef => apply(n, TypeComparer.bounds(tp)) - case tp: LazyRef if seen.contains(tp) => - n + case tp: LazyRef => + if seen.contains(tp) then n + else + seen += tp + foldOver(n, tp) case _ => foldOver(n, tp) } From 906c7554fd081e6f190ef69cbc02125d0870e345 Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Thu, 25 Sep 2025 14:22:45 +0200 Subject: [PATCH 4/4] bugfix: Fix issue with JDK 8 compatibility in mtagsShared This got introduced, because we no longer check in Scala Next. I added an additional check in metals to make sure this doesn't happen anymore. --- project/Build.scala | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 9724ed0b3aa5..eb9f7084127b 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -597,6 +597,17 @@ object Build { recur(lines, false) } + // Hotfix for JDK 8 + def replaceJDK11APIs(lines: List[String], file: File): List[String] = { + if (file.getName == "InlayHints.scala") { + lines.map{ + _.replace(".repeat(naiveIndent)", " * naiveIndent") + } + } else { + lines + } + } + /** replace imports of `com.google.protobuf.*` with compiler implemented version */ def replaceProtobuf(lines: List[String]): List[String] = { def recur(ls: List[String]): List[String] = ls match { @@ -1271,7 +1282,7 @@ object Build { val mtagsSharedSources = (targetDir ** "*.scala").get.toSet mtagsSharedSources.foreach(f => { val lines = IO.readLines(f) - val substitutions = (replaceProtobuf(_)) andThen (insertUnsafeNullsImport(_)) + val substitutions = (replaceProtobuf(_)) andThen (insertUnsafeNullsImport(_)) andThen (replaceJDK11APIs(_, f)) IO.writeLines(f, substitutions(lines)) }) mtagsSharedSources