From 927083945cda11c8aad5ae5afa767750198e6c7e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 17 Aug 2020 10:34:43 +0200 Subject: [PATCH 1/2] Fix #9568: Disallow partially undefined types as byname anchors Require that the type that a by-name implicit can recursively implement must be fully defined. --- .../dotty/tools/dotc/typer/Implicits.scala | 38 +++++++++++++++---- tests/neg/i3452.scala | 4 +- tests/neg/i9568.check | 11 ++++++ tests/neg/i9568.scala | 14 +++++++ tests/pos/i9568.scala | 25 ++++++++++++ 5 files changed, 81 insertions(+), 11 deletions(-) create mode 100644 tests/neg/i9568.check create mode 100644 tests/neg/i9568.scala create mode 100644 tests/pos/i9568.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 85bbafdc3426..5676881983eb 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -247,11 +247,9 @@ object Implicits: candidates += Candidate(ref, ckind, level) if considerExtension then - val tryExtension = tryCandidate(extensionOnly = true) - companionRefs.foreach(tryExtension) + companionRefs.foreach(tryCandidate(extensionOnly = true)) if refs.nonEmpty then - val tryGiven = tryCandidate(extensionOnly = false) - refs.foreach(tryGiven) + refs.foreach(tryCandidate(extensionOnly = false)) candidates.toList } } @@ -488,9 +486,10 @@ object Implicits: class DivergingImplicit(ref: TermRef, val expectedType: Type, - val argument: Tree) extends SearchFailureType { + val argument: Tree, + addendum: => String = "") extends SearchFailureType { def explanation(using Context): String = - em"${err.refStr(ref)} produces a diverging implicit search when trying to $qualify" + em"${err.refStr(ref)} produces a diverging implicit search when trying to $qualify$addendum" } class FailedExtension(extApp: Tree, val expectedType: Type) extends SearchFailureType: @@ -1101,7 +1100,13 @@ trait Implicits: */ def tryImplicit(cand: Candidate, contextual: Boolean): SearchResult = if checkDivergence(cand) then - SearchFailure(new DivergingImplicit(cand.ref, wideProto, argument)) + val addendum = ctx.searchHistory.disqualifiedType match + case NoType => "" + case disTp => + em""". + |Note that open search type $disTp cannot be re-used as a by-name implicit parameter + |since it is not fully defined""" + SearchFailure(new DivergingImplicit(cand.ref, wideProto, argument, addendum)) else { val history = ctx.searchHistory.nest(cand, pt) val result = @@ -1371,8 +1376,12 @@ trait Implicits: history match case prev @ OpenSearch(cand1, tp, outer) => if cand1.ref eq cand.ref then + def checkFullyDefined = + val result = isFullyDefined(tp, ForceDegree.failBottom) + if !result then prev.disqualified = true + result lazy val wildTp = wildApprox(tp.widenExpr) - if belowByname && (wildTp <:< wildPt) then + if belowByname && (wildTp <:< wildPt) && checkFullyDefined then false else if prev.typeSize > ptSize || prev.coveringSet != ptCoveringSet then loop(outer, tp.isByName || belowByname) @@ -1464,6 +1473,15 @@ abstract class SearchHistory: def defineBynameImplicit(tpe: Type, result: SearchSuccess)(using Context): SearchResult = root.defineBynameImplicit(tpe, result) + /** There is a qualifying call-by-name parameter in the history + * that cannot be used since is it not fully defined. Used for error reporting. + */ + def disqualifiedType: Type = + def loop(h: SearchHistory): Type = h match + case h: OpenSearch => if h.disqualified then h.pt else loop(h.outer) + case _ => NoType + loop(this) + // This is NOOP unless at the root of this search history. def emitDictionary(span: Span, result: SearchResult)(using Context): SearchResult = result @@ -1483,6 +1501,10 @@ case class OpenSearch(cand: Candidate, pt: Type, outer: SearchHistory)(using Con // An example is in neg/9504.scala lazy val typeSize = pt.typeSize lazy val coveringSet = pt.coveringSet + + // Set if there would be a qualifying call-by-name parameter + // that cannot be used since is it not fully defined + var disqualified: Boolean = false end OpenSearch /** diff --git a/tests/neg/i3452.scala b/tests/neg/i3452.scala index 1a439338f4d6..9899a71cc147 100644 --- a/tests/neg/i3452.scala +++ b/tests/neg/i3452.scala @@ -6,9 +6,7 @@ object Test { implicit def case1[F[_]](implicit t: => TC[F[Any]]): TC[Tuple2K[[_] =>> Any, F, Any]] = ??? implicit def case2[A, F[_]](implicit r: TC[F[Any]]): TC[A] = ??? - // Disabled because it leads to an infinite loop in implicit search - // this is probably the same issue as https://github.com/lampepfl/dotty/issues/9568 - // implicitly[TC[Int]] // was: error + implicitly[TC[Int]] // error } object Test1 { diff --git a/tests/neg/i9568.check b/tests/neg/i9568.check new file mode 100644 index 000000000000..529c15f74289 --- /dev/null +++ b/tests/neg/i9568.check @@ -0,0 +1,11 @@ +-- Error: tests/neg/i9568.scala:13:10 ---------------------------------------------------------------------------------- +13 | blaMonad.foo(bla) // error: diverges + | ^ + |no implicit argument of type => Monad[([_$3] =>> Any)] was found for parameter ev of method blaMonad in object Test. + |I found: + | + | Test.blaMonad[F, S](/* missing */summon[Monad[F]]) + | + |But method blaMonad in object Test produces a diverging implicit search when trying to match type Monad[F]. + |Note that open search type => Monad[([X] =>> Bla[F, X])] cannot be re-used as a by-name implicit parameter + |since it is not fully defined. diff --git a/tests/neg/i9568.scala b/tests/neg/i9568.scala new file mode 100644 index 000000000000..6fc9ba35a9fb --- /dev/null +++ b/tests/neg/i9568.scala @@ -0,0 +1,14 @@ +trait Monad[F[_]] { + def foo[A](fa: F[A]): Unit = {} +} + +class Bla[F[_], A] + +object Test { + type Id[A] = A + + val bla: Bla[Id, Unit] = ??? + implicit def blaMonad[F[_], S](implicit ev: => Monad[F]): Monad[({type L[X] = Bla[F, X]})#L] = ??? + + blaMonad.foo(bla) // error: diverges +} \ No newline at end of file diff --git a/tests/pos/i9568.scala b/tests/pos/i9568.scala new file mode 100644 index 000000000000..4b4a5843ab4f --- /dev/null +++ b/tests/pos/i9568.scala @@ -0,0 +1,25 @@ +trait Monad[F[_]] { + def foo[A](fa: F[A]): Unit = {} +} + +class Bla[F[_], A] + +object Test1 { + type Id[A] = A + + val bla: Bla[Id, Unit] = ??? + implicit def blaMonad[F[_]: Monad, S]: Monad[({type L[X] = Bla[F, X]})#L] = ??? + implicit def idMonad: Monad[Id] = ??? + + blaMonad.foo(bla) // does not diverge +} + +object Test2 { + type Id[A] = A + + val bla: Bla[Id, Unit] = ??? + implicit def blaMonad[F[_], S](implicit ev: => Monad[F]): Monad[({type L[X] = Bla[F, X]})#L] = ??? + implicit def idMonad: Monad[Id] = ??? + + blaMonad.foo(bla) // does not diverge +} From 0b4c2af64eb6d4e232cc8cedca11218399ba9971 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Mon, 17 Aug 2020 15:04:56 +0200 Subject: [PATCH 2/2] Allow type parameters in by-name implicit to be instantiated to Nothing Nothing has legitimate use as a type parameter, so special-casing inference to reject it can cause issues. Also by removing this special-case we can remove the error reporting logic dedicated to dealing with non-fully-defined types, instead the user will see that a type parameter was instantiated to `Nothing` in the error which should be clear enough. --- .../dotty/tools/dotc/typer/Implicits.scala | 33 +++---------------- tests/neg/i3452.scala | 2 +- tests/neg/i9568.check | 6 ++-- 3 files changed, 8 insertions(+), 33 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 5676881983eb..44995dddda5c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -486,10 +486,9 @@ object Implicits: class DivergingImplicit(ref: TermRef, val expectedType: Type, - val argument: Tree, - addendum: => String = "") extends SearchFailureType { + val argument: Tree) extends SearchFailureType { def explanation(using Context): String = - em"${err.refStr(ref)} produces a diverging implicit search when trying to $qualify$addendum" + em"${err.refStr(ref)} produces a diverging implicit search when trying to $qualify" } class FailedExtension(extApp: Tree, val expectedType: Type) extends SearchFailureType: @@ -1100,13 +1099,7 @@ trait Implicits: */ def tryImplicit(cand: Candidate, contextual: Boolean): SearchResult = if checkDivergence(cand) then - val addendum = ctx.searchHistory.disqualifiedType match - case NoType => "" - case disTp => - em""". - |Note that open search type $disTp cannot be re-used as a by-name implicit parameter - |since it is not fully defined""" - SearchFailure(new DivergingImplicit(cand.ref, wideProto, argument, addendum)) + SearchFailure(new DivergingImplicit(cand.ref, wideProto, argument)) else { val history = ctx.searchHistory.nest(cand, pt) val result = @@ -1376,12 +1369,9 @@ trait Implicits: history match case prev @ OpenSearch(cand1, tp, outer) => if cand1.ref eq cand.ref then - def checkFullyDefined = - val result = isFullyDefined(tp, ForceDegree.failBottom) - if !result then prev.disqualified = true - result lazy val wildTp = wildApprox(tp.widenExpr) - if belowByname && (wildTp <:< wildPt) && checkFullyDefined then + if belowByname && (wildTp <:< wildPt) then + fullyDefinedType(tp, "by-name implicit parameter", span) false else if prev.typeSize > ptSize || prev.coveringSet != ptCoveringSet then loop(outer, tp.isByName || belowByname) @@ -1473,15 +1463,6 @@ abstract class SearchHistory: def defineBynameImplicit(tpe: Type, result: SearchSuccess)(using Context): SearchResult = root.defineBynameImplicit(tpe, result) - /** There is a qualifying call-by-name parameter in the history - * that cannot be used since is it not fully defined. Used for error reporting. - */ - def disqualifiedType: Type = - def loop(h: SearchHistory): Type = h match - case h: OpenSearch => if h.disqualified then h.pt else loop(h.outer) - case _ => NoType - loop(this) - // This is NOOP unless at the root of this search history. def emitDictionary(span: Span, result: SearchResult)(using Context): SearchResult = result @@ -1501,10 +1482,6 @@ case class OpenSearch(cand: Candidate, pt: Type, outer: SearchHistory)(using Con // An example is in neg/9504.scala lazy val typeSize = pt.typeSize lazy val coveringSet = pt.coveringSet - - // Set if there would be a qualifying call-by-name parameter - // that cannot be used since is it not fully defined - var disqualified: Boolean = false end OpenSearch /** diff --git a/tests/neg/i3452.scala b/tests/neg/i3452.scala index 9899a71cc147..29a7d4b19219 100644 --- a/tests/neg/i3452.scala +++ b/tests/neg/i3452.scala @@ -6,7 +6,7 @@ object Test { implicit def case1[F[_]](implicit t: => TC[F[Any]]): TC[Tuple2K[[_] =>> Any, F, Any]] = ??? implicit def case2[A, F[_]](implicit r: TC[F[Any]]): TC[A] = ??? - implicitly[TC[Int]] // error + implicitly[TC[Int]] // typechecks because we infer F := Nothing (should we avoid inferring Nothing for higher-kinded types?) } object Test1 { diff --git a/tests/neg/i9568.check b/tests/neg/i9568.check index 529c15f74289..cb7e37ed5415 100644 --- a/tests/neg/i9568.check +++ b/tests/neg/i9568.check @@ -4,8 +4,6 @@ |no implicit argument of type => Monad[([_$3] =>> Any)] was found for parameter ev of method blaMonad in object Test. |I found: | - | Test.blaMonad[F, S](/* missing */summon[Monad[F]]) + | Test.blaMonad[Nothing, S](Test.blaMonad[F, S]) | - |But method blaMonad in object Test produces a diverging implicit search when trying to match type Monad[F]. - |Note that open search type => Monad[([X] =>> Bla[F, X])] cannot be re-used as a by-name implicit parameter - |since it is not fully defined. + |But method blaMonad in object Test does not match type => Monad[Nothing].