Skip to content

Commit

Permalink
Merge branch 'feature-scala-3-compile-matcher-err-msg' of github.com:…
Browse files Browse the repository at this point in the history
…cheeseng/scalatest into cheeseng-feature-scala-3-compile-matcher-err-msg
  • Loading branch information
bvenners committed Jun 13, 2022
2 parents db0c31e + adc8643 commit d652336
Show file tree
Hide file tree
Showing 5 changed files with 31 additions and 22 deletions.
Expand Up @@ -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 {

Expand Down
Expand Up @@ -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
Expand Down
Expand Up @@ -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
Expand Down
Expand Up @@ -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
}
Expand All @@ -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)
}

}
Expand All @@ -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
Expand All @@ -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)
}

}
Expand All @@ -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)
Expand All @@ -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)
}
}
}
Expand Down
Expand Up @@ -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:
Expand Down

0 comments on commit d652336

Please sign in to comment.