Skip to content

Commit d23269a

Browse files
committed
Check OrType in interpolated toString lint
1 parent 5429b1f commit d23269a

File tree

3 files changed

+82
-11
lines changed

3 files changed

+82
-11
lines changed

compiler/src/dotty/tools/dotc/transform/localopt/FormatChecker.scala

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,22 @@ class TypedFormatChecker(partsElems: List[Tree], parts: List[String], args: List
228228
case _ => true
229229

230230
def lintToString(arg: Type): Unit =
231-
if ctx.settings.Whas.toStringInterpolated && kind == StringXn && !(arg.widen =:= defn.StringType) && !arg.isPrimitiveValueType
232-
then warningAt(CC)("interpolation uses toString")
231+
def check(tp: Type): Boolean = tp.widen match
232+
case OrType(tp1, tp2) =>
233+
check(tp1) || check(tp2)
234+
case tp =>
235+
if tp =:= defn.StringType then
236+
false
237+
else if tp =:= defn.UnitType then
238+
warningAt(CC)("interpolated Unit value")
239+
true
240+
else if !tp.isPrimitiveValueType then
241+
warningAt(CC)("interpolation uses toString")
242+
true
243+
else
244+
false
245+
if ctx.settings.Whas.toStringInterpolated && kind == StringXn && check(arg) then
246+
()
233247

234248
// what arg type if any does the conversion accept
235249
def acceptableVariants: List[Type] =

compiler/src/dotty/tools/dotc/transform/localopt/StringInterpolatorOpt.scala

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,27 @@ class StringInterpolatorOpt extends MiniPhase:
105105
lintToString(elem)
106106
concat(elem)
107107
val str = stri.next()
108-
if !str.const.stringValue.isEmpty then concat(str)
108+
if !str.const.stringValue.isEmpty then
109+
concat(str)
109110
result
110111
end mkConcat
111112
def lintToString(t: Tree): Unit =
112-
val arg: Type = t.tpe
113-
if ctx.settings.Whas.toStringInterpolated && !(arg.widen =:= defn.StringType) && !arg.isPrimitiveValueType
114-
then report.warning("interpolation uses toString", t.srcPos)
113+
def check(tp: Type): Boolean = tp.widen match
114+
case OrType(tp1, tp2) =>
115+
check(tp1) || check(tp2)
116+
case tp =>
117+
if tp =:= defn.StringType then
118+
false
119+
else if tp =:= defn.UnitType then
120+
report.warning("interpolated Unit value", t.srcPos)
121+
true
122+
else if !tp.isPrimitiveValueType then
123+
report.warning("interpolation uses toString", t.srcPos)
124+
true
125+
else
126+
false
127+
if ctx.settings.Whas.toStringInterpolated && check(t.tpe) then
128+
()
115129
val sym = tree.symbol
116130
// Test names first to avoid loading scala.StringContext if not used, and common names first
117131
val isInterpolatedMethod =

tests/warn/tostring-interpolated.scala

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,18 @@ trait T {
1111

1212
def format = f"${c.x}%d in $c or $c%s" // warn using c.toString // warn
1313

14-
def bool = f"$c%b" // warn just a null check
15-
16-
def oops = s"${null} slipped thru my fingers" // warn
17-
1814
def ok = s"${c.toString}"
1915

2016
def sb = new StringBuilder().append("hello")
2117
def greeting = s"$sb, world" // warn
18+
19+
def literally = s"Hello, ${"world"}" // nowarn literal, widened to String
20+
21+
def bool = f"$c%b" // warn just a null check (quirk of Java format)
22+
23+
def oops = s"${null} slipped thru my fingers" // warn although conforms to String
24+
25+
def exceptionally = s"Hello, ${???}" // warn although conforms to String
2226
}
2327

2428
class Mitigations {
@@ -29,8 +33,47 @@ class Mitigations {
2933

3034
def ok = s"$s is ok"
3135
def jersey = s"number $i"
32-
def unitized = s"unfortunately $shown" // maybe tell them about unintended ()?
36+
def unitized = s"unfortunately $shown" // warn accidental unit value
37+
def funitized = f"unfortunately $shown" // warn accidental unit value
3338

3439
def nopct = f"$s is ok"
3540
def nofmt = f"number $i"
3641
}
42+
43+
class Branches {
44+
45+
class C {
46+
val shouldCaps = true
47+
val greeting = s"Hello ${if (shouldCaps) "WORLD" else "world"}"
48+
}
49+
50+
class D {
51+
val shouldCaps = true
52+
object world { override def toString = "world" }
53+
val greeting = s"Hello ${if (shouldCaps) "WORLD" else world}" // warn
54+
}
55+
56+
class E {
57+
def x = 42
58+
val greeting = s"Hello ${x match { case 42 => "WORLD" case 27 => "world" case _ => ??? }}"
59+
}
60+
61+
class F {
62+
def x = 42
63+
object world { override def toString = "world" }
64+
val greeting = s"Hello ${
65+
x match { // warn
66+
case 17 => "Welt"
67+
case 42 => "WORLD"
68+
case 27 => world
69+
case _ => ??? }
70+
}"
71+
}
72+
73+
class Z {
74+
val shouldCaps = true
75+
val greeting = s"Hello ${if (shouldCaps) ??? else null}" // warn
76+
val farewell = s"Bye-bye ${if (shouldCaps) "Bob" else null}" // warn
77+
}
78+
79+
}

0 commit comments

Comments
 (0)