From 1e2b23075f70d19025e548bb6c5c527367741a95 Mon Sep 17 00:00:00 2001 From: Solal Pirelli Date: Wed, 29 Apr 2026 13:16:56 +0200 Subject: [PATCH] fuel --- compiler/src/dotty/tools/dotc/Driver.scala | 9 +- .../tools/dotc/config/ScalaSettings.scala | 2 + .../src/dotty/tools/dotc/core/Contexts.scala | 19 +++ .../tools/dotc/core/OrderingConstraint.scala | 25 ++-- .../tools/dotc/core/RecursiveOperation.scala | 48 +++++++ .../tools/dotc/core/SymDenotations.scala | 9 +- .../tools/dotc/core/TypeApplications.scala | 62 ++++----- .../dotty/tools/dotc/core/TypeComparer.scala | 35 +++-- .../dotty/tools/dotc/core/TypeErasure.scala | 8 +- .../dotty/tools/dotc/core/TypeErrors.scala | 64 --------- .../src/dotty/tools/dotc/core/TypeOps.scala | 12 +- .../src/dotty/tools/dotc/core/Types.scala | 125 +++++++++--------- .../tools/dotc/core/tasty/TreePickler.scala | 3 +- .../dotty/tools/dotc/reporting/Message.scala | 2 +- .../dotty/tools/dotc/reporting/Reporter.scala | 7 +- .../dotty/tools/dotc/typer/Implicits.scala | 3 +- .../dotty/tools/dotc/typer/Inferencing.scala | 46 +++---- .../tools/dotc/typer/VarianceChecker.scala | 10 +- compiler/src/dotty/tools/package.scala | 5 - tests/pos-deep-subtype/i4036.scala | 1 - 20 files changed, 236 insertions(+), 259 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/core/RecursiveOperation.scala diff --git a/compiler/src/dotty/tools/dotc/Driver.scala b/compiler/src/dotty/tools/dotc/Driver.scala index 4896664c4e8f..134184098e98 100644 --- a/compiler/src/dotty/tools/dotc/Driver.scala +++ b/compiler/src/dotty/tools/dotc/Driver.scala @@ -176,7 +176,14 @@ class Driver { compileCtx.setReporter(reporter) if (callback != null) compileCtx.setCompilerCallback(callback) - process(args, compileCtx) + try + process(args, compileCtx) + catch { + case so: StackOverflowError => + report.error("oopsie")(using compileCtx) + so.printStackTrace() + compileCtx.reporter + } } /** Entry point to the compiler with no optional arguments. diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index d814269cf1c9..61b86a79c315 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -462,6 +462,8 @@ private sealed trait XSettings: val XjarCompressionLevel: Setting[Int] = IntChoiceSetting(AdvancedSetting, "Xjar-compression-level", "compression level to use when writing jar files", Deflater.DEFAULT_COMPRESSION to Deflater.BEST_COMPRESSION, Deflater.DEFAULT_COMPRESSION) val XkindProjector: Setting[String] = ChoiceSetting(AdvancedSetting, "Xkind-projector", "[underscores, enable, disable]", "Allow `*` as type lambda placeholder to be compatible with kind projector. When invoked as -Xkind-projector:underscores will repurpose `_` to be a type parameter placeholder, this will disable usage of underscore as a wildcard.", List("disable", "", "underscores"), "disable", legacyArgs = true) + val XmaxFuel: Setting[Int] = IntSetting(AdvancedSetting, "XmaxFuel", "Max fuel for recursive operations before abandoning.", 400) + /** Documentation related settings */ val XdropComments: Setting[Boolean] = BooleanSetting(AdvancedSetting, "Xdrop-docs", "Drop documentation when scanning source files.", aliases = List("-Xdrop-comments")) val XcookComments: Setting[Boolean] = BooleanSetting(AdvancedSetting, "Xcook-docs", "Cook the documentation (type check `@usecase`, etc.)", aliases = List("-Xcook-comments")) diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 4d5ed74d2040..59845db58081 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -523,6 +523,19 @@ object Contexts { final def withUncommittedTyperState: Context = withTyperState(typerState.uncommittedAncestor) + /** Ensures recursive operations obey the fuel limit, and throws user-friendly errors when they do not. */ + inline final def handleRecursive[T](name: String, details: => String, weight: Int = 1)(inline block: T): T = + val op = RecursiveOperation(name, details, weight) + if base.recursiveDepth >= settings.XmaxFuel.value then + throw new RecursionOverflow(op :: base.recursiveOperations) + base.recursiveOperations = op :: base.recursiveOperations + base.recursiveDepth += 1 + try + block + finally + base.recursiveDepth -= 1 + base.recursiveOperations = base.recursiveOperations.tail + final def withProperty[T](key: Key[T], value: Option[T]): Context = if (property(key) == value) this else value match { @@ -611,6 +624,9 @@ object Contexts { private var _source: SourceFile = uninitialized final def source: SourceFile = _source + private var _recursiveOperations: List[RecursiveOperation] = uninitialized + final def recursiveOperations: List[RecursiveOperation] = _recursiveOperations + private var _moreProperties: Map[Key[Any], Any] = uninitialized final def moreProperties: Map[Key[Any], Any] = _moreProperties @@ -1021,6 +1037,9 @@ object Contexts { */ private[dotc] var coverage: Coverage | Null = null + private[dotc] var recursiveDepth: Int = 0 + private[dotc] var recursiveOperations: List[RecursiveOperation] = Nil + // Types state /** A table for hash consing unique types */ private[core] val uniques: Uniques = Uniques() diff --git a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala index cd7de188faa0..f26a6d29d8ad 100644 --- a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala +++ b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala @@ -346,20 +346,19 @@ class OrderingConstraint(private val boundsMap: ParamBounds, if newSet.isEmpty then deps.remove(referenced) else deps.updated(referenced, newSet) - def traverse(t: Type) = try + def traverse(t: Type) = ctx.handleRecursive("adjust", t.show): t match - case param: TypeParamRef => - if hasBounds(param) then - if variance >= 0 then coDeps = update(coDeps, param) - if variance <= 0 then contraDeps = update(contraDeps, param) - else - traverse(entry(param)) - case tp: LazyRef => - if !seen.contains(tp) then - seen += tp - traverse(tp.ref) - case _ => traverseChildren(t) - catch case ex: Throwable => handleRecursive("adjust", t.show, ex) + case param: TypeParamRef => + if hasBounds(param) then + if variance >= 0 then coDeps = update(coDeps, param) + if variance <= 0 then contraDeps = update(contraDeps, param) + else + traverse(entry(param)) + case tp: LazyRef => + if !seen.contains(tp) then + seen += tp + traverse(tp.ref) + case _ => traverseChildren(t) end Adjuster /** Adjust dependencies to account for the delta of previous entry `prevEntry` diff --git a/compiler/src/dotty/tools/dotc/core/RecursiveOperation.scala b/compiler/src/dotty/tools/dotc/core/RecursiveOperation.scala new file mode 100644 index 000000000000..507493f44455 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/RecursiveOperation.scala @@ -0,0 +1,48 @@ +package dotty.tools +package dotc +package core + +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Decorators.* +import dotty.tools.dotc.reporting.Message + +/* TODO: +- Catch StackOverflowError properly in main + */ + +/** + * Operation that may cause unbounded recursion depending on user input. + * @param title the operation title + * @param details the lazily-initialized operation details + * @param weight the operation weight, used to prioritize some operations when displaying error messages + */ +class RecursiveOperation(val title: String, details: => String, val weight: Int): + def explanation: String = s"$title $details" + +/** + * Thrown when recursing too deep, as an alternative to triggering a stack overflow. + * + * @param ops the recursive operations, most recent first, i.e., in reverse order + */ +class RecursionOverflow(val ops: List[RecursiveOperation])(using Context) extends TypeError: + override def fillInStackTrace(): Throwable = + this + + private def opsString(rs: List[RecursiveOperation]): String = { + val maxShown = 20 + if (rs.lengthCompare(maxShown) > 0) + i"""${opsString(rs.take(maxShown / 2))} + | ... + |${opsString(rs.takeRight(maxShown / 2))}""" + else + rs.map(_.explanation).mkString("\n ", "\n| ", "") + } + + override def toMessage(using Context): Message = + val mostCommon = ops.groupBy(_.title).toList.maxBy(_._2.map(_.weight).sum)._2 + em"""Recursion limit exceeded. + |Maybe there is an illegal cyclic reference? + |If that's not the case, you could also try to increase the stacksize using the -Xss JVM option. + |For the unprocessed stack trace, compile with -Xno-enrich-error-messages. + |A recurring operation is (inner to outer): + |${opsString(mostCommon).stripMargin}""" diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 98716fd6f595..e05572da58f6 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -2343,7 +2343,7 @@ object SymDenotations { tp.derivedCapturingType(recur(parent), refs) case tp: TypeProxy => - def computeTypeProxy = { + def computeTypeProxy = ctx.handleRecursive("type proxy of", i"$tp"): val superTp = tp.superType val baseTp = recur(superTp) tp match { @@ -2352,7 +2352,6 @@ object SymDenotations { case _ => } baseTp - } computeTypeProxy case tp: AndOrType => @@ -2412,7 +2411,7 @@ object SymDenotations { def computeMemberNames(keepOnly: NameFilter)(implicit onBehalf: MemberNames, ctx: Context): Set[Name] = { var names = Set[Name]() def maybeAdd(name: Name) = if (keepOnly(thisType, name)) names += name - try { + ctx.handleRecursive("member names", i"of $this"): for ptype <- parentTypes do ptype.classSymbol match case pcls: ClassSymbol => @@ -2426,10 +2425,6 @@ object SymDenotations { else info.decls.iterator for (sym <- ownSyms) maybeAdd(sym.name) names - } - catch { - case ex: Throwable => handleRecursive("member names", i"of $this", ex) - } } override final def fullNameSeparated(kind: QualifiedNameKind)(using Context): Name = { diff --git a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala index 97b0af3e3a4f..88f70bfa8486 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala @@ -178,14 +178,15 @@ class TypeApplications(val self: Type) extends AnyVal { case NoPrefix => true case _ => false } - try self match { - case self: TypeRef => - val tsym = self.symbol - if (tsym.isClass) tsym.typeParams - else tsym.infoOrCompleter match { - case info: LazyType if isTrivial(self.prefix, tsym) => - val tparams = info.completerTypeParams(tsym) - if tsym.isCompleted then tsym.info.typeParams + ctx.handleRecursive("type parameters of", self.show): + self match + case self: TypeRef => + val tsym = self.symbol + if (tsym.isClass) tsym.typeParams + else tsym.infoOrCompleter match { + case info: LazyType if isTrivial(self.prefix, tsym) => + val tparams = info.completerTypeParams(tsym) + if tsym.isCompleted then tsym.info.typeParams // Completers sometimes represent parameters as symbols where // the completed type represents them as paramrefs. Make sure we get // a stable result by calling `typeParams` recursively. Test case @@ -193,28 +194,24 @@ class TypeApplications(val self: Type) extends AnyVal { // After calling its completerTypeParams, we get a list of parameter symbols // and as a side effect F0 is completed. Calling typeParams on the completed // type gives a list of paramrefs. - else tparams - case _ => self.info.typeParams - } - case self: AppliedType => - if (self.tycon.typeSymbol.isClass) Nil - else self.superType.typeParams - case self: ClassInfo => - self.cls.typeParams - case self: HKTypeLambda => - self.typeParams - case _: SingletonType | _: RefinedType | _: RecType => - Nil - case self: WildcardType => - self.optBounds.typeParams - case self: TypeProxy => - self.superType.typeParams - case _ => - Nil - } - catch { - case ex: Throwable => handleRecursive("type parameters of", self.show, ex) - } + else tparams + case _ => self.info.typeParams + } + case self: AppliedType => + if (self.tycon.typeSymbol.isClass) Nil + else self.superType.typeParams + case self: ClassInfo => + self.cls.typeParams + case self: HKTypeLambda => + self.typeParams + case _: SingletonType | _: RefinedType | _: RecType => + Nil + case self: WildcardType => + self.optBounds.typeParams + case self: TypeProxy => + self.superType.typeParams + case _ => + Nil } /** Substitute in `self` the type parameters of `tycon` by some other types. */ @@ -399,12 +396,9 @@ class TypeApplications(val self: Type) extends AnyVal { if hasParamsWithoutArg then AppliedType(self, args) else - try + ctx.handleRecursive("try to instantiate", i"$dealiased[$args%, %]"): val instantiated = dealiased.instantiate(args) if (followAlias) instantiated.normalized else instantiated - catch - case ex: Throwable => handleRecursive("try to instantiate", i"$dealiased[$args%, %]", ex) - else AppliedType(self, args) } else dealiased.resType match { diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 9bb179c80bbc..bf73bebc17b2 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -223,14 +223,12 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling this.leftRoot = tp1 } else this.approx = a - try recur(tp1, tp2) - catch { - case ex: Throwable => handleRecursive("subtype", i"$tp1 <:< $tp2", ex, weight = 2) - } - finally { + try + ctx.handleRecursive("subtype", i"$tp1 <:< $tp2", weight = 2): + recur(tp1, tp2) + finally this.approx = savedApprox this.leftRoot = savedLeftRoot - } } def isSubType(tp1: Type, tp2: Type): Boolean = isSubType(tp1, tp2, ApproxState.Fresh) @@ -3337,18 +3335,19 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling || (cannotBeNothing(tp1) || cannotBeNothing(tp2)) } - args1.lazyZip(args2).lazyZip(cls.typeParams).exists { - (arg1, arg2, tparam) => - val v = tparam.paramVarianceSign - if (v > 0) - covariantDisjoint(arg1, arg2, tparam) - else if (v < 0) - // Contravariant case: a value where this type parameter is - // instantiated to `Any` belongs to both types. - false - else - invariantDisjoint(arg1, arg2, tparam) - } + ctx.handleRecursive("are args provably disjoint for", i"$cls"): + args1.lazyZip(args2).lazyZip(cls.typeParams).exists { + (arg1, arg2, tparam) => + val v = tparam.paramVarianceSign + if (v > 0) + covariantDisjoint(arg1, arg2, tparam) + else if (v < 0) + // Contravariant case: a value where this type parameter is + // instantiated to `Any` belongs to both types. + false + else + invariantDisjoint(arg1, arg2, tparam) + } end provablyDisjointTypeArgs protected def explainingTypeComparer(short: Boolean) = ExplainingTypeComparer(comparerContext, short) diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 37ac623714ab..8e17794a420e 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -930,10 +930,10 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst else if sourceLanguage.isScala2 && (elemtp.hiBound.isNullType || elemtp.hiBound.isNothingType) then JavaArrayType(defn.ObjectType) else - try erasureFn(sourceLanguage, semiEraseVCs = false, isConstructor, isSymbol, inSigName)(elemtp) match - case _: WildcardType => WildcardType - case elem => JavaArrayType(elem) - catch case ex: Throwable => handleRecursive("erase array type", tp.show, ex) + ctx.handleRecursive("erase array type", tp.show): + erasureFn(sourceLanguage, semiEraseVCs = false, isConstructor, isSymbol, inSigName)(elemtp) match + case _: WildcardType => WildcardType + case elem => JavaArrayType(elem) } private def erasePair(tp: Type)(using Context): Type = { diff --git a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala index 4b28d17fc9d2..596d8d0b189c 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala @@ -16,7 +16,6 @@ import util.Property import config.Printers.{cyclicErrors, noPrinter} import collection.mutable -import scala.annotation.constructorOnly abstract class TypeError(using creationContext: Context) extends Exception(""): @@ -85,69 +84,6 @@ class MissingType(val pre: Type, val name: Name)(using Context) extends TypeErro |$reason.""" end MissingType -class RecursionOverflow(val op: String, details: => String, val previous: Throwable, val weight: Int)(using Context) -extends TypeError: - - def explanation: String = s"$op $details" - - private def recursions: List[RecursionOverflow] = { - val result = mutable.ListBuffer.empty[RecursionOverflow] - @annotation.tailrec def loop(throwable: Throwable): List[RecursionOverflow] = throwable match { - case ro: RecursionOverflow => - result += ro - loop(ro.previous) - case _ => result.toList - } - - loop(this) - } - - def opsString(rs: List[RecursionOverflow])(using Context): String = { - val maxShown = 20 - if (rs.lengthCompare(maxShown) > 0) - i"""${opsString(rs.take(maxShown / 2))} - | ... - |${opsString(rs.takeRight(maxShown / 2))}""" - else - (rs.map(_.explanation): List[String]).mkString("\n ", "\n| ", "") - } - - override def toMessage(using Context): Message = - val mostCommon = recursions.groupBy(_.op).toList.maxBy(_._2.map(_.weight).sum)._2.reverse - em"""Recursion limit exceeded. - |Maybe there is an illegal cyclic reference? - |If that's not the case, you could also try to increase the stacksize using the -Xss JVM option. - |For the unprocessed stack trace, compile with -Xno-enrich-error-messages. - |A recurring operation is (inner to outer): - |${opsString(mostCommon).stripMargin}""" - - override def fillInStackTrace(): Throwable = this - override def getStackTrace(): Array[StackTraceElement] = previous.getStackTrace().asInstanceOf -end RecursionOverflow - -/** Post-process exceptions that might result from StackOverflow to add - * tracing information while unwalking the stack. - */ -// Beware: Since this object is only used when handling a StackOverflow, this code -// cannot consume significant amounts of stack. -object handleRecursive: - private inline def underlyingStackOverflowOrNull(exc: Throwable): Throwable | Null = - var e: Throwable | Null = exc - while e != null && !e.isInstanceOf[StackOverflowError] do e = e.getCause - e - - def apply(op: String, details: => String, exc: Throwable, weight: Int = 1)(using Context): Nothing = - if ctx.settings.XnoEnrichErrorMessages.value then - throw exc - else exc match - case _: RecursionOverflow => - throw new RecursionOverflow(op, details, exc, weight) - case _ => - val so = underlyingStackOverflowOrNull(exc) - if so != null then throw new RecursionOverflow(op, details, so, weight) - else throw exc -end handleRecursive - /** * This TypeError signals that completing denot encountered a cycle: it asked for denot.info (or similar), * so it requires knowing denot already. diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 069cca698d31..1831c15239d1 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -32,7 +32,7 @@ object TypeOps: /** The type `tp` as seen from prefix `pre` and owner `cls`. See the spec * for what this means. */ - final def asSeenFrom(tp: Type, pre: Type, cls: Symbol)(using Context): Type = { + final def asSeenFrom(tp: Type, pre: Type, cls: Symbol)(using Context): Type = ctx.handleRecursive("checking 'as seen from' for", i"$tp from $pre and $cls"): pre match { case pre: QualSkolemType => // When a selection has an unstable qualifier, the qualifier type gets @@ -52,9 +52,7 @@ object TypeOps: Stats.record("asSeenFrom skolem prefix required") case _ => } - new AsSeenFromMap(pre, cls).apply(tp) - } /** The TypeMap handling the asSeenFrom */ class AsSeenFromMap(pre: Type, cls: Symbol)(using Context) extends ApproximatingTypeMap { @@ -130,7 +128,7 @@ object TypeOps: pre.isStable || !ctx.phase.isTyper && ctx.mode.is(Mode.ImplicitsEnabled) /** Implementation of Types#simplified */ - def simplify(tp: Type, theMap: SimplifyMap | Null)(using Context): Type = { + def simplify(tp: Type, theMap: SimplifyMap | Null)(using Context): Type = ctx.handleRecursive("simplify", i"$tp") { def mapOver = (if (theMap != null) theMap else new SimplifyMap).mapOver(tp) tp match { case tp: NamedType => @@ -472,7 +470,7 @@ object TypeOps: case _ => true override def apply(tp: Type): Type = - try + ctx.handleRecursive("traversing for avoiding local references", s"${tp.show}"): tp match case tp: TermRef if toAvoid(tp) => tp.info.widenExpr.dealiasKeepRefiningAnnots match { @@ -504,7 +502,6 @@ object TypeOps: mapOver(tl) case _ => super.apply(tp) - catch case ex: Throwable => handleRecursive("traversing for avoiding local references", s"${tp.show}", ex) end apply /** Three deviations from standard derivedSelect: @@ -790,7 +787,7 @@ object TypeOps: val singletons = util.HashMap[Symbol, SingletonType]() val gadtSyms = new mutable.ListBuffer[Symbol] - def traverse(tp: Type) = try + def traverse(tp: Type) = ctx.handleRecursive("traverseTp2", tp.show): val tpd = tp.dealias if tpd ne tp then traverse(tpd) else tp match @@ -809,7 +806,6 @@ object TypeOps: traverseChildren(tp.info) case _ => traverseChildren(tp) - catch case ex: Throwable => handleRecursive("traverseTp2", tp.show, ex) TraverseTp2.traverse(tp2) val singletons = TraverseTp2.singletons val gadtSyms = TraverseTp2.gadtSyms.toList diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index e6717b51dff5..b5a48b41bf9b 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -46,6 +46,7 @@ import transform.Recheck.currentRechecker import scala.annotation.internal.sharable import scala.annotation.threadUnsafe +import scala.util.control.NonFatal object Types extends TypeUtils { @@ -285,33 +286,33 @@ object Types extends TypeUtils { tp.isBottomType && (tp.hasClassSymbol(defn.NothingClass) || cls != defn.NothingClass && !cls.isValueClass) - def loop(tp: Type): Boolean = try tp match - case tp: TypeRef => - val sym = tp.symbol - if (sym.isClass) sym.derivesFrom(cls, defaultIfUnknown) else loop(tp.superType) - case tp: AppliedType => - tp.superType.derivesFrom(cls) - case tp: MatchType => - tp.bound.derivesFrom(cls) || tp.reduced.derivesFrom(cls) - case tp: TypeProxy => - loop(tp.underlying) - case tp: AndType => - loop(tp.tp1) || loop(tp.tp2) - case tp: OrType => - // If the type is `T | Null` or `T | Nothing`, the class is != Nothing, - // and `T` derivesFrom the class, then the OrType derivesFrom the class. - // Otherwise, we need to check both sides derivesFrom the class. - if isLowerBottomType(tp.tp1) then - loop(tp.tp2) - else if isLowerBottomType(tp.tp2) then - loop(tp.tp1) - else - loop(tp.tp1) && loop(tp.tp2) - case tp: JavaArrayType => - cls == defn.ObjectClass - case _ => - false - catch case ex: Throwable => handleRecursive(i"derivesFrom $cls:", show, ex) + def loop(tp: Type): Boolean = ctx.handleRecursive("derivesFrom", i"$cls $this"): + tp match + case tp: TypeRef => + val sym = tp.symbol + if (sym.isClass) sym.derivesFrom(cls, defaultIfUnknown) else loop(tp.superType) + case tp: AppliedType => + tp.superType.derivesFrom(cls) + case tp: MatchType => + tp.bound.derivesFrom(cls) || tp.reduced.derivesFrom(cls) + case tp: TypeProxy => + loop(tp.underlying) + case tp: AndType => + loop(tp.tp1) || loop(tp.tp2) + case tp: OrType => + // If the type is `T | Null` or `T | Nothing`, the class is != Nothing, + // and `T` derivesFrom the class, then the OrType derivesFrom the class. + // Otherwise, we need to check both sides derivesFrom the class. + if isLowerBottomType(tp.tp1) then + loop(tp.tp2) + else if isLowerBottomType(tp.tp2) then + loop(tp.tp1) + else + loop(tp.tp1) && loop(tp.tp2) + case tp: JavaArrayType => + cls == defn.ObjectClass + case _ => + false loop(this) } @@ -421,18 +422,18 @@ object Types extends TypeUtils { * (since these are relevant for inference or resolution) but never consider prefixes * (since these often do not constrain the search space anyway). */ - def unusableForInference(using Context): Boolean = try widenDealias match - case AppliedType(tycon, args) => tycon.unusableForInference || args.exists(_.unusableForInference) - case RefinedType(parent, _, rinfo) => parent.unusableForInference || rinfo.unusableForInference - case TypeBounds(lo, hi) => lo.unusableForInference || hi.unusableForInference - case tp: FlexibleType => tp.underlying.unusableForInference - case tp: AndOrType => tp.tp1.unusableForInference || tp.tp2.unusableForInference - case tp: LambdaType => tp.resultType.unusableForInference || tp.paramInfos.exists(_.unusableForInference) - case WildcardType(optBounds) => optBounds.unusableForInference - case CapturingType(parent, refs) => parent.unusableForInference || refs.elems.exists(_.coreType.unusableForInference) - case _: ErrorType => true - case _ => false - catch case ex: Throwable => handleRecursive("unusableForInference", show, ex) + def unusableForInference(using Context): Boolean = ctx.handleRecursive("unusableForInference", show): + widenDealias match + case AppliedType(tycon, args) => tycon.unusableForInference || args.exists(_.unusableForInference) + case RefinedType(parent, _, rinfo) => parent.unusableForInference || rinfo.unusableForInference + case TypeBounds(lo, hi) => lo.unusableForInference || hi.unusableForInference + case tp: FlexibleType => tp.underlying.unusableForInference + case tp: AndOrType => tp.tp1.unusableForInference || tp.tp2.unusableForInference + case tp: LambdaType => tp.resultType.unusableForInference || tp.paramInfos.exists(_.unusableForInference) + case WildcardType(optBounds) => optBounds.unusableForInference + case CapturingType(parent, refs) => parent.unusableForInference || refs.elems.exists(_.coreType.unusableForInference) + case _: ErrorType => true + case _ => false /** Does the type carry an annotation that is an instance of `cls`? */ @tailrec final def hasAnnotation(cls: ClassSymbol)(using Context): Boolean = stripTypeVar match @@ -727,7 +728,7 @@ object Types extends TypeUtils { */ def baseClasses(using Context): List[ClassSymbol] = record("baseClasses") - try + ctx.handleRecursive("base classes of", this.show): this match case tp: TypeProxy => tp.superType.baseClasses @@ -736,7 +737,6 @@ object Types extends TypeUtils { case tp: WildcardType => tp.effectiveBounds.hi.baseClasses case _ => Nil - catch case ex: Throwable => handleRecursive("base classes of", this.show, ex) // ----- Member access ------------------------------------------------- @@ -1017,25 +1017,24 @@ object Types extends TypeUtils { if (recCount >= Config.LogPendingFindMemberThreshold) ctx.base.pendingMemberSearches = name :: ctx.base.pendingMemberSearches ctx.base.findMemberCount = recCount + 1 - try go(this) - catch { - case ex: Throwable => - core.println(s"findMember exception for $this member $name, pre = $pre, recCount = $recCount") - def showPrefixSafely(pre: Type)(using Context): String = pre.stripTypeVar match { - case pre: TermRef => i"${pre.symbol.name}." - case pre: TypeRef => i"${pre.symbol.name}#" - case pre: TypeProxy => showPrefixSafely(pre.superType) - case _ => if (pre.typeSymbol.exists) i"${pre.typeSymbol.name}#" else "." - } + def showPrefixSafely(pre: Type)(using Context): String = pre.stripTypeVar match + case pre: TermRef => i"${pre.symbol.name}." + case pre: TypeRef => i"${pre.symbol.name}#" + case pre: TypeProxy => showPrefixSafely(pre.superType) + case _ => if (pre.typeSymbol.exists) i"${pre.typeSymbol.name}#" else "." - handleRecursive("find-member", i"${showPrefixSafely(pre)}$name", ex) - } - finally { + try + ctx.handleRecursive("find-member", i"${showPrefixSafely(pre)}$name"): + go(this) + catch + case NonFatal(t) => + core.println(s"findMember exception for $this member $name, pre = $pre, recCount = $recCount") + throw t + finally if (recCount >= Config.LogPendingFindMemberThreshold) ctx.base.pendingMemberSearches = ctx.base.pendingMemberSearches.tail ctx.base.findMemberCount = recCount - } } /** The set of names of members of this type that pass the given name filter @@ -4790,7 +4789,8 @@ object Types extends TypeUtils { override def tryNormalize(using Context): Type = if isMatchAlias && MatchTypeTrace.isRecording then - MatchTypeTrace.recurseWith(this)(superType.tryNormalize) + ctx.handleRecursive("try to normalize", i"$superType"): + MatchTypeTrace.recurseWith(this)(superType.tryNormalize) else super.tryNormalize /** Is this an unreducible application to wildcard arguments? @@ -5315,13 +5315,14 @@ object Types extends TypeUtils { if (myReduced != null) record("MatchType.reduce cache miss") val saved = ctx.typerState.snapshot() try - myReduced = trace(i"reduce match type $this $hashCode", matchTypes, show = true): - withMode(Mode.Type): - TypeComparer.reduceMatchWith: cmp => - cmp.matchCases(scrutinee.normalized, cases.map(MatchTypeCaseSpec.analyze)) - catch case ex: Throwable => + myReduced = ctx.handleRecursive("reduce type ", i"$scrutinee match ..."): + trace(i"reduce match type $this $hashCode", matchTypes, show = true): + withMode(Mode.Type): + TypeComparer.reduceMatchWith: cmp => + cmp.matchCases(scrutinee.normalized, cases.map(MatchTypeCaseSpec.analyze)) + catch case NonFatal(t) => myReduced = NoType - handleRecursive("reduce type ", i"$scrutinee match ...", ex) + throw t finally ctx.typerState.resetTo(saved) // this drops caseLambdas in constraint and undoes any typevar diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index a78d9c976000..e6591b4ca504 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -378,7 +378,7 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) { registerDef(sym) writeByte(tag) val addr = currentAddr - try + ctx.handleRecursive("tree pickling", mdef.show): withLength { pickleName(sym.name) pickleParams @@ -395,7 +395,6 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) { pickleTreeUnlessEmpty(rhs) pickleModifiers(sym, mdef) } - catch case t: Throwable => handleRecursive("tree pickling", mdef.show, t) if sym.is(Method) && sym.owner.isClass then profile.recordMethodSize(sym, (currentAddr.index - addr.index) max 1, mdef.span) for docCtx <- ctx.docCtx do diff --git a/compiler/src/dotty/tools/dotc/reporting/Message.scala b/compiler/src/dotty/tools/dotc/reporting/Message.scala index 7de9c0b640fa..d5407119cd38 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Message.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Message.scala @@ -88,7 +88,7 @@ object Message: */ private class Seen(disambiguate: Disambiguation): - val seen = new collection.mutable.HashMap[SeenKey, List[Recorded]].withDefaultValue(Nil) + private val seen = new collection.mutable.HashMap[SeenKey, List[Recorded]].withDefaultValue(Nil) var nonSensical = false diff --git a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala index 484989ef83bd..8e33f4430ea9 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala @@ -14,7 +14,6 @@ import java.io.{BufferedReader, PrintWriter} import scala.annotation.internal.sharable import scala.collection.mutable.ListBuffer import core.Decorators.{em, toMessage} -import core.handleRecursive object Reporter { /** Convert a SimpleReporter into a real Reporter */ @@ -169,12 +168,8 @@ abstract class Reporter extends interfaces.ReporterResult { addUnreported(key, 1) case _ => if !isHidden(dia) then // avoid isHidden test for summarized warnings so that message is not forced - try + ctx.handleRecursive("error reporting", dia.message): withMode(Mode.Printing)(doReport(dia)) - catch case ex: Throwable => - // #20158: Don't increment the error count, otherwise we might suppress - // the RecursiveOverflow error and not print any error at all. - handleRecursive("error reporting", dia.message, ex) dia match { case w: Warning => if w.isInstanceOf[LintWarning] then diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 4df2e2523f8f..84f69337b806 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -658,7 +658,7 @@ trait ImplicitRunInfo: private var parts: mutable.LinkedHashSet[Type] = uninitialized private val partSeen = util.HashSet[Type]() - def traverse(t: Type) = try + def traverse(t: Type) = ctx.handleRecursive("collectParts of", t.show): if partSeen.contains(t) then () else if implicitScopeCache.contains(t) then parts += t else @@ -690,7 +690,6 @@ trait ImplicitRunInfo: traverseChildren(t) case t => traverseChildren(t) - catch case ex: Throwable => handleRecursive("collectParts of", t.show, ex) def apply(tp: Type): collection.Set[Type] = parts = mutable.LinkedHashSet() diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index f28532d564eb..98327eb7a57d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -46,7 +46,7 @@ object Inferencing { if isFullyDefined(tp, ForceDegree.all) then tp else throw new Error(i"internal error: type of $what $tp is not fully defined, pos = $pos") catch case ex: RecursionOverflow => - report.error(ex, pos) + report.error(ex.getMessage, pos) // TODO better message UnspecifiedErrorType /** Instantiate selected type variables `tvars` in type `tp` in a special mode: @@ -229,29 +229,27 @@ object Inferencing { private var toMaximize: List[TypeVar] = Nil def apply(x: Boolean, tp: Type): Boolean = trace(i"isFullyDefined($tp, $force)", typr) { - try { - val tpd = tp.dealias - if tpd ne tp then apply(x, tpd) - else tp match - case _: WildcardType | _: ProtoType => - false - case tvar: TypeVar if !tvar.isInstantiated => - force.appliesTo(tvar) - && ctx.typerState.constraint.contains(tvar) - && { - var fail = false - var skip = false - instDecision(tvar, variance, minimizeSelected, force.ifBottom) match - case Decision.Min => skip = instantiate(tvar, fromBelow = true) - case Decision.Max => skip = instantiate(tvar, fromBelow = false) - case Decision.Skip => // hold off instantiating unbounded unconstrained variable - case Decision.Fail => fail = true - case Decision.ToMax => toMaximize ::= tvar - !fail && (skip || foldOver(x, tvar)) - } - case tp => foldOver(x, tp) - } - catch case ex: Throwable => handleRecursive("check fully defined", tp.showSummary(20), ex) + ctx.handleRecursive("check fully defined", tp.showSummary(20)): + val tpd = tp.dealias + if tpd ne tp then apply(x, tpd) + else tp match + case _: WildcardType | _: ProtoType => + false + case tvar: TypeVar if !tvar.isInstantiated => + force.appliesTo(tvar) + && ctx.typerState.constraint.contains(tvar) + && { + var fail = false + var skip = false + instDecision(tvar, variance, minimizeSelected, force.ifBottom) match + case Decision.Min => skip = instantiate(tvar, fromBelow = true) + case Decision.Max => skip = instantiate(tvar, fromBelow = false) + case Decision.Skip => // hold off instantiating unbounded unconstrained variable + case Decision.Fail => fail = true + case Decision.ToMax => toMaximize ::= tvar + !fail && (skip || foldOver(x, tvar)) + } + case tp => foldOver(x, tp) } def process(tp: Type): Boolean = diff --git a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala index 354f09382d82..07923b974931 100644 --- a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala +++ b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala @@ -121,15 +121,15 @@ class VarianceChecker(using Context) { * same is true of the parameters (ValDefs). */ def apply(status: Option[VarianceError], tp: Type): Option[VarianceError] = trace(s"variance checking $tp of $base at $variance", variances) { - try + ctx.handleRecursive("variance check of", tp.show): if (status.isDefined) status - else tp match { + else tp match case tp: TypeRef => val sym = tp.symbol if (sym.isOneOf(VarianceFlags) && base.isContainedIn(sym.owner)) checkVarianceOfSymbol(sym) else sym.info match { case MatchAlias(_) => foldOver(status, tp) - case TypeAlias(alias) => this(status, alias) + case TypeAlias(alias) => this (status, alias) case _ => foldOver(status, tp) } case AnnotatedType(_, annot) if annot.symbol == defn.UncheckedVarianceAnnot => @@ -138,10 +138,6 @@ class VarianceChecker(using Context) { foldOver(status, tp.parents) case _ => foldOver(status, tp) - } - catch { - case ex: Throwable => handleRecursive("variance check of", tp.show, ex) - } } def checkInfo(info: Type): Option[VarianceError] = info match diff --git a/compiler/src/dotty/tools/package.scala b/compiler/src/dotty/tools/package.scala index 475befce3807..31ace83331a2 100644 --- a/compiler/src/dotty/tools/package.scala +++ b/compiler/src/dotty/tools/package.scala @@ -36,9 +36,4 @@ package object tools { type WrappedResult[T] = resultWrapper.WrappedResult[T] def WrappedResult[T](x: T): WrappedResult[T] = resultWrapper.wrap(x) def result[T](using x: WrappedResult[T]): T = resultWrapper.unwrap(x) - - // Ensure this object is already classloaded, since it's only actually used - // when handling stack overflows and every operation (including class loading) - // risks failing. - dotty.tools.dotc.core.handleRecursive } diff --git a/tests/pos-deep-subtype/i4036.scala b/tests/pos-deep-subtype/i4036.scala index 08ff248caf9d..cdf2e1e65478 100644 --- a/tests/pos-deep-subtype/i4036.scala +++ b/tests/pos-deep-subtype/i4036.scala @@ -45,6 +45,5 @@ object A { T#T#T#T#T#T#T#T#T#T#T#T#T#T#T#T# T#T#T#T#T#T#T#T#T#T#T#T#T#T#T#T# T#T#T#T#T#T#T#T#T#T#T#T#T#T#T#T# - T#T#T#T#T#T#T#T#T#T#T#T#T#T#T#T# T#T#T#T#T#T#T#T#T#T#T#T#T#T#T#T }