Skip to content

Commit 521f042

Browse files
committed
Fix scala#4709 and scala#3253: report CyclicError in implicit search better
- Record in CyclicReference if it's emitted during implicit search. - Before checking if the error is on an implicit, check if we have an error during implicit search that happens while inferring a return type; that can happen whether the member we're typechecking is implicit or not, and needs the same cure. - Add ErrorMessagesTests as requested This improves the error message, and also puts more effort into diagnosing the cycle: we now use similar logic as for recursive/overloaded members to check we're indeed dealing with a (potentially) recursive implicit. Remaining `CyclicReferenceInvolvingImplicit` are unexpected and might involve bugs, so make the error message and explanation more tentative, following `CyclicReferenceInvolving`.
1 parent 4b8c998 commit 521f042

File tree

8 files changed

+201
-43
lines changed

8 files changed

+201
-43
lines changed

compiler/src/dotty/tools/dotc/core/TypeErrors.scala

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,21 @@ object handleRecursive {
9898
}
9999
}
100100

101-
class CyclicReference private (val denot: SymDenotation) extends TypeError {
102-
103-
override def toMessage(implicit ctx: Context) = {
101+
class CyclicReference private (val denot: SymDenotation, var inImplicitSearch: Boolean = false) extends TypeError {
102+
override def toMessage(implicit ctx: Context): Message = {
103+
val cycleSym = denot.symbol
104104

105-
def errorMsg(cx: Context): Message =
105+
/* This CyclicReference might have arisen from asking for `m`'s type while trying to infer it.
106+
* To try to diagnose this, walk the context chain searching for context in
107+
* Mode.InferringReturnType for the innermost member without type
108+
* annotations (!tree.tpt.typeOpt.exists).
109+
*/
110+
def errorMsg(cx: Context): Message = {
106111
if (cx.mode is Mode.InferringReturnType) {
107112
cx.tree match {
113+
case tree: untpd.ValOrDefDef if inImplicitSearch && !tree.tpt.typeOpt.exists =>
114+
// Can happen in implicit defs (#4709) or outside (#3253).
115+
TermMemberNeedsNeedsResultTypeForImplicitSearch(cycleSym)
108116
case tree: untpd.DefDef if !tree.tpt.typeOpt.exists =>
109117
OverloadedOrRecursiveMethodNeedsResultType(tree.name)
110118
case tree: untpd.ValDef if !tree.tpt.typeOpt.exists =>
@@ -113,13 +121,14 @@ class CyclicReference private (val denot: SymDenotation) extends TypeError {
113121
errorMsg(cx.outer)
114122
}
115123
}
116-
else CyclicReferenceInvolving(denot)
124+
// Give up and give generic errors.
125+
else if (cycleSym.is(Implicit, butNot = Method) && cycleSym.owner.isTerm)
126+
CyclicReferenceInvolvingImplicit(cycleSym)
127+
else
128+
CyclicReferenceInvolving(denot)
129+
}
117130

118-
val cycleSym = denot.symbol
119-
if (cycleSym.is(Implicit, butNot = Method) && cycleSym.owner.isTerm)
120-
CyclicReferenceInvolvingImplicit(cycleSym)
121-
else
122-
errorMsg(ctx)
131+
errorMsg(ctx)
123132
}
124133
}
125134

compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ public enum ErrorMessageID {
131131
MatchCaseOnlyNullWarningID,
132132
ImportRenamedTwiceID,
133133
TypeTestAlwaysSucceedsID,
134+
TermMemberNeedsNeedsResultTypeForImplicitSearchID
134135
;
135136

136137
public int errorNumber() {

compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,7 +1260,7 @@ object messages {
12601260
|""".stripMargin
12611261
}
12621262

1263-
case class OverloadedOrRecursiveMethodNeedsResultType(method: Names.TermName)(implicit ctx: Context)
1263+
case class OverloadedOrRecursiveMethodNeedsResultType(method: Names.Name)(implicit ctx: Context)
12641264
extends Message(OverloadedOrRecursiveMethodNeedsResultTypeID) {
12651265
val kind = "Syntax"
12661266
val msg = hl"""overloaded or recursive method ${method} needs return type"""
@@ -1300,8 +1300,10 @@ object messages {
13001300
val kind = "Syntax"
13011301
val msg = hl"""cyclic reference involving implicit $cycleSym"""
13021302
val explanation =
1303-
hl"""|This happens when the right hand-side of $cycleSym's definition involves an implicit search.
1304-
|To avoid this error, give `${cycleSym.name}` an explicit type.
1303+
hl"""|$cycleSym is declared as part of a cycle which makes it impossible for the
1304+
|compiler to decide upon ${cycleSym.name}'s type.
1305+
|This might happen when the right hand-side of $cycleSym's definition involves an implicit search.
1306+
|To avoid this error, try giving `${cycleSym.name}` an explicit type.
13051307
|""".stripMargin
13061308
}
13071309

@@ -2107,4 +2109,15 @@ object messages {
21072109
}
21082110
val explanation = ""
21092111
}
2112+
2113+
// Relative of CyclicReferenceInvolvingImplicit and RecursiveValueNeedsResultType
2114+
case class TermMemberNeedsNeedsResultTypeForImplicitSearch(cycleSym: Symbol)(implicit ctx: Context)
2115+
extends Message(TermMemberNeedsNeedsResultTypeForImplicitSearchID) {
2116+
val kind = "Syntax"
2117+
val msg = hl"""$cycleSym needs result type because its right-hand side attempts implicit search"""
2118+
val explanation =
2119+
hl"""|The right hand-side of $cycleSym's definition requires an implicit search at the highlighted position.
2120+
|To avoid this error, give `${cycleSym.name}` an explicit type.
2121+
|""".stripMargin
2122+
}
21102123
}

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

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -811,36 +811,42 @@ trait Implicits { self: Typer =>
811811
* !!! todo: catch potential cycles
812812
*/
813813
def inferImplicit(pt: Type, argument: Tree, pos: Position)(implicit ctx: Context): SearchResult = track("inferImplicit") {
814-
assert(ctx.phase.allowsImplicitSearch,
815-
if (argument.isEmpty) i"missing implicit parameter of type $pt after typer"
816-
else i"type error: ${argument.tpe} does not conform to $pt${err.whyNoMatchStr(argument.tpe, pt)}")
817-
trace(s"search implicit ${pt.show}, arg = ${argument.show}: ${argument.tpe.show}", implicits, show = true) {
818-
assert(!pt.isInstanceOf[ExprType])
819-
val result = new ImplicitSearch(pt, argument, pos).bestImplicit(contextual = true)
820-
result match {
821-
case result: SearchSuccess =>
822-
result.tstate.commit()
823-
implicits.println(i"success: $result")
824-
implicits.println(i"committing ${result.tstate.constraint} yielding ${ctx.typerState.constraint} in ${ctx.typerState}")
825-
result
826-
case result: SearchFailure if result.isAmbiguous =>
827-
val deepPt = pt.deepenProto
828-
if (deepPt ne pt) inferImplicit(deepPt, argument, pos)
829-
else if (ctx.scala2Mode && !ctx.mode.is(Mode.OldOverloadingResolution)) {
830-
inferImplicit(pt, argument, pos)(ctx.addMode(Mode.OldOverloadingResolution)) match {
831-
case altResult: SearchSuccess =>
832-
ctx.migrationWarning(
833-
s"According to new implicit resolution rules, this will be ambiguous:\n${result.reason.explanation}",
834-
pos)
835-
altResult
836-
case _ =>
837-
result
814+
try {
815+
assert(ctx.phase.allowsImplicitSearch,
816+
if (argument.isEmpty) i"missing implicit parameter of type $pt after typer"
817+
else i"type error: ${argument.tpe} does not conform to $pt${err.whyNoMatchStr(argument.tpe, pt)}")
818+
trace(s"search implicit ${pt.show}, arg = ${argument.show}: ${argument.tpe.show}", implicits, show = true) {
819+
assert(!pt.isInstanceOf[ExprType])
820+
val result = new ImplicitSearch(pt, argument, pos).bestImplicit(contextual = true)
821+
result match {
822+
case result: SearchSuccess =>
823+
result.tstate.commit()
824+
implicits.println(i"success: $result")
825+
implicits.println(i"committing ${result.tstate.constraint} yielding ${ctx.typerState.constraint} in ${ctx.typerState}")
826+
result
827+
case result: SearchFailure if result.isAmbiguous =>
828+
val deepPt = pt.deepenProto
829+
if (deepPt ne pt) inferImplicit(deepPt, argument, pos)
830+
else if (ctx.scala2Mode && !ctx.mode.is(Mode.OldOverloadingResolution)) {
831+
inferImplicit(pt, argument, pos)(ctx.addMode(Mode.OldOverloadingResolution)) match {
832+
case altResult: SearchSuccess =>
833+
ctx.migrationWarning(
834+
s"According to new implicit resolution rules, this will be ambiguous:\n${result.reason.explanation}",
835+
pos)
836+
altResult
837+
case _ =>
838+
result
839+
}
838840
}
839-
}
840-
else result
841-
case _ =>
842-
result
841+
else result
842+
case _ =>
843+
result
844+
}
843845
}
846+
} catch {
847+
case ce: CyclicReference =>
848+
ce.inImplicitSearch = true
849+
throw ce
844850
}
845851
}
846852

compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,64 @@ class ErrorMessagesTests extends ErrorMessagesTest {
260260
assertEquals("value x", denot.show)
261261
}
262262

263-
@Test def cyclicReferenceInvolvingImplicit =
263+
@Test def cyclicReferenceInvolving2 =
264+
checkMessagesAfter(FrontEnd.name) {
265+
"""
266+
|class A {
267+
| implicit val x: T = ???
268+
| type T <: x.type // error: cyclic reference involving value x
269+
|}
270+
""".stripMargin
271+
}
272+
.expect { (ictx, messages) =>
273+
implicit val ctx: Context = ictx
274+
275+
assertMessageCount(1, messages)
276+
val CyclicReferenceInvolving(denot) :: Nil = messages
277+
assertEquals("value x", denot.show)
278+
}
279+
280+
@Test def mutualRecursionre_i2001 =
281+
checkMessagesAfter(FrontEnd.name) {
282+
"""
283+
|class A {
284+
| def odd(x: Int) = if (x == 0) false else !even(x-1)
285+
| def even(x: Int) = if (x == 0) true else !odd(x-1) // error: overloaded or recursive method needs result type
286+
|}
287+
""".stripMargin
288+
}
289+
.expect { (ictx, messages) =>
290+
implicit val ctx: Context = ictx
291+
292+
assertMessageCount(1, messages)
293+
val OverloadedOrRecursiveMethodNeedsResultType(name) :: Nil = messages
294+
assertEquals("odd", name.show)
295+
}
296+
297+
@Test def mutualRecursion_i2001a =
298+
checkMessagesAfter(FrontEnd.name) {
299+
"""
300+
|class A {
301+
| def odd(x: Int) = if (x == 0) false else !even(x-1)
302+
| def even(x: Int) = {
303+
| def foo = {
304+
| if (x == 0) true else !odd(x-1) // error: overloaded or recursive method needs result type
305+
| }
306+
| false
307+
| }
308+
|}
309+
""".stripMargin
310+
}
311+
.expect { (ictx, messages) =>
312+
implicit val ctx: Context = ictx
313+
314+
assertMessageCount(1, messages)
315+
val CyclicReferenceInvolving(denot) :: Nil = messages
316+
assertEquals("value x", denot.show)
317+
}
318+
319+
320+
@Test def termMemberNeedsNeedsResultTypeForImplicitSearch =
264321
checkMessagesAfter(FrontEnd.name) {
265322
"""
266323
|object implicitDefs {
@@ -276,10 +333,53 @@ class ErrorMessagesTests extends ErrorMessagesTest {
276333
implicit val ctx: Context = ictx
277334

278335
assertMessageCount(1, messages)
279-
val CyclicReferenceInvolvingImplicit(tree) :: Nil = messages
336+
val TermMemberNeedsNeedsResultTypeForImplicitSearch(tree) :: Nil = messages
280337
assertEquals("x", tree.name.show)
281338
}
282339

340+
@Test def implicitSearchForcesImplicitRetType_i4709 =
341+
checkMessagesAfter(FrontEnd.name) {
342+
"""
343+
|import scala.language.implicitConversions
344+
|
345+
|class Context
346+
|class ContextBase { def settings = 1 }
347+
|
348+
|class Test {
349+
| implicit def toBase(ctx: Context): ContextBase = ???
350+
|
351+
| def test(ctx0: Context) = {
352+
| implicit val ctx = { ctx0.settings; ??? }
353+
| }
354+
|}
355+
""".stripMargin
356+
}
357+
.expect{ (ictx, messages) =>
358+
implicit val ctx: Context = ictx
359+
360+
assertMessageCount(1, messages)
361+
val TermMemberNeedsNeedsResultTypeForImplicitSearch(tree) :: Nil = messages
362+
assertEquals("ctx", tree.name.show)
363+
}
364+
365+
@Test def implicitSearchForcesNonImplicitRetTypeOnExplicitImport_i3253 =
366+
checkMessagesAfter(FrontEnd.name) {
367+
"""
368+
|import Test.test
369+
|
370+
|object Test {
371+
| def test = " " * 10
372+
|}
373+
""".stripMargin
374+
}
375+
.expect{ (ictx, messages) =>
376+
implicit val ctx: Context = ictx
377+
378+
assertMessageCount(1, messages)
379+
val TermMemberNeedsNeedsResultTypeForImplicitSearch(tree) :: Nil = messages
380+
assertEquals("test", tree.name.show)
381+
}
382+
283383
@Test def superQualMustBeParent =
284384
checkMessagesAfter(FrontEnd.name) {
285385
"""

tests/neg/i2001a.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class A {
2+
def odd(x: Int) = if (x == 0) false else !even(x-1)
3+
def even(x: Int) = {
4+
def foo = {
5+
if (x == 0) true else !odd(x-1) // error: overloaded or recursive method needs result type
6+
}
7+
foo
8+
}
9+
lazy val x = {
10+
def foo = x // error
11+
foo
12+
}
13+
}

tests/neg/i3253.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import Test.test
2+
3+
object Test {
4+
def test = " " * 10 // error
5+
}

tests/neg/i4709.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class Context
2+
class ContextBase { def settings = 1 }
3+
4+
class Test {
5+
implicit def toBase(ctx: Context): ContextBase = ???
6+
7+
def test(ctx0: Context) = {
8+
implicit val ctx = { ctx0.settings; ??? } // error
9+
}
10+
def f: Unit = { implicitly[Int]; implicit val i = implicitly[Int] } // error
11+
}

0 commit comments

Comments
 (0)