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 {
3132import definitions ._
3233import 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 bodyfinally linting= linted
41+ }
42+
3443private def bail (msg :String )= global.abort(msg)
3544
3645def concatenate (parts :List [Tree ],args :List [Tree ]): Tree
@@ -87,8 +96,9 @@ abstract class FormatInterpolator {
8796
8897def argType (argi :Int ,types :Type * ): Type = {
8998val 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 {
93103val msg = " type mismatch" + {
94104val req = raw " required: (.*) " .r.unanchored
@@ -120,7 +130,7 @@ abstract class FormatInterpolator {
120130// Check the % fields in this part.
121131def loop (remaining :List [Tree ],n :Int ): Unit =
122132 remainingmatch {
123- case part0:: more =>
133+ case part0:: remaining =>
124134val part1 = part0match {
125135case Literal (Constant (x :String ))=> x
126136case _=> throw new IllegalArgumentException (" internal error: argument parts must be a list of string literals" )
@@ -130,8 +140,9 @@ abstract class FormatInterpolator {
130140
131141def 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
135146 }
136147def errorLeading (op :Conversion )= op.errorAt(Spec )(s " conversions must follow a splice; ${Conversion .literalHelp}" )
137148def accept (op :Conversion ): Unit = {
@@ -164,7 +175,7 @@ abstract class FormatInterpolator {
164175else if (! cv.isLiteral&& ! cv.isIndexed) errorLeading(cv)
165176 formatting= true
166177 }
167- loop(more , n= n+ 1 )
178+ loop(remaining , n= n+ 1 )
168179case Nil =>
169180 }
170181 loop(parts, n= 0 )
@@ -178,7 +189,11 @@ abstract class FormatInterpolator {
178189val format = amended.mkString
179190if (actuals.isEmpty&& ! formatting) constantly(format)
180191else 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)
192+ else if (! formatting) {
193+ withoutLinting {// already warned
194+ concatenate(amended.map(p=> constantly(p.stripPrefix(" %s" ))).toList, actuals.toList)
195+ }
196+ }
182197else {
183198val scalaPackage = Select (Ident (nme.ROOTPKG ),TermName (" scala" ))
184199val newStringOps = Select (
@@ -218,6 +233,7 @@ abstract class FormatInterpolator {
218233case ErrorXn => op(0 )
219234case DateTimeXn if op.length> 1 => op(1 )
220235case DateTimeXn => '?'
236+ case StringXn if op.isEmpty=> 's' // accommodate the default %s
221237case _=> op(0 )
222238 }
223239
@@ -293,6 +309,7 @@ abstract class FormatInterpolator {
293309 arg== BigIntTpe || ! cond(cc) {
294310case 'o' | 'x' | 'X' if hasAnyFlag(" + (" )=> " + (" .filter(hasFlag).foreach(bad=> badFlag(bad,s " only use ' $bad' for BigInt conversions to o, x, X " )) ;true
295311 }
312+ case StringXn => arg== StringTpe orElse (if (linting) warningAt(CC )(" String format uses toString" ,WFlagTostringInterpolated ))
296313case _=> true
297314 }
298315
@@ -301,7 +318,7 @@ abstract class FormatInterpolator {
301318 kindmatch {
302319case StringXn if hasFlag('#' )=> FormattableTpe :: Nil
303320case StringXn => AnyTpe :: Nil
304- case BooleanXn => BooleanTpe :: NullTpe :: Nil
321+ case BooleanXn => BooleanTpe :: NullTpe :: AnyTpe :: Nil // warn if not boolean
305322case HashXn => AnyTpe :: Nil
306323case CharacterXn => CharTpe :: ByteTpe :: ShortTpe :: IntTpe :: Nil
307324case IntegralXn => IntTpe :: LongTpe :: ByteTpe :: ShortTpe :: BigIntTpe :: Nil
@@ -331,7 +348,7 @@ abstract class FormatInterpolator {
331348
332349def groupPosAt (g :SpecGroup ,i :Int )= pos.withPoint(pos.point+ descriptor.offset(g, i))
333350def 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)
351+ def warningAt (g :SpecGroup ,i :Int = 0 )(msg :String , cat : WarningCategory = WarningCategory . Other )= c.callsiteTyper.context. warning(groupPosAt(g, i), msg, cat, Nil )
335352 }
336353
337354object Conversion {
@@ -356,6 +373,9 @@ abstract class FormatInterpolator {
356373case None => new Conversion (m, p,ErrorXn , argc).tap(_.errorAt(Spec )(s " Missing conversion operator in ' ${m.matched}'; $literalHelp" ))
357374 }
358375 }
376+ // construct a default %s conversion
377+ def apply (p :Position ,argc :Int ): Conversion =
378+ new Conversion (formatPattern.findAllMatchIn(" %" ).next(), p,StringXn , argc)
359379val literalHelp = " use %% for literal %, %n for newline"
360380 }
361381