Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Impose implicit search limit #13886

Merged
merged 7 commits into from Nov 9, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Expand Up @@ -218,6 +218,7 @@ private sealed trait XSettings:
val Xtarget: Setting[String] = ChoiceSetting("-Xtarget", "target", "Emit bytecode for the specified version of the Java platform. This might produce bytecode that will break at runtime. When on JDK 9+, consider -release as a safer alternative.", ScalaSettings.supportedTargetVersions, "", aliases = List("--Xtarget"))
val XcheckMacros: Setting[Boolean] = BooleanSetting("-Xcheck-macros", "Check some invariants of macro generated code while expanding macros", aliases = List("--Xcheck-macros"))
val XmainClass: Setting[String] = StringSetting("-Xmain-class", "path", "Class for manifest's Main-Class entry (only useful with -d <jar>)", "")
val XimplicitSearchLimit: Setting[Int] = IntSetting("-Ximplicit-search-limit", "Maximal number of expressions to be generated in an implicit search", 100000)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this default, it takes the original problem 330 seconds (5.5 minutes) to finish. It seems a bit long, was that intended? I also noticed that if we use 90000 it takes 90 seconds and with 50000 it only takes 50 seconds to finish, the original time does not seem to be proportional. I wonder if we reached some memory limitations. Not sure if we should make this limit smaller.


val XmixinForceForwarders = ChoiceSetting(
name = "-Xmixin-force-forwarders",
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala
Expand Up @@ -175,7 +175,8 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID]:
OverrideErrorID,
MatchableWarningID,
CannotExtendFunctionID,
LossyWideningConstantConversionID
LossyWideningConstantConversionID,
ImplicitSearchTooLargeID

def errorNumber = ordinal - 2

Expand Down
7 changes: 7 additions & 0 deletions compiler/src/dotty/tools/dotc/reporting/Message.scala
Expand Up @@ -124,6 +124,13 @@ abstract class Message(val errorId: ErrorMessageID) { self =>
def explain = self.explain ++ suffix
override def canExplain = true

/** Override with `true` for messages that should always be shown even if their
* position overlaps another messsage of a different class. On the other hand
* multiple messages of the same class with overlapping positions will lead
* to only a single message of that class to be issued.
*/
def showAlways = false

override def toString = msg
}

Expand Down
Expand Up @@ -10,19 +10,24 @@ import core.Contexts._
* are suppressed, unless they are of increasing severity. */
trait UniqueMessagePositions extends Reporter {

private val positions = new mutable.HashMap[(SourceFile, Int), Int]
private val positions = new mutable.HashMap[(SourceFile, Int), Diagnostic]

/** Logs a position and returns true if it was already logged.
* @note Two positions are considered identical for logging if they have the same point.
*/
override def isHidden(dia: Diagnostic)(using Context): Boolean =
extension (dia1: Diagnostic) def hides(dia2: Diagnostic): Boolean =
if dia2.msg.showAlways then dia1.msg.getClass == dia2.msg.getClass
else dia1.level >= dia2.level
super.isHidden(dia) || {
dia.pos.exists && !ctx.settings.YshowSuppressedErrors.value && {
dia.pos.exists
&& !ctx.settings.YshowSuppressedErrors.value
&& {
var shouldHide = false
for (pos <- dia.pos.start to dia.pos.end)
positions get (ctx.source, pos) match {
case Some(level) if level >= dia.level => shouldHide = true
case _ => positions((ctx.source, pos)) = dia.level
case Some(dia1) if dia1.hides(dia) => shouldHide = true
case _ => positions((ctx.source, pos)) = dia
}
shouldHide
}
Expand Down
24 changes: 24 additions & 0 deletions compiler/src/dotty/tools/dotc/reporting/messages.scala
Expand Up @@ -18,6 +18,7 @@ import ast.Trees
import config.{Feature, ScalaVersion}
import typer.ErrorReporting.{err, matchReductionAddendum}
import typer.ProtoTypes.ViewProto
import typer.Implicits.Candidate
import scala.util.control.NonFatal
import StdNames.nme
import printing.Formatting.hl
Expand Down Expand Up @@ -2515,3 +2516,26 @@ import transform.SymUtils._
|Inlining such definition would multiply this footprint for each call site.
|""".stripMargin
}

class ImplicitSearchTooLargeWarning(limit: Int, openSearchPairs: List[(Candidate, Type)])(using Context)
extends TypeMsg(ImplicitSearchTooLargeID):
override def showAlways = true
def showQuery(query: (Candidate, Type)): String =
i" ${query._1.ref.symbol.showLocated} for ${query._2}}"
def msg =
em"""Implicit search problem too large.
|an implicit search was terminated with failure after trying $limit expressions.
|The root candidate for the search was:
|
|${showQuery(openSearchPairs.last)}
|
|You can change the behavior by setting the `-Ximplicit-search-limit` value.
|Smaller values cause the search to fail faster.
|Larger values might make a very large search problem succeed.
|"""
def explain =
em"""The overflow happened with the following lists of tried expressions and target types,
|starting with the root query:
|
|${openSearchPairs.reverse.map(showQuery)}%\n%
"""
39 changes: 33 additions & 6 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Expand Up @@ -489,6 +489,11 @@ object Implicits:
@sharable val NoMatchingImplicitsFailure: SearchFailure =
SearchFailure(NoMatchingImplicits, NoSpan)(using NoContext)

@sharable object ImplicitSearchTooLarge extends NoMatchingImplicits(NoType, EmptyTree, OrderingConstraint.empty)

@sharable val ImplicitSearchTooLargeFailure: SearchFailure =
SearchFailure(ImplicitSearchTooLarge, NoSpan)(using NoContext)

/** An ambiguous implicits failure */
class AmbiguousImplicits(val alt1: SearchSuccess, val alt2: SearchSuccess, val expectedType: Type, val argument: Tree) extends SearchFailureType {
def explanation(using Context): String =
Expand Down Expand Up @@ -1129,18 +1134,36 @@ trait Implicits:

val isNotGiven: Boolean = wildProto.classSymbol == defn.NotGivenClass

private def searchTooLarge(): Boolean = ctx.searchHistory match
case root: SearchRoot =>
root.nestedSearches = 1
false
case h =>
val limit = ctx.settings.XimplicitSearchLimit.value
val nestedSearches = h.root.nestedSearches
val result = nestedSearches > limit
if result then
var c = ctx
while c.outer.typer eq ctx.typer do c = c.outer
report.warning(ImplicitSearchTooLargeWarning(limit, h.openSearchPairs), ctx.source.atSpan(span))(using c)
else
h.root.nestedSearches = nestedSearches + 1
result

/** Try to type-check implicit reference, after checking that this is not
* a diverging search
*/
def tryImplicit(cand: Candidate, contextual: Boolean): SearchResult =
if checkDivergence(cand) then
SearchFailure(new DivergingImplicit(cand.ref, wideProto, argument), span)
else {
else if searchTooLarge() then
ImplicitSearchTooLargeFailure
else
val history = ctx.searchHistory.nest(cand, pt)
val typingCtx =
nestedContext().setNewTyperState().setFreshGADTBounds.setSearchHistory(history)
val result = typedImplicit(cand, pt, argument, span)(using typingCtx)
result match {
result match
case res: SearchSuccess =>
ctx.searchHistory.defineBynameImplicit(wideProto, res)
case _ =>
Expand All @@ -1152,8 +1175,6 @@ trait Implicits:
// tests/neg/implicitSearch.check
typingCtx.typerState.gc()
result
}
}

/** Search a list of eligible implicit references */
private def searchImplicit(eligible: List[Candidate], contextual: Boolean): SearchResult =
Expand Down Expand Up @@ -1242,7 +1263,9 @@ trait Implicits:

negateIfNot(tryImplicit(cand, contextual)) match {
case fail: SearchFailure =>
if (fail.isAmbiguous)
if fail eq ImplicitSearchTooLargeFailure then
fail
else if (fail.isAmbiguous)
if migrateTo3 then
val result = rank(remaining, found, NoMatchingImplicitsFailure :: rfailures)
if (result.isSuccess)
Expand Down Expand Up @@ -1610,13 +1633,17 @@ case class OpenSearch(cand: Candidate, pt: Type, outer: SearchHistory)(using Con
end OpenSearch

/**
* The the state corresponding to the outermost context of an implicit searcch.
* The state corresponding to the outermost context of an implicit searcch.
*/
final class SearchRoot extends SearchHistory:
val root = this
val byname = false
def openSearchPairs = Nil

/** How many expressions were constructed so far in the current toplevel implicit search?
*/
var nestedSearches: Int = 0

/** The dictionary of recursive implicit types and corresponding terms for this search. */
var myImplicitDictionary: mutable.Map[Type, (TermRef, tpd.Tree)] = null
private def implicitDictionary =
Expand Down
1 change: 1 addition & 0 deletions compiler/test/dotty/tools/dotc/CompilationTests.scala
Expand Up @@ -183,6 +183,7 @@ class CompilationTests {
compileFile("tests/neg-custom-args/feature-shadowing.scala", defaultOptions.and("-Xfatal-warnings", "-feature")),
compileDir("tests/neg-custom-args/hidden-type-errors", defaultOptions.and("-explain")),
compileFile("tests/neg-custom-args/i13026.scala", defaultOptions.and("-print-lines")),
compileFile("tests/neg-custom-args/i13838.scala", defaultOptions.and("-Ximplicit-search-limit", "1000")),
).checkExpectedErrors()
}

Expand Down
32 changes: 32 additions & 0 deletions tests/neg-custom-args/i13838.check
@@ -0,0 +1,32 @@
-- [E007] Type Mismatch Error: tests/neg-custom-args/i13838.scala:8:48 -------------------------------------------------
8 | def liftF[F[_], A](fa: F[A]): F[Foo[A]] = map(fa)(???) // error
| ^^
| Found: (fa : F[A])
| Required: F²[Foo[A²]]
|
| where: A is a type in method liftF
| A² is a type variable
| F is a type in method liftF with bounds <: [_] =>> Any
| F² is a type variable with constraint <: [_] =>> Any
|
|
| The following import might make progress towards fixing the problem:
|
| import collection.Searching.search
|

longer explanation available when compiling with `-explain`
-- [E168] Type Warning: tests/neg-custom-args/i13838.scala:8:50 --------------------------------------------------------
8 | def liftF[F[_], A](fa: F[A]): F[Foo[A]] = map(fa)(???) // error
| ^
| Implicit search problem too large.
| an implicit search was terminated with failure after trying 1000 expressions.
| The root candidate for the search was:
|
| method catsSyntaxEq for ([_] =>> Any)[Foo[Any]]}
|
| You can change the behavior by setting the `-Ximplicit-search-limit` value.
| Smaller values cause the search to fail faster.
| Larger values might make a very large search problem succeed.

longer explanation available when compiling with `-explain`
40 changes: 40 additions & 0 deletions tests/neg-custom-args/i13838.scala
@@ -0,0 +1,40 @@
implicit def catsSyntaxEq[A: Eq](a: A): Foo[A] = ???

class Foo[A]
object Foo:
given [A: Eq]: Eq[Foo[A]] = ???

object FooT:
def liftF[F[_], A](fa: F[A]): F[Foo[A]] = map(fa)(???) // error

def map[F[_], A](ffa: F[Foo[A]])(f: A): Nothing = ???

given OrderFFooA[F[_], A](using Ord: Order[F[Foo[A]]]): Order[F[Foo[A]]] = ???

trait Eq[A]
trait Order[A] extends Eq[A]

object Eq {
given catsKernelOrderForTuple1[A0](using A0: Order[A0]): Order[Tuple1[A0]] = ???
given catsKernelOrderForTuple2[A0, A1](using A0: Order[A0], A1: Order[A1]): Order[(A0, A1)] = ???
given catsKernelOrderForTuple3[A0, A1, A2](using A0: Order[A0], A1: Order[A1], A2: Order[A2]): Order[(A0, A1, A2)] = ???
given catsKernelOrderForTuple4[A0, A1, A2, A3](using A0: Order[A0], A1: Order[A1], A2: Order[A2], A3: Order[A3]): Order[(A0, A1, A2, A3)] = ???
given catsKernelOrderForTuple5[A0, A1, A2, A3, A4](using A0: Order[A0], A1: Order[A1], A2: Order[A2], A3: Order[A3], A4: Order[A4]): Order[(A0, A1, A2, A3, A4)] = ???
given catsKernelOrderForTuple6[A0, A1, A2, A3, A4, A5](using A0: Order[A0], A1: Order[A1], A2: Order[A2], A3: Order[A3], A4: Order[A4], A5: Order[A5]): Order[(A0, A1, A2, A3, A4, A5)] = ???
given catsKernelOrderForTuple7[A0, A1, A2, A3, A4, A5, A6](using A0: Order[A0], A1: Order[A1], A2: Order[A2], A3: Order[A3], A4: Order[A4], A5: Order[A5], A6: Order[A6]): Order[(A0, A1, A2, A3, A4, A5, A6)] = ???
given catsKernelOrderForTuple8[A0, A1, A2, A3, A4, A5, A6, A7](using A0: Order[A0], A1: Order[A1], A2: Order[A2], A3: Order[A3], A4: Order[A4], A5: Order[A5], A6: Order[A6], A7: Order[A7]): Order[(A0, A1, A2, A3, A4, A5, A6, A7)] = ???
given catsKernelOrderForTuple9[A0, A1, A2, A3, A4, A5, A6, A7, A8](using A0: Order[A0], A1: Order[A1], A2: Order[A2], A3: Order[A3], A4: Order[A4], A5: Order[A5], A6: Order[A6], A7: Order[A7], A8: Order[A8]): Order[(A0, A1, A2, A3, A4, A5, A6, A7, A8)] = ???
given catsKernelOrderForTuple10[A0, A1, A2, A3, A4, A5, A6, A7, A8, A9](using A0: Order[A0], A1: Order[A1], A2: Order[A2], A3: Order[A3], A4: Order[A4], A5: Order[A5], A6: Order[A6], A7: Order[A7], A8: Order[A8], A9: Order[A9]): Order[(A0, A1, A2, A3, A4, A5, A6, A7, A8, A9)] = ???
given catsKernelOrderForTuple11[A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10](using A0: Order[A0], A1: Order[A1], A2: Order[A2], A3: Order[A3], A4: Order[A4], A5: Order[A5], A6: Order[A6], A7: Order[A7], A8: Order[A8], A9: Order[A9], A10: Order[A10]): Order[(A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10)] = ???
given catsKernelOrderForTuple12[A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11](using A0: Order[A0], A1: Order[A1], A2: Order[A2], A3: Order[A3], A4: Order[A4], A5: Order[A5], A6: Order[A6], A7: Order[A7], A8: Order[A8], A9: Order[A9], A10: Order[A10], A11: Order[A11]): Order[(A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11)] = ???
given catsKernelOrderForTuple13[A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12](using A0: Order[A0], A1: Order[A1], A2: Order[A2], A3: Order[A3], A4: Order[A4], A5: Order[A5], A6: Order[A6], A7: Order[A7], A8: Order[A8], A9: Order[A9], A10: Order[A10], A11: Order[A11], A12: Order[A12]): Order[(A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12)] = ???
given catsKernelOrderForTuple14[A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13](using A0: Order[A0], A1: Order[A1], A2: Order[A2], A3: Order[A3], A4: Order[A4], A5: Order[A5], A6: Order[A6], A7: Order[A7], A8: Order[A8], A9: Order[A9], A10: Order[A10], A11: Order[A11], A12: Order[A12], A13: Order[A13]): Order[(A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13)] = ???
given catsKernelOrderForTuple15[A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14](using A0: Order[A0], A1: Order[A1], A2: Order[A2], A3: Order[A3], A4: Order[A4], A5: Order[A5], A6: Order[A6], A7: Order[A7], A8: Order[A8], A9: Order[A9], A10: Order[A10], A11: Order[A11], A12: Order[A12], A13: Order[A13], A14: Order[A14]): Order[(A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14)] = ???
given catsKernelOrderForTuple16[A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15](using A0: Order[A0], A1: Order[A1], A2: Order[A2], A3: Order[A3], A4: Order[A4], A5: Order[A5], A6: Order[A6], A7: Order[A7], A8: Order[A8], A9: Order[A9], A10: Order[A10], A11: Order[A11], A12: Order[A12], A13: Order[A13], A14: Order[A14], A15: Order[A15]): Order[(A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15)] = ???
given catsKernelOrderForTuple17[A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16](using A0: Order[A0], A1: Order[A1], A2: Order[A2], A3: Order[A3], A4: Order[A4], A5: Order[A5], A6: Order[A6], A7: Order[A7], A8: Order[A8], A9: Order[A9], A10: Order[A10], A11: Order[A11], A12: Order[A12], A13: Order[A13], A14: Order[A14], A15: Order[A15], A16: Order[A16]): Order[(A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16)] = ???
given catsKernelOrderForTuple18[A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17](using A0: Order[A0], A1: Order[A1], A2: Order[A2], A3: Order[A3], A4: Order[A4], A5: Order[A5], A6: Order[A6], A7: Order[A7], A8: Order[A8], A9: Order[A9], A10: Order[A10], A11: Order[A11], A12: Order[A12], A13: Order[A13], A14: Order[A14], A15: Order[A15], A16: Order[A16], A17: Order[A17]): Order[(A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17)] = ???
given catsKernelOrderForTuple19[A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18](using A0: Order[A0], A1: Order[A1], A2: Order[A2], A3: Order[A3], A4: Order[A4], A5: Order[A5], A6: Order[A6], A7: Order[A7], A8: Order[A8], A9: Order[A9], A10: Order[A10], A11: Order[A11], A12: Order[A12], A13: Order[A13], A14: Order[A14], A15: Order[A15], A16: Order[A16], A17: Order[A17], A18: Order[A18]): Order[(A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18)] = ???
given catsKernelOrderForTuple20[A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19](using A0: Order[A0], A1: Order[A1], A2: Order[A2], A3: Order[A3], A4: Order[A4], A5: Order[A5], A6: Order[A6], A7: Order[A7], A8: Order[A8], A9: Order[A9], A10: Order[A10], A11: Order[A11], A12: Order[A12], A13: Order[A13], A14: Order[A14], A15: Order[A15], A16: Order[A16], A17: Order[A17], A18: Order[A18], A19: Order[A19]): Order[(A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19)] = ???
given catsKernelOrderForTuple21[A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20](using A0: Order[A0], A1: Order[A1], A2: Order[A2], A3: Order[A3], A4: Order[A4], A5: Order[A5], A6: Order[A6], A7: Order[A7], A8: Order[A8], A9: Order[A9], A10: Order[A10], A11: Order[A11], A12: Order[A12], A13: Order[A13], A14: Order[A14], A15: Order[A15], A16: Order[A16], A17: Order[A17], A18: Order[A18], A19: Order[A19], A20: Order[A20]): Order[(A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20)] = ???
given catsKernelOrderForTuple22[A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21](using A0: Order[A0], A1: Order[A1], A2: Order[A2], A3: Order[A3], A4: Order[A4], A5: Order[A5], A6: Order[A6], A7: Order[A7], A8: Order[A8], A9: Order[A9], A10: Order[A10], A11: Order[A11], A12: Order[A12], A13: Order[A13], A14: Order[A14], A15: Order[A15], A16: Order[A16], A17: Order[A17], A18: Order[A18], A19: Order[A19], A20: Order[A20], A21: Order[A21]): Order[(A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21)] = ???
}