Skip to content

Commit e19b972

Browse files
committed
Analyzed CyclicErrors: report the correct symbol
Make reports about cycleSym not tree.name. We need then to use flagsUNSAFE, see description. * Store cycleSym in all relevant errors * Mention cycleSym instead of tree.name in errors Tests pass, and have the correct expectations both for `tree.name` and `cycleSym.name`, tho we just show `cycleSym.name` and `tree.name` is less accurate. But at this point, I might be able to stop using `tree.name` altogether.
1 parent 42e478f commit e19b972

File tree

4 files changed

+102
-36
lines changed

4 files changed

+102
-36
lines changed

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

+15-7
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,12 @@ class CyclicReference private (val denot: SymDenotation) extends TypeError {
109109
override def toMessage(implicit ctx: Context): Message = {
110110
val cycleSym = denot.symbol
111111

112+
// cycleSym.flags would try completing denot and would fail, but here we can use flagsUNSAFE to detect flags
113+
// set by the parser.
114+
val unsafeFlags = cycleSym.flagsUNSAFE
115+
val isMethod = unsafeFlags.is(Method)
116+
val isVal = !isMethod && cycleSym.isTerm
117+
112118
/* This CyclicReference might have arisen from asking for `m`'s type while trying to infer it.
113119
* To try to diagnose this, walk the context chain searching for context in
114120
* Mode.InferringReturnType for the innermost member without type
@@ -117,13 +123,15 @@ class CyclicReference private (val denot: SymDenotation) extends TypeError {
117123
def errorMsg(cx: Context): Message = {
118124
if (cx.mode is Mode.InferringReturnType) {
119125
cx.tree match {
120-
case tree: untpd.ValOrDefDef if inImplicitSearch && !tree.tpt.typeOpt.exists =>
121-
// Can happen in implicit defs (#4709) or outside (#3253).
122-
TermMemberNeedsResultTypeForImplicitSearch(cycleSym)
123-
case tree: untpd.DefDef if !tree.tpt.typeOpt.exists =>
124-
OverloadedOrRecursiveMethodNeedsResultType(tree.name)
125-
case tree: untpd.ValDef if !tree.tpt.typeOpt.exists =>
126-
RecursiveValueNeedsResultType(tree.name)
126+
case tree: untpd.ValOrDefDef if !tree.tpt.typeOpt.exists =>
127+
if (inImplicitSearch)
128+
TermMemberNeedsResultTypeForImplicitSearch(tree.name, cycleSym)
129+
else if (isMethod)
130+
OverloadedOrRecursiveMethodNeedsResultType(tree.name, cycleSym)
131+
else if (isVal)
132+
RecursiveValueNeedsResultType(tree.name, cycleSym)
133+
else
134+
errorMsg(cx.outer)
127135
case _ =>
128136
errorMsg(cx.outer)
129137
}

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

+12-12
Original file line numberDiff line numberDiff line change
@@ -1260,27 +1260,27 @@ object messages {
12601260
|""".stripMargin
12611261
}
12621262

1263-
case class OverloadedOrRecursiveMethodNeedsResultType(method: Names.Name)(implicit ctx: Context)
1263+
case class OverloadedOrRecursiveMethodNeedsResultType(methodName: Names.TermName, cycleSym: Symbol)(implicit ctx: Context)
12641264
extends Message(OverloadedOrRecursiveMethodNeedsResultTypeID) {
12651265
val kind = "Syntax"
1266-
val msg = hl"""overloaded or recursive method ${method} needs return type"""
1266+
val msg = hl"""overloaded or recursive ${cycleSym} needs return type"""
12671267
val explanation =
1268-
hl"""Case 1: ${method} is overloaded
1269-
|If there are multiple methods named `${method}` and at least one definition of
1268+
hl"""Case 1: ${cycleSym} is overloaded
1269+
|If there are multiple methods named `${cycleSym}` and at least one definition of
12701270
|it calls another, you need to specify the calling method's return type.
12711271
|
1272-
|Case 2: ${method} is recursive
1273-
|If `${method}` calls itself on any path (even through mutual recursion), you need to specify the return type
1274-
|of `${method}` or of a definition it's mutually recursive with.
1272+
|Case 2: ${cycleSym} is recursive
1273+
|If `${cycleSym}` calls itself on any path (even through mutual recursion), you need to specify the return type
1274+
|of `${cycleSym}` or of a definition it's mutually recursive with.
12751275
|""".stripMargin
12761276
}
12771277

1278-
case class RecursiveValueNeedsResultType(value: Names.TermName)(implicit ctx: Context)
1278+
case class RecursiveValueNeedsResultType(valName: Names.TermName, cycleSym: Symbol)(implicit ctx: Context)
12791279
extends Message(RecursiveValueNeedsResultTypeID) {
12801280
val kind = "Syntax"
1281-
val msg = hl"""recursive value ${value} needs type"""
1281+
val msg = hl"""recursive ${cycleSym} needs type"""
12821282
val explanation =
1283-
hl"""The definition of `${value}` is recursive and you need to specify its type.
1283+
hl"""The definition of `${cycleSym}` is recursive and you need to specify its type.
12841284
|""".stripMargin
12851285
}
12861286

@@ -2111,13 +2111,13 @@ object messages {
21112111
}
21122112

21132113
// Relative of CyclicReferenceInvolvingImplicit and RecursiveValueNeedsResultType
2114-
case class TermMemberNeedsResultTypeForImplicitSearch(cycleSym: Symbol)(implicit ctx: Context)
2114+
case class TermMemberNeedsResultTypeForImplicitSearch(memberName: Names.TermName, cycleSym: Symbol)(implicit ctx: Context)
21152115
extends Message(TermMemberNeedsNeedsResultTypeForImplicitSearchID) {
21162116
val kind = "Syntax"
21172117
val msg = hl"""$cycleSym needs result type because its right-hand side attempts implicit search"""
21182118
val explanation =
21192119
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.
2120+
|To avoid this error, give `$cycleSym` an explicit type.
21212121
|""".stripMargin
21222122
}
21232123
}

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

+66-17
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,9 @@ class ErrorMessagesTests extends ErrorMessagesTest {
207207
implicit val ctx: Context = ictx
208208

209209
assertMessageCount(1, messages)
210-
val OverloadedOrRecursiveMethodNeedsResultType(tree) :: Nil = messages
211-
assertEquals("foo", tree.show)
210+
val OverloadedOrRecursiveMethodNeedsResultType(methodName, cycleSym) :: Nil = messages
211+
assertEquals("foo", methodName.show)
212+
assertEquals("foo", cycleSym.name.show)
212213
}
213214

214215
@Test def recursiveMethodNeedsReturnType =
@@ -223,8 +224,9 @@ class ErrorMessagesTests extends ErrorMessagesTest {
223224
implicit val ctx: Context = ictx
224225

225226
assertMessageCount(1, messages)
226-
val OverloadedOrRecursiveMethodNeedsResultType(tree) :: Nil = messages
227-
assertEquals("i", tree.show)
227+
val OverloadedOrRecursiveMethodNeedsResultType(methodName, cycleSym) :: Nil = messages
228+
assertEquals("i", methodName.show)
229+
assertEquals("i", cycleSym.name.show)
228230
}
229231

230232
@Test def recursiveValueNeedsReturnType =
@@ -239,10 +241,29 @@ class ErrorMessagesTests extends ErrorMessagesTest {
239241
implicit val ctx: Context = ictx
240242

241243
assertMessageCount(1, messages)
242-
val RecursiveValueNeedsResultType(tree) :: Nil = messages
243-
assertEquals("i", tree.show)
244+
val RecursiveValueNeedsResultType(valName, cycleSym) :: Nil = messages
245+
assertEquals("i", valName.show)
246+
assertEquals("i", cycleSym.name.show)
244247
}
245248

249+
@Test def recursiveValueNeedsReturnType2 =
250+
checkMessagesAfter(FrontEnd.name) {
251+
"""
252+
|class Scope() {
253+
| lazy val i = j + 5
254+
| lazy val j = i
255+
|}
256+
""".stripMargin
257+
}
258+
.expect { (ictx, messages) =>
259+
implicit val ctx: Context = ictx
260+
261+
assertMessageCount(1, messages)
262+
val RecursiveValueNeedsResultType(valName, cycleSym) :: Nil = messages
263+
assertEquals("j", valName.show)
264+
assertEquals("i", cycleSym.name.show)
265+
}
266+
246267
@Test def cyclicReferenceInvolving =
247268
checkMessagesAfter(FrontEnd.name) {
248269
"""
@@ -290,8 +311,9 @@ class ErrorMessagesTests extends ErrorMessagesTest {
290311
implicit val ctx: Context = ictx
291312

292313
assertMessageCount(1, messages)
293-
val OverloadedOrRecursiveMethodNeedsResultType(name) :: Nil = messages
294-
assertEquals("even", name.show)
314+
val OverloadedOrRecursiveMethodNeedsResultType(methodName, cycleSym) :: Nil = messages
315+
assertEquals("even", methodName.show)
316+
assertEquals("odd", cycleSym.name.show)
295317
}
296318

297319
@Test def mutualRecursion_i2001a =
@@ -312,13 +334,37 @@ class ErrorMessagesTests extends ErrorMessagesTest {
312334
implicit val ctx: Context = ictx
313335

314336
assertMessageCount(1, messages)
315-
val OverloadedOrRecursiveMethodNeedsResultType(denot) :: Nil = messages
337+
val OverloadedOrRecursiveMethodNeedsResultType(methodName, cycleSym) :: Nil = messages
316338
// Not ideal behavior
317-
assertEquals("foo", denot.show)
339+
assertEquals("foo", methodName.show)
340+
assertEquals("odd", cycleSym.name.show)
318341
}
319342

343+
@Test def mutualRecursion_i2001b =
344+
checkMessagesAfter(FrontEnd.name) {
345+
"""
346+
|class A {
347+
| def odd(x: Int) = if (x == 0) false else !even(x-1)
348+
| def even(x: Int) = {
349+
| val foo = {
350+
| if (x == 0) true else !odd(x-1) // error: overloaded or recursive method needs result type
351+
| }
352+
| false
353+
| }
354+
|}
355+
""".stripMargin
356+
}
357+
.expect { (ictx, messages) =>
358+
implicit val ctx: Context = ictx
359+
360+
assertMessageCount(1, messages)
361+
val OverloadedOrRecursiveMethodNeedsResultType(methodName, cycleSym) :: Nil = messages
362+
// Not ideal behavior
363+
assertEquals("foo", methodName.show)
364+
assertEquals("odd", cycleSym.name.show)
365+
}
320366

321-
@Test def termMemberNeedsNeedsResultTypeForImplicitSearch =
367+
@Test def termMemberNeedsResultTypeForImplicitSearch =
322368
checkMessagesAfter(FrontEnd.name) {
323369
"""
324370
|object implicitDefs {
@@ -334,8 +380,9 @@ class ErrorMessagesTests extends ErrorMessagesTest {
334380
implicit val ctx: Context = ictx
335381

336382
assertMessageCount(1, messages)
337-
val TermMemberNeedsResultTypeForImplicitSearch(tree) :: Nil = messages
338-
assertEquals("x", tree.name.show)
383+
val TermMemberNeedsResultTypeForImplicitSearch(memberName, cycleSym) :: Nil = messages
384+
assertEquals("x", cycleSym.name.show)
385+
assertEquals("x", memberName.show)
339386
}
340387

341388
@Test def implicitSearchForcesImplicitRetType_i4709 =
@@ -359,8 +406,9 @@ class ErrorMessagesTests extends ErrorMessagesTest {
359406
implicit val ctx: Context = ictx
360407

361408
assertMessageCount(1, messages)
362-
val TermMemberNeedsResultTypeForImplicitSearch(tree) :: Nil = messages
363-
assertEquals("ctx", tree.name.show)
409+
val TermMemberNeedsResultTypeForImplicitSearch(memberName, cycleSym) :: Nil = messages
410+
assertEquals("ctx", memberName.show)
411+
assertEquals("ctx", cycleSym.name.show)
364412
}
365413

366414
@Test def implicitSearchForcesNonImplicitRetTypeOnExplicitImport_i3253 =
@@ -377,8 +425,9 @@ class ErrorMessagesTests extends ErrorMessagesTest {
377425
implicit val ctx: Context = ictx
378426

379427
assertMessageCount(1, messages)
380-
val TermMemberNeedsResultTypeForImplicitSearch(tree) :: Nil = messages
381-
assertEquals("test", tree.name.show)
428+
val TermMemberNeedsResultTypeForImplicitSearch(memberName, cycleSym) :: Nil = messages
429+
assertEquals("test", memberName.show)
430+
assertEquals("test", cycleSym.name.show)
382431
}
383432

384433
@Test def superQualMustBeParent =

tests/neg/i2001b.scala

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

0 commit comments

Comments
 (0)