Skip to content

Commit 6abaa10

Browse files
authored
Merge pull request #2049 from ennru/ennru_RecursiveNeedsType
Change "recursive/cyclic definitions needs type" errors to Message
2 parents bf9bdae + 98465f9 commit 6abaa10

File tree

5 files changed

+151
-10
lines changed

5 files changed

+151
-10
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import util.Positions.{Position, NoPosition}
1919
import util.Stats._
2020
import util.{DotClass, SimpleMap}
2121
import reporting.diagnostic.Message
22+
import reporting.diagnostic.messages.CyclicReferenceInvolving
2223
import ast.tpd._
2324
import ast.TreeTypeMap
2425
import printing.Texts._
@@ -3853,7 +3854,7 @@ object Types {
38533854

38543855
class CyclicReference private (val denot: SymDenotation)
38553856
extends TypeError(s"cyclic reference involving $denot") {
3856-
def show(implicit ctx: Context) = s"cyclic reference involving ${denot.show}"
3857+
def toMessage(implicit ctx: Context) = CyclicReferenceInvolving(denot)
38573858
}
38583859

38593860
object CyclicReference {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ public enum ErrorMessageID {
5151
MixedLeftAndRightAssociativeOpsID,
5252
CantInstantiateAbstractClassOrTraitID,
5353
AnnotatedPrimaryConstructorRequiresModifierOrThisID,
54+
OverloadedOrRecursiveMethodNeedsResultTypeID,
55+
RecursiveValueNeedsResultTypeID,
56+
CyclicReferenceInvolvingID,
57+
CyclicReferenceInvolvingImplicitID,
5458
;
5559

5660
public int errorNumber() {

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

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import dotc.parsing.Tokens
1818
import printing.Highlighting._
1919
import printing.Formatting
2020
import ErrorMessageID._
21+
import dotty.tools.dotc.core.SymDenotations.SymDenotation
2122

2223
object messages {
2324

@@ -1134,7 +1135,7 @@ object messages {
11341135
}
11351136

11361137
case class AnnotatedPrimaryConstructorRequiresModifierOrThis(cls: Name)(implicit ctx: Context)
1137-
extends Message(AnnotatedPrimaryConstructorRequiresModifierOrThisID) {
1138+
extends Message(AnnotatedPrimaryConstructorRequiresModifierOrThisID) {
11381139
val kind = "Syntax"
11391140
val msg = hl"""${"private"}, ${"protected"}, or ${"this"} expected for annotated primary constructor"""
11401141
val explanation =
@@ -1147,4 +1148,48 @@ object messages {
11471148
| ^^^^
11481149
|""".stripMargin
11491150
}
1151+
1152+
case class OverloadedOrRecursiveMethodNeedsResultType(tree: Names.TermName)(implicit ctx: Context)
1153+
extends Message(OverloadedOrRecursiveMethodNeedsResultTypeID) {
1154+
val kind = "Syntax"
1155+
val msg = hl"""overloaded or recursive method ${tree} needs return type"""
1156+
val explanation =
1157+
hl"""Case 1: ${tree} is overloaded
1158+
|If there are multiple methods named `${tree.name}` and at least one definition of
1159+
|it calls another, you need to specify the calling method's return type.
1160+
|
1161+
|Case 2: ${tree} is recursive
1162+
|If `${tree.name}` calls itself on any path, you need to specify its return type.
1163+
|""".stripMargin
1164+
}
1165+
1166+
case class RecursiveValueNeedsResultType(tree: Names.TermName)(implicit ctx: Context)
1167+
extends Message(RecursiveValueNeedsResultTypeID) {
1168+
val kind = "Syntax"
1169+
val msg = hl"""recursive value ${tree.name} needs type"""
1170+
val explanation =
1171+
hl"""The definition of `${tree.name}` is recursive and you need to specify its type.
1172+
|""".stripMargin
1173+
}
1174+
1175+
case class CyclicReferenceInvolving(denot: SymDenotation)(implicit ctx: Context)
1176+
extends Message(CyclicReferenceInvolvingID) {
1177+
val kind = "Syntax"
1178+
val msg = hl"""cyclic reference involving $denot"""
1179+
val explanation =
1180+
hl"""|$denot is declared as part of a cycle which makes it impossible for the
1181+
|compiler to decide upon ${denot.name}'s type.
1182+
|""".stripMargin
1183+
}
1184+
1185+
case class CyclicReferenceInvolvingImplicit(cycleSym: Symbol)(implicit ctx: Context)
1186+
extends Message(CyclicReferenceInvolvingImplicitID) {
1187+
val kind = "Syntax"
1188+
val msg = hl"""cyclic reference involving implicit $cycleSym"""
1189+
val explanation =
1190+
hl"""|This happens when the right hand-side of $cycleSym's definition involves an implicit search.
1191+
|To avoid this error, give `${cycleSym.name}` an explicit type.
1192+
|""".stripMargin
1193+
}
1194+
11501195
}

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

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,22 @@ object ErrorReporting {
2828

2929
def cyclicErrorMsg(ex: CyclicReference)(implicit ctx: Context) = {
3030
val cycleSym = ex.denot.symbol
31-
def errorMsg(msg: String, cx: Context): String =
31+
def errorMsg(msg: Message, cx: Context): Message =
3232
if (cx.mode is Mode.InferringReturnType) {
3333
cx.tree match {
3434
case tree: untpd.DefDef if !tree.tpt.typeOpt.exists =>
35-
em"overloaded or recursive method ${tree.name} needs result type"
35+
OverloadedOrRecursiveMethodNeedsResultType(tree.name)
3636
case tree: untpd.ValDef if !tree.tpt.typeOpt.exists =>
37-
em"recursive value ${tree.name} needs type"
37+
RecursiveValueNeedsResultType(tree.name)
3838
case _ =>
3939
errorMsg(msg, cx.outer)
4040
}
4141
} else msg
4242

4343
if (cycleSym.is(Implicit, butNot = Method) && cycleSym.owner.isTerm)
44-
em"""cyclic reference involving implicit $cycleSym
45-
|This happens when the right hand-side of $cycleSym's definition involves an implicit search.
46-
|To avoid the error, give $cycleSym an explicit type."""
44+
CyclicReferenceInvolvingImplicit(cycleSym)
4745
else
48-
errorMsg(ex.show, ctx)
46+
errorMsg(ex.toMessage, ctx)
4947
}
5048

5149
def wrongNumberOfTypeArgs(fntpe: Type, expectedArgs: List[TypeParamInfo], actual: List[untpd.Tree], pos: Position)(implicit ctx: Context) =

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

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import core.Contexts.Context
66
import diagnostic.messages._
77
import dotty.tools.dotc.parsing.Tokens
88
import org.junit.Assert._
9-
import org.junit.Test
9+
import org.junit.{Ignore, Test}
1010

1111
class ErrorMessagesTests extends ErrorMessagesTest {
1212
// In the case where there are no errors, we can do "expectNoErrors" in the
@@ -212,4 +212,97 @@ class ErrorMessagesTests extends ErrorMessagesTest {
212212
val AnnotatedPrimaryConstructorRequiresModifierOrThis(cls) :: Nil = messages
213213
assertEquals("AnotherClass", cls.show)
214214
}
215+
216+
@Test def overloadedMethodNeedsReturnType =
217+
checkMessagesAfter("frontend") {
218+
"""
219+
|class Scope() {
220+
| def foo(i: Int) = foo(i.toString)
221+
| def foo(s: String) = s
222+
|}
223+
""".stripMargin
224+
}
225+
.expect { (ictx, messages) =>
226+
implicit val ctx: Context = ictx
227+
val defn = ictx.definitions
228+
229+
assertMessageCount(1, messages)
230+
val OverloadedOrRecursiveMethodNeedsResultType(tree) :: Nil = messages
231+
assertEquals("foo", tree.show)
232+
}
233+
234+
@Test def recursiveMethodNeedsReturnType =
235+
checkMessagesAfter("frontend") {
236+
"""
237+
|class Scope() {
238+
| def i = i + 5
239+
|}
240+
""".stripMargin
241+
}
242+
.expect { (ictx, messages) =>
243+
implicit val ctx: Context = ictx
244+
val defn = ictx.definitions
245+
246+
assertMessageCount(1, messages)
247+
val OverloadedOrRecursiveMethodNeedsResultType(tree) :: Nil = messages
248+
assertEquals("i", tree.show)
249+
}
250+
251+
@Test def recursiveValueNeedsReturnType =
252+
checkMessagesAfter("frontend") {
253+
"""
254+
|class Scope() {
255+
| lazy val i = i + 5
256+
|}
257+
""".stripMargin
258+
}
259+
.expect { (ictx, messages) =>
260+
implicit val ctx: Context = ictx
261+
val defn = ictx.definitions
262+
263+
assertMessageCount(1, messages)
264+
val RecursiveValueNeedsResultType(tree) :: Nil = messages
265+
assertEquals("i", tree.show)
266+
}
267+
268+
@Test def cyclicReferenceInvolving =
269+
checkMessagesAfter("frontend") {
270+
"""
271+
|class A {
272+
| val x: T = ???
273+
| type T <: x.type // error: cyclic reference involving value x
274+
|}
275+
""".stripMargin
276+
}
277+
.expect { (ictx, messages) =>
278+
implicit val ctx: Context = ictx
279+
val defn = ictx.definitions
280+
281+
assertMessageCount(1, messages)
282+
val CyclicReferenceInvolving(denot) :: Nil = messages
283+
assertEquals("value x", denot.show)
284+
}
285+
286+
@Test def cyclicReferenceInvolvingImplicit =
287+
checkMessagesAfter("frontend") {
288+
"""
289+
|object implicitDefs {
290+
| def foo(implicit x: String) = 1
291+
| def bar() = {
292+
| implicit val x = foo
293+
| x
294+
| }
295+
|}
296+
""".stripMargin
297+
}
298+
.expect { (ictx, messages) =>
299+
implicit val ctx: Context = ictx
300+
val defn = ictx.definitions
301+
302+
assertMessageCount(1, messages)
303+
val CyclicReferenceInvolvingImplicit(tree) :: Nil = messages
304+
assertEquals("x", tree.name.show)
305+
}
306+
307+
215308
}

0 commit comments

Comments
 (0)