Skip to content

Commit e294cfd

Browse files
authored
Merge pull request #559 from scala/backport-lts-3.3-23697
Backport "Do not discard amended format when f-interpolator warns" to 3.3 LTS
2 parents 69a0acb + 641a408 commit e294cfd

File tree

6 files changed

+102
-46
lines changed

6 files changed

+102
-46
lines changed

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ class TypedFormatChecker(partsElems: List[Tree], parts: List[String], args: List
116116
case Nil =>
117117

118118
loop(parts, n = 0)
119-
if reported then (Nil, Nil)
119+
if reported then (Nil, Nil) // on error, Transform.checked will revert to unamended inputs
120120
else
121121
assert(argc == actuals.size, s"Expected ${argc} args but got ${actuals.size} for [${parts.mkString(", ")}]")
122122
(amended.toList, actuals.toList)
@@ -320,5 +320,4 @@ class TypedFormatChecker(partsElems: List[Tree], parts: List[String], args: List
320320
.tap(_ => reported = true)
321321
def partWarning(message: String, index: Int, offset: Int, end: Int): Unit =
322322
r.warning(BadFormatInterpolation(message), partPosAt(index, offset, end))
323-
.tap(_ => reported = true)
324323
end TypedFormatChecker

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

Lines changed: 0 additions & 39 deletions
This file was deleted.

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

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import dotty.tools.dotc.core.Contexts.*
1010
import dotty.tools.dotc.core.StdNames.*
1111
import dotty.tools.dotc.core.Symbols.*
1212
import dotty.tools.dotc.core.Types.*
13+
import dotty.tools.dotc.printing.Formatting.*
14+
import dotty.tools.dotc.reporting.BadFormatInterpolation
1315
import dotty.tools.dotc.transform.MegaPhase.MiniPhase
1416
import dotty.tools.dotc.typer.ConstFold
1517

@@ -22,16 +24,17 @@ import dotty.tools.dotc.typer.ConstFold
2224
*/
2325
class StringInterpolatorOpt extends MiniPhase:
2426
import tpd.*
27+
import StringInterpolatorOpt.*
2528

26-
override def phaseName: String = StringInterpolatorOpt.name
29+
override def phaseName: String = name
2730

2831
override def description: String = StringInterpolatorOpt.description
2932

3033
override def checkPostCondition(tree: tpd.Tree)(using Context): Unit =
3134
tree match
3235
case tree: RefTree =>
3336
val sym = tree.symbol
34-
assert(!StringInterpolatorOpt.isCompilerIntrinsic(sym),
37+
assert(!isCompilerIntrinsic(sym),
3538
i"$tree in ${ctx.owner.showLocated} should have been rewritten by phase $phaseName")
3639
case _ =>
3740

@@ -116,10 +119,10 @@ class StringInterpolatorOpt extends MiniPhase:
116119
!(tp =:= defn.StringType)
117120
&& {
118121
tp =:= defn.UnitType
119-
&& { report.warning("interpolated Unit value", t.srcPos); true }
122+
&& { report.warning(bfi"interpolated Unit value", t.srcPos); true }
120123
||
121124
!tp.isPrimitiveValueType
122-
&& { report.warning("interpolation uses toString", t.srcPos); true }
125+
&& { report.warning(bfi"interpolation uses toString", t.srcPos); true }
123126
}
124127
if ctx.settings.Whas.toStringInterpolated then
125128
checkIsStringify(t.tpe): Unit
@@ -133,10 +136,38 @@ class StringInterpolatorOpt extends MiniPhase:
133136
case _ => false
134137
// Perform format checking and normalization, then make it StringOps(fmt).format(args1) with tweaked args
135138
def transformF(fun: Tree, args: Tree): Tree =
136-
val (fmt, args1) = FormatInterpolatorTransform.checked(fun, args)
139+
// For f"${arg}%xpart", check format conversions and return (format, args) for String.format(format, args).
140+
def checked(args0: Tree)(using Context): (Tree, Tree) =
141+
val (partsExpr, parts) = fun match
142+
case TypeApply(Select(Apply(_, (parts: SeqLiteral) :: Nil), _), _) =>
143+
(parts.elems, parts.elems.map { case Literal(Constant(s: String)) => s })
144+
case _ =>
145+
report.error("Expected statically known StringContext", fun.srcPos)
146+
(Nil, Nil)
147+
val (args, elemtpt) = args0 match
148+
case seqlit: SeqLiteral => (seqlit.elems, seqlit.elemtpt)
149+
case _ =>
150+
report.error("Expected statically known argument list", args0.srcPos)
151+
(Nil, EmptyTree)
152+
153+
def literally(s: String) = Literal(Constant(s))
154+
if parts.lengthIs != args.length + 1 then
155+
val badParts =
156+
if parts.isEmpty then "there are no parts"
157+
else s"too ${if parts.lengthIs > args.length + 1 then "few" else "many"} arguments for interpolated string"
158+
report.error(badParts, fun.srcPos)
159+
(literally(""), args0)
160+
else
161+
val checker = TypedFormatChecker(partsExpr, parts, args)
162+
val (format, formatArgs) = checker.checked
163+
if format.isEmpty then (literally(parts.mkString), args0) // on error just use unchecked inputs
164+
else (literally(format.mkString), SeqLiteral(formatArgs.toList, elemtpt))
165+
end checked
166+
val (fmt, args1) = checked(args)
137167
resolveConstructor(defn.StringOps.typeRef, List(fmt))
138168
.select(nme.format)
139169
.appliedTo(args1)
170+
end transformF
140171
// Starting with Scala 2.13, s and raw are macros in the standard
141172
// library, so we need to expand them manually.
142173
// sc.s(args) --> standardInterpolator(processEscapes, args, sc.parts)
@@ -185,3 +216,7 @@ object StringInterpolatorOpt:
185216
sym == defn.StringContext_s ||
186217
sym == defn.StringContext_f ||
187218
sym == defn.StringContext_raw
219+
220+
extension (sc: StringContext)
221+
def bfi(args: Shown*)(using Context): BadFormatInterpolation =
222+
BadFormatInterpolation(i(sc)(args*))

tests/run/i23693.scala

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//> using options -Wtostring-interpolated
2+
3+
// verify ~warning messages and~ runtime result
4+
// never mind, the test rig doesn't log diagnostics! unlike beloved partest.
5+
6+
// Sadly, junit is not available.
7+
//import org.junit.Assert.assertEquals as jassert
8+
9+
def assertEquals(expected: String)(actual: String): Unit = assert(expected == actual)
10+
11+
case class K(i: Int)
12+
13+
@main def Test =
14+
val k = K(42)
15+
assertEquals("k == K(42)"):
16+
s"k == $k"
17+
assertEquals("\\k == \\K(42)"):
18+
raw"\k == \$k"
19+
assertEquals("k == K(42)"):
20+
f"k == $k"
21+
assertEquals("k == K(42)"):
22+
f"k == $k%s"

tests/warn/i23693.check

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
-- [E209] Interpolation Warning: tests/warn/i23693.scala:11:12 ---------------------------------------------------------
2+
11 | s"k == $k" // warn
3+
| ^
4+
| interpolation uses toString
5+
-- [E209] Interpolation Warning: tests/warn/i23693.scala:13:16 ---------------------------------------------------------
6+
13 | raw"\k == \$k" // warn
7+
| ^
8+
| interpolation uses toString
9+
-- [E209] Interpolation Warning: tests/warn/i23693.scala:15:12 ---------------------------------------------------------
10+
15 | f"k == $k" // warn
11+
| ^
12+
| interpolation uses toString
13+
-- [E209] Interpolation Warning: tests/warn/i23693.scala:17:14 ---------------------------------------------------------
14+
17 | f"k == $k%s" // warn
15+
| ^
16+
| interpolation uses toString
17+
-- [E209] Interpolation Warning: tests/warn/i23693.scala:19:18 ---------------------------------------------------------
18+
19 | s"show == ${k.show}" // warn
19+
| ^^^^^^
20+
| interpolated Unit value

tests/warn/i23693.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//> using options -Wtostring-interpolated
2+
3+
// verify warning messages; cf run test; we must verify runtime while warning.
4+
5+
case class K(i: Int):
6+
def show: Unit = ()
7+
8+
@main def Test =
9+
val k = K(42)
10+
println:
11+
s"k == $k" // warn
12+
println:
13+
raw"\k == \$k" // warn
14+
println:
15+
f"k == $k" // warn
16+
println:
17+
f"k == $k%s" // warn
18+
println:
19+
s"show == ${k.show}" // warn

0 commit comments

Comments
 (0)