Skip to content

Change "recursive/cyclic definitions needs type" errors to Message #2049

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 9, 2017
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import util.Positions.{Position, NoPosition}
import util.Stats._
import util.{DotClass, SimpleMap}
import reporting.diagnostic.Message
import reporting.diagnostic.messages.CyclicReferenceInvolving
import ast.tpd._
import ast.TreeTypeMap
import printing.Texts._
Expand Down Expand Up @@ -3856,7 +3857,7 @@ object Types {

class CyclicReference private (val denot: SymDenotation)
extends TypeError(s"cyclic reference involving $denot") {
def show(implicit ctx: Context) = s"cyclic reference involving ${denot.show}"
def toMessage(implicit ctx: Context) = CyclicReferenceInvolving(denot)
}

object CyclicReference {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ public enum ErrorMessageID {
MixedLeftAndRightAssociativeOpsID,
CantInstantiateAbstractClassOrTraitID,
AnnotatedPrimaryConstructorRequiresModifierOrThisID,
OverloadedMethodNeedsResultTypeID,
RecursiveMethodNeedsResultTypeID,
RecursiveValueNeedsResultTypeID,
CyclicReferenceInvolvingID,
CyclicReferenceInvolvingImplicitID,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a more general question: Why are we using a Java enumeration for this? Would it not be easier to use a scala.Enumeration?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@felixmulder recently changed from directly specified int constants to this Java enum.

;

public int errorNumber() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import dotc.parsing.Tokens
import printing.Highlighting._
import printing.Formatting
import ErrorMessageID._
import dotty.tools.dotc.core.SymDenotations.SymDenotation

object messages {

Expand Down Expand Up @@ -1147,7 +1148,7 @@ object messages {
}

case class AnnotatedPrimaryConstructorRequiresModifierOrThis(cls: Name)(implicit ctx: Context)
extends Message(AnnotatedPrimaryConstructorRequiresModifierOrThisID) {
extends Message(AnnotatedPrimaryConstructorRequiresModifierOrThisID) {
val kind = "Syntax"
val msg = hl"""${"private"}, ${"protected"}, or ${"this"} expected for annotated primary constructor"""
val explanation =
Expand All @@ -1160,4 +1161,53 @@ object messages {
| ^^^^
|""".stripMargin
}

case class OverloadedMethodNeedsResultType(tree: Names.TermName)(implicit ctx: Context)
extends Message(OverloadedMethodNeedsResultTypeID) {
val kind = "Syntax"
val msg = hl"""overloaded method ${tree} needs result type"""
val explanation =
hl"""|${tree} is overloaded and at least one definition of it calls another.
|You need to specify the calling method's return type.
""".stripMargin
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case the method might be overloaded but it could also be simply recursive. So claiming that it is overloaded is false. We might try to find out whether it is overloaded to improve the error message. Essentially, it's overloaded if its owner is a class and taking the member of the name in the owner gives several alternatives.

}

case class RecursiveMethodNeedsResultType(tree: Names.TermName)(implicit ctx: Context)
extends Message(RecursiveMethodNeedsResultTypeID) {
val kind = "Syntax"
val msg = hl"""recursive method ${tree} needs result type"""
val explanation =
hl"""|The definition of `${tree.name}` is recursive and you need to specify its type.
""".stripMargin
}

case class RecursiveValueNeedsResultType(tree: Names.TermName)(implicit ctx: Context)
extends Message(RecursiveValueNeedsResultTypeID) {
val kind = "Syntax"
val msg = hl"""recursive value ${tree.name} needs type"""
val explanation =
hl"""|The definition of `${tree.name}` is recursive and you need to specify its type.
""".stripMargin
}

case class CyclicReferenceInvolving(denot: SymDenotation)(implicit ctx: Context)
extends Message(CyclicReferenceInvolvingID) {
val kind = "Syntax"
val msg = hl"""cyclic reference involving $denot"""
val explanation =
hl"""|$denot is declared as part of a cycle which makes it impossible for the
|compiler to decide upon ${denot.name}'s type.
|""".stripMargin
}

case class CyclicReferenceInvolvingImplicit(cycleSym: Symbol)(implicit ctx: Context)
extends Message(CyclicReferenceInvolvingImplicitID) {
val kind = "Syntax"
val msg = hl"""cyclic reference involving implicit $cycleSym"""
val explanation =
hl"""|This happens when the right hand-side of $cycleSym's definition involves an implicit search.
|To avoid this error, give `${cycleSym.name}` an explicit type.
|""".stripMargin
}

}
15 changes: 8 additions & 7 deletions compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,25 @@ object ErrorReporting {

def cyclicErrorMsg(ex: CyclicReference)(implicit ctx: Context) = {
val cycleSym = ex.denot.symbol
def errorMsg(msg: String, cx: Context): String =
def errorMsg(msg: Message, cx: Context): Message =
if (cx.mode is Mode.InferringReturnType) {
cx.tree match {
case tree: untpd.DefDef if !tree.tpt.typeOpt.exists =>
em"overloaded or recursive method ${tree.name} needs result type"
// TODO: analysis if tree is an overloaded method (or directly recursive)
Copy link
Contributor

@odersky odersky Mar 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this will do the trick:

cycleSym.owner.isClass && cycleSym.owner.info.member(cycleSym.name).isOverloaded

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that direction was what I tried, but member fails in Tree.withType as it encounters an ErrorType.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need Tree.withtype?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is what I get upon calling member(cycleSym.name) - not sure how the code gets there.

[error] Test dotty.tools.dotc.reporting.ErrorMessagesTests.recursiveMethodNeedsReturnType failed: java.lang.AssertionError: assertion failed, took 0.01 sec
[error] at scala.Predef$.assert(Predef.scala:151)
[error] at dotty.tools.dotc.ast.Trees$Tree.withType(Trees.scala:116)
[error] at dotty.tools.dotc.typer.ErrorReporting$.errorTree(ErrorReporting.scala:22)
[error] at dotty.tools.dotc.typer.Typer$$anonfun$typed$2.apply(Typer.scala:1584)
[error] at dotty.tools.dotc.typer.Typer$$anonfun$typed$2.apply(Typer.scala:1582)
[error] at dotty.tools.dotc.reporting.Reporting$class.traceIndented(Reporter.scala:136)
[error] at dotty.tools.dotc.core.Contexts$Context.traceIndented(Contexts.scala:57)
[error] at dotty.tools.dotc.typer.Typer.typed(Typer.scala:1582)
...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It complains that we assign an error type into a tree, yet not errors were reported. Is that stacktrace reached from the call

cycleSym.owner.info.member(cycleSym.name).isOverloaded

? If yes, can you show me the full stacktrace up to the call?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you go: stacktrace.txt

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to that stacktrace the error is not caused by the call

cycleSym.owner.info.member(cycleSym.name).isOverloaded

That one shows up nowhere. So it most likely happens later. Could it be that an error message is not reported?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error is triggered by using member, even though it is not on the stack. The error is reported correctly if I don't call it and it fails when I try to, at the time of calling the error is not reported yet.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the member call causes another CyclicReference exception. That's not easy to avoid. So I think we should give up. Don't try to distinguish the error message and just say "recursive or overloaded".

val overloaded = true
if (overloaded) OverloadedMethodNeedsResultType(tree.name)
else RecursiveMethodNeedsResultType(tree.name)
case tree: untpd.ValDef if !tree.tpt.typeOpt.exists =>
em"recursive value ${tree.name} needs type"
RecursiveValueNeedsResultType(tree.name)
case _ =>
errorMsg(msg, cx.outer)
}
} else msg

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

def wrongNumberOfTypeArgs(fntpe: Type, expectedArgs: List[TypeParamInfo], actual: List[untpd.Tree], pos: Position)(implicit ctx: Context) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import core.Contexts.Context
import diagnostic.messages._
import dotty.tools.dotc.parsing.Tokens
import org.junit.Assert._
import org.junit.Test
import org.junit.{Ignore, Test}

class ErrorMessagesTests extends ErrorMessagesTest {
// In the case where there are no errors, we can do "expectNoErrors" in the
Expand Down Expand Up @@ -212,4 +212,97 @@ class ErrorMessagesTests extends ErrorMessagesTest {
val AnnotatedPrimaryConstructorRequiresModifierOrThis(cls) :: Nil = messages
assertEquals("AnotherClass", cls.show)
}

@Test def overloadedMethodNeedsReturnType =
checkMessagesAfter("frontend") {
"""
|class Scope() {
| def foo(i: Int) = foo(i.toString)
| def foo(s: String) = s
|}
""".stripMargin
}
.expect { (ictx, messages) =>
implicit val ctx: Context = ictx
val defn = ictx.definitions

assertMessageCount(1, messages)
val OverloadedMethodNeedsResultType(tree) :: Nil = messages
assertEquals("foo", tree.show)
}

@Test @Ignore def recursiveMethodNeedsReturnType =
checkMessagesAfter("frontend") {
"""
|class Scope() {
| def i = i + 5
|}
""".stripMargin
}
.expect { (ictx, messages) =>
implicit val ctx: Context = ictx
val defn = ictx.definitions

assertMessageCount(1, messages)
val RecursiveMethodNeedsResultType(tree) :: Nil = messages
assertEquals("i", tree.show)
}

@Test def recursiveValueNeedsReturnType =
checkMessagesAfter("frontend") {
"""
|class Scope() {
| lazy val i = i + 5
|}
""".stripMargin
}
.expect { (ictx, messages) =>
implicit val ctx: Context = ictx
val defn = ictx.definitions

assertMessageCount(1, messages)
val RecursiveValueNeedsResultType(tree) :: Nil = messages
assertEquals("i", tree.show)
}

@Test def cyclicReferenceInvolving =
checkMessagesAfter("frontend") {
"""
|class A {
| val x: T = ???
| type T <: x.type // error: cyclic reference involving value x
|}
""".stripMargin
}
.expect { (ictx, messages) =>
implicit val ctx: Context = ictx
val defn = ictx.definitions

assertMessageCount(1, messages)
val CyclicReferenceInvolving(denot) :: Nil = messages
assertEquals("value x", denot.show)
}

@Test def cyclicReferenceInvolvingImplicit =
checkMessagesAfter("frontend") {
"""
|object implicitDefs {
| def foo(implicit x: String) = 1
| def bar() = {
| implicit val x = foo
| x
| }
|}
""".stripMargin
}
.expect { (ictx, messages) =>
implicit val ctx: Context = ictx
val defn = ictx.definitions

assertMessageCount(1, messages)
val CyclicReferenceInvolvingImplicit(tree) :: Nil = messages
assertEquals("x", tree.name.show)
}


}