Skip to content

Commit 9270839

Browse files
oderskysmarter
authored andcommitted
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.
1 parent d5efc05 commit 9270839

File tree

5 files changed

+81
-11
lines changed

5 files changed

+81
-11
lines changed

compiler/src/dotty/tools/dotc/typer/Implicits.scala

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -247,11 +247,9 @@ object Implicits:
247247
candidates += Candidate(ref, ckind, level)
248248

249249
if considerExtension then
250-
val tryExtension = tryCandidate(extensionOnly = true)
251-
companionRefs.foreach(tryExtension)
250+
companionRefs.foreach(tryCandidate(extensionOnly = true))
252251
if refs.nonEmpty then
253-
val tryGiven = tryCandidate(extensionOnly = false)
254-
refs.foreach(tryGiven)
252+
refs.foreach(tryCandidate(extensionOnly = false))
255253
candidates.toList
256254
}
257255
}
@@ -488,9 +486,10 @@ object Implicits:
488486

489487
class DivergingImplicit(ref: TermRef,
490488
val expectedType: Type,
491-
val argument: Tree) extends SearchFailureType {
489+
val argument: Tree,
490+
addendum: => String = "") extends SearchFailureType {
492491
def explanation(using Context): String =
493-
em"${err.refStr(ref)} produces a diverging implicit search when trying to $qualify"
492+
em"${err.refStr(ref)} produces a diverging implicit search when trying to $qualify$addendum"
494493
}
495494

496495
class FailedExtension(extApp: Tree, val expectedType: Type) extends SearchFailureType:
@@ -1101,7 +1100,13 @@ trait Implicits:
11011100
*/
11021101
def tryImplicit(cand: Candidate, contextual: Boolean): SearchResult =
11031102
if checkDivergence(cand) then
1104-
SearchFailure(new DivergingImplicit(cand.ref, wideProto, argument))
1103+
val addendum = ctx.searchHistory.disqualifiedType match
1104+
case NoType => ""
1105+
case disTp =>
1106+
em""".
1107+
|Note that open search type $disTp cannot be re-used as a by-name implicit parameter
1108+
|since it is not fully defined"""
1109+
SearchFailure(new DivergingImplicit(cand.ref, wideProto, argument, addendum))
11051110
else {
11061111
val history = ctx.searchHistory.nest(cand, pt)
11071112
val result =
@@ -1371,8 +1376,12 @@ trait Implicits:
13711376
history match
13721377
case prev @ OpenSearch(cand1, tp, outer) =>
13731378
if cand1.ref eq cand.ref then
1379+
def checkFullyDefined =
1380+
val result = isFullyDefined(tp, ForceDegree.failBottom)
1381+
if !result then prev.disqualified = true
1382+
result
13741383
lazy val wildTp = wildApprox(tp.widenExpr)
1375-
if belowByname && (wildTp <:< wildPt) then
1384+
if belowByname && (wildTp <:< wildPt) && checkFullyDefined then
13761385
false
13771386
else if prev.typeSize > ptSize || prev.coveringSet != ptCoveringSet then
13781387
loop(outer, tp.isByName || belowByname)
@@ -1464,6 +1473,15 @@ abstract class SearchHistory:
14641473
def defineBynameImplicit(tpe: Type, result: SearchSuccess)(using Context): SearchResult =
14651474
root.defineBynameImplicit(tpe, result)
14661475

1476+
/** There is a qualifying call-by-name parameter in the history
1477+
* that cannot be used since is it not fully defined. Used for error reporting.
1478+
*/
1479+
def disqualifiedType: Type =
1480+
def loop(h: SearchHistory): Type = h match
1481+
case h: OpenSearch => if h.disqualified then h.pt else loop(h.outer)
1482+
case _ => NoType
1483+
loop(this)
1484+
14671485
// This is NOOP unless at the root of this search history.
14681486
def emitDictionary(span: Span, result: SearchResult)(using Context): SearchResult = result
14691487

@@ -1483,6 +1501,10 @@ case class OpenSearch(cand: Candidate, pt: Type, outer: SearchHistory)(using Con
14831501
// An example is in neg/9504.scala
14841502
lazy val typeSize = pt.typeSize
14851503
lazy val coveringSet = pt.coveringSet
1504+
1505+
// Set if there would be a qualifying call-by-name parameter
1506+
// that cannot be used since is it not fully defined
1507+
var disqualified: Boolean = false
14861508
end OpenSearch
14871509

14881510
/**

tests/neg/i3452.scala

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ object Test {
66
implicit def case1[F[_]](implicit t: => TC[F[Any]]): TC[Tuple2K[[_] =>> Any, F, Any]] = ???
77
implicit def case2[A, F[_]](implicit r: TC[F[Any]]): TC[A] = ???
88

9-
// Disabled because it leads to an infinite loop in implicit search
10-
// this is probably the same issue as https://github.com/lampepfl/dotty/issues/9568
11-
// implicitly[TC[Int]] // was: error
9+
implicitly[TC[Int]] // error
1210
}
1311

1412
object Test1 {

tests/neg/i9568.check

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-- Error: tests/neg/i9568.scala:13:10 ----------------------------------------------------------------------------------
2+
13 | blaMonad.foo(bla) // error: diverges
3+
| ^
4+
|no implicit argument of type => Monad[([_$3] =>> Any)] was found for parameter ev of method blaMonad in object Test.
5+
|I found:
6+
|
7+
| Test.blaMonad[F, S](/* missing */summon[Monad[F]])
8+
|
9+
|But method blaMonad in object Test produces a diverging implicit search when trying to match type Monad[F].
10+
|Note that open search type => Monad[([X] =>> Bla[F, X])] cannot be re-used as a by-name implicit parameter
11+
|since it is not fully defined.

tests/neg/i9568.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
trait Monad[F[_]] {
2+
def foo[A](fa: F[A]): Unit = {}
3+
}
4+
5+
class Bla[F[_], A]
6+
7+
object Test {
8+
type Id[A] = A
9+
10+
val bla: Bla[Id, Unit] = ???
11+
implicit def blaMonad[F[_], S](implicit ev: => Monad[F]): Monad[({type L[X] = Bla[F, X]})#L] = ???
12+
13+
blaMonad.foo(bla) // error: diverges
14+
}

tests/pos/i9568.scala

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
trait Monad[F[_]] {
2+
def foo[A](fa: F[A]): Unit = {}
3+
}
4+
5+
class Bla[F[_], A]
6+
7+
object Test1 {
8+
type Id[A] = A
9+
10+
val bla: Bla[Id, Unit] = ???
11+
implicit def blaMonad[F[_]: Monad, S]: Monad[({type L[X] = Bla[F, X]})#L] = ???
12+
implicit def idMonad: Monad[Id] = ???
13+
14+
blaMonad.foo(bla) // does not diverge
15+
}
16+
17+
object Test2 {
18+
type Id[A] = A
19+
20+
val bla: Bla[Id, Unit] = ???
21+
implicit def blaMonad[F[_], S](implicit ev: => Monad[F]): Monad[({type L[X] = Bla[F, X]})#L] = ???
22+
implicit def idMonad: Monad[Id] = ???
23+
24+
blaMonad.foo(bla) // does not diverge
25+
}

0 commit comments

Comments
 (0)