Skip to content

Fix #8464: Refine recursion limits for printing #8498

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -974,7 +974,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
}
}

def summaryString: String = tp.showSummary
def summaryString: String = tp.showSummary()

def params: List[Symbol] =
Nil // backend uses this to emit annotations on parameter lists of forwarders
Expand Down
6 changes: 1 addition & 5 deletions compiler/src/dotty/tools/dotc/core/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ object Contexts {
owner = NoSymbol
tree = untpd.EmptyTree
typeAssigner = TypeAssigner
moreProperties = Map.empty
moreProperties = Map(MessageLimiter -> DefaultMessageLimiter())
source = NoSource
store = initialStore
.updated(settingsStateLoc, settingsGroup.defaultState)
Expand Down Expand Up @@ -772,10 +772,6 @@ object Contexts {

private[core] var denotTransformers: Array[DenotTransformer] = _

// Printers state
/** Number of recursive invocations of a show method on current stack */
private[dotc] var toTextRecursions: Int = 0

// Reporters state
private[dotc] var indent: Int = 0

Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Mode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ object Mode {
/** Assume -language:strictEquality */
val StrictEquality: Mode = newMode(9, "StrictEquality")

/** We are currently printing something: avoid to produce more logs about
* the printing
/** We are currently printing something: avoid producing more logs about
* the printing.
*/
val Printing: Mode = newMode(10, "Printing")

Expand Down
26 changes: 17 additions & 9 deletions compiler/src/dotty/tools/dotc/printing/Formatting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ object Formatting {
case NonFatal(ex)
if !ctx.mode.is(Mode.PrintShowExceptions) &&
!ctx.settings.YshowPrintErrors.value =>
val msg = ex match { case te: TypeError => te.toMessage case _ => ex.getMessage }
val msg = ex match
case te: TypeError => te.toMessage
case _ => ex.getMessage
s"[cannot display due to $msg, raw string = ${arg.toString}]"
}
case _ => arg.toString
Expand Down Expand Up @@ -69,10 +71,9 @@ object Formatting {
* like concatenation, stripMargin etc on the values returned by em"...", and in the current error
* message composition methods, this is crucial.
*/
class ErrorMessageFormatter(sc: StringContext) extends StringFormatter(sc) {
class ErrorMessageFormatter(sc: StringContext) extends StringFormatter(sc):
override protected def showArg(arg: Any)(implicit ctx: Context): String =
wrapNonSensical(arg, super.showArg(arg))
}
wrapNonSensical(arg, super.showArg(arg)(using errorMessageCtx))

class SyntaxFormatter(sc: StringContext) extends StringFormatter(sc) {
override protected def showArg(arg: Any)(implicit ctx: Context): String =
Expand Down Expand Up @@ -254,12 +255,19 @@ object Formatting {
if (explainLines.isEmpty) "" else i"where: $explainLines%\n %\n"
}

private def errorMessageCtx(using ctx: Context): Context =
ctx.property(MessageLimiter) match
case Some(_: ErrorMessageLimiter) => ctx
case _ => ctx.fresh.setProperty(MessageLimiter, ErrorMessageLimiter())

/** Context with correct printer set for explanations */
private def explainCtx(seen: Seen)(implicit ctx: Context): Context = ctx.printer match {
case dp: ExplainingPrinter =>
ctx // re-use outer printer and defer explanation to it
case _ => ctx.fresh.setPrinterFn(ctx => new ExplainingPrinter(seen)(ctx))
}
private def explainCtx(seen: Seen)(implicit ctx: Context): Context =
val ectx = errorMessageCtx
ectx.printer match
case dp: ExplainingPrinter =>
ectx // re-use outer printer and defer explanation to it
case _ =>
ectx.fresh.setPrinterFn(ctx => new ExplainingPrinter(seen)(ctx))

/** Entrypoint for explanation string interpolator:
*
Expand Down
55 changes: 55 additions & 0 deletions compiler/src/dotty/tools/dotc/printing/MessageLimiter.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package dotty.tools
package dotc
package printing

import core._
import Contexts.Context
import util.Property
import Texts.Text

abstract class MessageLimiter:

protected def recurseLimit = 100
protected var recurseCount: Int = 0

def register(str: String): Unit = ()

protected def recursionLimitExceeded()(using Context): Unit = ()

final def controlled(op: => Text)(using Context): Text =
if recurseCount < recurseLimit then
try
recurseCount += 1
op
finally
recurseCount -= 1
else
recursionLimitExceeded()
"..."

object MessageLimiter extends Property.Key[MessageLimiter]

class DefaultMessageLimiter extends MessageLimiter:
override def recursionLimitExceeded()(using ctx: Context): Unit =
if ctx.debug then
ctx.warning("Exceeded recursion depth attempting to print.")
Thread.dumpStack()

class SummarizeMessageLimiter(depth: Int) extends MessageLimiter:
override val recurseLimit = recurseCount + depth
override def recursionLimitExceeded()(using ctx: Context): Unit = ()

class ErrorMessageLimiter extends MessageLimiter:
private val initialRecurseLimit = 50
private val sizeLimit = 10000

private var textLength: Int = 0

override def register(str: String): Unit =
textLength += str.length

override def recurseLimit =
val freeFraction: Double = ((sizeLimit - textLength) max 0).toDouble / sizeLimit
(initialRecurseLimit * freeFraction).toInt


40 changes: 10 additions & 30 deletions compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import typer.ImportInfo
import Variances.varianceSign
import util.SourcePosition
import java.lang.Integer.toOctalString
import config.Config.summarizeDepth
import scala.util.control.NonFatal
import scala.annotation.switch

Expand All @@ -23,24 +22,15 @@ class PlainPrinter(_ctx: Context) extends Printer {

protected def maxToTextRecursions: Int = 100

protected final def controlled(op: => Text): Text =
if (ctx.base.toTextRecursions < maxToTextRecursions && ctx.base.toTextRecursions < maxSummarized)
try {
ctx.base.toTextRecursions += 1
op
}
finally
ctx.base.toTextRecursions -= 1
else {
if (ctx.base.toTextRecursions >= maxToTextRecursions)
recursionLimitExceeded()
"..."
}
protected final def limiter: MessageLimiter = ctx.property(MessageLimiter).get

protected def recursionLimitExceeded(): Unit = {
ctx.warning("Exceeded recursion depth attempting to print.")
if (ctx.debug) Thread.dumpStack()
}
protected def controlled(op: => Text): Text = limiter.controlled(op)

def Str(str: String, lineRange: LineRange = EmptyLineRange): Str =
limiter.register(str)
Texts.Str(str, lineRange)

given stringToText as Conversion[String, Text] = Str(_)

/** If true, tweak output so it is the same before and after pickling */
protected def homogenizedView: Boolean = ctx.settings.YtestPickler.value
Expand Down Expand Up @@ -112,7 +102,8 @@ class PlainPrinter(_ctx: Context) extends Printer {
/** Pretty-print comma-separated type arguments for a constructor to be inserted among parentheses or brackets
* (hence with `GlobalPrec` precedence).
*/
protected def argsText(args: List[Type]): Text = atPrec(GlobalPrec) { Text(args.map(arg => argText(arg) ), ", ") }
protected def argsText(args: List[Type]): Text =
atPrec(GlobalPrec) { Text(args.map(arg => argText(arg) ), ", ") }

/** The longest sequence of refinement types, starting at given type
* and following parents.
Expand Down Expand Up @@ -573,17 +564,6 @@ class PlainPrinter(_ctx: Context) extends Printer {
case _ => "{...}"
s"import $exprStr.$selectorStr"

private var maxSummarized = Int.MaxValue

def summarized[T](depth: Int)(op: => T): T = {
val saved = maxSummarized
maxSummarized = ctx.base.toTextRecursions + depth
try op
finally maxSummarized = saved
}

def summarized[T](op: => T): T = summarized(summarizeDepth)(op)

def plain: PlainPrinter = this

protected def keywordStr(text: String): String = coloredStr(text, SyntaxHighlighting.KeywordColor)
Expand Down
5 changes: 0 additions & 5 deletions compiler/src/dotty/tools/dotc/printing/Printer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -167,11 +167,6 @@ abstract class Printer {
def toTextGlobal(elems: Traversable[Showable], sep: String): Text =
atPrec(GlobalPrec) { toText(elems, sep) }

/** Perform string or text-producing operation `op` so that only a
* summarized text with given recursion depth is shown
*/
def summarized[T](depth: Int)(op: => T): T

/** A plain printer without any embellishments */
def plain: Printer
}
Expand Down
4 changes: 1 addition & 3 deletions compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
case _ => false
}

override protected def recursionLimitExceeded(): Unit = {}

protected def PrintableFlags(isType: Boolean): FlagSet = {
val fs =
if (isType) TypeSourceModifierFlags | Module | Local // DOTTY problem: cannot merge these two statements
Expand Down Expand Up @@ -467,7 +465,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
!homogenizedView && ctx.settings.XprintInline.value) ~
blockText(bindings :+ body)
case tpt: untpd.DerivedTypeTree =>
"<derived typetree watching " ~ summarized(toText(tpt.watched)) ~ ">"
"<derived typetree watching " ~ tpt.watched.showSummary() ~ ">"
case TypeTree() =>
typeText(toText(tree.typeOpt))
case SingletonTypeTree(ref) =>
Expand Down
6 changes: 2 additions & 4 deletions compiler/src/dotty/tools/dotc/printing/Showable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ trait Showable extends Any {
* Recursion depth is limited to some smallish value. Default is
* Config.summarizeDepth.
*/
def showSummary(depth: Int)(implicit ctx: Context): String =
ctx.printer.summarized(depth)(show)

def showSummary(implicit ctx: Context): String = showSummary(summarizeDepth)
def showSummary(depth: Int = summarizeDepth)(using ctx: Context): String =
show(using ctx.fresh.setProperty(MessageLimiter, SummarizeMessageLimiter(depth)))
}
1 change: 1 addition & 0 deletions compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ class CompilationTests extends ParallelTesting {
compileFilesInDir("tests/neg-custom-args/fatal-warnings", defaultOptions.and("-Xfatal-warnings")),
compileFilesInDir("tests/neg-custom-args/erased", defaultOptions.and("-Yerased-terms")),
compileFilesInDir("tests/neg-custom-args/allow-double-bindings", allowDoubleBindings),
compileFilesInDir("tests/neg-custom-args/allow-deep-subtypes", allowDeepSubtypes),
compileFilesInDir("tests/neg-custom-args/explicit-nulls", defaultOptions.and("-Yexplicit-nulls")),
compileDir("tests/neg-custom-args/impl-conv", defaultOptions.and("-Xfatal-warnings", "-feature")),
compileFile("tests/neg-custom-args/implicit-conversions.scala", defaultOptions.and("-Xfatal-warnings", "-feature")),
Expand Down
19 changes: 19 additions & 0 deletions tests/neg-custom-args/allow-deep-subtypes/i8464a.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
type Id = String

enum Kind {
case Type
}

enum Term[T <: Term[T, K], K] {
case Wrap(t: T)
case Fun(id: Id, tag: K, ret: Term[T, K])
}

enum Type {
case Var(id: Id)
}

val tExp: Term[Type, Kind] =
Term.Fun("x", Kind.Type, Term.Wrap(Type.Var("x"))) // error

def main(args: Array[String]): Unit = { }
11 changes: 11 additions & 0 deletions tests/neg/i8464.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
enum Term[T <: Term[T]] {
case Wrap(t: T)
case Fun(id: Id, tag: K, ret: Term[T]) // error // error
}

enum Type {
case Var(id: Id) // error
}

val tExp: Term[Type, Kind] = // error
Term.Fun("x", Term.Wrap(Type.Var("x"))) // error