diff --git a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala index aa6aebe17146..ad4a073e46fb 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala @@ -238,8 +238,10 @@ abstract class Reporter extends interfaces.ReporterResult { def isHidden(m: MessageContainer)(implicit ctx: Context): Boolean = ctx.mode.is(Mode.Printing) - /** Does this reporter contain not yet reported errors or warnings? */ - def hasPendingErrors: Boolean = false + /** Does this reporter contain errors that have yet to be reported by its outer reporter ? + * Note: this is always false when there is no outer reporter. + */ + def hasUnreportedErrors: Boolean = false /** If this reporter buffers messages, remove and return all buffered messages. */ def removeBufferedMessages(implicit ctx: Context): List[MessageContainer] = Nil diff --git a/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala b/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala index e642e294fcf4..7707dfd9c5e1 100644 --- a/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala @@ -28,8 +28,8 @@ class StoreReporter(outer: Reporter) extends Reporter { infos += m } - override def hasPendingErrors: Boolean = - infos != null && infos.exists(_.isInstanceOf[Error]) + override def hasUnreportedErrors: Boolean = + outer != null && infos != null && infos.exists(_.isInstanceOf[Error]) override def removeBufferedMessages(implicit ctx: Context): List[MessageContainer] = if (infos != null) try infos.toList finally infos = null diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index fd21974a7ccc..6ac21e44e9c0 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -291,11 +291,10 @@ object messages { val kind: String = "Type Mismatch" val msg: String = { val (where, printCtx) = Formatting.disambiguateTypes(found, expected) + val whereSuffix = if (where.isEmpty) where else s"\n\n$where" val (fnd, exp) = Formatting.typeDiff(found, expected)(printCtx) s"""|found: $fnd - |required: $exp - | - |$where""".stripMargin + whyNoMatch + implicitFailure + |required: $exp""".stripMargin + whereSuffix + whyNoMatch + implicitFailure } val explanation: String = "" diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index eae27f16df0c..befc06112068 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -432,10 +432,7 @@ trait Inferencing { this: Typer => // found : Int(1) // required: String // val y: List[List[String]] = List(List(1)) - val hasUnreportedErrors = state.reporter match { - case r: StoreReporter if r.hasErrors => true - case _ => false - } + val hasUnreportedErrors = state.reporter.hasUnreportedErrors def constraint = state.constraint for (tvar <- qualifying) if (!tvar.isInstantiated && state.constraint.contains(tvar)) { diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 91960ed68850..8e97fce05348 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -264,7 +264,7 @@ object ProtoTypes { targ = arg.withType(WildcardType) else { targ = typerFn(arg) - if (!ctx.reporter.hasPendingErrors) { + if (!ctx.reporter.hasUnreportedErrors) { // FIXME: This can swallow warnings by updating the typerstate from a nested // context that gets discarded later. But we do have to update the // typerstate if there are no errors. If we also omitted the next two lines diff --git a/compiler/src/dotty/tools/dotc/util/ParsedComment.scala b/compiler/src/dotty/tools/dotc/util/ParsedComment.scala index 98cff1a325d1..9d1b7e374b72 100644 --- a/compiler/src/dotty/tools/dotc/util/ParsedComment.scala +++ b/compiler/src/dotty/tools/dotc/util/ParsedComment.scala @@ -117,7 +117,6 @@ object ParsedComment { "@throws" -> TagFormatter("Throws", toDescriptionList), "@see" -> TagFormatter("See Also", toMarkdownList), "@example" -> TagFormatter("Examples", toCodeFences("scala")), - "@usecase" -> TagFormatter("Usecases", toCodeFences("scala")), "@note" -> TagFormatter("Note", toMarkdownList), "@author" -> TagFormatter("Authors", toMarkdownList), "@since" -> TagFormatter("Since", toMarkdownList), diff --git a/compiler/test-resources/repl/errmsgs b/compiler/test-resources/repl/errmsgs index aef05a07c103..4c122eb19a40 100644 --- a/compiler/test-resources/repl/errmsgs +++ b/compiler/test-resources/repl/errmsgs @@ -5,36 +5,30 @@ scala> val x: List[String] = List(1) | ^ | found: Int(1) | required: String - | scala> val y: List[List[String]] = List(List(1)) 1 | val y: List[List[String]] = List(List(1)) | ^ | found: Int(1) | required: String - | scala> val z: (List[String], List[Int]) = (List(1), List("a")) 1 | val z: (List[String], List[Int]) = (List(1), List("a")) | ^ | found: Int(1) | required: String - | 1 | val z: (List[String], List[Int]) = (List(1), List("a")) | ^^^ | found: String("a") | required: Int - | scala> val a: Inv[String] = new Inv(new Inv(1)) 1 | val a: Inv[String] = new Inv(new Inv(1)) | ^^^^^^^^^^ | found: Inv[Int] | required: String - | scala> val b: Inv[String] = new Inv(1) 1 | val b: Inv[String] = new Inv(1) | ^ | found: Int(1) | required: String - | scala> abstract class C { type T; val x: T; val s: Unit = { type T = String; var y: T = x; locally { def f() = { type T = Int; val z: T = y }; f() } }; } 1 | abstract class C { type T; val x: T; val s: Unit = { type T = String; var y: T = x; locally { def f() = { type T = Int; val z: T = y }; f() } }; } | ^ @@ -59,7 +53,6 @@ scala> val x: List[Int] = "foo" :: List(1) | ^^^^^ | found: String("foo") | required: Int - | scala> { def f: Int = g; val x: Int = 1; def g: Int = 5; } 1 | { def f: Int = g; val x: Int = 1; def g: Int = 5; } | ^ diff --git a/compiler/test-resources/repl/importFromObj b/compiler/test-resources/repl/importFromObj index f70b24d8d1c4..b3655c3f094a 100644 --- a/compiler/test-resources/repl/importFromObj +++ b/compiler/test-resources/repl/importFromObj @@ -9,7 +9,6 @@ scala> buf += xs | ^^ | found: List[Int](o.xs) | required: Int - | scala> buf ++= xs val res0: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 3) scala> import util.foo diff --git a/compiler/test-resources/type-printer/type-mismatch b/compiler/test-resources/type-printer/type-mismatch index 4452d880a324..27c0ad9384e5 100644 --- a/compiler/test-resources/type-printer/type-mismatch +++ b/compiler/test-resources/type-printer/type-mismatch @@ -7,4 +7,3 @@ scala> val x: Foo[String] = res0 | ^^^^ | found: Foo[Int](res0) | required: Foo[String] - | diff --git a/language-server/test/dotty/tools/languageserver/DiagnosticsTest.scala b/language-server/test/dotty/tools/languageserver/DiagnosticsTest.scala new file mode 100644 index 000000000000..541042c598b8 --- /dev/null +++ b/language-server/test/dotty/tools/languageserver/DiagnosticsTest.scala @@ -0,0 +1,41 @@ +package dotty.tools.languageserver + +import org.junit.Test + +import dotty.tools.dotc.reporting.diagnostic.ErrorMessageID._ +import dotty.tools.languageserver.util.Code._ +import org.eclipse.lsp4j.DiagnosticSeverity._ + +class DiagnosticsTest { + @Test def diagnosticWrongType: Unit = + code"""object Test { + | val x: Int = $m1"foo"$m2 + |}""".withSource + .diagnostics(m1, + (m1 to m2, """found: String("foo") + |required: Int""".stripMargin, Error, Some(TypeMismatchID)) + ) + + @Test def diagnosticMissingLambdaBody: Unit = + code"""object Test { + | Nil.map(x => x).filter(x$m1 =>$m2) + |$m3}""".withSource + .diagnostics(m1, + (m2 to m3, "illegal start of simple expression", Error, Some(IllegalStartSimpleExprID)), + (m1 to m1, """found: Null + |required: Boolean""".stripMargin, Error, Some(TypeMismatchID)) + ) + + @Test def diagnosticPureExpression: Unit = + code"""object Test { + | ${m1}1$m2 + |}""".withSource + .diagnostics(m1, + (m1 to m2, + "a pure expression does nothing in statement position; you may be omitting necessary parentheses", + Warning, Some(PureExpressionInStatementPositionID))) + + @Test def diagnosticWorksheetPureExpression: Unit = + ws"""${m1}1""".withSource + .diagnostics(m1 /* no "pure expression" warning because this is a worksheet */) +} diff --git a/language-server/test/dotty/tools/languageserver/HoverTest.scala b/language-server/test/dotty/tools/languageserver/HoverTest.scala index acb003a9a872..2de9d5faafaa 100644 --- a/language-server/test/dotty/tools/languageserver/HoverTest.scala +++ b/language-server/test/dotty/tools/languageserver/HoverTest.scala @@ -153,11 +153,6 @@ class HoverTest { | myFoo.bar[Int, String](0, "hello, world") | ``` | - |**Usecases** - | - ```scala - | def bar(fizz: Int, buzz: String): Any - | ``` - | |**Note** | - A note | diff --git a/language-server/test/dotty/tools/languageserver/SymbolTest.scala b/language-server/test/dotty/tools/languageserver/SymbolTest.scala index e9e6318b8e38..5bb876cd633c 100644 --- a/language-server/test/dotty/tools/languageserver/SymbolTest.scala +++ b/language-server/test/dotty/tools/languageserver/SymbolTest.scala @@ -21,11 +21,11 @@ class SymbolTest { code"class $Bar", code"""package foo |class $fooFoo { - | class Bar + | class ${m7}Bar$m8 |} """ ) .symbol("Foo", Foo.range.symInfo("Foo", SymbolKind.Class), fooFoo.range.symInfo("Foo", SymbolKind.Class, "foo")) - .symbol("Bar", Bar.range.symInfo("Bar", SymbolKind.Class)) + .symbol("Bar", Bar.range.symInfo("Bar", SymbolKind.Class), (m7 to m8).symInfo("Bar", SymbolKind.Class, "Foo")) } @Test def symbolShowModule: Unit = { diff --git a/language-server/test/dotty/tools/languageserver/util/Code.scala b/language-server/test/dotty/tools/languageserver/util/Code.scala index c5a742d21fa5..9d9bc5f7c291 100644 --- a/language-server/test/dotty/tools/languageserver/util/Code.scala +++ b/language-server/test/dotty/tools/languageserver/util/Code.scala @@ -106,7 +106,7 @@ object Code { } if (pi.hasNext) - stringBuilder.append(pi.next()) + stringBuilder.append(pi.next().stripMargin) (stringBuilder.result(), positions.result()) } diff --git a/language-server/test/dotty/tools/languageserver/util/CodeRange.scala b/language-server/test/dotty/tools/languageserver/util/CodeRange.scala index 60a965955f21..1cdcd63dba8b 100644 --- a/language-server/test/dotty/tools/languageserver/util/CodeRange.scala +++ b/language-server/test/dotty/tools/languageserver/util/CodeRange.scala @@ -19,7 +19,7 @@ case class CodeRange(start: CodeMarker, end: CodeMarker) { if (!checked) { assert(start.file == end.file, s"$start and $end where not in the same file") assert(start.line <= end.line, s"Expected $end to be after $start") - assert(start.line != end.line || start.character < end.character, s"Expected $end to be after $start") + assert(start.line != end.line || start.character <= end.character, s"Expected $end to be at or after $start") checked = true } } diff --git a/language-server/test/dotty/tools/languageserver/util/CodeTester.scala b/language-server/test/dotty/tools/languageserver/util/CodeTester.scala index c808ff6b8d05..28036655b51e 100644 --- a/language-server/test/dotty/tools/languageserver/util/CodeTester.scala +++ b/language-server/test/dotty/tools/languageserver/util/CodeTester.scala @@ -5,9 +5,12 @@ import dotty.tools.languageserver.util.actions._ import dotty.tools.languageserver.util.embedded.CodeMarker import dotty.tools.languageserver.util.server.{TestFile, TestServer} +import dotty.tools.dotc.reporting.diagnostic.ErrorMessageID import dotty.tools.dotc.util.Signatures.Signature -import org.eclipse.lsp4j.{CompletionItemKind, DocumentHighlightKind} +import org.eclipse.lsp4j.{ CompletionItemKind, DocumentHighlightKind, Diagnostic, DiagnosticSeverity } + +import org.junit.Assert.assertEquals /** * Simulates an LSP client for test in a project defined by `sources`. @@ -30,6 +33,31 @@ class CodeTester(projects: List[Project]) { private val positions: PositionContext = getPositions(files) + /** Check that the last diagnostics that have been published so far by the server + * for a given file match `expected`. + * + * @param marker The marker defining the source file from which to query. + * @param expected The expected diagnostics to be found + */ + def diagnostics(marker: CodeMarker, + expected: (CodeRange, String, DiagnosticSeverity, Option[ErrorMessageID])*): this.type = { + implicit val posCtx = positions + + def toDiagnostic(range: CodeRange, message: String, severity: DiagnosticSeverity, + errorCode: Option[ErrorMessageID]): Diagnostic = { + new Diagnostic(range.toRange, message, severity, /*source =*/"", + errorCode.map(_.errorNumber.toString).orNull) + } + + val expectedParams = marker.toPublishDiagnosticsParams(expected.toList.map(toDiagnostic)) + // Find the latest published diagnostics for the current source file + val actualParams = testServer.client.diagnostics.get.reverse.find(_.getUri == marker.uri) + .getOrElse(throw new Exception(s"No published diagnostics for ${marker.uri}")) + assertEquals(expectedParams, actualParams) + + this + } + /** * Perform a hover over `range`, verifies that result matches `expected`. * diff --git a/language-server/test/dotty/tools/languageserver/util/embedded/CodeMarker.scala b/language-server/test/dotty/tools/languageserver/util/embedded/CodeMarker.scala index 8c198ec12900..392c40060556 100644 --- a/language-server/test/dotty/tools/languageserver/util/embedded/CodeMarker.scala +++ b/language-server/test/dotty/tools/languageserver/util/embedded/CodeMarker.scala @@ -3,6 +3,8 @@ package dotty.tools.languageserver.util.embedded import dotty.tools.languageserver.util.server.TestFile import dotty.tools.languageserver.util.{CodeRange, PositionContext} +import scala.collection.JavaConverters._ + import org.eclipse.lsp4j._ import PositionContext.PosCtx @@ -16,6 +18,9 @@ class CodeMarker(val name: String) extends Embedded { /** The file containing this marker. */ def file: PosCtx[TestFile] = posCtx.positionOf(this)._1 + /** The uri of the file containing this marker. */ + def uri: PosCtx[String] = file.uri + /** The line containing this marker. */ def line: PosCtx[Int] = posCtx.positionOf(this)._2 @@ -34,6 +39,9 @@ class CodeMarker(val name: String) extends Embedded { def toCompletionParams: PosCtx[CompletionParams] = new CompletionParams(toTextDocumentIdentifier, toPosition) + def toPublishDiagnosticsParams(diagnostics: List[Diagnostic]): PosCtx[PublishDiagnosticsParams] = + new PublishDiagnosticsParams(file.uri, diagnostics.asJava) + def toRenameParams(newName: String): PosCtx[RenameParams] = new RenameParams(toTextDocumentIdentifier, toPosition, newName) diff --git a/project/Build.scala b/project/Build.scala index de79d4d8a79b..bc68566826bd 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -894,7 +894,7 @@ object Build { ). settings( ideTestsCompilerVersion := (version in `dotty-compiler`).value, - ideTestsCompilerArguments := (scalacOptions in `dotty-compiler`).value, + ideTestsCompilerArguments := Seq(), ideTestsDependencyClasspath := { val dottyLib = (classDirectory in `dotty-library-bootstrapped` in Compile).value val scalaLib =