@@ -68,48 +68,51 @@ class TypedFormatChecker(partsElems: List[Tree], parts: List[String], args: List
68
68
val amended = ListBuffer .empty[String ]
69
69
val convert = ListBuffer .empty[Conversion ]
70
70
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
+
71
108
@ 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 =>
113
116
114
117
loop(parts, n = 0 )
115
118
if reported then (Nil , Nil )
@@ -260,27 +263,30 @@ class TypedFormatChecker(partsElems: List[Tree], parts: List[String], args: List
260
263
261
264
object Conversion :
262
265
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
278
266
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" ))
281
287
end apply
282
288
// 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 )
284
290
val literalHelp = " use %% for literal %, %n for newline"
285
291
end Conversion
286
292
0 commit comments