diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index d5d028530434..9435dfb94801 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -1863,15 +1863,33 @@ trait Implicits: // is larger (see `typeSize`) and is constructed using the same set of types and type // constructors (see `coveringSet`). // + // We also treat two candidates as equivalent for this check when they are sibling + // givens declared in the same owner that share a declared signature. Such siblings + // (a common pattern when modeling type class hierarchies, see i24914) demand the + // same context parameters, so exploring one and then another under the same + // prototype shape makes no progress and would otherwise lead to a factorial + // blow-up of the search before the expression-count limit eventually kicks in. + // We use `frozen_=:=` so the check does not commit constraint updates. + // // We are able to tie a recursive knot if there is compatible term already under // construction which is separated from this context by at least one by name argument // as we ascend the chain of open implicits to the outermost search context. + def equivalentCandidates(c1: Candidate, c2: Candidate): Boolean = + if c1.ref eq c2.ref then true + else + val sym1 = c1.ref.symbol + val sym2 = c2.ref.symbol + sym1.exists + && sym2.exists + && sym1.maybeOwner == sym2.maybeOwner + && c1.ref.info.frozen_=:=(c2.ref.info) + @tailrec def loop(history: SearchHistory, belowByname: Boolean): Boolean = history match case prev @ OpenSearch(cand1, tp, outer) => - if cand1.ref eq cand.ref then + if equivalentCandidates(cand1, cand) then lazy val wildTp = wildApprox(tp.widenExpr) if belowByname && (wildTp <:< wildPt) then fullyDefinedType(tp, "by-name implicit parameter", srcPos) diff --git a/tests/neg/i24914.scala b/tests/neg/i24914.scala new file mode 100644 index 000000000000..e83359400845 --- /dev/null +++ b/tests/neg/i24914.scala @@ -0,0 +1,31 @@ +// Regression test for #24914: make sure that a forest of sibling givens +// sharing the same signature cannot blow up the implicit search into +// factorial territory. Before the divergence check considered candidates +// with identical declared types equivalent, this compiled for tens of +// seconds and eventually failed with a "search problem too large" warning. +// The search should now be cut short by ordinary divergence detection. + +trait Functor[F[_]] +trait Monad[F[_]] extends Functor[F] + +class T[F[_], A, X] + +object Functor: + given t1[F[_]: Functor, A]: Functor[[X] =>> T[F, A, X]] = ??? + given t2[F[_]: Functor, A]: Functor[[X] =>> T[F, A, X]] = ??? + given t3[F[_]: Functor, A]: Functor[[X] =>> T[F, A, X]] = ??? + given t4[F[_]: Monad, A]: Functor[[X] =>> T[F, A, X]] = ??? + given t5[F[_]: Functor, A]: Functor[[X] =>> T[F, A, X]] = ??? + given t6[F[_]: Functor, A]: Functor[[X] =>> T[F, A, X]] = ??? + +object Monad: + given t1[F[_]: Monad, A]: Monad[[X] =>> T[F, A, X]] = ??? + given t2[F[_]: Monad, A]: Monad[[X] =>> T[F, A, X]] = ??? + given t3[F[_]: Monad, A]: Monad[[X] =>> T[F, A, X]] = ??? + given t4[F[_]: Monad, A]: Monad[[X] =>> T[F, A, X]] = ??? + given t5[F[_]: Monad, A]: Monad[[X] =>> T[F, A, X]] = ??? + given t6[F[_]: Monad, A]: Monad[[X] =>> T[F, A, X]] = ??? + +def m[F[_]: Functor]: F[String] = ??? + +val x = for _ <- m yield () // error