Skip to content

Commit 719be84

Browse files
committed
Respect conversion before conform to Any
1 parent 9f444ac commit 719be84

File tree

1 file changed

+18
-14
lines changed

1 file changed

+18
-14
lines changed

compiler/src/dotty/tools/dotc/transform/localopt/FormatChecker.scala

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ class TypedFormatChecker(partsElems: List[Tree], parts: List[String], args: List
3030
def argType(argi: Int, types: Type*): Type =
3131
require(argi < argc, s"$argi out of range picking from $types")
3232
val tpe = argTypes(argi)
33-
types.find(t => argConformsTo(argi, tpe, t))
34-
.orElse(types.find(t => argConvertsTo(argi, tpe, t)))
33+
types.find(t => t != defn.AnyType && argConformsTo(argi, tpe, t))
34+
.orElse(types.find(t => t != defn.AnyType && argConvertsTo(argi, tpe, t)))
35+
.orElse(types.find(t => t == defn.AnyType && argConformsTo(argi, tpe, t)))
3536
.getOrElse {
3637
report.argError(s"Found: ${tpe.show}, Required: ${types.map(_.show).mkString(", ")}", argi)
3738
actuals += args(argi)
@@ -73,15 +74,16 @@ class TypedFormatChecker(partsElems: List[Tree], parts: List[String], args: List
7374
@tailrec
7475
def loop(remaining: List[String], n: Int): Unit =
7576
remaining match
76-
case part0 :: more =>
77+
case part0 :: remaining =>
7778
def badPart(t: Throwable): String = "".tap(_ => report.partError(t.getMessage.nn, index = n, offset = 0))
7879
val part = try StringContext.processEscapes(part0) catch badPart
7980
val matches = formatPattern.findAllMatchIn(part)
8081

8182
def insertStringConversion(): Unit =
8283
amended += "%s" + part
83-
convert += Conversion(formatPattern.findAllMatchIn("%s").next(), n) // improve
84-
argType(n-1, defn.AnyType)
84+
val cv = Conversion(n)
85+
cv.accepts(argType(n-1, defn.AnyType))
86+
convert += cv
8587
def errorLeading(op: Conversion) = op.errorAt(Spec)(s"conversions must follow a splice; ${Conversion.literalHelp}")
8688
def accept(op: Conversion): Unit =
8789
if !op.isLeading then errorLeading(op)
@@ -105,8 +107,8 @@ class TypedFormatChecker(partsElems: List[Tree], parts: List[String], args: List
105107
if n == 0 && cv.hasFlag('<') then cv.badFlag('<', "No last arg")
106108
else if !cv.isLiteral && !cv.isIndexed then errorLeading(cv)
107109

108-
loop(more, n + 1)
109-
case Nil => ()
110+
loop(remaining, n + 1)
111+
case Nil =>
110112
end loop
111113

112114
loop(parts, n = 0)
@@ -147,9 +149,10 @@ class TypedFormatChecker(partsElems: List[Tree], parts: List[String], args: List
147149
// the conversion char is the head of the op string (but see DateTimeXn)
148150
val cc: Char =
149151
kind match
150-
case ErrorXn => if op.isEmpty then '?' else op(0)
151-
case DateTimeXn => if op.length > 1 then op(1) else '?'
152-
case _ => op(0)
152+
case ErrorXn => if op.isEmpty then '?' else op(0)
153+
case DateTimeXn => if op.length <= 1 then '?' else op(1)
154+
case StringXn => if op.isEmpty then 's' else op(0) // accommodate the default %s
155+
case _ => op(0)
153156

154157
def isIndexed: Boolean = index.nonEmpty || hasFlag('<')
155158
def isError: Boolean = kind == ErrorXn
@@ -210,10 +213,9 @@ class TypedFormatChecker(partsElems: List[Tree], parts: List[String], args: List
210213
def accepts(arg: Type): Boolean =
211214
kind match
212215
case BooleanXn => arg == defn.BooleanType orElse warningAt(CC)("Boolean format is null test for non-Boolean")
213-
case IntegralXn =>
214-
arg == BigIntType || !cond(cc) {
215-
case 'o' | 'x' | 'X' if hasAnyFlag("+ (") => "+ (".filter(hasFlag).foreach(bad => badFlag(bad, s"only use '$bad' for BigInt conversions to o, x, X")) ; true
216-
}
216+
case IntegralXn => arg == BigIntType || !cond(cc) {
217+
case 'o' | 'x' | 'X' if hasAnyFlag("+ (") => "+ (".filter(hasFlag).foreach(bad => badFlag(bad, s"only use '$bad' for BigInt conversions to o, x, X")); true
218+
}
217219
case _ => true
218220

219221
// what arg type if any does the conversion accept
@@ -268,6 +270,8 @@ class TypedFormatChecker(partsElems: List[Tree], parts: List[String], args: List
268270
case Some(cc) => new Conversion(m, i, kindOf(cc(0))).tap(_.verify)
269271
case None => new Conversion(m, i, ErrorXn).tap(_.errorAt(Spec)(s"Missing conversion operator in '${m.matched}'; $literalHelp"))
270272
end apply
273+
// construct a default %s conversion
274+
def apply(i: Int): Conversion = new Conversion(formatPattern.findAllMatchIn("%").next(), i, StringXn)
271275
val literalHelp = "use %% for literal %, %n for newline"
272276
end Conversion
273277

0 commit comments

Comments
 (0)