diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index feae0bf68469..0ac705ab5738 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 92a6eee9b285..b2787422d5e6 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -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) @@ -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 diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index de07aa242c95..eaecd58082c7 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -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") diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index 57872eccc01c..25a6430b7d15 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -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 @@ -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 = @@ -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: * diff --git a/compiler/src/dotty/tools/dotc/printing/MessageLimiter.scala b/compiler/src/dotty/tools/dotc/printing/MessageLimiter.scala new file mode 100644 index 000000000000..c317d99f3b1e --- /dev/null +++ b/compiler/src/dotty/tools/dotc/printing/MessageLimiter.scala @@ -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 + + diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 3b7d08e2525b..468358700758 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -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 @@ -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 @@ -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. @@ -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) diff --git a/compiler/src/dotty/tools/dotc/printing/Printer.scala b/compiler/src/dotty/tools/dotc/printing/Printer.scala index 9d301a869f8d..b2aae6e8bf2a 100644 --- a/compiler/src/dotty/tools/dotc/printing/Printer.scala +++ b/compiler/src/dotty/tools/dotc/printing/Printer.scala @@ -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 } diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 3e2fe80ee3c1..4eb1a49f0bf6 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -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 @@ -467,7 +465,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { !homogenizedView && ctx.settings.XprintInline.value) ~ blockText(bindings :+ body) case tpt: untpd.DerivedTypeTree => - "" + "" case TypeTree() => typeText(toText(tree.typeOpt)) case SingletonTypeTree(ref) => diff --git a/compiler/src/dotty/tools/dotc/printing/Showable.scala b/compiler/src/dotty/tools/dotc/printing/Showable.scala index 550d80060a15..87f98fdc1891 100644 --- a/compiler/src/dotty/tools/dotc/printing/Showable.scala +++ b/compiler/src/dotty/tools/dotc/printing/Showable.scala @@ -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))) } diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 2b534dac7fbc..1a7530e8bb5e 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -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")), diff --git a/tests/neg-custom-args/allow-deep-subtypes/i8464a.scala b/tests/neg-custom-args/allow-deep-subtypes/i8464a.scala new file mode 100644 index 000000000000..518cc43f8add --- /dev/null +++ b/tests/neg-custom-args/allow-deep-subtypes/i8464a.scala @@ -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 = { } \ No newline at end of file diff --git a/tests/neg/i8464.scala b/tests/neg/i8464.scala new file mode 100644 index 000000000000..a8c3947d6ffd --- /dev/null +++ b/tests/neg/i8464.scala @@ -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 \ No newline at end of file