From db5956b36a7e62b77f4b3a9335ab1587e0e47e2c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 9 Nov 2021 12:34:07 +0100 Subject: [PATCH] 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