From adc86433fee561a54d19b6813f2b8053a9b60fa2 Mon Sep 17 00:00:00 2001 From: Chua Chee Seng Date: Sat, 7 May 2022 16:40:18 +0800 Subject: [PATCH] Fixed missing original error message in the error message raised by 'should compile' matcher in Scala 3, also made it to use different message for parse and type error. --- .../org/scalatest/matchers/CompileMacro.scala | 26 ++++++++++++++----- .../matchers/must/CompileMacro.scala | 2 +- .../matchers/should/CompileMacro.scala | 2 +- .../org/scalatest/ShouldCompileSpec.scala | 21 ++++++--------- .../scalatest/matchers/should/Matchers.scala | 2 +- 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/dotty/matchers-core/src/main/scala/org/scalatest/matchers/CompileMacro.scala b/dotty/matchers-core/src/main/scala/org/scalatest/matchers/CompileMacro.scala index adfdc9ec39..95a89e17b5 100644 --- a/dotty/matchers-core/src/main/scala/org/scalatest/matchers/CompileMacro.scala +++ b/dotty/matchers-core/src/main/scala/org/scalatest/matchers/CompileMacro.scala @@ -26,16 +26,30 @@ import scala.compiletime.testing.{Error, ErrorKind} object CompileMacro { // check that a code snippet compiles - def assertCompileImpl[T](self: Expr[T], typeChecked: Expr[Boolean], compileWord: Expr[CompileWord], pos: Expr[source.Position])(shouldOrMust: String)(using Quotes): Expr[Assertion] = { + def assertCompileImpl[T](self: Expr[T], typeChecked: Expr[List[Error]], compileWord: Expr[CompileWord], pos: Expr[source.Position])(shouldOrMust: String)(using Quotes): Expr[Assertion] = { import quotes.reflect._ // parse and type check a code snippet, generate code to throw TestFailedException if both parse and type check succeeded - def checkCompile(code: String): Expr[Assertion] = - if (typeChecked.valueOrError) '{ Succeeded } - else '{ - val messageExpr = Resources.expectedNoErrorButGotTypeError("", ${ Expr(code) }) - throw new TestFailedException((_: StackDepthException) => Some(messageExpr), None, $pos) + def checkCompile(code: String): Expr[Assertion] = { + // For some reason `typeChecked.valueOrError` is failing here, so instead we grab + // the varargs argument to List.apply and use that to extract the list of errors + val errors = typeChecked.asTerm.underlyingArgument match { + case Apply(TypeApply(Select(Ident("List"), "apply"), _), List(seq)) => + seq.asExprOf[Seq[Error]].valueOrError.toList + } + + errors match { + case Error(msg, _, _, ErrorKind.Typer) :: _ => '{ + val messageExpr = Resources.expectedNoErrorButGotTypeError(${ Expr(msg) }, ${ Expr(code) }) + throw new TestFailedException((_: StackDepthException) => Some(messageExpr), None, $pos) + } + case Error(msg, _, _, ErrorKind.Parser) :: _ => '{ + val messageExpr = Resources.expectedNoErrorButGotParseError(${ Expr(msg) }, ${ Expr(code) }) + throw new TestFailedException((_: StackDepthException) => Some(messageExpr), None, $pos) + } + case Nil => '{ Succeeded } } + } self.asTerm.underlyingArgument match { diff --git a/dotty/mustmatchers/src/main/scala/org/scalatest/matchers/must/CompileMacro.scala b/dotty/mustmatchers/src/main/scala/org/scalatest/matchers/must/CompileMacro.scala index 39d32e722e..cf2cbc4152 100644 --- a/dotty/mustmatchers/src/main/scala/org/scalatest/matchers/must/CompileMacro.scala +++ b/dotty/mustmatchers/src/main/scala/org/scalatest/matchers/must/CompileMacro.scala @@ -25,7 +25,7 @@ import scala.compiletime.testing.Error object CompileMacro { // used by must compile syntax, delegate to assertCompileImpl to generate code - def mustCompileImpl(code: Expr[String], typeChecked: Expr[Boolean], compileWord: Expr[CompileWord])(pos: Expr[source.Position])(using Quotes): Expr[Assertion] = + def mustCompileImpl(code: Expr[String], typeChecked: Expr[List[Error]], compileWord: Expr[CompileWord])(pos: Expr[source.Position])(using Quotes): Expr[Assertion] = org.scalatest.matchers.CompileMacro.assertCompileImpl(code, typeChecked, compileWord, pos)("must") // used by mustNot compile syntax, delegate to assertNotCompileImpl to generate code diff --git a/dotty/shouldmatchers/src/main/scala/org/scalatest/matchers/should/CompileMacro.scala b/dotty/shouldmatchers/src/main/scala/org/scalatest/matchers/should/CompileMacro.scala index 72d4140938..f11a97878a 100644 --- a/dotty/shouldmatchers/src/main/scala/org/scalatest/matchers/should/CompileMacro.scala +++ b/dotty/shouldmatchers/src/main/scala/org/scalatest/matchers/should/CompileMacro.scala @@ -25,7 +25,7 @@ import scala.compiletime.testing.Error object CompileMacro { // used by should compile syntax, delegate to assertCompileImpl to generate code - def shouldCompileImpl(code: Expr[String], typeChecked: Expr[Boolean], compileWord: Expr[CompileWord])(pos: Expr[source.Position])(using Quotes): Expr[Assertion] = + def shouldCompileImpl(code: Expr[String], typeChecked: Expr[List[Error]], compileWord: Expr[CompileWord])(pos: Expr[source.Position])(using Quotes): Expr[Assertion] = org.scalatest.matchers.CompileMacro.assertCompileImpl(code, typeChecked, compileWord, pos)("should") // used by shouldNot compile syntax, delegate to assertNotCompileImpl to generate code diff --git a/jvm/scalatest-test/src/test/scala/org/scalatest/ShouldCompileSpec.scala b/jvm/scalatest-test/src/test/scala/org/scalatest/ShouldCompileSpec.scala index e7e56d0728..c8109267ac 100644 --- a/jvm/scalatest-test/src/test/scala/org/scalatest/ShouldCompileSpec.scala +++ b/jvm/scalatest-test/src/test/scala/org/scalatest/ShouldCompileSpec.scala @@ -41,15 +41,12 @@ class ShouldCompileSpec extends AnyFunSpec { assert(e.message.get.indexOf("val a: String = 2") >= 0) assert(e.failedCodeFileName === (Some(fileName))) assert(e.failedCodeLineNumber === (Some(thisLineNumber - 6))) + assert(e.message.get.indexOf(if (ScalaTestVersions.BuiltForScalaVersion.startsWith("3.")) "Required: String" else "required: String") >= 0) } it("should throw TestFailedException with correct message and stack depth when parse failed") { - - // SKIP-DOTTY-START val errMsg = Resources.expectedNoErrorButGotParseError("", "") - // SKIP-DOTTY-END - //DOTTY-ONLY val errMsg = Resources.expectedNoErrorButGotTypeError("", "") - + val e = intercept[TestFailedException] { "println(\"test)" should compile } @@ -58,6 +55,7 @@ class ShouldCompileSpec extends AnyFunSpec { assert(e.message.get.indexOf("println(\"test)") >= 0) assert(e.failedCodeFileName === (Some(fileName))) assert(e.failedCodeLineNumber === (Some(thisLineNumber - 6))) + assert(e.message.get.indexOf(if (ScalaTestVersions.BuiltForScalaVersion.startsWith("3.")) "expression expected but erroneous token found" else "unclosed string literal") >= 0) } } @@ -77,14 +75,11 @@ class ShouldCompileSpec extends AnyFunSpec { assert(e.message.get.indexOf("val a: String = 2") >= 0) assert(e.failedCodeFileName === (Some(fileName))) assert(e.failedCodeLineNumber === (Some(thisLineNumber - 6))) + assert(e.message.get.indexOf(if (ScalaTestVersions.BuiltForScalaVersion.startsWith("3.")) "Required: String" else "required: String") >= 0) } it("should throw TestFailedException with correct message and stack depth when parse failed") { - - // SKIP-DOTTY-START val errMsg = Resources.expectedNoErrorButGotParseError("", "") - // SKIP-DOTTY-END - //DOTTY-ONLY val errMsg = Resources.expectedNoErrorButGotTypeError("", "") val e = intercept[TestFailedException] { """println("test)""" should compile @@ -94,6 +89,7 @@ class ShouldCompileSpec extends AnyFunSpec { assert(e.message.get.indexOf("println(\"test)") >= 0) assert(e.failedCodeFileName === (Some(fileName))) assert(e.failedCodeLineNumber === (Some(thisLineNumber - 6))) + assert(e.message.get.indexOf(if (ScalaTestVersions.BuiltForScalaVersion.startsWith("3.")) "expression expected but erroneous token found" else "unclosed string literal") >= 0) } } @@ -117,14 +113,12 @@ class ShouldCompileSpec extends AnyFunSpec { assert(e.message.get.indexOf("val a: String = 2") >= 0) assert(e.failedCodeFileName === (Some(fileName))) assert(e.failedCodeLineNumber === (Some(thisLineNumber - 6))) + assert(e.message.get.indexOf(if (ScalaTestVersions.BuiltForScalaVersion.startsWith("3.")) "Required: String" else "required: String") >= 0) } it("should throw TestFailedException with correct message and stack depth when parse failed") { - // SKIP-DOTTY-START val errMsg = Resources.expectedNoErrorButGotParseError("", "") - // SKIP-DOTTY-END - //DOTTY-ONLY val errMsg = Resources.expectedNoErrorButGotTypeError("", "") - + val e = intercept[TestFailedException] { """ |println("test) @@ -135,6 +129,7 @@ class ShouldCompileSpec extends AnyFunSpec { assert(e.message.get.indexOf("println(\"test)") >= 0) assert(e.failedCodeFileName === (Some(fileName))) assert(e.failedCodeLineNumber === (Some(thisLineNumber - 6))) + assert(e.message.get.indexOf(if (ScalaTestVersions.BuiltForScalaVersion.startsWith("3.")) "')' expected, but eof found" else "unclosed string literal") >= 0) } } } diff --git a/jvm/shouldmatchers/src/main/scala/org/scalatest/matchers/should/Matchers.scala b/jvm/shouldmatchers/src/main/scala/org/scalatest/matchers/should/Matchers.scala index 43207bb86e..4c94b27e22 100644 --- a/jvm/shouldmatchers/src/main/scala/org/scalatest/matchers/should/Matchers.scala +++ b/jvm/shouldmatchers/src/main/scala/org/scalatest/matchers/should/Matchers.scala @@ -7793,7 +7793,7 @@ org.scalatest.exceptions.TestFailedException: org.scalatest.Matchers$ResultOfCol // SKIP-DOTTY-START def should(compileWord: CompileWord)(implicit pos: source.Position): Assertion = macro CompileMacro.shouldCompileImpl // SKIP-DOTTY-END - //DOTTY-ONLY extension (inline leftSideString: String)(using pos: source.Position, prettifier: Prettifier) transparent inline def should(compileWord: CompileWord): Assertion = ${ org.scalatest.matchers.should.CompileMacro.shouldCompileImpl('{leftSideString}, '{typeChecks(leftSideString)}, '{compileWord})('{pos}) } + //DOTTY-ONLY extension (inline leftSideString: String)(using pos: source.Position, prettifier: Prettifier) transparent inline def should(compileWord: CompileWord): Assertion = ${ org.scalatest.matchers.should.CompileMacro.shouldCompileImpl('{leftSideString}, '{typeCheckErrors(leftSideString)}, '{compileWord})('{pos}) } /** * This method enables syntax such as the following: