Skip to content
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

Scala 3: Should Compile Matcher Fix #2125

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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