@@ -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
@@ -117,10 +120,10 @@ class StringInterpolatorOpt extends MiniPhase:
117120 ! (tp =:= defn.StringType )
118121 && {
119122 tp =:= defn.UnitType
120- && { report.warning(" interpolated Unit value" , t.srcPos); true }
123+ && { report.warning(bfi " interpolated Unit value " , t.srcPos); true }
121124 ||
122125 ! tp.isPrimitiveValueType
123- && { report.warning(" interpolation uses toString" , t.srcPos); true }
126+ && { report.warning(bfi " interpolation uses toString " , t.srcPos); true }
124127 }
125128 if ctx.settings.Whas .toStringInterpolated then
126129 checkIsStringify(t.tpe): Unit
@@ -134,10 +137,38 @@ class StringInterpolatorOpt extends MiniPhase:
134137 case _ => false
135138 // Perform format checking and normalization, then make it StringOps(fmt).format(args1) with tweaked args
136139 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)
138168 resolveConstructor(defn.StringOps .typeRef, List (fmt))
139169 .select(nme.format)
140170 .appliedTo(args1)
171+ end transformF
141172 // Starting with Scala 2.13, s and raw are macros in the standard
142173 // library, so we need to expand them manually.
143174 // sc.s(args) --> standardInterpolator(processEscapes, args, sc.parts)
@@ -186,3 +217,7 @@ object StringInterpolatorOpt:
186217 sym == defn.StringContext_s ||
187218 sym == defn.StringContext_f ||
188219 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