1212
1313package scala .tools .reflect
1414
15- import scala .reflect .macros .runtime .Context
16- import scala .collection .mutable .ListBuffer
1715import scala .PartialFunction .cond
18- import scala .util .chaining ._
16+ import scala .collection .mutable .ListBuffer
17+ import scala .reflect .macros .runtime .Context
18+ import scala .tools .nsc .Reporting .WarningCategory , WarningCategory .WFlagTostringInterpolated
1919import scala .util .matching .Regex .Match
20+ import scala .util .chaining ._
2021
2122import java .util .Formattable
2223
@@ -31,6 +32,14 @@ abstract class FormatInterpolator {
3132 import definitions ._
3233 import treeInfo .Applied
3334
35+ protected var linting = settings.warnToString.value
36+
37+ protected final def withoutLinting [A ](body : => A ): A = {
38+ val linted = linting
39+ linting = false
40+ try body finally linting = linted
41+ }
42+
3443 private def bail (msg : String ) = global.abort(msg)
3544
3645 def concatenate (parts : List [Tree ], args : List [Tree ]): Tree
@@ -87,8 +96,9 @@ abstract class FormatInterpolator {
8796
8897 def argType (argi : Int , types : Type * ): Type = {
8998 val tpe = argTypes(argi)
90- types.find(t => argConformsTo(argi, tpe, t))
91- .orElse(types.find(t => argConvertsTo(argi, tpe, t)))
99+ types.find(t => t != AnyTpe && argConformsTo(argi, tpe, t))
100+ .orElse(types.find(t => t != AnyTpe && argConvertsTo(argi, tpe, t)))
101+ .orElse(types.find(t => t == AnyTpe && argConformsTo(argi, tpe, t)))
92102 .getOrElse {
93103 val msg = " type mismatch" + {
94104 val req = raw " required: (.*) " .r.unanchored
@@ -120,7 +130,7 @@ abstract class FormatInterpolator {
120130 // Check the % fields in this part.
121131 def loop (remaining : List [Tree ], n : Int ): Unit =
122132 remaining match {
123- case part0 :: more =>
133+ case part0 :: remaining =>
124134 val part1 = part0 match {
125135 case Literal (Constant (x : String )) => x
126136 case _ => throw new IllegalArgumentException (" internal error: argument parts must be a list of string literals" )
@@ -130,14 +140,17 @@ abstract class FormatInterpolator {
130140
131141 def insertStringConversion (): Unit = {
132142 amended += " %s" + part
133- convert += Conversion (formatPattern.findAllMatchIn(" %s" ).next(), part0.pos, argc) // improve
134- argType(n- 1 , AnyTpe )
143+ val cv = Conversion (part0.pos, argc)
144+ cv.accepts(argType(n- 1 , AnyTpe ))
145+ convert += cv
146+ cv.lintToString(argTypes(n- 1 ))
135147 }
136148 def errorLeading (op : Conversion ) = op.errorAt(Spec )(s " conversions must follow a splice; ${Conversion .literalHelp}" )
137149 def accept (op : Conversion ): Unit = {
138150 if (! op.isLeading) errorLeading(op)
139151 op.accepts(argType(n- 1 , op.acceptableVariants: _* ))
140152 amended += part
153+ op.lintToString(argTypes(n- 1 ))
141154 }
142155
143156 if (n == 0 ) amended += part
@@ -164,7 +177,7 @@ abstract class FormatInterpolator {
164177 else if (! cv.isLiteral && ! cv.isIndexed) errorLeading(cv)
165178 formatting = true
166179 }
167- loop(more , n = n + 1 )
180+ loop(remaining , n = n + 1 )
168181 case Nil =>
169182 }
170183 loop(parts, n = 0 )
@@ -178,7 +191,11 @@ abstract class FormatInterpolator {
178191 val format = amended.mkString
179192 if (actuals.isEmpty && ! formatting) constantly(format)
180193 else if (! reported && actuals.forall(treeInfo.isLiteralString)) constantly(format.format(actuals.map(_.asInstanceOf [Literal ].value.value).toIndexedSeq: _* ))
181- else if (! formatting) concatenate(amended.map(p => constantly(p.stripPrefix(" %s" ))).toList, actuals.toList)
194+ else if (! formatting) {
195+ withoutLinting { // already warned
196+ concatenate(amended.map(p => constantly(p.stripPrefix(" %s" ))).toList, actuals.toList)
197+ }
198+ }
182199 else {
183200 val scalaPackage = Select (Ident (nme.ROOTPKG ), TermName (" scala" ))
184201 val newStringOps = Select (
@@ -218,6 +235,7 @@ abstract class FormatInterpolator {
218235 case ErrorXn => op(0 )
219236 case DateTimeXn if op.length > 1 => op(1 )
220237 case DateTimeXn => '?'
238+ case StringXn if op.isEmpty => 's' // accommodate the default %s
221239 case _ => op(0 )
222240 }
223241
@@ -295,13 +313,19 @@ abstract class FormatInterpolator {
295313 }
296314 case _ => true
297315 }
316+ def lintToString (arg : Type ): Unit =
317+ if (linting && kind == StringXn && ! (arg =:= StringTpe ))
318+ if (arg.typeSymbol eq UnitClass )
319+ warningAt(CC )(" interpolated Unit value" , WFlagTostringInterpolated )
320+ else if (! definitions.isPrimitiveValueType(arg))
321+ warningAt(CC )(" interpolation uses toString" , WFlagTostringInterpolated )
298322
299323 // what arg type if any does the conversion accept
300324 def acceptableVariants : List [Type ] =
301325 kind match {
302326 case StringXn if hasFlag('#' ) => FormattableTpe :: Nil
303327 case StringXn => AnyTpe :: Nil
304- case BooleanXn => BooleanTpe :: NullTpe :: Nil
328+ case BooleanXn => BooleanTpe :: NullTpe :: AnyTpe :: Nil // warn if not boolean
305329 case HashXn => AnyTpe :: Nil
306330 case CharacterXn => CharTpe :: ByteTpe :: ShortTpe :: IntTpe :: Nil
307331 case IntegralXn => IntTpe :: LongTpe :: ByteTpe :: ShortTpe :: BigIntTpe :: Nil
@@ -331,7 +355,7 @@ abstract class FormatInterpolator {
331355
332356 def groupPosAt (g : SpecGroup , i : Int ) = pos.withPoint(pos.point + descriptor.offset(g, i))
333357 def errorAt (g : SpecGroup , i : Int = 0 )(msg : String ) = c.error(groupPosAt(g, i), msg).tap(_ => reported = true )
334- def warningAt (g : SpecGroup , i : Int = 0 )(msg : String ) = c.warning(groupPosAt(g, i), msg)
358+ def warningAt (g : SpecGroup , i : Int = 0 )(msg : String , cat : WarningCategory = WarningCategory . Other ) = c.callsiteTyper.context. warning(groupPosAt(g, i), msg, cat, Nil )
335359 }
336360
337361 object Conversion {
@@ -356,6 +380,9 @@ abstract class FormatInterpolator {
356380 case None => new Conversion (m, p, ErrorXn , argc).tap(_.errorAt(Spec )(s " Missing conversion operator in ' ${m.matched}'; $literalHelp" ))
357381 }
358382 }
383+ // construct a default %s conversion
384+ def apply (p : Position , argc : Int ): Conversion =
385+ new Conversion (formatPattern.findAllMatchIn(" %" ).next(), p, StringXn , argc)
359386 val literalHelp = " use %% for literal %, %n for newline"
360387 }
361388
0 commit comments