From 34ee767791f463fb51d5aa3cab21160e769e8a6f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 5 Nov 2021 15:00:33 +0100 Subject: [PATCH 1/7] Impose implicit search limit Impose a configurable limit on the total number of nodes constructed during an implicit search. Fixes #13838 --- .../tools/dotc/config/ScalaSettings.scala | 1 + .../dotty/tools/dotc/typer/Implicits.scala | 47 ++++++++++++++++--- .../dotty/tools/dotc/CompilationTests.scala | 1 + tests/neg-custom-args/i13838.check | 18 +++++++ tests/neg-custom-args/i13838.scala | 40 ++++++++++++++++ 5 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 tests/neg-custom-args/i13838.check create mode 100644 tests/neg-custom-args/i13838.scala diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 6147cd78f3e6..7f8c1a53bec3 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -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 )", "") + val XimplicitSearchLimit: Setting[Int] = IntSetting("-Ximplicit-search-limit", "Maximal number of expressions to be generated in an implicit search", 100000) val XmixinForceForwarders = ChoiceSetting( name = "-Xmixin-force-forwarders", diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 6c24dc6be119..fd2adc0358de 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -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 = @@ -1129,18 +1134,44 @@ 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.echo( + em"""Implicit search problem too large. + |an implicit search was terminated with failure after trying $limit expressions. + | + |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. + |""", + 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 _ => @@ -1152,8 +1183,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 = @@ -1242,7 +1271,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) @@ -1610,13 +1641,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 = diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index a9f376d3cda6..06526aa6a924 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -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() } diff --git a/tests/neg-custom-args/i13838.check b/tests/neg-custom-args/i13838.check new file mode 100644 index 000000000000..e6e5a0eb9e2e --- /dev/null +++ b/tests/neg-custom-args/i13838.check @@ -0,0 +1,18 @@ +-- [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` diff --git a/tests/neg-custom-args/i13838.scala b/tests/neg-custom-args/i13838.scala new file mode 100644 index 000000000000..92e7a6d48439 --- /dev/null +++ b/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)] = ??? +} From 31c1991783cae344e504385d261229f848e78563 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 5 Nov 2021 16:44:29 +0100 Subject: [PATCH 2/7] Issue "search too large" as warnings that cannot be hidden --- .../dotty/tools/dotc/reporting/ErrorMessageID.scala | 3 ++- .../src/dotty/tools/dotc/reporting/Message.scala | 7 +++++++ .../dotc/reporting/UniqueMessagePositions.scala | 13 +++++++++---- .../src/dotty/tools/dotc/reporting/messages.scala | 12 ++++++++++++ compiler/src/dotty/tools/dotc/typer/Implicits.scala | 10 +--------- tests/neg-custom-args/i13838.check | 9 +++++++++ 6 files changed, 40 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 342e640ca819..f55196f82a8e 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -175,7 +175,8 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID]: OverrideErrorID, MatchableWarningID, CannotExtendFunctionID, - LossyWideningConstantConversionID + LossyWideningConstantConversionID, + ImplicitSearchTooLargeID def errorNumber = ordinal - 2 diff --git a/compiler/src/dotty/tools/dotc/reporting/Message.scala b/compiler/src/dotty/tools/dotc/reporting/Message.scala index 8ce94f4fa3a3..cd7456cafcc2 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Message.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Message.scala @@ -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 } diff --git a/compiler/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala b/compiler/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala index fb4c92c12f83..e4312f46f779 100644 --- a/compiler/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala +++ b/compiler/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala @@ -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 } diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 62bf295fed8c..398827f1a279 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2515,3 +2515,15 @@ import transform.SymUtils._ |Inlining such definition would multiply this footprint for each call site. |""".stripMargin } + + class ImplicitSearchTooLargeWarning(limit: Int)(using Context) extends TypeMsg(ImplicitSearchTooLargeID): + override def showAlways = true + def msg = + em"""Implicit search problem too large. + |an implicit search was terminated with failure after trying $limit expressions. + | + |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 = "" \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index fd2adc0358de..51a9ddfbda16 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -1145,15 +1145,7 @@ trait Implicits: if result then var c = ctx while c.outer.typer eq ctx.typer do c = c.outer - report.echo( - em"""Implicit search problem too large. - |an implicit search was terminated with failure after trying $limit expressions. - | - |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. - |""", - ctx.source.atSpan(span))(using c) + report.warning(ImplicitSearchTooLargeWarning(limit), ctx.source.atSpan(span))(using c) else h.root.nestedSearches = nestedSearches + 1 result diff --git a/tests/neg-custom-args/i13838.check b/tests/neg-custom-args/i13838.check index e6e5a0eb9e2e..8d51bbc36f81 100644 --- a/tests/neg-custom-args/i13838.check +++ b/tests/neg-custom-args/i13838.check @@ -16,3 +16,12 @@ | 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. + | + | 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. From b59afe80b859965bef88d6dd6f48a5b8bde94dc4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 6 Nov 2021 11:37:10 +0100 Subject: [PATCH 3/7] Give more information when "search too large" is hit - show what the root query was - under -explain, show the trace until the overflow occurred. --- .../dotty/tools/dotc/reporting/messages.scala | 16 ++++++++++++++-- .../src/dotty/tools/dotc/typer/Implicits.scala | 2 +- tests/neg-custom-args/i13838.check | 5 +++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 398827f1a279..4a1efab782a1 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -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 @@ -2516,14 +2517,25 @@ import transform.SymUtils._ |""".stripMargin } - class ImplicitSearchTooLargeWarning(limit: Int)(using Context) extends TypeMsg(ImplicitSearchTooLargeID): + 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 = "" \ No newline at end of file + 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% + """ diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 51a9ddfbda16..84a913b47f58 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -1145,7 +1145,7 @@ trait Implicits: if result then var c = ctx while c.outer.typer eq ctx.typer do c = c.outer - report.warning(ImplicitSearchTooLargeWarning(limit), ctx.source.atSpan(span))(using c) + report.warning(ImplicitSearchTooLargeWarning(limit, h.openSearchPairs), ctx.source.atSpan(span))(using c) else h.root.nestedSearches = nestedSearches + 1 result diff --git a/tests/neg-custom-args/i13838.check b/tests/neg-custom-args/i13838.check index 8d51bbc36f81..9b77e1566c16 100644 --- a/tests/neg-custom-args/i13838.check +++ b/tests/neg-custom-args/i13838.check @@ -21,7 +21,12 @@ longer explanation available when compiling with `-explain` | ^ | 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` From 807743a9799249244bcbf19adc1db3a75533ea6b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 9 Nov 2021 11:59:51 +0100 Subject: [PATCH 4/7] Reduce limit for implicit search too large Half the limit so that we fail in a bit less than a minute instead of more than 5 minutes. --- compiler/src/dotty/tools/dotc/config/ScalaSettings.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 7f8c1a53bec3..d66c5d7ab4c2 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -218,7 +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 )", "") - val XimplicitSearchLimit: Setting[Int] = IntSetting("-Ximplicit-search-limit", "Maximal number of expressions to be generated in an implicit search", 100000) + val XimplicitSearchLimit: Setting[Int] = IntSetting("-Ximplicit-search-limit", "Maximal number of expressions to be generated in an implicit search", 50000) val XmixinForceForwarders = ChoiceSetting( name = "-Xmixin-force-forwarders", From db5956b36a7e62b77f4b3a9335ab1587e0e47e2c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 9 Nov 2021 12:34:07 +0100 Subject: [PATCH 5/7] Refine checking for underspecified implicit queries - Use the wildcard approximation instead of the original type since that one determined what is eligible, and the goal is to refuse the search if everything is eligible. - Also refuse underspecified implicit parameters, not just conversions. - Treat wildcard types as underspecified. Two tests had to be reclassified. But the original tests were not meant to compile anyway. They were bout misleading error messages (no longer the case) and crashers. --- .../dotty/tools/dotc/typer/Implicits.scala | 68 +++++++++++-------- .../dotty/tools/dotc/CompilationTests.scala | 1 - tests/neg-custom-args/i13838.check | 32 --------- tests/{pos => neg}/i10082.scala | 0 tests/{neg-custom-args => neg}/i13838.scala | 0 tests/neg/i13838a.scala | 47 +++++++++++++ tests/{pos => neg}/i7745.scala | 2 +- 7 files changed, 86 insertions(+), 64 deletions(-) delete mode 100644 tests/neg-custom-args/i13838.check rename tests/{pos => neg}/i10082.scala (100%) rename tests/{neg-custom-args => neg}/i13838.scala (100%) create mode 100644 tests/neg/i13838a.scala rename tests/{pos => neg}/i7745.scala (72%) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 84a913b47f58..a3b0dd02714d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -795,16 +795,8 @@ trait Implicits: */ def inferView(from: Tree, to: Type)(using Context): SearchResult = { record("inferView") - val wfromtp = from.tpe.widen - if to.isAny - || to.isAnyRef - || to.isRef(defn.UnitClass) - || wfromtp.isRef(defn.NothingClass) - || wfromtp.isRef(defn.NullClass) - || !ctx.mode.is(Mode.ImplicitsEnabled) - || from.isInstanceOf[Super] - || (wfromtp eq NoPrefix) - then NoMatchingImplicitsFailure + if !ctx.mode.is(Mode.ImplicitsEnabled) || from.isInstanceOf[Super] then + NoMatchingImplicitsFailure else { def adjust(to: Type) = to.stripTypeVar.widenExpr match { case SelectionProto(name, memberProto, compat, true) => @@ -1434,27 +1426,43 @@ trait Implicits: rank(sort(eligible), NoMatchingImplicitsFailure, Nil) end searchImplicit + def isUnderSpecifiedArgument(tp: Type): Boolean = + tp.isRef(defn.NothingClass) || tp.isRef(defn.NullClass) || (tp eq NoPrefix) + + private def isUnderspecified(tp: Type): Boolean = tp.stripTypeVar match + case tp: WildcardType => + !tp.optBounds.exists || isUnderspecified(tp.optBounds.hiBound) + case tp: ViewProto => + isUnderspecified(tp.resType) + || tp.resType.isRef(defn.UnitClass) + || isUnderSpecifiedArgument(tp.argType.widen) + case _ => + tp.isAny || tp.isAnyRef + private def searchImplicit(contextual: Boolean): SearchResult = - val eligible = - if contextual then ctx.implicits.eligible(wildProto) - else implicitScope(wildProto).eligible - searchImplicit(eligible, contextual) match - case result: SearchSuccess => - result - case failure: SearchFailure => - failure.reason match - case _: AmbiguousImplicits => failure - case reason => - if contextual then - searchImplicit(contextual = false).recoverWith { - failure2 => failure2.reason match - case _: AmbiguousImplicits => failure2 - case _ => - reason match - case (_: DivergingImplicit) => failure - case _ => List(failure, failure2).maxBy(_.tree.treeSize) - } - else failure + if isUnderspecified(wildProto) then + NoMatchingImplicitsFailure + else + val eligible = + if contextual then ctx.implicits.eligible(wildProto) + else implicitScope(wildProto).eligible + searchImplicit(eligible, contextual) match + case result: SearchSuccess => + result + case failure: SearchFailure => + failure.reason match + case _: AmbiguousImplicits => failure + case reason => + if contextual then + searchImplicit(contextual = false).recoverWith { + failure2 => failure2.reason match + case _: AmbiguousImplicits => failure2 + case _ => + reason match + case (_: DivergingImplicit) => failure + case _ => List(failure, failure2).maxBy(_.tree.treeSize) + } + else failure end searchImplicit /** Find a unique best implicit reference */ diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 06526aa6a924..a9f376d3cda6 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -183,7 +183,6 @@ 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() } diff --git a/tests/neg-custom-args/i13838.check b/tests/neg-custom-args/i13838.check deleted file mode 100644 index 9b77e1566c16..000000000000 --- a/tests/neg-custom-args/i13838.check +++ /dev/null @@ -1,32 +0,0 @@ --- [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` diff --git a/tests/pos/i10082.scala b/tests/neg/i10082.scala similarity index 100% rename from tests/pos/i10082.scala rename to tests/neg/i10082.scala diff --git a/tests/neg-custom-args/i13838.scala b/tests/neg/i13838.scala similarity index 100% rename from tests/neg-custom-args/i13838.scala rename to tests/neg/i13838.scala diff --git a/tests/neg/i13838a.scala b/tests/neg/i13838a.scala new file mode 100644 index 000000000000..9fcb7be7bdcf --- /dev/null +++ b/tests/neg/i13838a.scala @@ -0,0 +1,47 @@ +object TooSlow { + trait EqSyntax { + implicit def catsSyntaxEq[A: Eq](a: A): EqOps[A] = ??? + } + + final class EqOps[A] + + object eq extends EqSyntax + + import eq._ + + sealed abstract class Foo[A] + object Foo { + implicit def eqFoo[A: Eq]: Eq[Foo[A]] = ??? + } + + type FooT[F[_], A] = F[Foo[A]] + object FooT { + def liftF[F[_], A](fa: F[A]): F[Foo[A]] = + map(fa)(???) // error + + def map[F[_], A, B](ffa: F[Foo[A]])(f: A => B): F[Foo[B]] = + ??? + } + + trait Order[A] extends Eq[A] + + trait Eq[A] + + object Eq { + implicit def catsKernelOrderForTuple14[A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13](implicit 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)] = ??? + implicit def catsKernelOrderForTuple13[A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12](implicit 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)] = ??? + implicit def catsKernelOrderForTuple12[A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11](implicit 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)] = ??? + implicit def catsKernelOrderForTuple11[A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10](implicit 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)] = ??? + implicit def catsKernelOrderForTuple10[A0, A1, A2, A3, A4, A5, A6, A7, A8, A9](implicit 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)] = ??? + implicit def catsKernelOrderForTuple9[A0, A1, A2, A3, A4, A5, A6, A7, A8](implicit 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)] = ??? + implicit def catsKernelOrderForTuple8[A0, A1, A2, A3, A4, A5, A6, A7](implicit 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)] = ??? + implicit def catsKernelOrderForTuple7[A0, A1, A2, A3, A4, A5, A6](implicit 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)] = ??? + implicit def catsKernelOrderForTuple6[A0, A1, A2, A3, A4, A5](implicit 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)] = ??? + implicit def catsKernelOrderForTuple5[A0, A1, A2, A3, A4](implicit A0: Order[A0], A1: Order[A1], A2: Order[A2], A3: Order[A3], A4: Order[A4]): Order[(A0, A1, A2, A3, A4)] = ??? + implicit def catsKernelOrderForTuple4[A0, A1, A2, A3](implicit A0: Order[A0], A1: Order[A1], A2: Order[A2], A3: Order[A3]): Order[(A0, A1, A2, A3)] = ??? + implicit def catsKernelOrderForTuple3[A0, A1, A2](implicit A0: Order[A0], A1: Order[A1], A2: Order[A2]): Order[(A0, A1, A2)] = ??? + implicit def catsKernelOrderForTuple2[A0, A1](implicit A0: Order[A0], A1: Order[A1]): Order[(A0, A1)] = ??? + implicit def catsKernelOrderForTuple1[A0](implicit A0: Order[A0]): Order[Tuple1[A0]] = ??? + } + +} \ No newline at end of file diff --git a/tests/pos/i7745.scala b/tests/neg/i7745.scala similarity index 72% rename from tests/pos/i7745.scala rename to tests/neg/i7745.scala index de03d3995d33..7b54be159661 100644 --- a/tests/pos/i7745.scala +++ b/tests/neg/i7745.scala @@ -1,3 +1,3 @@ trait F[x] implicit def foo[f[_], y, x <: f[y]](implicit ev: F[y]): F[x] = ??? -val test = implicitly \ No newline at end of file +val test = implicitly // error \ No newline at end of file From 4fb5b173005a23728afea865f2f0f00efb59b347 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 9 Nov 2021 17:24:40 +0100 Subject: [PATCH 6/7] Fix neg test --- tests/neg/i9330.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/neg/i9330.scala b/tests/neg/i9330.scala index 6ba57c033473..ca25582ef7e8 100644 --- a/tests/neg/i9330.scala +++ b/tests/neg/i9330.scala @@ -1,4 +1,4 @@ val x = { - () == "" + () == "" // error implicit def foo[A: A] // error // error // error } From 739dec3c774c372408798b364574e8096db0735b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 9 Nov 2021 18:13:24 +0100 Subject: [PATCH 7/7] Revive original test Make another test to exercise the original large search behavior --- .../dotty/tools/dotc/CompilationTests.scala | 1 + tests/neg-custom-args/i13838.check | 26 +++++++++++++++++++ tests/{neg => neg-custom-args}/i13838.scala | 4 ++- 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests/neg-custom-args/i13838.check rename tests/{neg => neg-custom-args}/i13838.scala (99%) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index a9f376d3cda6..06526aa6a924 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -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() } diff --git a/tests/neg-custom-args/i13838.check b/tests/neg-custom-args/i13838.check new file mode 100644 index 000000000000..5730cb144e52 --- /dev/null +++ b/tests/neg-custom-args/i13838.check @@ -0,0 +1,26 @@ +-- Error: tests/neg-custom-args/i13838.scala:10:5 ---------------------------------------------------------------------- +10 | foo // error + | ^ + |no implicit argument of type Order[X] was found for parameter x$1 of method foo in object FooT + | + |where: X is a type variable + |. + |I found: + | + | FooT.OrderFFooA[F, A](FooT.OrderFFooA[F, A](/* missing */summon[Order[F[Foo[A]]]])) + | + |But given instance OrderFFooA in object FooT produces a diverging implicit search when trying to match type Order[F[Foo[A]]]. +-- [E168] Type Warning: tests/neg-custom-args/i13838.scala:10:5 -------------------------------------------------------- +10 | foo // error + | ^ + | Implicit search problem too large. + | an implicit search was terminated with failure after trying 1000 expressions. + | The root candidate for the search was: + | + | given instance OrderFFooA in object FooT for Order[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` diff --git a/tests/neg/i13838.scala b/tests/neg-custom-args/i13838.scala similarity index 99% rename from tests/neg/i13838.scala rename to tests/neg-custom-args/i13838.scala index 92e7a6d48439..c99d3fa1f82d 100644 --- a/tests/neg/i13838.scala +++ b/tests/neg-custom-args/i13838.scala @@ -5,7 +5,9 @@ object Foo: given [A: Eq]: Eq[Foo[A]] = ??? object FooT: - def liftF[F[_], A](fa: F[A]): F[Foo[A]] = map(fa)(???) // error + + def foo[X](using Order[X]): Unit = ??? + foo // error def map[F[_], A](ffa: F[Foo[A]])(f: A): Nothing = ???