@@ -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
@@ -117,10 +120,10 @@ class StringInterpolatorOpt extends MiniPhase:
117
120
! (tp =:= defn.StringType )
118
121
&& {
119
122
tp =:= defn.UnitType
120
- && { report.warning(" interpolated Unit value" , t.srcPos); true }
123
+ && { report.warning(bfi " interpolated Unit value " , t.srcPos); true }
121
124
||
122
125
! tp.isPrimitiveValueType
123
- && { report.warning(" interpolation uses toString" , t.srcPos); true }
126
+ && { report.warning(bfi " interpolation uses toString " , t.srcPos); true }
124
127
}
125
128
if ctx.settings.Whas .toStringInterpolated then
126
129
checkIsStringify(t.tpe): Unit
@@ -134,10 +137,38 @@ class StringInterpolatorOpt extends MiniPhase:
134
137
case _ => false
135
138
// Perform format checking and normalization, then make it StringOps(fmt).format(args1) with tweaked args
136
139
def transformF (fun : Tree , args : Tree ): Tree =
137
- val (fmt, args1) = FormatInterpolatorTransform .checked(fun, args)
140
+ // For f"${arg}%xpart", check format conversions and return (format, args) for String.format(format, args).
141
+ def checked (args0 : Tree )(using Context ): (Tree , Tree ) =
142
+ val (partsExpr, parts) = fun match
143
+ case TypeApply (Select (Apply (_, (parts : SeqLiteral ) :: Nil ), _), _) =>
144
+ (parts.elems, parts.elems.map { case Literal (Constant (s : String )) => s })
145
+ case _ =>
146
+ report.error(" Expected statically known StringContext" , fun.srcPos)
147
+ (Nil , Nil )
148
+ val (args, elemtpt) = args0 match
149
+ case seqlit : SeqLiteral => (seqlit.elems, seqlit.elemtpt)
150
+ case _ =>
151
+ report.error(" Expected statically known argument list" , args0.srcPos)
152
+ (Nil , EmptyTree )
153
+
154
+ def literally (s : String ) = Literal (Constant (s))
155
+ if parts.lengthIs != args.length + 1 then
156
+ val badParts =
157
+ if parts.isEmpty then " there are no parts"
158
+ else s " too ${if parts.lengthIs > args.length + 1 then " few" else " many" } arguments for interpolated string "
159
+ report.error(badParts, fun.srcPos)
160
+ (literally(" " ), args0)
161
+ else
162
+ val checker = TypedFormatChecker (partsExpr, parts, args)
163
+ val (format, formatArgs) = checker.checked
164
+ if format.isEmpty then (literally(parts.mkString), args0) // on error just use unchecked inputs
165
+ else (literally(format.mkString), SeqLiteral (formatArgs.toList, elemtpt))
166
+ end checked
167
+ val (fmt, args1) = checked(args)
138
168
resolveConstructor(defn.StringOps .typeRef, List (fmt))
139
169
.select(nme.format)
140
170
.appliedTo(args1)
171
+ end transformF
141
172
// Starting with Scala 2.13, s and raw are macros in the standard
142
173
// library, so we need to expand them manually.
143
174
// sc.s(args) --> standardInterpolator(processEscapes, args, sc.parts)
@@ -186,3 +217,7 @@ object StringInterpolatorOpt:
186
217
sym == defn.StringContext_s ||
187
218
sym == defn.StringContext_f ||
188
219
sym == defn.StringContext_raw
220
+
221
+ extension (sc : StringContext )
222
+ def bfi (args : Shown * )(using Context ): BadFormatInterpolation =
223
+ BadFormatInterpolation (i(sc)(args* ))
0 commit comments