@@ -10,6 +10,8 @@ import dotty.tools.dotc.core.Contexts.*
10
10
import dotty .tools .dotc .core .StdNames .*
11
11
import dotty .tools .dotc .core .Symbols .*
12
12
import dotty .tools .dotc .core .Types .*
13
+ import dotty .tools .dotc .printing .Formatting .*
14
+ import dotty .tools .dotc .reporting .BadFormatInterpolation
13
15
import dotty .tools .dotc .transform .MegaPhase .MiniPhase
14
16
import dotty .tools .dotc .typer .ConstFold
15
17
@@ -22,16 +24,17 @@ import dotty.tools.dotc.typer.ConstFold
22
24
*/
23
25
class StringInterpolatorOpt extends MiniPhase :
24
26
import tpd .*
27
+ import StringInterpolatorOpt .*
25
28
26
- override def phaseName : String = StringInterpolatorOpt . name
29
+ override def phaseName : String = name
27
30
28
31
override def description : String = StringInterpolatorOpt .description
29
32
30
33
override def checkPostCondition (tree : tpd.Tree )(using Context ): Unit =
31
34
tree match
32
35
case tree : RefTree =>
33
36
val sym = tree.symbol
34
- assert(! StringInterpolatorOpt . isCompilerIntrinsic(sym),
37
+ assert(! isCompilerIntrinsic(sym),
35
38
i " $tree in ${ctx.owner.showLocated} should have been rewritten by phase $phaseName" )
36
39
case _ =>
37
40
@@ -116,10 +119,10 @@ class StringInterpolatorOpt extends MiniPhase:
116
119
! (tp =:= defn.StringType )
117
120
&& {
118
121
tp =:= defn.UnitType
119
- && { report.warning(" interpolated Unit value" , t.srcPos); true }
122
+ && { report.warning(bfi " interpolated Unit value " , t.srcPos); true }
120
123
||
121
124
! tp.isPrimitiveValueType
122
- && { report.warning(" interpolation uses toString" , t.srcPos); true }
125
+ && { report.warning(bfi " interpolation uses toString " , t.srcPos); true }
123
126
}
124
127
if ctx.settings.Whas .toStringInterpolated then
125
128
checkIsStringify(t.tpe): Unit
@@ -133,10 +136,38 @@ class StringInterpolatorOpt extends MiniPhase:
133
136
case _ => false
134
137
// Perform format checking and normalization, then make it StringOps(fmt).format(args1) with tweaked args
135
138
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)
137
167
resolveConstructor(defn.StringOps .typeRef, List (fmt))
138
168
.select(nme.format)
139
169
.appliedTo(args1)
170
+ end transformF
140
171
// Starting with Scala 2.13, s and raw are macros in the standard
141
172
// library, so we need to expand them manually.
142
173
// sc.s(args) --> standardInterpolator(processEscapes, args, sc.parts)
@@ -185,3 +216,7 @@ object StringInterpolatorOpt:
185
216
sym == defn.StringContext_s ||
186
217
sym == defn.StringContext_f ||
187
218
sym == defn.StringContext_raw
219
+
220
+ extension (sc : StringContext )
221
+ def bfi (args : Shown * )(using Context ): BadFormatInterpolation =
222
+ BadFormatInterpolation (i(sc)(args* ))
0 commit comments