diff --git a/project/GenScalaTestDotty.scala b/project/GenScalaTestDotty.scala index dc37d5341c..0e62531328 100644 --- a/project/GenScalaTestDotty.scala +++ b/project/GenScalaTestDotty.scala @@ -178,9 +178,10 @@ object GenScalaTestDotty { def genTest(targetDir: File, version: String, scalaVersion: String): Seq[File] = { copyFiles("scalatest-test/src/test/scala/org/scalatest", "org/scalatest", targetDir, List( - "AssertionsSpec.scala", - "ShouldCompileSpec.scala", - "ShouldNotCompileSpec.scala", + "AssertionsSpec.scala", + "ExpectationsSpec.scala", + "ShouldCompileSpec.scala", + "ShouldNotCompileSpec.scala", "ShouldNotTypeCheckSpec.scala" ) ) /*++ diff --git a/project/GenScalacticDotty.scala b/project/GenScalacticDotty.scala index cd4d68cf1b..8ced5dafc5 100644 --- a/project/GenScalacticDotty.scala +++ b/project/GenScalacticDotty.scala @@ -159,7 +159,6 @@ object GenScalacticDotty { "OrSpec.scala", // Compilation error to be investigated further. "PrettifierSpec.scala", // Test failed with java.lang.IllegalAccessException "RequirementsSpec.scala", // Error during macro expansion - "SnapshotsSpec.scala", // Pending macro implementation "TolerantEquivalenceSpec.scala", // Compilation error to be investigated further. "TripleEqualsSpec.for210" // Old staff, we shall delete this soon. )) ++ diff --git a/project/scalatest.scala b/project/scalatest.scala index bd34c6cefe..554ed5e825 100644 --- a/project/scalatest.scala +++ b/project/scalatest.scala @@ -1209,7 +1209,7 @@ object ScalatestBuild { Def.task { GenScalaTestNative.genTest((sourceManaged in Test).value / "scala", version.value, scalaVersion.value) }.taskValue - }, + }, sourceGenerators in Test += Def.task { GenGen.genTestForNative((sourceManaged in Test).value, version.value, scalaVersion.value) @@ -2133,8 +2133,8 @@ object ScalatestBuild { import dotty.tools.sbtplugin.DottyPlugin.autoImport._ // List of available night build at https://repo1.maven.org/maven2/ch/epfl/lamp/dotty-compiler_0.14/ - //lazy val dottyVersion = dottyLatestNightlyBuild.get - lazy val dottyVersion = "0.14.0-bin-20190403-d00a7ba-NIGHTLY" + lazy val dottyVersion = dottyLatestNightlyBuild.get + // lazy val dottyVersion = "0.14.0-bin-20190403-d00a7ba-NIGHTLY" lazy val dottySettings = List( scalaVersion := dottyVersion, libraryDependencies := libraryDependencies.value.map(_.withDottyCompat(scalaVersion.value)), diff --git a/scalactic.dotty/src/main/scala/org/scalactic/BooleanMacro.scala b/scalactic.dotty/src/main/scala/org/scalactic/BooleanMacro.scala index 52d89191f6..240d705f2b 100644 --- a/scalactic.dotty/src/main/scala/org/scalactic/BooleanMacro.scala +++ b/scalactic.dotty/src/main/scala/org/scalactic/BooleanMacro.scala @@ -21,10 +21,12 @@ import scala.tasty._ object BooleanMacro { def parse(condition: Expr[Boolean], prettifier: Expr[Prettifier])(implicit refl: Reflection): Expr[Bool] = { import refl._ - implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(this.getClass.getClassLoader) import util._ - def exprStr: String = condition.show + // TODO: remove once `Expr[T].show` handles color correctly + def (str: String) clean: String = str.replaceAll("\u001B\\[[;\\d]*m", "") + + def exprStr: String = condition.show.clean def defaultCase = '{ Bool.simpleMacroBool($condition, ${exprStr.toExpr}, $prettifier) } def isImplicitMethodType(tp: Type): Boolean = Type.IsMethodType.unapply(tp).flatMap(tp => if tp.isImplicit then Some(true) else None).nonEmpty diff --git a/scalactic.dotty/src/main/scala/org/scalactic/Requirements.scala b/scalactic.dotty/src/main/scala/org/scalactic/Requirements.scala index 43d366f08c..267e4f6118 100644 --- a/scalactic.dotty/src/main/scala/org/scalactic/Requirements.scala +++ b/scalactic.dotty/src/main/scala/org/scalactic/Requirements.scala @@ -233,7 +233,7 @@ object RequirementsMacro { */ def require(condition: Expr[Boolean], prettifier: Expr[Prettifier], clue: Expr[Any])(implicit refl: Reflection): Expr[Unit] = { import refl._ - + val bool = BooleanMacro.parse(condition, prettifier) '{ Requirements.requirementsHelper.macroRequire($bool, $clue) } } @@ -247,7 +247,7 @@ object RequirementsMacro { */ def requireState(condition: Expr[Boolean], prettifier: Expr[Prettifier], clue: Expr[Any])(implicit refl: Reflection): Expr[Unit] = { import refl._ - + val bool = BooleanMacro.parse(condition, prettifier) '{ Requirements.requirementsHelper.macroRequireState($bool, $clue) } } @@ -261,7 +261,9 @@ object RequirementsMacro { */ def requireNonNull(arguments: Expr[Seq[Any]], prettifier: Expr[Prettifier], pos: Expr[source.Position])(implicit reflect: Reflection): Expr[Unit] = { import reflect._ - implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(this.getClass.getClassLoader) + + // TODO: remove once `Expr[T].show` handles color correctly + def (str: String) clean: String = str.replaceAll("\u001B\\[[;\\d]*m", "") def liftSeq(args: Seq[Expr[String]]): Expr[Seq[String]] = args match { case x :: xs => '{ ($x) +: ${ liftSeq(xs) } } @@ -270,7 +272,7 @@ object RequirementsMacro { val argStr: List[Expr[String]] = arguments.unseal.underlyingArgument match { case Typed(Repeated(args, _), _) => // only sequence literal - args.map(arg => arg.seal.cast[Any].show.toExpr) + args.map(arg => arg.seal.cast[Any].show.clean.toExpr) case _ => throw QuoteError("requireNonNull can only be used with sequence literal, not `seq : _*`") } diff --git a/scalactic.dotty/src/main/scala/org/scalactic/Snapshots.scala b/scalactic.dotty/src/main/scala/org/scalactic/Snapshots.scala new file mode 100644 index 0000000000..c5f6bce81f --- /dev/null +++ b/scalactic.dotty/src/main/scala/org/scalactic/Snapshots.scala @@ -0,0 +1,247 @@ +/* + * Copyright 2001-2014 Artima, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.scalactic + +import scala.quoted._ +import scala.tasty._ + +/** + * Case class that stores the name and value of a variable or expression. + * + *
+ * See the main documentation for trait Snapshots
for more information and examples.
+ *
toString
to print in {name} = {value} format.
+ *
+ * @return string in {name} = {value} format
+ */
+ override def toString: String = Resources.variableWasValue(name, Prettifier.default(value))
+}
+
+/**
+ * Trait that provides a snap
method that takes one or more arguments and results in a
+ * SnapshotSeq
, whose toString
lists the names
+ * and values of each argument.
+ *
+ *
+ * The intended use case of this trait is to help you write debug and log + * messages that give a "snapshot" of program state. Here's an example: + *
+ * + *+ * scala> import Snapshots._ + * import Snapshots._ + * + * scala> snap(a, b, c, d, e, f) + * res3: org.scalactic.SnapshotSeq = a was 1, b was 2, c was 3, d was 4, e was null, f was null + *+ * + *
SnapshotSeq
offers a lines
method that places each variable name/value pair on its own line:
+ * + *
+ * scala> snap(a, b, c, d, e, f).lines + * res4: String = + * a was 1 + * b was 2 + * c was 3 + * d was 4 + * e was null + * f was null + *+ * + *
+ * Or, because a SnapshotSeq
is a IndexedSeq[Snapshot]
, you can process it just like any other Seq
, for example:
+ *
+ * scala> snap(a, b, c, d, e, f).mkString("Wow! ", ", and ", ". That's so awesome!") + * res6: String = Wow! a was 1, and b was 2, and c was 3, and d was 4, and e was null, and f was null. That's so awesome! + *+ */ +trait Snapshots { + + /** + * Snap the given expressions. + * + * @param expressions expressions to be snapped + * @return an
IndexedSeq
of Snapshot
for the given expressions.
+ */
+ inline def snap(expressions: Any*): SnapshotSeq = ${ SnapshotsMacro.snap('expressions) }
+}
+
+/**
+ * An IndexedSeq[Snapshot]
providing toString
and lines
methods that
+ * can be useful for debug and log messages about program state.
+ *
+ *
+ * See the main documentation for trait Snapshots
for more information and examples.
+ *
+ * This method invokes apply
on the underlying immutable IndexedSeq[String]
, passing in idx
, and returns the result.
+ *
idx
, where 0 indicates the first element
+ */
+ def apply(idx: Int): Snapshot = underlying.apply(idx)
+
+ /**
+ * The length of this sequence.
+ *
+ *
+ * This method invokes length
on the underlying immutable IndexedSeq[String]
and returns the result.
+ *
+ * If the string element already exists in this sequence, this method returns itself. If not,
+ * this method returns a new MultiSelOptionSeq
with the passed value appended to the
+ * end of the original MultiSelOptionSeq
.
+ *
MultiSelOptionSeq
that contains the passed string value
+ */
+ def +(value: Snapshot): SnapshotSeq = {
+ if (!underlying.contains(value))
+ new SnapshotSeq(underlying :+ value)
+ else
+ this
+ }
+
+ /**
+ * Removes a string element to this sequence, if it already exists in the sequence.
+ *
+ *
+ * If the string element does not already exist in this sequence, this method returns itself. If the element
+ * is contained in this sequence, this method returns a new MultiSelOptionSeq
with the passed value
+ * removed from the the original MultiSelOptionSeq
, leaving any other elements in the same order.
+ *
MultiSelOptionSeq
that contains the passed string value
+ */
+ def -(value: Snapshot): SnapshotSeq = {
+ if (underlying.contains(value))
+ new SnapshotSeq(underlying.filter(_ != value))
+ else
+ this
+ }
+
+ /**
+ * The default way to present the result of the snap
method of trait Snapshots.
+ *
+ * Here's an example:
+ *
+ * + * scala> snap(a, b, c, d, e, f) + * res3: org.scalactic.SnapshotSeq = a was 1, b was 2, c was 3, d was 4, e was null, f was null + *+ */ + override def toString: String = mkString(", ") + + /** + * An alternate way to present the result of the
snap
method of trait Snapshots that
+ * puts each variable or expression on its own line.
+ *
+ * Here's an example:
+ *
+ * + * scala> snap(a, b, c, d, e, f).lines + * res4: String = + * a was 1 + * b was 2 + * c was 3 + * d was 4 + * e was null + * f was null + *+ */ + def lines: String = mkString("\n") +} + +object SnapshotSeq { + def apply(snapshots: Snapshot*): SnapshotSeq = new SnapshotSeq(Vector(snapshots: _*)) +} + +/** + * Companion object that facilitates the importing of
Snapshots
members as
+ * an alternative to mixing it in. One use case is to import Snapshots
members so you can use
+ * them in the Scala interpreter:
+ *
+ * + * $scala -classpath scalatest.jar + * Welcome to Scala version 2.10.3.final (Java HotSpot(TM) Client VM, Java xxxxxx). + * Type in expressions to have them evaluated. + * Type :help for more information. + * + * scala> import org.scalactic.Snapshots._ + * import org.scalatest.Snapshots._ + * + * scala> val a = 8 + * a: Int = 8 + * + * scala> snap(a) + * res0: scala.collection.immutable.Vector[org.scalactic.Snapshot] = Vector(a = 8) + *+ */ +object Snapshots extends Snapshots + +object SnapshotsMacro { + + def snap(expressions: Expr[Seq[Any]])(implicit refl: Reflection): Expr[SnapshotSeq] = { + import refl._ + // TODO: remove once `Expr[T].show` handles color correctly + def (str: String) clean: String = str.replaceAll("\u001B\\[[;\\d]*m", "") + + def liftSeq(args: Seq[Expr[Snapshot]]): Expr[Seq[Snapshot]] = args match { + case x :: xs => '{ ($x) +: ${ liftSeq(xs) } } + case Nil => '{ Seq(): Seq[Snapshot] } + } + + val snapshots: List[Expr[Snapshot]] = expressions.unseal.underlyingArgument match { + case Typed(Repeated(args, _), _) => // only sequence literal + args.map { arg => + val str = arg.seal.cast[Any].show.clean.toExpr + '{ Snapshot($str, ${ arg.seal.cast[Any] }) } + } + case arg => + throw QuoteError("snap can only be used with sequence literal, not `seq : _*`") + } + + val argumentsS: Expr[Seq[Snapshot]] = liftSeq(snapshots) + '{ SnapshotSeq($argumentsS : _*) } + } +} diff --git a/scalactic.dotty/src/main/scala/org/scalactic/source/TypeInfoMacro.scala b/scalactic.dotty/src/main/scala/org/scalactic/source/TypeInfoMacro.scala index 7db3134c38..56466747e5 100644 --- a/scalactic.dotty/src/main/scala/org/scalactic/source/TypeInfoMacro.scala +++ b/scalactic.dotty/src/main/scala/org/scalactic/source/TypeInfoMacro.scala @@ -29,7 +29,10 @@ object TypeInfoMacro { def genTypeInfo[T](tp: Type[T])(implicit refl: Reflection): Expr[TypeInfo[T]] = { import refl._ - val name = typeOf(tp).show.toExpr + // TODO: remove once `Expr[T].show` handles color correctly + def (str: String) clean: String = str.replaceAll("\u001B\\[[;\\d]*m", "") + + val name = tp.show.clean.toExpr '{ TypeInfo[$tp]($name) } } } \ No newline at end of file diff --git a/scalatest-test/src/test/scala/org/scalatest/ExpectationsSpec.scala b/scalatest-test/src/test/scala/org/scalatest/ExpectationsSpec.scala index f4b338bbb6..67f96d2183 100644 --- a/scalatest-test/src/test/scala/org/scalatest/ExpectationsSpec.scala +++ b/scalatest-test/src/test/scala/org/scalatest/ExpectationsSpec.scala @@ -25,7 +25,7 @@ class ExpectationsSpec extends FunSpec with Expectations { describe("The expectResult method") { it("should give a correct Fact result when the expectation fails") { - val fact = expectResult(3) { 2 } + val fact = expectResult(3) { 2 } assert(fact.isNo) assert(fact.factMessage == "Expected 3, but got 2") assert(fact.simplifiedFactMessage == "3 did not equal 2") @@ -43,7 +43,7 @@ class ExpectationsSpec extends FunSpec with Expectations { assert(!fact.isVacuousYes) } it("should give a correct Fact result when the expectation succeeds") { - val fact = expectResult(3) { 3 } + val fact = expectResult(3) { 3 } assert(fact.isYes) assert(fact.factMessage == "Expected 3, and got 3") assert(fact.simplifiedFactMessage == "3 equaled 3") @@ -118,11 +118,11 @@ class ExpectationsSpec extends FunSpec with Expectations { assert(fact2.isNo) assert(fact2.factMessage == "3 equaled 3, but 4 equaled 4") assert(fact2.toString == - "No(" + NEWLINE + - " Yes(expected 3, and got 3) &&" + NEWLINE + - " No(" + NEWLINE + - " !Yes(expected 4, and got 4)" + NEWLINE + - " )" + NEWLINE + + "No(" + NEWLINE + + " Yes(expected 3, and got 3) &&" + NEWLINE + + " No(" + NEWLINE + + " !Yes(expected 4, and got 4)" + NEWLINE + + " )" + NEWLINE + ")" ) assert(!fact2.isVacuousYes) @@ -206,7 +206,7 @@ class ExpectationsSpec extends FunSpec with Expectations { " !Yes(expected 4, and got 4)" + NEWLINE + " )" + NEWLINE + " ) ||" + NEWLINE + - " No(expected 5, but got 6)" + NEWLINE + + " No(expected 5, but got 6)" + NEWLINE + ")" ) assert(fact.toString == @@ -324,6 +324,7 @@ class ExpectationsSpec extends FunSpec with Expectations { } } + // SKIP-DOTTY-START describe("The expectThrows method") { it("should catch subtypes") { class MyException extends RuntimeException @@ -368,6 +369,7 @@ class ExpectationsSpec extends FunSpec with Expectations { } } } + // SKIP-DOTTY-END describe("The expect method") { @@ -525,6 +527,7 @@ class ExpectationsSpec extends FunSpec with Expectations { assert(!fact.isVacuousYes) } + // SKIP-DOTTY-START it("should return No with correct fact message when type check failed") { val fact = expectCompiles("val a: String = 2") assert(fact.isInstanceOf[Fact.Leaf]) @@ -545,6 +548,7 @@ class ExpectationsSpec extends FunSpec with Expectations { assert(!fact.isVacuousYes) } + // SKIP-DOTTY-END it("should return Yes with correct fact message when the code compiles with implicit view in scope") { import scala.collection.JavaConverters._ @@ -581,6 +585,7 @@ class ExpectationsSpec extends FunSpec with Expectations { assert(!fact.isVacuousYes) } + // SKIP-DOTTY-START it("should return No with correct fact message when type check failed") { val fact = expectCompiles( @@ -620,6 +625,7 @@ class ExpectationsSpec extends FunSpec with Expectations { )) assert(!fact.isVacuousYes) } + // SKIP-DOTTY-END it("should return Yes with correct fact message when the code compiles with implicit view in scope") { import scala.collection.JavaConverters._ @@ -663,6 +669,7 @@ class ExpectationsSpec extends FunSpec with Expectations { assert(!fact.isVacuousYes) } + // SKIP-DOTTY-START it("should return No with correct fact message when parse failed ") { val fact = expectTypeError("println(\"test)") assert(fact.isInstanceOf[Fact.Leaf]) @@ -673,6 +680,7 @@ class ExpectationsSpec extends FunSpec with Expectations { assert(fact.factMessage == Resources.expectedTypeErrorButGotParseError("unclosed string literal", "println(\"test)")) assert(!fact.isVacuousYes) } + // SKIP-DOTTY-END it("should return Yes with correct fact message when used with 'val i: Int = null'") { val fact = expectTypeError("val i: Int = null") @@ -735,6 +743,7 @@ class ExpectationsSpec extends FunSpec with Expectations { assert(!fact.isVacuousYes) } + // SKIP-DOTTY-START it("should return No with correct fact message when parse failed ") { val fact = expectTypeError( @@ -756,6 +765,7 @@ class ExpectationsSpec extends FunSpec with Expectations { )) assert(!fact.isVacuousYes) } + // SKIP-DOTTY-END it("should return Yes with correct fact message when used with 'val i: Int = null'") { val fact = diff --git a/scalatest.dotty/src/main/scala/org/scalatest/CompileMacro.scala b/scalatest.dotty/src/main/scala/org/scalatest/CompileMacro.scala index 302899e201..b289e08cd0 100644 --- a/scalatest.dotty/src/main/scala/org/scalatest/CompileMacro.scala +++ b/scalatest.dotty/src/main/scala/org/scalatest/CompileMacro.scala @@ -27,7 +27,7 @@ object CompileMacro { // parse and type check a code snippet, generate code to throw TestFailedException when type check passes or parse error def assertTypeErrorImpl(code: String, pos: Expr[source.Position])(implicit refl: Reflection): Expr[Assertion] = { import refl._ - + if (!typing.typeChecks(code)) '{ Succeeded } else '{ val messageExpr = Resources.expectedCompileErrorButGotNone(${ code.toExpr }) @@ -54,7 +54,7 @@ object CompileMacro { } else '{ - val messageExpr = Resources.expectedTypeErrorButGotNone(${ code.toExpr }) + val messageExpr = Resources.gotTypeErrorAsExpected(${ code.toExpr }) Fact.Yes( messageExpr, @@ -86,7 +86,7 @@ object CompileMacro { if (typing.typeChecks(code)) '{ - val messageExpr = Resources.expectedTypeErrorButGotNone(${ code.toExpr }) + val messageExpr = Resources.expectedCompileErrorButGotNone(${ code.toExpr }) Fact.No( messageExpr, messageExpr, @@ -100,7 +100,7 @@ object CompileMacro { } else '{ - val messageExpr = Resources.expectedTypeErrorButGotNone(${ code.toExpr }) + val messageExpr = Resources.didNotCompile(${ code.toExpr }) Fact.Yes( messageExpr, @@ -131,7 +131,7 @@ object CompileMacro { if (typing.typeChecks(code)) '{ - val messageExpr = Resources.expectedTypeErrorButGotNone(${ code.toExpr }) + val messageExpr = Resources.compiledSuccessfully(${ code.toExpr }) Fact.Yes( messageExpr, messageExpr, @@ -145,7 +145,7 @@ object CompileMacro { } else '{ - val messageExpr = Resources.expectedTypeErrorButGotNone(${ code.toExpr }) + val messageExpr = Resources.expectedNoErrorButGotTypeError("", ${ code.toExpr }) Fact.No( messageExpr, @@ -174,7 +174,7 @@ object CompileMacro { } self.unseal.underlyingArgument match { - + case Apply( Apply( Select(_, shouldOrMustTerconvertToStringShouldOrMustWrapperTermName), @@ -196,7 +196,7 @@ object CompileMacro { ), _ ) if shouldOrMustTerconvertToStringShouldOrMustWrapperTermName == "convertToString" + shouldOrMust.capitalize + "Wrapper" => - checkNotCompile(code.toString) + checkNotCompile(code.toString) case other => throw QuoteError("The '" + shouldOrMust + " compile' syntax only works with String literals.") @@ -209,7 +209,7 @@ object CompileMacro { // used by mustNot compile syntax, delegate to assertNotCompileImpl to generate code def mustNotCompileImpl(self: Expr[MustMatchers#AnyMustWrapper[_]], compileWord: Expr[CompileWord])(pos: Expr[source.Position])(implicit refl: Reflection): Expr[Assertion] = - assertNotCompileImpl(self, compileWord, pos)("must") + assertNotCompileImpl(self, compileWord, pos)("must") // check that a code snippet does not compile def assertNotTypeCheckImpl(self: Expr[Matchers#AnyShouldWrapper[_]], typeCheckWord: Expr[TypeCheckWord], pos: Expr[source.Position])(shouldOrMust: String)(implicit refl: Reflection): Expr[Assertion] = { @@ -249,7 +249,7 @@ object CompileMacro { _ ) if shouldOrMustTerconvertToStringShouldOrMustWrapperTermName == "convertToString" + shouldOrMust.capitalize + "Wrapper" => // LHS is a normal string literal, call checkNotTypeCheck with the extracted code string to generate code - checkNotTypeCheck(code.toString) + checkNotTypeCheck(code.toString) case _ => throw QuoteError("The '" + shouldOrMust + "Not typeCheck' syntax only works with String literals.") @@ -301,7 +301,7 @@ object CompileMacro { _ ) if shouldOrMustTerconvertToStringShouldOrMustWrapperTermName == "convertToString" + shouldOrMust.capitalize + "Wrapper" => // LHS is a normal string literal, call checkCompile with the extracted code string to generate code - checkCompile(code.toString) + checkCompile(code.toString) case other => println("###other: " + other) diff --git a/scalatest.dotty/src/main/scala/org/scalatest/DiagrammedAssertionsMacro.scala b/scalatest.dotty/src/main/scala/org/scalatest/DiagrammedAssertionsMacro.scala index 75f9291243..d9caeee3f0 100644 --- a/scalatest.dotty/src/main/scala/org/scalatest/DiagrammedAssertionsMacro.scala +++ b/scalatest.dotty/src/main/scala/org/scalatest/DiagrammedAssertionsMacro.scala @@ -31,7 +31,6 @@ private[scalatest] object DiagrammedAssertionsMacro { prettifier: Expr[Prettifier], pos: Expr[source.Position])(implicit refl: Reflection): Expr[Assertion] = { import refl._ - implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(this.getClass.getClassLoader) val startLine = refl.rootPosition.startLine // Get the expression first line number val endLine = refl.rootPosition.endLine // Get the expression last line number diff --git a/scalatest.dotty/src/main/scala/org/scalatest/Expectations.scala b/scalatest.dotty/src/main/scala/org/scalatest/Expectations.scala index 8b7023246a..9f6f28aa24 100644 --- a/scalatest.dotty/src/main/scala/org/scalatest/Expectations.scala +++ b/scalatest.dotty/src/main/scala/org/scalatest/Expectations.scala @@ -21,8 +21,14 @@ import scala.concurrent.ExecutionContext import scala.concurrent.Future import scala.reflect.ClassTag +import scala.tasty._ +import scala.quoted._ + private[scalatest] trait Expectations { + inline def (inline x: String) stripMargin <: String = + ${ Expectations.stripMarginImpl(x) } + // TODO: Need to make this and assertResult use custom equality I think. def expectResult(expected: Any)(actual: Any)(implicit prettifier: Prettifier, pos: source.Position): Fact = { if (!DefaultEquality.areEqualComparingArraysStructurally(actual, expected)) { @@ -130,6 +136,8 @@ private[scalatest] trait Expectations { } object Expectations extends Expectations { + def stripMarginImpl(x: String)(implicit refl: Reflection): Expr[String] = + new scala.collection.immutable.StringOps(x).stripMargin.toExpr class ExpectationsHelper { diff --git a/scalatest.dotty/src/main/scala/org/scalatest/ExpectationsMacro.scala b/scalatest.dotty/src/main/scala/org/scalatest/ExpectationsMacro.scala index e9ef9dc02f..0182982174 100644 --- a/scalatest.dotty/src/main/scala/org/scalatest/ExpectationsMacro.scala +++ b/scalatest.dotty/src/main/scala/org/scalatest/ExpectationsMacro.scala @@ -22,7 +22,7 @@ import scala.tasty._ /** * Macro implementation that provides rich error message for boolean expression assertion. */ -private[scalatest] object ExpectationsMacro { +object ExpectationsMacro { def expect(condition: Expr[Boolean])(prettifier: Expr[Prettifier], pos: Expr[source.Position])(implicit refl: Reflection): Expr[Fact] = { import refl._