Skip to content

Commit

Permalink
Drop special reporter
Browse files Browse the repository at this point in the history
  • Loading branch information
som-snytt committed Jan 23, 2022
1 parent 5e62091 commit 047779a
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 98 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import dotty.tools.dotc.core.Types._
import dotty.tools.dotc.core.Phases.typerPhase

/** Formatter string checker. */
class TypedFormatChecker(args: List[Tree])(using Context)(using reporter: InterpolationReporter):
class TypedFormatChecker(partsElems: List[Tree], parts: List[String], args: List[Tree])(using Context):

val argTypes = args.map(_.tpe)
val actuals = ListBuffer.empty[Tree]
Expand All @@ -33,7 +33,7 @@ class TypedFormatChecker(args: List[Tree])(using Context)(using reporter: Interp
types.find(t => argConformsTo(argi, tpe, t))
.orElse(types.find(t => argConvertsTo(argi, tpe, t)))
.getOrElse {
reporter.argError(s"Found: ${tpe.show}, Required: ${types.mkString(", ")}", argi)
report.argError(s"Found: ${tpe.show}, Required: ${types.mkString(", ")}", argi)
actuals += args(argi)
types.head
}
Expand Down Expand Up @@ -64,20 +64,17 @@ class TypedFormatChecker(args: List[Tree])(using Context)(using reporter: Interp

/** For N part strings and N-1 args to interpolate, normalize parts and check arg types.
*
* Returns parts, possibly updated with explicit leading "%s",
* and conversions for each arg.
*
* Implementation must emit conversions required by invocations of `argType`.
* Returns normalized part strings and args, where args correcpond to conversions in tail of parts.
*/
def checked(parts0: List[String]): (List[String], List[Conversion]) =
def checked: (List[String], List[Tree]) =
val amended = ListBuffer.empty[String]
val convert = ListBuffer.empty[Conversion]

@tailrec
def loop(parts: List[String], n: Int): Unit =
parts match
def loop(remaining: List[String], n: Int): Unit =
remaining match
case part0 :: more =>
def badPart(t: Throwable): String = "".tap(_ => reporter.partError(t.getMessage, index = n, offset = 0))
def badPart(t: Throwable): String = "".tap(_ => report.partError(t.getMessage, index = n, offset = 0))
val part = try StringContext.processEscapes(part0) catch badPart
val matches = formatPattern.findAllMatchIn(part)

Expand Down Expand Up @@ -112,8 +109,11 @@ class TypedFormatChecker(args: List[Tree])(using Context)(using reporter: Interp
case Nil => ()
end loop

loop(parts0, n = 0)
(amended.toList, convert.toList)
loop(parts, n = 0)
if reported then (Nil, Nil)
else
assert(argc == actuals.size, s"Expected ${argc} args but got ${actuals.size} for [${parts.mkString(", ")}]")
(amended.toList, actuals.toList)
end checked

extension (descriptor: Match)
Expand Down Expand Up @@ -146,7 +146,7 @@ class TypedFormatChecker(args: List[Tree])(using Context)(using reporter: Interp
// the conversion char is the head of the op string (but see DateTimeXn)
val cc: Char =
kind match
case ErrorXn => '?'
case ErrorXn => if op.isEmpty then '?' else op(0)
case DateTimeXn => if op.length > 1 then op(1) else '?'
case _ => op(0)

Expand Down Expand Up @@ -243,8 +243,8 @@ class TypedFormatChecker(args: List[Tree])(using Context)(using reporter: Interp
val i = flags.indexOf(f) match { case -1 => 0 case j => j }
errorAt(Flags, i)(msg)

def errorAt(g: SpecGroup, i: Int = 0)(msg: String) = reporter.partError(msg, argi, descriptor.offset(g, i))
def warningAt(g: SpecGroup, i: Int = 0)(msg: String) = reporter.partWarning(msg, argi, descriptor.offset(g, i))
def errorAt(g: SpecGroup, i: Int = 0)(msg: String) = report.partError(msg, argi, descriptor.offset(g, i))
def warningAt(g: SpecGroup, i: Int = 0)(msg: String) = report.partWarning(msg, argi, descriptor.offset(g, i))

object Conversion:
def apply(m: Match, i: Int): Conversion =
Expand All @@ -269,4 +269,15 @@ class TypedFormatChecker(args: List[Tree])(using Context)(using reporter: Interp
end apply
val literalHelp = "use %% for literal %, %n for newline"
end Conversion

var reported = false

private def partPosAt(index: Int, offset: Int) =
val pos = partsElems(index).sourcePos
pos.withSpan(pos.span.shift(offset))

extension (r: report.type)
def argError(message: String, index: Int): Unit = r.error(message, args(index).srcPos).tap(_ => reported = true)
def partError(message: String, index: Int, offset: Int): Unit = r.error(message, partPosAt(index, offset)).tap(_ => reported = true)
def partWarning(message: String, index: Int, offset: Int): Unit = r.warning(message, partPosAt(index, offset)).tap(_ => reported = true)
end TypedFormatChecker
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,6 @@ import dotty.tools.dotc.core.Contexts.*

object FormatInterpolatorTransform:

class PartsReporter(fun: Tree, args0: Tree, parts: List[Tree], args: List[Tree])(using Context) extends InterpolationReporter:
private var reported = false
private var oldReported = false
private def partPosAt(index: Int, offset: Int) =
val pos = parts(index).sourcePos
pos.withSpan(pos.span.shift(offset))
def partError(message: String, index: Int, offset: Int): Unit =
reported = true
report.error(message, partPosAt(index, offset))
def partWarning(message: String, index: Int, offset: Int): Unit =
reported = true
report.warning(message, partPosAt(index, offset))
def argError(message: String, index: Int): Unit =
reported = true
report.error(message, args(index).srcPos)
def strCtxError(message: String): Unit =
reported = true
report.error(message, fun.srcPos)
def argsError(message: String): Unit =
reported = true
report.error(message, args0.srcPos)
def hasReported: Boolean = reported
def resetReported(): Unit =
oldReported = reported
reported = false
def restoreReported(): Unit = reported = oldReported
end PartsReporter

/** For f"${arg}%xpart", check format conversions and return (format, args)
* suitable for String.format(format, args).
*/
Expand All @@ -50,67 +22,18 @@ object FormatInterpolatorTransform:
case _ =>
report.error("Expected statically known argument list", args0.srcPos)
(Nil, EmptyTree)
given reporter: InterpolationReporter = PartsReporter(fun, args0, partsExpr, args)

def literally(s: String) = Literal(Constant(s))
inline val skip = false
if parts.lengthIs != args.length + 1 then
reporter.strCtxError {
val badParts =
if parts.isEmpty then "there are no parts"
else s"too ${if parts.lengthIs > args.length + 1 then "few" else "many"} arguments for interpolated string"
}
report.error(badParts, fun.srcPos)
(literally(""), args0)
else if skip then
val checked = parts.head :: parts.tail.map(p => if p.startsWith("%") then p else "%s" + p)
(literally(checked.mkString), args0)
else
val checker = TypedFormatChecker(args)
val (checked, cvs) = checker.checked(parts)
if reporter.hasReported then (literally(parts.mkString), args0)
else
assert(checker.argc == checker.actuals.size, s"Expected ${checker.argc}, actuals size is ${checker.actuals.size} for [${parts.mkString(", ")}]")
(literally(checked.mkString), SeqLiteral(checker.actuals.toList, elemtpt))
val checker = TypedFormatChecker(partsExpr, parts, args)
val (format, formatArgs) = checker.checked
if format.isEmpty then (literally(parts.mkString), args0)
else (literally(format.mkString), SeqLiteral(formatArgs.toList, elemtpt))
end checked
end FormatInterpolatorTransform

/** This trait defines a tool to report errors/warnings that do not depend on Position. */
trait InterpolationReporter:

/** Reports error/warning of size 1 linked with a part of the StringContext.
*
* @param message the message to report as error/warning
* @param index the index of the part inside the list of parts of the StringContext
* @param offset the index in the part String where the error is
* @return an error/warning depending on the function
*/
def partError(message: String, index: Int, offset: Int): Unit
def partWarning(message: String, index: Int, offset: Int): Unit

/** Reports error linked with an argument to format.
*
* @param message the message to report as error/warning
* @param index the index of the argument inside the list of arguments of the format function
* @return an error depending on the function
*/
def argError(message: String, index: Int): Unit

/** Reports error linked with the list of arguments or the StringContext.
*
* @param message the message to report in the error
* @return an error
*/
def strCtxError(message: String): Unit
def argsError(message: String): Unit

/** Claims whether an error or a warning has been reported
*
* @return true if an error/warning has been reported, false
*/
def hasReported: Boolean

/** Stores the old value of the reported and reset it to false */
def resetReported(): Unit

/** Restores the value of the reported boolean that has been reset */
def restoreReported(): Unit
end InterpolationReporter

0 comments on commit 047779a

Please sign in to comment.