Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitab4a1d1

Browse files
committed
Warn if interpolator uses toString
1 parentb5bfde4 commitab4a1d1

File tree

8 files changed

+102
-27
lines changed

8 files changed

+102
-27
lines changed

‎src/compiler/scala/tools/nsc/Reporting.scala‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,7 @@ object Reporting {
572572
WFlagNumericWiden,
573573
WFlagSelfImplicit,
574574
WFlagUnnamedBooleanLiteral,
575+
WFlagTostringInterpolated,
575576
WFlagValueDiscard
576577
= wflag()
577578

‎src/compiler/scala/tools/nsc/settings/Warnings.scala‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ trait Warnings {
123123
valwarnOctalLiteral=BooleanSetting("-Woctal-literal","Warn on obsolete octal syntax.") withAbbreviation"-Ywarn-octal-literal"
124124
valwarnUnnamedBoolean=BooleanSetting("-Wunnamed-boolean-literal","Warn about unnamed boolean literals if there is more than one or defaults are used, unless parameter has @deprecatedName.")
125125
valwarnUnnamedStrict=BooleanSetting("-Wunnamed-boolean-literal-strict","Warn about all unnamed boolean literals, unless parameter has @deprecatedName or the method has a single leading boolean parameter.").enabling(warnUnnamedBoolean::Nil)
126+
valwarnToString=BooleanSetting("-Wtostring-interpolated","Warn when a standard interpolator uses toString.")
126127

127128
objectPerformanceWarningsextendsMultiChoiceEnumeration {
128129
valCaptured=Choice("captured","Modification of var in closure causes boxing.")

‎src/compiler/scala/tools/reflect/FastStringInterpolator.scala‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
packagescala.tools
1414
packagereflect
1515

16-
importnsc.Reporting.WarningCategory.Scala3Migration
16+
importnsc.Reporting.WarningCategory.{Scala3Migration,WFlagTostringInterpolated}
1717

1818
traitFastStringInterpolatorextendsFormatInterpolator {
1919
importc.universe._
@@ -101,11 +101,12 @@ trait FastStringInterpolator extends FormatInterpolator {
101101
valtreatedContents= lit.asInstanceOf[Literal].value.stringValue
102102
valemptyLit= treatedContents.isEmpty
103103
if (i< numLits-1) {
104-
concatArgs+= argsIndexed(i)
105-
if (!emptyLit) concatArgs+= lit
106-
}elseif (!emptyLit) {
107-
concatArgs+=lit
104+
valarg= argsIndexed(i)
105+
if (linting&&!(arg.tpe=:= definitions.StringTpe))
106+
runReporting.warning(arg.pos,"interpolation uses toString",WFlagTostringInterpolated, c.internal.enclosingOwner)
107+
concatArgs+=arg
108108
}
109+
if (!emptyLit) concatArgs+= lit
109110
}
110111
defmkConcat(pos:Position,lhs:Tree,rhs:Tree):Tree=
111112
atPos(pos)(gen.mkMethodCall(gen.mkAttributedSelect(lhs, definitions.String_+), rhs::Nil)).setType(definitions.StringTpe)

‎src/compiler/scala/tools/reflect/FormatInterpolator.scala‎

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@
1212

1313
packagescala.tools.reflect
1414

15-
importscala.reflect.macros.runtime.Context
16-
importscala.collection.mutable.ListBuffer
1715
importscala.PartialFunction.cond
18-
importscala.util.chaining._
16+
importscala.collection.mutable.ListBuffer
17+
importscala.reflect.macros.runtime.Context
18+
importscala.tools.nsc.Reporting.WarningCategory,WarningCategory.WFlagTostringInterpolated
1919
importscala.util.matching.Regex.Match
20+
importscala.util.chaining._
2021

2122
importjava.util.Formattable
2223

@@ -31,6 +32,14 @@ abstract class FormatInterpolator {
3132
importdefinitions._
3233
importtreeInfo.Applied
3334

35+
protectedvarlinting= settings.warnToString.value
36+
37+
protectedfinaldefwithoutLinting[A](body:=>A):A= {
38+
vallinted= linting
39+
linting=false
40+
try bodyfinally linting= linted
41+
}
42+
3443
privatedefbail(msg:String)= global.abort(msg)
3544

3645
defconcatenate(parts:List[Tree],args:List[Tree]):Tree
@@ -87,8 +96,9 @@ abstract class FormatInterpolator {
8796

8897
defargType(argi:Int,types:Type*):Type= {
8998
valtpe= 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
valmsg="type mismatch"+ {
94104
valreq=raw"required: (.*)".r.unanchored
@@ -120,7 +130,7 @@ abstract class FormatInterpolator {
120130
// Check the % fields in this part.
121131
defloop(remaining:List[Tree],n:Int):Unit=
122132
remainingmatch {
123-
case part0::more=>
133+
case part0::remaining=>
124134
valpart1= part0match {
125135
caseLiteral(Constant(x:String))=> x
126136
case _=>thrownewIllegalArgumentException("internal error: argument parts must be a list of string literals")
@@ -130,8 +140,9 @@ abstract class FormatInterpolator {
130140

131141
definsertStringConversion():Unit= {
132142
amended+="%s"+ part
133-
convert+=Conversion(formatPattern.findAllMatchIn("%s").next(), part0.pos, argc)// improve
134-
argType(n-1,AnyTpe)
143+
valcv=Conversion(part0.pos, argc)
144+
cv.accepts(argType(n-1,AnyTpe))
145+
convert+= cv
135146
}
136147
deferrorLeading(op:Conversion)= op.errorAt(Spec)(s"conversions must follow a splice;${Conversion.literalHelp}")
137148
defaccept(op:Conversion):Unit= {
@@ -164,7 +175,7 @@ abstract class FormatInterpolator {
164175
elseif (!cv.isLiteral&&!cv.isIndexed) errorLeading(cv)
165176
formatting=true
166177
}
167-
loop(more, n= n+1)
178+
loop(remaining, n= n+1)
168179
caseNil=>
169180
}
170181
loop(parts, n=0)
@@ -178,7 +189,11 @@ abstract class FormatInterpolator {
178189
valformat= amended.mkString
179190
if (actuals.isEmpty&&!formatting) constantly(format)
180191
elseif (!reported&& actuals.forall(treeInfo.isLiteralString)) constantly(format.format(actuals.map(_.asInstanceOf[Literal].value.value).toIndexedSeq:_*))
181-
elseif (!formatting) concatenate(amended.map(p=> constantly(p.stripPrefix("%s"))).toList, actuals.toList)
192+
elseif (!formatting) {
193+
withoutLinting {// already warned
194+
concatenate(amended.map(p=> constantly(p.stripPrefix("%s"))).toList, actuals.toList)
195+
}
196+
}
182197
else {
183198
valscalaPackage=Select(Ident(nme.ROOTPKG),TermName("scala"))
184199
valnewStringOps=Select(
@@ -218,6 +233,7 @@ abstract class FormatInterpolator {
218233
caseErrorXn=> op(0)
219234
caseDateTimeXnif op.length>1=> op(1)
220235
caseDateTimeXn=>'?'
236+
caseStringXnif op.isEmpty=>'s'// accommodate the default %s
221237
case _=> op(0)
222238
}
223239

@@ -293,6 +309,7 @@ abstract class FormatInterpolator {
293309
arg==BigIntTpe||!cond(cc) {
294310
case'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+
caseStringXn=> arg==StringTpe orElse (if (linting) warningAt(CC)("String format uses toString",WFlagTostringInterpolated))
296313
case _=>true
297314
}
298315

@@ -301,7 +318,7 @@ abstract class FormatInterpolator {
301318
kindmatch {
302319
caseStringXnif hasFlag('#')=>FormattableTpe::Nil
303320
caseStringXn=>AnyTpe::Nil
304-
caseBooleanXn=>BooleanTpe::NullTpe::Nil
321+
caseBooleanXn=>BooleanTpe::NullTpe::AnyTpe::Nil// warn if not boolean
305322
caseHashXn=>AnyTpe::Nil
306323
caseCharacterXn=>CharTpe::ByteTpe::ShortTpe::IntTpe::Nil
307324
caseIntegralXn=>IntTpe::LongTpe::ByteTpe::ShortTpe::BigIntTpe::Nil
@@ -331,7 +348,7 @@ abstract class FormatInterpolator {
331348

332349
defgroupPosAt(g:SpecGroup,i:Int)= pos.withPoint(pos.point+ descriptor.offset(g, i))
333350
deferrorAt(g:SpecGroup,i:Int=0)(msg:String)= c.error(groupPosAt(g, i), msg).tap(_=> reported=true)
334-
defwarningAt(g:SpecGroup,i:Int=0)(msg:String)= c.warning(groupPosAt(g, i), msg)
351+
defwarningAt(g:SpecGroup,i:Int=0)(msg:String,cat:WarningCategory=WarningCategory.Other)= c.callsiteTyper.context.warning(groupPosAt(g, i), msg, cat,Nil)
335352
}
336353

337354
objectConversion {
@@ -356,6 +373,9 @@ abstract class FormatInterpolator {
356373
caseNone=>newConversion(m, p,ErrorXn, argc).tap(_.errorAt(Spec)(s"Missing conversion operator in '${m.matched}';$literalHelp"))
357374
}
358375
}
376+
// construct a default %s conversion
377+
defapply(p:Position,argc:Int):Conversion=
378+
newConversion(formatPattern.findAllMatchIn("%").next(), p,StringXn, argc)
359379
valliteralHelp="use %% for literal %, %n for newline"
360380
}
361381

‎test/files/neg/stringinterpolation_macro-neg.check‎

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,6 @@ stringinterpolation_macro-neg.scala:15: error: too many arguments for interpolat
1010
stringinterpolation_macro-neg.scala:16: error: too few arguments for interpolated string
1111
new StringContext("", "").f()
1212
^
13-
stringinterpolation_macro-neg.scala:19: error: type mismatch;
14-
found : String
15-
required: Boolean, Null
16-
f"$s%b"
17-
^
1813
stringinterpolation_macro-neg.scala:20: error: type mismatch;
1914
found : String
2015
required: Char, Byte, Short, Int
@@ -162,6 +157,9 @@ stringinterpolation_macro-neg.scala:77: error: Missing conversion operator in '%
162157
stringinterpolation_macro-neg.scala:80: error: conversions must follow a splice; use %% for literal %, %n for newline
163158
f"${d}random-leading-junk%d"
164159
^
160+
stringinterpolation_macro-neg.scala:19: warning: Boolean format is null test for non-Boolean
161+
f"$s%b"
162+
^
165163
stringinterpolation_macro-neg.scala:63: warning: Index is not this arg
166164
f"${8}%d ${9}%1$$d"
167165
^
@@ -171,5 +169,5 @@ stringinterpolation_macro-neg.scala:64: warning: Argument index ignored if '<' f
171169
stringinterpolation_macro-neg.scala:65: warning: Index is not this arg
172170
f"$s%s $s%1$$s"
173171
^
174-
3 warnings
175-
46 errors
172+
4 warnings
173+
45 errors
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
tostring-interpolated.scala:7: error: String format uses toString
2+
Applicable -Wconf / @nowarn filters for this fatal warning: msg=<part of the message>, cat=w-flag-tostring-interpolated, site=T.f
3+
def f = f"$c"
4+
^
5+
tostring-interpolated.scala:8: error: interpolation uses toString
6+
Applicable -Wconf / @nowarn filters for this fatal warning: msg=<part of the message>, cat=w-flag-tostring-interpolated, site=T.s
7+
def s = s"$c"
8+
^
9+
tostring-interpolated.scala:9: error: interpolation uses toString
10+
Applicable -Wconf / @nowarn filters for this fatal warning: msg=<part of the message>, cat=w-flag-tostring-interpolated, site=T.r
11+
def r = raw"$c"
12+
^
13+
tostring-interpolated.scala:11: error: String format uses toString
14+
Applicable -Wconf / @nowarn filters for this fatal warning: msg=<part of the message>, cat=w-flag-tostring-interpolated, site=T.format
15+
def format = f"${c.x}%d in $c or $c%s" // warn using c.toString
16+
^
17+
tostring-interpolated.scala:11: error: String format uses toString
18+
Applicable -Wconf / @nowarn filters for this fatal warning: msg=<part of the message>, cat=w-flag-tostring-interpolated, site=T.format
19+
def format = f"${c.x}%d in $c or $c%s" // warn using c.toString
20+
^
21+
tostring-interpolated.scala:13: warning: Boolean format is null test for non-Boolean
22+
def bool = f"$c%b" // warn just a null check
23+
^
24+
tostring-interpolated.scala:15: error: interpolation uses toString
25+
Applicable -Wconf / @nowarn filters for this fatal warning: msg=<part of the message>, cat=w-flag-tostring-interpolated, site=T.oops
26+
def oops = s"${null} slipped thru my fingers"
27+
^
28+
tostring-interpolated.scala:20: error: interpolation uses toString
29+
Applicable -Wconf / @nowarn filters for this fatal warning: msg=<part of the message>, cat=w-flag-tostring-interpolated, site=T.greeting
30+
def greeting = s"$sb, world"
31+
^
32+
1 warning
33+
7 errors
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//>usingoptions-Wconf:cat=w-flag-tostring-interpolated:e-Wtostring-interpolated
2+
3+
caseclassC(x:Int)
4+
5+
traitT {
6+
defc=C(42)
7+
deff=f"$c"
8+
defs=s"$c"
9+
defr=raw"$c"
10+
11+
defformat=f"${c.x}%d in$c or$c%s"// warn using c.toString
12+
13+
defbool=f"$c%b"// warn just a null check
14+
15+
defoops=s"${null} slipped thru my fingers"
16+
17+
defok=s"${c.toString}"
18+
19+
defsb=newStringBuilder().append("hello")
20+
defgreeting=s"$sb, world"
21+
}

‎test/files/run/f-interpolator-unit.scala‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ object Test extends App {
3333
finalvaltester="hello"
3434
finalvalnumber="42"// strings only, alas
3535

36-
defassertEquals(s0:String,s1:String)= assert(s0== s1,s"$s0 ==$s1")
36+
defassertEquals(s0:String,s1:String,i:Int=-1)= assert(s0== s1,s"$s0 ==$s1${if (i>=0)" at"+ i.toStringelse""}")
3737

3838
defnoEscape()= {
3939
vals="string"
@@ -134,7 +134,7 @@ object Test extends App {
134134
f"${null}%b"->"false",
135135
f"${false}%b"->"false",
136136
f"${true}%b"->"true",
137-
f"${true&&false}%b"->"false",
137+
f"${true&&false}%b"->"false",
138138
f"${java.lang.Boolean.valueOf(false)}%b"->"false",
139139
f"${java.lang.Boolean.valueOf(true)}%b"->"true",
140140

@@ -255,7 +255,7 @@ object Test extends App {
255255
f"z"->"z"
256256
)
257257

258-
for ((f, s)<- ss) assertEquals(s, f)
258+
for (((f, s), i)<- ss.zipWithIndex) assertEquals(s, f, i)
259259
}
260260

261261
noEscape()

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp