diff --git a/src/main/scala-2/com/typesafe/scalalogging/LoggerMacro.scala b/src/main/scala-2/com/typesafe/scalalogging/LoggerMacro.scala index dbef6b2..0a7cb81 100644 --- a/src/main/scala-2/com/typesafe/scalalogging/LoggerMacro.scala +++ b/src/main/scala-2/com/typesafe/scalalogging/LoggerMacro.scala @@ -295,7 +295,15 @@ private[scalalogging] object LoggerMacro { private def formatArgs(c: LoggerContext)(args: c.Expr[Any]*) = { import c.universe._ args.map { arg => - c.Expr[AnyRef](if (arg.tree.tpe <:< weakTypeOf[AnyRef]) q"$arg: _root_.scala.AnyRef" else q"$arg.asInstanceOf[_root_.scala.AnyRef]") + // If arg is a varargs, it is also a AnyRef and we need to check the subtree. + val argument = arg.tree.children.map(_.tpe) match { + case head :: next :: Nil if head <:< weakTypeOf[scala.collection.Seq[_]] && next <:< weakTypeOf[AnyRef] => + q"$arg" + case _ => + if (arg.tree.tpe <:< weakTypeOf[AnyRef]) q"$arg: _root_.scala.AnyRef" + else q"$arg.asInstanceOf[_root_.scala.AnyRef]" + } + c.Expr[AnyRef](argument) } } } diff --git a/src/main/scala-3/com/typesafe/scalalogging/LoggerMacro.scala b/src/main/scala-3/com/typesafe/scalalogging/LoggerMacro.scala index a52ff05..8f262d2 100644 --- a/src/main/scala-3/com/typesafe/scalalogging/LoggerMacro.scala +++ b/src/main/scala-3/com/typesafe/scalalogging/LoggerMacro.scala @@ -261,22 +261,34 @@ private[scalalogging] object LoggerMacro { case _ => (message, Seq.empty) } } - def formatArgs(args: Expr[Seq[Any]])(using q: Quotes): Seq[Expr[AnyRef]] = { + + def formatArgs(args: Expr[Seq[Any]])(using q: Quotes):Seq[Expr[AnyRef]] = { import quotes.reflect.* import util.* + // we recursively obtain the actual value of inline parameters + def map(term: Term) = { + term match { + case t if t.tpe <:< TypeRepr.of[AnyRef] => t.asExprOf[AnyRef] + case t => '{ ${ t.asExpr }.asInstanceOf[AnyRef] } + } + } - args.asTerm match { - case p@Inlined(_, _, Typed(Repeated(v, _),_)) => - v.map{ - case t if t.tpe <:< TypeRepr.of[AnyRef] => t.asExprOf[AnyRef] - case t => '{${t.asExpr}.asInstanceOf[AnyRef]} + def rec(tree: Term): Option[Seq[Expr[AnyRef]]] = tree match { + case Repeated(elems, _) => Some(elems.map(map)) + case Block(Nil, e) => rec(e) + case tped@Typed(Ident(_), _) => + tped.symbol.tree match { + case ValDef(_, _, Some(rhs)) => rec(rhs) + case _ => None } - case Repeated(v, _) => - v.map{ - case t if t.tpe <:< TypeRepr.of[AnyRef] => t.asExprOf[AnyRef] - case t => '{${t.asExpr}.asInstanceOf[AnyRef]} - } - case _ => Seq.empty + case Typed(e, _) => rec(e) + case Inlined(_, Nil, e) => rec(e) + case Apply(TypeApply(_, _), List(Typed(Repeated(elems, _), _))) => + Some(elems.map(map)) + case _ => + None } + + rec(args.asTerm).getOrElse(Seq.empty) } } diff --git a/src/test/scala-2/com/typesafe/scalalogging/Scala2LoggerSpec.scala b/src/test/scala-2/com/typesafe/scalalogging/Scala2LoggerSpec.scala new file mode 100644 index 0000000..3486f5d --- /dev/null +++ b/src/test/scala-2/com/typesafe/scalalogging/Scala2LoggerSpec.scala @@ -0,0 +1,45 @@ +package com.typesafe.scalalogging + +import org.mockito.Mockito._ +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec +import org.scalatestplus.mockito.MockitoSugar +import org.slf4j.{ Logger => Underlying } + +class Scala2LoggerSpec extends AnyWordSpec with Matchers with Varargs with MockitoSugar { + + // Info + "Calling info with an interpolated message, only scala 2" should { + "fix Varargs compilation error issue 354 only scala 2" in { + val f = fixture(_.isInfoEnabled, isEnabled = true) + import f._ + class LogWrapper(val underlying: Logger) { + def info(message: String, args: AnyRef*): Unit = underlying.info(message, args: _*) + } + val logWrapper = new LogWrapper(logger) + logWrapper.info("""Hello {}""", arg5ref) + verify(underlying).info("""Hello {}""", forceVarargs(arg5ref): _*) + } + } + + private def fixture(p: Underlying => Boolean, isEnabled: Boolean) = new LoggerF(p, isEnabled) + + private class LoggerF(p: Underlying => Boolean, isEnabled: Boolean) { + val msg = "msg" + val cause = new RuntimeException("cause") + val arg1 = "arg1" + val arg2: Integer = Integer.valueOf(1) + val arg3 = "arg3" + val arg4 = 4 + val arg4ref: AnyRef = arg4.asInstanceOf[AnyRef] + val arg5 = true + val arg5ref: AnyRef = arg5.asInstanceOf[AnyRef] + val arg6 = 6L + val arg6ref: AnyRef = arg6.asInstanceOf[AnyRef] + val arg7 = new Throwable + val arg7ref: AnyRef = arg7.asInstanceOf[AnyRef] + val underlying: Underlying = mock[org.slf4j.Logger] + when(p(underlying)).thenReturn(isEnabled) + val logger: Logger = Logger(underlying) + } +} diff --git a/src/test/scala-3/com/typesafe/scalalogging/Scala3LoggerSpec.scala b/src/test/scala-3/com/typesafe/scalalogging/Scala3LoggerSpec.scala new file mode 100644 index 0000000..d3094b7 --- /dev/null +++ b/src/test/scala-3/com/typesafe/scalalogging/Scala3LoggerSpec.scala @@ -0,0 +1,66 @@ +package com.typesafe.scalalogging + +import org.mockito.Mockito._ +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec +import org.scalatestplus.mockito.MockitoSugar +import org.slf4j.{Logger => Underlying} + +class Scala3LoggerSpec extends AnyWordSpec with Matchers with Varargs with MockitoSugar { + + // Info + "Calling info with an interpolated message, only scala 3" should { + "fix Varargs compilation error issue 354 only scala 3" in { + val f = fixture(_.isInfoEnabled, isEnabled = true) + import f._ + class LogWrapper(val underlying: Logger) { + inline def info(message: String, inline args: AnyRef*): Unit = underlying.info(message, args: _*) + } + val logWrapper = new LogWrapper(logger) + logWrapper.info("""Hello {}""", arg5ref) + verify(underlying).info("""Hello {}""", arg5ref) + } + + "work when passing a Seq as repeated arguments" in { + val f = fixture(_.isInfoEnabled, isEnabled = true) + import f._ + logger.info("""Hello {}""", Seq(arg5ref):_*) + verify(underlying).info("""Hello {}""", arg5ref) + } + + "work when passing a fun as repeated arguments" in { + val f = fixture(_.isInfoEnabled, isEnabled = true) + import f._ + logger.info("""Hello {}""", forceVarargs(arg5ref):_*) + verify(underlying).info("""Hello {}""", arg5ref) + } + + "work when passing a inline def as repeated arguments" in { + val f = fixture(_.isInfoEnabled, isEnabled = true) + import f._ + inline def argss = Seq(arg5ref, arg5ref, arg5ref) + logger.info("""Hello {} {} {}""", argss:_*) + verify(underlying).info("""Hello {} {} {}""", Seq(arg5ref, arg5ref, arg5ref):_*) + } + } + + private def fixture(p: Underlying => Boolean, isEnabled: Boolean) = new LoggerF(p, isEnabled) + private class LoggerF(p: Underlying => Boolean, isEnabled: Boolean) { + val msg = "msg" + val cause = new RuntimeException("cause") + val arg1 = "arg1" + val arg2: Integer = Integer.valueOf(1) + val arg3 = "arg3" + val arg4 = 4 + val arg4ref: AnyRef = arg4.asInstanceOf[AnyRef] + val arg5 = true + val arg5ref: AnyRef = arg5.asInstanceOf[AnyRef] + val arg6 = 6L + val arg6ref: AnyRef = arg6.asInstanceOf[AnyRef] + val arg7 = new Throwable + val arg7ref: AnyRef = arg7.asInstanceOf[AnyRef] + val underlying: Underlying = mock[org.slf4j.Logger] + when(p(underlying)).thenReturn(isEnabled) + val logger: Logger = Logger(underlying) + } +} diff --git a/src/test/scala/com/typesafe/scalalogging/LoggerSpec.scala b/src/test/scala/com/typesafe/scalalogging/LoggerSpec.scala index 0082c6d..bc0cf42 100644 --- a/src/test/scala/com/typesafe/scalalogging/LoggerSpec.scala +++ b/src/test/scala/com/typesafe/scalalogging/LoggerSpec.scala @@ -207,6 +207,22 @@ class LoggerSpec extends AnyWordSpec with Matchers with Varargs with MockitoSuga logger.info(s"msg $arg1 $arg2") verify(underlying).info("msg {} {}", forceVarargs(arg1, arg2): _*) } + + "fix Varargs compilation error, issue 191" in { + val f = fixture(_.isInfoEnabled, isEnabled = true) + import f._ + val message = "Hello" + logger.info(s"Message with id=$message, submittedAt=$message will be dropped.") + verify(underlying).info("""Message with id={}, submittedAt={} will be dropped.""", forceVarargs(message, message): _*) + } + + "work when passing a val as repeated arguments, issue 436" in { + val f = fixture(_.isInfoEnabled, isEnabled = true) + import f._ + val argss = Seq(arg5ref, arg5ref, arg5ref) + logger.info("""Hello {} {} {}""", argss: _*) + verify(underlying).info("""Hello {} {} {}""", forceVarargs(arg5ref, arg5ref, arg5ref): _*) + } } "Calling info with a message and cause" should {