@@ -68,48 +68,51 @@ class TypedFormatChecker(partsElems: List[Tree], parts: List[String], args: List
6868 val amended = ListBuffer .empty[String ]
6969 val convert = ListBuffer .empty[Conversion ]
7070
71+ def checkPart (part : String , n : Int ): Unit =
72+ val matches = formatPattern.findAllMatchIn(part)
73+
74+ def insertStringConversion (): Unit =
75+ amended += " %s" + part
76+ val cv = Conversion .stringXn(n)
77+ cv.accepts(argType(n- 1 , defn.AnyType ))
78+ convert += cv
79+ cv.lintToString(argTypes(n- 1 ))
80+
81+ def errorLeading (op : Conversion ) = op.errorAt(Spec ):
82+ s " conversions must follow a splice; ${Conversion .literalHelp}"
83+
84+ def accept (op : Conversion ): Unit =
85+ if ! op.isLeading then errorLeading(op)
86+ op.accepts(argType(n- 1 , op.acceptableVariants* ))
87+ amended += part
88+ convert += op
89+ op.lintToString(argTypes(n- 1 ))
90+
91+ // after the first part, a leading specifier is required for the interpolated arg; %s is supplied if needed
92+ if n == 0 then amended += part
93+ else if ! matches.hasNext then insertStringConversion()
94+ else
95+ val cv = Conversion (matches.next(), n)
96+ if cv.isLiteral then insertStringConversion()
97+ else if cv.isIndexed then
98+ if cv.index.getOrElse(- 1 ) == n then accept(cv) else insertStringConversion()
99+ else if ! cv.isError then accept(cv)
100+
101+ // any remaining conversions in this part must be either literals or indexed
102+ while matches.hasNext do
103+ val cv = Conversion (matches.next(), n)
104+ if n == 0 && cv.hasFlag('<' ) then cv.badFlag('<' , " No last arg" )
105+ else if ! cv.isLiteral && ! cv.isIndexed then errorLeading(cv)
106+ end checkPart
107+
71108 @ tailrec
72- def loop (remaining : List [String ], n : Int ): Unit =
73- remaining match
74- case part0 :: remaining =>
75- def badPart (t : Throwable ): String = " " .tap(_ => report.partError(t.getMessage.nn, index = n, offset = 0 ))
76- val part = try StringContext .processEscapes(part0) catch badPart
77- val matches = formatPattern.findAllMatchIn(part)
78-
79- def insertStringConversion (): Unit =
80- amended += " %s" + part
81- val cv = Conversion (n)
82- cv.accepts(argType(n- 1 , defn.AnyType ))
83- convert += cv
84- cv.lintToString(argTypes(n- 1 ))
85-
86- def errorLeading (op : Conversion ) = op.errorAt(Spec )(s " conversions must follow a splice; ${Conversion .literalHelp}" )
87- def accept (op : Conversion ): Unit =
88- if ! op.isLeading then errorLeading(op)
89- op.accepts(argType(n- 1 , op.acceptableVariants* ))
90- amended += part
91- convert += op
92- op.lintToString(argTypes(n- 1 ))
93-
94- // after the first part, a leading specifier is required for the interpolated arg; %s is supplied if needed
95- if n == 0 then amended += part
96- else if ! matches.hasNext then insertStringConversion()
97- else
98- val cv = Conversion (matches.next(), n)
99- if cv.isLiteral then insertStringConversion()
100- else if cv.isIndexed then
101- if cv.index.getOrElse(- 1 ) == n then accept(cv) else insertStringConversion()
102- else if ! cv.isError then accept(cv)
103-
104- // any remaining conversions in this part must be either literals or indexed
105- while matches.hasNext do
106- val cv = Conversion (matches.next(), n)
107- if n == 0 && cv.hasFlag('<' ) then cv.badFlag('<' , " No last arg" )
108- else if ! cv.isLiteral && ! cv.isIndexed then errorLeading(cv)
109-
110- loop(remaining, n + 1 )
111- case Nil =>
112- end loop
109+ def loop (remaining : List [String ], n : Int ): Unit = remaining match
110+ case part0 :: remaining =>
111+ def badPart (t : Throwable ): String = " " .tap(_ => report.partError(t.getMessage.nn, index = n, offset = 0 ))
112+ val part = try StringContext .processEscapes(part0) catch badPart
113+ checkPart(part, n)
114+ loop(remaining, n + 1 )
115+ case Nil =>
113116
114117 loop(parts, n = 0 )
115118 if reported then (Nil , Nil )
@@ -260,27 +263,30 @@ class TypedFormatChecker(partsElems: List[Tree], parts: List[String], args: List
260263
261264 object Conversion :
262265 def apply (m : Match , i : Int ): Conversion =
263- def kindOf (cc : Char ) = cc match
264- case 's' | 'S' => StringXn
265- case 'h' | 'H' => HashXn
266- case 'b' | 'B' => BooleanXn
267- case 'c' | 'C' => CharacterXn
268- case 'd' | 'o' |
269- 'x' | 'X' => IntegralXn
270- case 'e' | 'E' |
271- 'f' |
272- 'g' | 'G' |
273- 'a' | 'A' => FloatingPointXn
274- case 't' | 'T' => DateTimeXn
275- case '%' | 'n' => LiteralXn
276- case _ => ErrorXn
277- end kindOf
278266 m.group(CC ) match
279- case Some (cc) => new Conversion (m, i, kindOf(cc(0 ))).tap(_.verify)
280- case None => new Conversion (m, i, ErrorXn ).tap(_.errorAt(Spec )(s " Missing conversion operator in ' ${m.matched}'; $literalHelp" ))
267+ case Some (cc) =>
268+ val xn = cc(0 ) match
269+ case 's' | 'S' => StringXn
270+ case 'h' | 'H' => HashXn
271+ case 'b' | 'B' => BooleanXn
272+ case 'c' | 'C' => CharacterXn
273+ case 'd' | 'o' |
274+ 'x' | 'X' => IntegralXn
275+ case 'e' | 'E' |
276+ 'f' |
277+ 'g' | 'G' |
278+ 'a' | 'A' => FloatingPointXn
279+ case 't' | 'T' => DateTimeXn
280+ case '%' | 'n' => LiteralXn
281+ case _ => ErrorXn
282+ new Conversion (m, i, xn)
283+ .tap(_.verify)
284+ case None =>
285+ new Conversion (m, i, ErrorXn )
286+ .tap(_.errorAt(Spec )(s " Missing conversion operator in ' ${m.matched}'; $literalHelp" ))
281287 end apply
282288 // construct a default %s conversion
283- def apply (i : Int ): Conversion = new Conversion (formatPattern.findAllMatchIn(" %" ).next(), i, StringXn )
289+ def stringXn (i : Int ): Conversion = new Conversion (formatPattern.findAllMatchIn(" %" ).next(), i, StringXn )
284290 val literalHelp = " use %% for literal %, %n for newline"
285291 end Conversion
286292
0 commit comments