Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
31 changes: 31 additions & 0 deletions tests/neg/i24914.scala
Original file line number Diff line number Diff line change
@@ -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
Loading