@@ -10,6 +10,8 @@ import dotty.tools.dotc.core.Contexts.*
1010import dotty .tools .dotc .core .StdNames .*
1111import dotty .tools .dotc .core .Symbols .*
1212import dotty .tools .dotc .core .Types .*
13+ import dotty .tools .dotc .printing .Formatting .*
14+ import dotty .tools .dotc .reporting .BadFormatInterpolation
1315import dotty .tools .dotc .transform .MegaPhase .MiniPhase
1416import dotty .tools .dotc .typer .ConstFold
1517
@@ -22,16 +24,17 @@ import dotty.tools.dotc.typer.ConstFold
2224 */
2325class 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* ))
0 commit comments