Skip to content

Commit 87e8407

Browse files
authored
Merge pull request #87 from jcracknell/interpolations
Fix compile errors for interpolated AnyVals
2 parents 4bd1fef + 0f59f97 commit 87e8407

File tree

2 files changed

+118
-21
lines changed

2 files changed

+118
-21
lines changed

src/main/scala/com/typesafe/scalalogging/LoggerMacro.scala

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ import scala.reflect.macros.blackbox.Context
2121

2222
private object LoggerMacro {
2323

24-
private final val ArgumentMarker = "{}"
25-
2624
type LoggerContext = Context { type PrefixType = Logger }
2725

2826
// Error
@@ -42,7 +40,7 @@ private object LoggerMacro {
4240
import c.universe._
4341
val underlying = q"${c.prefix}.underlying"
4442
if (args.length == 2)
45-
q"if ($underlying.isErrorEnabled) $underlying.error($message, _root_.scala.List(${args(0)}, ${args(1)}): _*)"
43+
q"if ($underlying.isErrorEnabled) $underlying.error($message, _root_.scala.Array(${args(0)}, ${args(1)}): _*)"
4644
else
4745
q"if ($underlying.isErrorEnabled) $underlying.error($message, ..$args)"
4846
}
@@ -62,7 +60,7 @@ private object LoggerMacro {
6260
import c.universe._
6361
val underlying = q"${c.prefix}.underlying"
6462
if (args.length == 2)
65-
q"if ($underlying.isErrorEnabled) $underlying.error($marker, $message, _root_.scala.List(${args(0)}, ${args(1)}): _*)"
63+
q"if ($underlying.isErrorEnabled) $underlying.error($marker, $message, _root_.scala.Array(${args(0)}, ${args(1)}): _*)"
6664
else
6765
q"if ($underlying.isErrorEnabled) $underlying.error($marker, $message, ..$args)"
6866
}
@@ -84,7 +82,7 @@ private object LoggerMacro {
8482
import c.universe._
8583
val underlying = q"${c.prefix}.underlying"
8684
if (args.length == 2)
87-
q"if ($underlying.isWarnEnabled) $underlying.warn($message, _root_.scala.List(${args(0)}, ${args(1)}): _*)"
85+
q"if ($underlying.isWarnEnabled) $underlying.warn($message, _root_.scala.Array(${args(0)}, ${args(1)}): _*)"
8886
else
8987
q"if ($underlying.isWarnEnabled) $underlying.warn($message, ..$args)"
9088
}
@@ -104,7 +102,7 @@ private object LoggerMacro {
104102
import c.universe._
105103
val underlying = q"${c.prefix}.underlying"
106104
if (args.length == 2)
107-
q"if ($underlying.isWarnEnabled) $underlying.warn($marker, $message, _root_.scala.List(${args(0)}, ${args(1)}): _*)"
105+
q"if ($underlying.isWarnEnabled) $underlying.warn($marker, $message, _root_.scala.Array(${args(0)}, ${args(1)}): _*)"
108106
else
109107
q"if ($underlying.isWarnEnabled) $underlying.warn($marker, $message, ..$args)"
110108
}
@@ -126,7 +124,7 @@ private object LoggerMacro {
126124
import c.universe._
127125
val underlying = q"${c.prefix}.underlying"
128126
if (args.length == 2)
129-
q"if ($underlying.isInfoEnabled) $underlying.info($message, _root_.scala.List(${args(0)}, ${args(1)}): _*)"
127+
q"if ($underlying.isInfoEnabled) $underlying.info($message, _root_.scala.Array(${args(0)}, ${args(1)}): _*)"
130128
else
131129
q"if ($underlying.isInfoEnabled) $underlying.info($message, ..$args)"
132130
}
@@ -146,7 +144,7 @@ private object LoggerMacro {
146144
import c.universe._
147145
val underlying = q"${c.prefix}.underlying"
148146
if (args.length == 2)
149-
q"if ($underlying.isInfoEnabled) $underlying.info($marker, $message, _root_.scala.List(${args(0)}, ${args(1)}): _*)"
147+
q"if ($underlying.isInfoEnabled) $underlying.info($marker, $message, _root_.scala.Array(${args(0)}, ${args(1)}): _*)"
150148
else
151149
q"if ($underlying.isInfoEnabled) $underlying.info($marker, $message, ..$args)"
152150
}
@@ -168,7 +166,7 @@ private object LoggerMacro {
168166
import c.universe._
169167
val underlying = q"${c.prefix}.underlying"
170168
if (args.length == 2)
171-
q"if ($underlying.isDebugEnabled) $underlying.debug($message, _root_.scala.List(${args(0)}, ${args(1)}): _*)"
169+
q"if ($underlying.isDebugEnabled) $underlying.debug($message, _root_.scala.Array(${args(0)}, ${args(1)}): _*)"
172170
else
173171
q"if ($underlying.isDebugEnabled) $underlying.debug($message, ..$args)"
174172
}
@@ -188,7 +186,7 @@ private object LoggerMacro {
188186
import c.universe._
189187
val underlying = q"${c.prefix}.underlying"
190188
if (args.length == 2)
191-
q"if ($underlying.isDebugEnabled) $underlying.debug($marker, $message, _root_.scala.List(${args(0)}, ${args(1)}): _*)"
189+
q"if ($underlying.isDebugEnabled) $underlying.debug($marker, $message, _root_.scala.Array(${args(0)}, ${args(1)}): _*)"
192190
else
193191
q"if ($underlying.isDebugEnabled) $underlying.debug($marker, $message, ..$args)"
194192
}
@@ -210,7 +208,7 @@ private object LoggerMacro {
210208
import c.universe._
211209
val underlying = q"${c.prefix}.underlying"
212210
if (args.length == 2)
213-
q"if ($underlying.isTraceEnabled) $underlying.trace($message, _root_.scala.List(${args(0)}, ${args(1)}): _*)"
211+
q"if ($underlying.isTraceEnabled) $underlying.trace($message, _root_.scala.Array(${args(0)}, ${args(1)}): _*)"
214212
else
215213
q"if ($underlying.isTraceEnabled) $underlying.trace($message, ..$args)"
216214
}
@@ -230,7 +228,7 @@ private object LoggerMacro {
230228
import c.universe._
231229
val underlying = q"${c.prefix}.underlying"
232230
if (args.length == 2)
233-
q"if ($underlying.isTraceEnabled) $underlying.trace($marker, $message, _root_.scala.List(${args(0)}, ${args(1)}): _*)"
231+
q"if ($underlying.isTraceEnabled) $underlying.trace($marker, $message, _root_.scala.Array(${args(0)}, ${args(1)}): _*)"
234232
else
235233
q"if ($underlying.isTraceEnabled) $underlying.trace($marker, $message, ..$args)"
236234
}
@@ -241,9 +239,18 @@ private object LoggerMacro {
241239

242240
message.tree match {
243241
case q"scala.StringContext.apply(..$parts).s(..$args)" =>
244-
val strings = parts.collect { case Literal(Constant(s: String)) => s }
245-
val messageFormat = strings.mkString(ArgumentMarker)
246-
(c.Expr(q"$messageFormat"), args.map(arg => q"$arg").map(c.Expr[Any](_)))
242+
val format = parts.iterator.map({ case Literal(Constant(str: String)) => str })
243+
// Emulate standard interpolator escaping
244+
.map(StringContext.treatEscapes)
245+
// Escape literal slf4j format anchors if the resulting call will require a format string
246+
.map(str => if (args.nonEmpty) str.replace("{}", "\\{}") else str)
247+
.mkString("{}")
248+
249+
val formatArgs = args map { arg =>
250+
c.Expr[AnyRef](if (arg.tpe <:< weakTypeOf[AnyRef]) arg else q"$arg.asInstanceOf[_root_.scala.AnyRef]")
251+
}
252+
253+
(c.Expr(q"$format"), formatArgs)
247254

248255
case _ => (message, Seq.empty)
249256
}

src/test/scala/com/typesafe/scalalogging/LoggerSpec.scala

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,24 @@ class LoggerSpec extends WordSpec with Matchers with MockitoSugar {
4343
logger.error(msg)
4444
verify(underlying, never).error(anyString)
4545
}
46+
}
47+
48+
"Calling error with an interpolated message" should {
4649

47-
"call the underlying logger's error method with arguments if the error level is enabled and string is interpolated" in {
50+
"call the underlying logger's error method with arguments if the error level is enabled" in {
4851
val f = fixture(_.isErrorEnabled, true)
4952
import f._
5053
logger.error(s"msg $arg1 $arg2 $arg3")
5154
verify(underlying).error("msg {} {} {}", arg1, arg2, arg3)
5255
}
5356

54-
"call the underlying logger's error method with two arguments if the error level is enabled and string is interpolated" in {
57+
"call the underlying logger's error method with two arguments if the error level is enabled" in {
5558
val f = fixture(_.isErrorEnabled, true)
5659
import f._
5760
logger.error(s"msg $arg1 $arg2")
5861
verify(underlying).error("msg {} {}", List(arg1, arg2): _*)
5962
}
63+
6064
}
6165

6266
"Calling error with a message and cause" should {
@@ -118,13 +122,23 @@ class LoggerSpec extends WordSpec with Matchers with MockitoSugar {
118122
logger.warn(msg)
119123
verify(underlying, never).warn(anyString)
120124
}
125+
}
121126

122-
"call the underlying logger's warn method if the warn level is enabled and string is interpolated" in {
127+
"Calling warn with an interpolated message" should {
128+
129+
"call the underlying logger's warn method if the warn level is enabled" in {
123130
val f = fixture(_.isWarnEnabled, true)
124131
import f._
125132
logger.warn(s"msg $arg1 $arg2 $arg3")
126133
verify(underlying).warn("msg {} {} {}", arg1, arg2, arg3)
127134
}
135+
136+
"call the underlying logger's warn method with two arguments if the warn level is enabled" in {
137+
val f = fixture(_.isWarnEnabled, true)
138+
import f._
139+
logger.warn(s"msg $arg1 $arg2")
140+
verify(underlying).warn("msg {} {}", List(arg1, arg2): _*)
141+
}
128142
}
129143

130144
"Calling warn with a message and cause" should {
@@ -186,13 +200,23 @@ class LoggerSpec extends WordSpec with Matchers with MockitoSugar {
186200
logger.info(msg)
187201
verify(underlying, never).info(anyString)
188202
}
203+
}
204+
205+
"Calling info with an interpolated message" should {
189206

190-
"call the underlying logger's info method if the info level is enabled and string is interpolated" in {
207+
"call the underlying logger's info method if the info level is enabled" in {
191208
val f = fixture(_.isInfoEnabled, true)
192209
import f._
193210
logger.info(s"msg $arg1 $arg2 $arg3")
194211
verify(underlying).info("msg {} {} {}", arg1, arg2, arg3)
195212
}
213+
214+
"call the underlying logger's info method with two arguments if the info level is enabled" in {
215+
val f = fixture(_.isInfoEnabled, true)
216+
import f._
217+
logger.info(s"msg $arg1 $arg2")
218+
verify(underlying).info("msg {} {}", List(arg1, arg2): _*)
219+
}
196220
}
197221

198222
"Calling info with a message and cause" should {
@@ -254,13 +278,22 @@ class LoggerSpec extends WordSpec with Matchers with MockitoSugar {
254278
logger.debug(msg)
255279
verify(underlying, never).debug(anyString)
256280
}
281+
}
282+
"Calling debug with an interpolated message" should {
257283

258-
"call the underlying logger's debug method if the debug level is enabled and string is interpolated" in {
284+
"call the underlying logger's debug method if the debug level is enabled" in {
259285
val f = fixture(_.isDebugEnabled, true)
260286
import f._
261287
logger.debug(s"msg $arg1 $arg2 $arg3")
262288
verify(underlying).debug("msg {} {} {}", arg1, arg2, arg3)
263289
}
290+
291+
"call the underlying logger's debug method with two arguments if the debug level is enabled" in {
292+
val f = fixture(_.isDebugEnabled, true)
293+
import f._
294+
logger.debug(s"msg $arg1 $arg2")
295+
verify(underlying).debug("msg {} {}", List(arg1, arg2): _*)
296+
}
264297
}
265298

266299
"Calling debug with a message and cause" should {
@@ -322,13 +355,23 @@ class LoggerSpec extends WordSpec with Matchers with MockitoSugar {
322355
logger.trace(msg)
323356
verify(underlying, never).trace(anyString)
324357
}
358+
}
325359

326-
"call the underlying logger's trace method if the trace level is enabled and string is interpolated" in {
360+
"Calling trace with an interpolated message" should {
361+
362+
"call the underlying logger's trace method if the trace level is enabled" in {
327363
val f = fixture(_.isTraceEnabled, true)
328364
import f._
329365
logger.trace(s"msg $arg1 $arg2 $arg3")
330366
verify(underlying).trace("msg {} {} {}", arg1, arg2, arg3)
331367
}
368+
369+
"call the underlying logger's trace method with two arguments if the trace level is enabled" in {
370+
val f = fixture(_.isTraceEnabled, true)
371+
import f._
372+
logger.trace(s"msg $arg1 $arg2")
373+
verify(underlying).trace("msg {} {}", List(arg1, arg2): _*)
374+
}
332375
}
333376

334377
"Calling trace with a message and cause" should {
@@ -373,6 +416,53 @@ class LoggerSpec extends WordSpec with Matchers with MockitoSugar {
373416
}
374417
}
375418

419+
// Interpolator destructuring corner cases
420+
421+
"Logging a message using the standard string interpolator" should {
422+
423+
"call the underlying format method with boxed versions of value arguments" in {
424+
val f = fixture(_.isErrorEnabled, true)
425+
import f._
426+
logger.error(s"msg ${1}")
427+
verify(underlying).error("msg {}", 1.asInstanceOf[AnyRef])
428+
}
429+
430+
"call the underlying format method with boxed versions of arguments of type Any" in {
431+
val f = fixture(_.isErrorEnabled, true)
432+
import f._
433+
logger.error(s"msg ${1.asInstanceOf[Any]}")
434+
verify(underlying).error("msg {}", 1.asInstanceOf[AnyRef])
435+
}
436+
437+
"call the underlying format method escaping literal format anchors" in {
438+
val f = fixture(_.isErrorEnabled, true)
439+
import f._
440+
logger.error(s"foo {} bar $arg1")
441+
verify(underlying).error("foo \\{} bar {}", arg1)
442+
}
443+
444+
"call the underlying method without escaping format anchors when the message has no interpolations" in {
445+
val f = fixture(_.isErrorEnabled, true)
446+
import f._
447+
logger.error(s"foo {} bar")
448+
verify(underlying).error("foo {} bar")
449+
}
450+
451+
"call the underlying format method when the interpolated string contains escape sequences" in {
452+
val f = fixture(_.isErrorEnabled, true)
453+
import f._
454+
logger.error(s"foo\nbar $arg1")
455+
verify(underlying).error(s"foo\nbar {}", arg1)
456+
}
457+
458+
"call the underlying format method when the interpolated string is triple quoted and contains escape sequences" in {
459+
val f = fixture(_.isErrorEnabled, true)
460+
import f._
461+
logger.error(s"""foo\nbar $arg1""")
462+
verify(underlying).error(s"""foo\nbar {}""", arg1)
463+
}
464+
}
465+
376466
"Serializing Logger" should {
377467

378468
def serialize(logger: Logger): Array[Byte] = {

0 commit comments

Comments
 (0)