12
12
13
13
package scala .tools .reflect
14
14
15
- import scala .reflect .macros .runtime .Context
16
- import scala .collection .mutable .ListBuffer
17
15
import 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
19
19
import scala .util .matching .Regex .Match
20
+ import scala .util .chaining ._
20
21
21
22
import java .util .Formattable
22
23
@@ -31,6 +32,14 @@ abstract class FormatInterpolator {
31
32
import definitions ._
32
33
import treeInfo .Applied
33
34
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
+
34
43
private def bail (msg : String ) = global.abort(msg)
35
44
36
45
def concatenate (parts : List [Tree ], args : List [Tree ]): Tree
@@ -87,8 +96,9 @@ abstract class FormatInterpolator {
87
96
88
97
def argType (argi : Int , types : Type * ): Type = {
89
98
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)))
92
102
.getOrElse {
93
103
val msg = " type mismatch" + {
94
104
val req = raw " required: (.*) " .r.unanchored
@@ -120,7 +130,7 @@ abstract class FormatInterpolator {
120
130
// Check the % fields in this part.
121
131
def loop (remaining : List [Tree ], n : Int ): Unit =
122
132
remaining match {
123
- case part0 :: more =>
133
+ case part0 :: remaining =>
124
134
val part1 = part0 match {
125
135
case Literal (Constant (x : String )) => x
126
136
case _ => throw new IllegalArgumentException (" internal error: argument parts must be a list of string literals" )
@@ -130,14 +140,17 @@ abstract class FormatInterpolator {
130
140
131
141
def insertStringConversion (): Unit = {
132
142
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 ))
135
147
}
136
148
def errorLeading (op : Conversion ) = op.errorAt(Spec )(s " conversions must follow a splice; ${Conversion .literalHelp}" )
137
149
def accept (op : Conversion ): Unit = {
138
150
if (! op.isLeading) errorLeading(op)
139
151
op.accepts(argType(n- 1 , op.acceptableVariants: _* ))
140
152
amended += part
153
+ op.lintToString(argTypes(n- 1 ))
141
154
}
142
155
143
156
if (n == 0 ) amended += part
@@ -164,7 +177,7 @@ abstract class FormatInterpolator {
164
177
else if (! cv.isLiteral && ! cv.isIndexed) errorLeading(cv)
165
178
formatting = true
166
179
}
167
- loop(more , n = n + 1 )
180
+ loop(remaining , n = n + 1 )
168
181
case Nil =>
169
182
}
170
183
loop(parts, n = 0 )
@@ -178,7 +191,11 @@ abstract class FormatInterpolator {
178
191
val format = amended.mkString
179
192
if (actuals.isEmpty && ! formatting) constantly(format)
180
193
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
+ }
182
199
else {
183
200
val scalaPackage = Select (Ident (nme.ROOTPKG ), TermName (" scala" ))
184
201
val newStringOps = Select (
@@ -218,6 +235,7 @@ abstract class FormatInterpolator {
218
235
case ErrorXn => op(0 )
219
236
case DateTimeXn if op.length > 1 => op(1 )
220
237
case DateTimeXn => '?'
238
+ case StringXn if op.isEmpty => 's' // accommodate the default %s
221
239
case _ => op(0 )
222
240
}
223
241
@@ -295,13 +313,19 @@ abstract class FormatInterpolator {
295
313
}
296
314
case _ => true
297
315
}
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 )
298
322
299
323
// what arg type if any does the conversion accept
300
324
def acceptableVariants : List [Type ] =
301
325
kind match {
302
326
case StringXn if hasFlag('#' ) => FormattableTpe :: Nil
303
327
case StringXn => AnyTpe :: Nil
304
- case BooleanXn => BooleanTpe :: NullTpe :: Nil
328
+ case BooleanXn => BooleanTpe :: NullTpe :: AnyTpe :: Nil // warn if not boolean
305
329
case HashXn => AnyTpe :: Nil
306
330
case CharacterXn => CharTpe :: ByteTpe :: ShortTpe :: IntTpe :: Nil
307
331
case IntegralXn => IntTpe :: LongTpe :: ByteTpe :: ShortTpe :: BigIntTpe :: Nil
@@ -331,7 +355,7 @@ abstract class FormatInterpolator {
331
355
332
356
def groupPosAt (g : SpecGroup , i : Int ) = pos.withPoint(pos.point + descriptor.offset(g, i))
333
357
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 )
335
359
}
336
360
337
361
object Conversion {
@@ -356,6 +380,9 @@ abstract class FormatInterpolator {
356
380
case None => new Conversion (m, p, ErrorXn , argc).tap(_.errorAt(Spec )(s " Missing conversion operator in ' ${m.matched}'; $literalHelp" ))
357
381
}
358
382
}
383
+ // construct a default %s conversion
384
+ def apply (p : Position , argc : Int ): Conversion =
385
+ new Conversion (formatPattern.findAllMatchIn(" %" ).next(), p, StringXn , argc)
359
386
val literalHelp = " use %% for literal %, %n for newline"
360
387
}
361
388
0 commit comments