From 275716804f2afbc38318d2a9381bc93669ff832b Mon Sep 17 00:00:00 2001 From: zainab-ali Date: Fri, 11 Jul 2025 15:49:23 +0100 Subject: [PATCH] Propagate source locations in expect macro. --- .../src/main/scala-2/weaver/ExpectMacro.scala | 30 ++++++++-------- .../src/main/scala-3/weaver/ExpectMacro.scala | 36 ++++++++++--------- .../shared/src/test/scala/DogFoodTests.scala | 18 +++++----- .../shared/src/test/scala/TracingTests.scala | 2 +- 4 files changed, 45 insertions(+), 41 deletions(-) diff --git a/modules/core/shared/src/main/scala-2/weaver/ExpectMacro.scala b/modules/core/shared/src/main/scala-2/weaver/ExpectMacro.scala index be4e8b1f..e637f6d8 100644 --- a/modules/core/shared/src/main/scala-2/weaver/ExpectMacro.scala +++ b/modules/core/shared/src/main/scala-2/weaver/ExpectMacro.scala @@ -10,14 +10,16 @@ private[weaver] trait ExpectMacro { * * Use the [[Expectations.Helpers.clue]] function to investigate any failures. */ - def apply(value: Boolean): Expectations = macro ExpectMacro.applyImpl + def apply(value: Boolean)(implicit loc: SourceLocation): Expectations = + macro ExpectMacro.applyImpl /** * Asserts that a boolean value is true and displays a failure message if not. * * Use the [[Expectations.Helpers.clue]] function to investigate any failures. */ - def apply(value: Boolean, message: => String): Expectations = + def apply(value: Boolean, message: => String)(implicit + loc: SourceLocation): Expectations = macro ExpectMacro.messageImpl /** @@ -25,7 +27,8 @@ private[weaver] trait ExpectMacro { * * Use the [[Expectations.Helpers.clue]] function to investigate any failures. */ - def all(values: Boolean*): Expectations = macro ExpectMacro.allImpl + def all(values: Boolean*)(implicit loc: SourceLocation): Expectations = + macro ExpectMacro.allImpl } private[weaver] object ExpectMacro { @@ -36,9 +39,9 @@ private[weaver] object ExpectMacro { * If any value evaluates to false, all generated clues are displayed as part * of the failed expectation. */ - def allImpl(c: blackbox.Context)(values: c.Tree*): c.Tree = { + def allImpl(c: blackbox.Context)(values: c.Tree*)( + loc: c.Tree): c.Tree = { import c.universe._ - val sourceLoc = new weaver.macros.Macros(c).fromContext.asInstanceOf[c.Tree] val clueMethodSymbol = getClueMethodSymbol(c) val allExpectations = values.toList.map { value => val (cluesName, cluesValDef) = makeClues(c) @@ -50,7 +53,7 @@ private[weaver] object ExpectMacro { makeExpectations(c)(cluesName = cluesName, cluesValDef = cluesValDef, value = transformedValue, - sourceLoc = sourceLoc, + loc = loc, sourceCode = sourceCode, message = q"None") } @@ -65,9 +68,8 @@ private[weaver] object ExpectMacro { */ def messageImpl(c: blackbox.Context)( value: c.Tree, - message: c.Tree): c.Tree = { + message: c.Tree)(loc: c.Tree): c.Tree = { import c.universe._ - val sourceLoc = new weaver.macros.Macros(c).fromContext.asInstanceOf[c.Tree] val sourcePos = c.enclosingPosition val sourceCode = new String(sourcePos.source.content.slice(sourcePos.start, sourcePos.end)) @@ -79,7 +81,7 @@ private[weaver] object ExpectMacro { makeExpectations(c)(cluesName = cluesName, cluesValDef = cluesValDef, value = transformedValue, - sourceLoc = sourceLoc, + loc = loc, sourceCode = sourceCode, message = q"Some($message)") } @@ -97,10 +99,10 @@ private[weaver] object ExpectMacro { * After the value is evaluated, the [[Clues]] collection is used to contruct * [[Expectations]]. */ - def applyImpl(c: blackbox.Context)(value: c.Tree): c.Tree = { + def applyImpl(c: blackbox.Context)(value: c.Tree)( + loc: c.Tree): c.Tree = { import c.universe._ - val sourceLoc = new weaver.macros.Macros(c).fromContext.asInstanceOf[c.Tree] val sourcePos = c.enclosingPosition val sourceCode = new String(sourcePos.source.content.slice(sourcePos.start, sourcePos.end)) @@ -113,7 +115,7 @@ private[weaver] object ExpectMacro { makeExpectations(c)(cluesName = cluesName, cluesValDef = cluesValDef, value = transformedValue, - sourceLoc = sourceLoc, + loc = loc, sourceCode = sourceCode, message = q"None") } @@ -123,12 +125,12 @@ private[weaver] object ExpectMacro { cluesName: c.TermName, cluesValDef: c.Tree, value: c.Tree, - sourceLoc: c.Tree, + loc: c.Tree, sourceCode: String, message: c.Tree): c.Tree = { import c.universe._ val block = - q"$cluesValDef; _root_.weaver.internals.Clues.toExpectations($sourceLoc, Some($sourceCode), $message, $cluesName, $value)" + q"$cluesValDef; _root_.weaver.internals.Clues.toExpectations($loc, Some($sourceCode), $message, $cluesName, $value)" val untyped = c.untypecheck(block) val retyped = c.typecheck(untyped, pt = c.typeOf[Expectations]) retyped diff --git a/modules/core/shared/src/main/scala-3/weaver/ExpectMacro.scala b/modules/core/shared/src/main/scala-3/weaver/ExpectMacro.scala index d7228af7..e651c4e2 100644 --- a/modules/core/shared/src/main/scala-3/weaver/ExpectMacro.scala +++ b/modules/core/shared/src/main/scala-3/weaver/ExpectMacro.scala @@ -13,8 +13,9 @@ private[weaver] trait ExpectMacro { * * Use the [[Expectations.Helpers.clue]] function to investigate any failures. */ - inline def apply(assertion: Clues ?=> Boolean): Expectations = - ${ ExpectMacro.applyImpl('assertion) } + inline def apply(assertion: Clues ?=> Boolean)(using + loc: SourceLocation): Expectations = + ${ ExpectMacro.applyImpl('assertion, 'loc) } /** * Asserts that a boolean value is true and displays a failure message if @@ -25,16 +26,17 @@ private[weaver] trait ExpectMacro { */ inline def apply( assertion: Clues ?=> Boolean, - message: => String): Expectations = - ${ ExpectMacro.applyMessageImpl('assertion, 'message) } + message: => String)(using loc: SourceLocation): Expectations = + ${ ExpectMacro.applyMessageImpl('assertion, 'message, 'loc) } /** * Asserts that boolean values are all true. * * Use the [[Expectations.Helpers.clue]] function to investigate any failures. */ - inline def all(inline assertions: (Clues ?=> Boolean)*): Expectations = - ${ ExpectMacro.allImpl('assertions) } + inline def all(inline assertions: (Clues ?=> Boolean)*)(using + loc: SourceLocation): Expectations = + ${ ExpectMacro.allImpl('assertions, 'loc) } } private[weaver] object ExpectMacro { @@ -49,10 +51,10 @@ private[weaver] object ExpectMacro { * After the assertion is evaluated, the [[Clues]] collection is used to * contruct [[Expectations]]. */ - def applyImpl[T: Type](assertion: Expr[Clues ?=> Boolean])(using - q: Quotes): Expr[Expectations] = { + def applyImpl[T: Type]( + assertion: Expr[Clues ?=> Boolean], + loc: Expr[SourceLocation])(using q: Quotes): Expr[Expectations] = { import q.reflect.* - val sourceLoc = weaver.macros.fromContextImpl(using q) // The compiler doesn't return the correct position information // for `assertion.asTerm.pos`. Use the position of the entire // expect statement instead. @@ -60,7 +62,7 @@ private[weaver] object ExpectMacro { '{ val clues = new Clues val result = ${ assertion }(using clues) - Clues.toExpectations($sourceLoc, + Clues.toExpectations($loc, sourceCode = $sourceCode, message = None, clues, @@ -76,14 +78,14 @@ private[weaver] object ExpectMacro { */ def applyMessageImpl[T: Type]( assertion: Expr[Clues ?=> Boolean], - message: => Expr[String])(using q: Quotes): Expr[Expectations] = { + message: => Expr[String], + loc: Expr[SourceLocation])(using q: Quotes): Expr[Expectations] = { import q.reflect.* - val sourceLoc = weaver.macros.fromContextImpl(using q) val sourceCode = Expr(Position.ofMacroExpansion.sourceCode) '{ val clues = new Clues val result = ${ assertion }(using clues) - Clues.toExpectations($sourceLoc, + Clues.toExpectations($loc, sourceCode = $sourceCode, message = Some($message), clues, @@ -97,10 +99,10 @@ private[weaver] object ExpectMacro { * If any assertion evaluates to false, all generated clues are displayed as * part of the failed expectation. */ - def allImpl[T: Type](assertions: Expr[Seq[(Clues ?=> Boolean)]])(using - q: Quotes): Expr[Expectations] = { + def allImpl[T: Type]( + assertions: Expr[Seq[(Clues ?=> Boolean)]], + loc: Expr[SourceLocation])(using q: Quotes): Expr[Expectations] = { import q.reflect.* - val sourceLoc = weaver.macros.fromContextImpl(using q) val sourceCodes: Expr[List[Option[String]]] = Expr(assertions match { case Varargs(exprs) => exprs.toList.map(_.asTerm.pos.sourceCode) case _ => Nil @@ -111,7 +113,7 @@ private[weaver] object ExpectMacro { val clues = new Clues val result = assertion(using clues) val sourceCode = ${ sourceCodes }.get(index).flatten - Clues.toExpectations($sourceLoc, + Clues.toExpectations($loc, sourceCode = sourceCode, message = None, clues, diff --git a/modules/framework-cats/shared/src/test/scala/DogFoodTests.scala b/modules/framework-cats/shared/src/test/scala/DogFoodTests.scala index 207bd3b1..89484754 100644 --- a/modules/framework-cats/shared/src/test/scala/DogFoodTests.scala +++ b/modules/framework-cats/shared/src/test/scala/DogFoodTests.scala @@ -181,7 +181,7 @@ object DogFoodTests extends IOSuite { | of | multiline | (failure) - | assertion failed (modules/framework-cats/shared/src/test/scala/Meta.scala:34) + | assertion failed (src/main/DogFoodTests.scala:5) | | expect(clue(x) == y) | @@ -317,7 +317,7 @@ object DogFoodTests extends IOSuite { val expected = s""" |- (failure) 0ms - | assertion failed (modules/framework-cats/shared/src/test/scala/Meta.scala:83) + | assertion failed (src/main/DogFoodTests.scala:5) | | expect(clue(x) == clue(y)) | @@ -339,7 +339,7 @@ object DogFoodTests extends IOSuite { val expected = s""" |- (nested) 0ms - | assertion failed (modules/framework-cats/shared/src/test/scala/Meta.scala:89) + | assertion failed (src/main/DogFoodTests.scala:5) | | expect(clue(List(clue(x), clue(y))) == List(x, x)) | @@ -363,7 +363,7 @@ object DogFoodTests extends IOSuite { val expected = s""" |- (map) 0ms - | assertion failed (modules/framework-cats/shared/src/test/scala/Meta.scala:95) + | assertion failed (src/main/DogFoodTests.scala:5) | | expect(List(x, y).map(v => clue(v)) == List(x, x)) | @@ -384,7 +384,7 @@ object DogFoodTests extends IOSuite { val expected = s""" |- (all) 0ms - | [0] assertion failed (modules/framework-cats/shared/src/test/scala/Meta.scala:102) + | [0] assertion failed (src/main/DogFoodTests.scala:5) | [0] | [0] clue(x) == clue(y) | [0] @@ -393,7 +393,7 @@ object DogFoodTests extends IOSuite { | [0] y: Int = 2 | [0] } | - | [1] assertion failed (modules/framework-cats/shared/src/test/scala/Meta.scala:102) + | [1] assertion failed (src/main/DogFoodTests.scala:5) | [1] | [1] clue(y) == clue(z) | [1] @@ -414,7 +414,7 @@ object DogFoodTests extends IOSuite { val expected = s""" |- (show) 0ms - | assertion failed (modules/framework-cats/shared/src/test/scala/Meta.scala:109) + | assertion failed (src/main/DogFoodTests.scala:5) | | expect(clue(x) == clue(y)) | @@ -437,7 +437,7 @@ object DogFoodTests extends IOSuite { val expected = s""" |- (show-from-to-string) 0ms - | assertion failed (modules/framework-cats/shared/src/test/scala/Meta.scala:119) + | assertion failed (src/main/DogFoodTests.scala:5) | | expect(clue(x) == clue(y)) | @@ -459,7 +459,7 @@ object DogFoodTests extends IOSuite { val expected = s""" |- (helpers) 0ms - | assertion failed (modules/framework-cats/shared/src/test/scala/Meta.scala:128) + | assertion failed (src/main/DogFoodTests.scala:5) | | expect(CustomHelpers.clue(x) == otherclue(y) || x == clue(z)) | diff --git a/modules/framework-cats/shared/src/test/scala/TracingTests.scala b/modules/framework-cats/shared/src/test/scala/TracingTests.scala index b5acb9b8..f66baa1a 100644 --- a/modules/framework-cats/shared/src/test/scala/TracingTests.scala +++ b/modules/framework-cats/shared/src/test/scala/TracingTests.scala @@ -30,7 +30,7 @@ object TracingTests extends SimpleIOSuite { val locations = e.head.locations.toList val paths = locations.map(_.fileRelativePath).map(standardise) forEach(paths)(p => expect(p == thisFile)) && - expect(locations.map(_.line).distinct.size == 4) + expect(locations.map(_.line).distinct.size == 3) case Valid(_) => failure("Should have been invalid") } }