diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 9e4a1edd6e84..c438c3fd0592 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1118,7 +1118,7 @@ trait Applications extends Compatibility { */ def convertNewGenericArray(tree: Tree)(using Context): Tree = tree match { case Apply(TypeApply(tycon, targs@(targ :: Nil)), args) if tycon.symbol == defn.ArrayConstructor => - fullyDefinedType(tree.tpe, "array", tree.span) + fullyDefinedType(tree.tpe, "array", tree.srcPos) def newGenericArrayCall = ref(defn.DottyArraysModule) @@ -1333,7 +1333,7 @@ trait Applications extends Compatibility { val ownType = if (selType <:< unapplyArgType) { unapp.println(i"case 1 $unapplyArgType ${ctx.typerState.constraint}") - fullyDefinedType(unapplyArgType, "pattern selector", tree.span) + fullyDefinedType(unapplyArgType, "pattern selector", tree.srcPos) selType.dropAnnot(defn.UncheckedAnnot) // need to drop @unchecked. Just because the selector is @unchecked, the pattern isn't. } else { @@ -1564,7 +1564,7 @@ trait Applications extends Compatibility { // `isSubType` as a TypeVar might get constrained by a TypeRef it's // part of. val tp1Params = tp1.newLikeThis(tp1.paramNames, tp1.paramInfos, defn.AnyType) - fullyDefinedType(tp1Params, "type parameters of alternative", alt1.symbol.span) + fullyDefinedType(tp1Params, "type parameters of alternative", alt1.symbol.srcPos) val tparams = newTypeParams(alt1.symbol, tp1.paramNames, EmptyFlags, tp1.instantiateParamInfos(_)) isAsSpecific(alt1, tp1.instantiate(tparams.map(_.typeRef)), alt2, tp2) diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index a1010f48c5b7..e81e6acf3d2c 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -257,7 +257,7 @@ class ImplicitSearchError( ++ ErrorReporting.matchReductionAddendum(pt) } - private def formatMsg(shortForm: String)(headline: String = shortForm) = arg match + private def formatMsg(shortForm: String)(headline: String = shortForm) = arg match case arg: Trees.SearchFailureIdent[?] => arg.tpe match case _: NoMatchingImplicits => headline @@ -318,7 +318,7 @@ class ImplicitSearchError( case _ => Nil } def resolveTypes(targs: List[tpd.Tree])(using Context) = - targs.map(a => Inferencing.fullyDefinedType(a.tpe, "type argument", a.span)) + targs.map(a => Inferencing.fullyDefinedType(a.tpe, "type argument", a.srcPos)) // We can extract type arguments from: // - a function call: diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 2544fe1bb04e..dff672ae739d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -549,16 +549,16 @@ object Implicits: override def msg(using Context) = _msg def explanation(using Context) = msg.toString - /** A search failure type for failed synthesis of terms for special types */ + /** A search failure type for failed synthesis of terms for special types */ class SynthesisFailure(reasons: List[String], val expectedType: Type) extends SearchFailureType: def argument = EmptyTree - private def formatReasons = - if reasons.length > 1 then - reasons.mkString("\n\t* ", "\n\t* ", "") - else + private def formatReasons = + if reasons.length > 1 then + reasons.mkString("\n\t* ", "\n\t* ", "") + else reasons.mkString - + def explanation(using Context) = em"Failed to synthesize an instance of type ${clarify(expectedType)}: ${formatReasons}" end Implicits @@ -871,7 +871,7 @@ trait Implicits: SearchFailure(new SynthesisFailure(errors, formal), span).tree else tree.orElse(failed) - + /** Search an implicit argument and report error if not found */ def implicitArgTree(formal: Type, span: Span)(using Context): Tree = { @@ -1149,15 +1149,17 @@ trait Implicits: private def isCoherent = pt.isRef(defn.CanEqualClass) - val wideProto = pt.widenExpr + private val wideProto = pt.widenExpr + + private val srcPos = ctx.source.atSpan(span) /** The expected type where parameters and uninstantiated typevars are replaced by wildcard types */ - val wildProto: Type = + private val wildProto: Type = if argument.isEmpty then wildApprox(pt) else ViewProto(wildApprox(argument.tpe.widen.normalized), wildApprox(pt)) // Not clear whether we need to drop the `.widen` here. All tests pass with it in place, though. - val isNotGiven: Boolean = wildProto.classSymbol == defn.NotGivenClass + private val isNotGiven: Boolean = wildProto.classSymbol == defn.NotGivenClass private def searchTooLarge(): Boolean = ctx.searchHistory match case root: SearchRoot => @@ -1170,7 +1172,7 @@ trait Implicits: if result then var c = ctx while c.outer.typer eq ctx.typer do c = c.outer - report.warning(ImplicitSearchTooLargeWarning(limit, h.openSearchPairs), ctx.source.atSpan(span))(using c) + report.warning(ImplicitSearchTooLargeWarning(limit, h.openSearchPairs), srcPos)(using c) else h.root.nestedSearches = nestedSearches + 1 result @@ -1347,7 +1349,7 @@ trait Implicits: |the search will fail with a global ambiguity error instead. | |Consider using the scala.util.NotGiven class to implement similar functionality.""", - ctx.source.atSpan(span)) + srcPos) /** Compare the length of the baseClasses of two symbols (except for objects, * where we use the length of the companion class instead if it's bigger). @@ -1565,7 +1567,7 @@ trait Implicits: if cand1.ref eq cand.ref then lazy val wildTp = wildApprox(tp.widenExpr) if belowByname && (wildTp <:< wildPt) then - fullyDefinedType(tp, "by-name implicit parameter", span) + fullyDefinedType(tp, "by-name implicit parameter", srcPos) false else if prev.typeSize > ptSize || prev.coveringSet != ptCoveringSet then loop(outer, tp.isByName || belowByname) diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 20da4723d27f..cec6ad2481f1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -8,7 +8,7 @@ import Contexts._, Types._, Flags._, Symbols._ import ProtoTypes._ import NameKinds.{AvoidNameKind, UniqueName} import util.Spans._ -import util.{Stats, SimpleIdentityMap} +import util.{Stats, SimpleIdentityMap, SrcPos} import Decorators._ import config.Printers.{gadts, typr} import annotation.tailrec @@ -28,11 +28,11 @@ object Inferencing { * but only if the overall result of `isFullyDefined` is `true`. * Variables that are successfully minimized do not count as uninstantiated. */ - def isFullyDefined(tp: Type, force: ForceDegree.Value)(using Context): Boolean = { + def isFullyDefined(tp: Type, force: ForceDegree.Value, handleOverflow: Boolean = false)(using Context): Boolean = { val nestedCtx = ctx.fresh.setNewTyperState() val result = try new IsFullyDefinedAccumulator(force)(using nestedCtx).process(tp) - catch case ex: StackOverflowError => + catch case ex: RecursionOverflow if handleOverflow => false // can happen for programs with illegal recusions, e.g. neg/recursive-lower-constraint.scala if (result) nestedCtx.typerState.commit() result @@ -49,9 +49,13 @@ object Inferencing { /** The fully defined type, where all type variables are forced. * Throws an error if type contains wildcards. */ - def fullyDefinedType(tp: Type, what: String, span: Span)(using Context): Type = - if (isFullyDefined(tp, ForceDegree.all)) tp - else throw new Error(i"internal error: type of $what $tp is not fully defined, pos = $span") // !!! DEBUG + def fullyDefinedType(tp: Type, what: String, pos: SrcPos)(using Context): Type = + try + 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) + UnspecifiedErrorType /** Instantiate selected type variables `tvars` in type `tp` in a special mode: * 1. If a type variable is constrained from below (i.e. constraint bound != given lower bound) @@ -171,33 +175,37 @@ object Inferencing { private var toMaximize: List[TypeVar] = Nil - def apply(x: Boolean, tp: Type): Boolean = tp.dealias match { - case _: WildcardType | _: ProtoType => - false - case tvar: TypeVar if !tvar.isInstantiated => - force.appliesTo(tvar) - && ctx.typerState.constraint.contains(tvar) - && { - val direction = instDirection(tvar.origin) - if minimizeSelected then - if direction <= 0 && tvar.hasLowerBound then + def apply(x: Boolean, tp: Type): Boolean = + try tp.dealias match + case _: WildcardType | _: ProtoType => + false + case tvar: TypeVar if !tvar.isInstantiated => + force.appliesTo(tvar) + && ctx.typerState.constraint.contains(tvar) + && { + val direction = instDirection(tvar.origin) + if minimizeSelected then + if direction <= 0 && tvar.hasLowerBound then + instantiate(tvar, fromBelow = true) + else if direction >= 0 && tvar.hasUpperBound then + instantiate(tvar, fromBelow = false) + // else hold off instantiating unbounded unconstrained variable + else if direction != 0 then + instantiate(tvar, fromBelow = direction < 0) + else if variance >= 0 && (force.ifBottom == IfBottom.ok || tvar.hasLowerBound) then instantiate(tvar, fromBelow = true) - else if direction >= 0 && tvar.hasUpperBound then - instantiate(tvar, fromBelow = false) - // else hold off instantiating unbounded unconstrained variable - else if direction != 0 then - instantiate(tvar, fromBelow = direction < 0) - else if variance >= 0 && (force.ifBottom == IfBottom.ok || tvar.hasLowerBound) then - instantiate(tvar, fromBelow = true) - else if variance >= 0 && force.ifBottom == IfBottom.fail then - return false - else - toMaximize = tvar :: toMaximize - foldOver(x, tvar) - } - case tp => - foldOver(x, tp) - } + else if variance >= 0 && force.ifBottom == IfBottom.fail then + return false + else + toMaximize = tvar :: toMaximize + foldOver(x, tvar) + } + case tp => + reporting.trace(s"IFT $tp") { + foldOver(x, tp) + } + catch case ex: Throwable => + handleRecursive("check fully defined", tp.show, ex) def process(tp: Type): Boolean = // Maximize type vars in the order they were visited before */ @@ -313,7 +321,7 @@ object Inferencing { val (tl1, tvars) = constrained(tl, tree) var tree1 = AppliedTypeTree(tree.withType(tl1), tvars) tree1.tpe <:< pt - fullyDefinedType(tree1.tpe, "template parent", tree.span) + fullyDefinedType(tree1.tpe, "template parent", tree.srcPos) tree1 case _ => tree diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 1b523bb71cf5..3b9a9e1ac382 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1467,7 +1467,7 @@ class Namer { typer: Typer => else { if (denot.is(ModuleClass) && denot.sourceModule.isOneOf(GivenOrImplicit)) missingType(denot.symbol, "parent ")(using creationContext) - fullyDefinedType(typedAheadExpr(parent).tpe, "class parent", parent.span) + fullyDefinedType(typedAheadExpr(parent).tpe, "class parent", parent.srcPos) } case _ => UnspecifiedErrorType.assertingErrorsReported @@ -1890,7 +1890,7 @@ class Namer { typer: Typer => def dealiasIfUnit(tp: Type) = if (tp.isRef(defn.UnitClass)) defn.UnitType else tp def cookedRhsType = dealiasIfUnit(rhsType).deskolemized - def lhsType = fullyDefinedType(cookedRhsType, "right-hand side", mdef.span) + def lhsType = fullyDefinedType(cookedRhsType, "right-hand side", mdef.srcPos) //if (sym.name.toString == "y") println(i"rhs = $rhsType, cooked = $cookedRhsType") if (inherited.exists) if sym.isInlineVal then lhsType else inherited diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index 171e171f33f1..f2b7b82a680e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -53,8 +53,9 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): val synthesizedTypeTest: SpecialHandler = (formal, span) => formal.argInfos match { case arg1 :: arg2 :: Nil if !defn.isBottomClass(arg2.typeSymbol) => - val tp1 = fullyDefinedType(arg1, "TypeTest argument", span) - val tp2 = fullyDefinedType(arg2, "TypeTest argument", span).normalized + val srcPos = ctx.source.atSpan(span) + val tp1 = fullyDefinedType(arg1, "TypeTest argument", srcPos) + val tp2 = fullyDefinedType(arg2, "TypeTest argument", srcPos).normalized val sym2 = tp2.typeSymbol if tp1 <:< tp2 then // optimization when we know the typetest will always succeed @@ -192,7 +193,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): formal.argTypes match case args @ (arg1 :: arg2 :: Nil) => - List(arg1, arg2).foreach(fullyDefinedType(_, "eq argument", span)) + List(arg1, arg2).foreach(fullyDefinedType(_, "eq argument", ctx.source.atSpan(span))) if canComparePredefined(arg1, arg2) || !Implicits.strictEquality && explore(validEqAnyArgs(arg1, arg2)) then withNoErrors(ref(defn.CanEqual_canEqualAny).appliedToTypes(args).withSpan(span)) @@ -209,7 +210,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): New(defn.ValueOfClass.typeRef.appliedTo(t.tpe), t :: Nil).withSpan(span) formal.argInfos match case arg :: Nil => - fullyDefinedType(arg, "ValueOf argument", span).normalized.dealias match + fullyDefinedType(arg, "ValueOf argument", ctx.source.atSpan(span)).normalized.dealias match case ConstantType(c: Constant) => withNoErrors(success(Literal(c))) case tp: TypeRef if tp.isRef(defn.UnitClass) => @@ -649,7 +650,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): formal.argInfos match case arg :: Nil => - val manifest = synthesize(fullyDefinedType(arg, "Manifest argument", span), kind, topLevel = true) + val manifest = synthesize(fullyDefinedType(arg, "Manifest argument", ctx.source.atSpan(span)), kind, topLevel = true) if manifest != EmptyTree then report.deprecationWarning( i"""Compiler synthesis of Manifest and OptManifest is deprecated, instead diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index d28c2a9a8d3b..9a62ac480c30 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1101,7 +1101,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def noLeaks(t: Tree): Boolean = escapingRefs(t, localSyms).isEmpty if (noLeaks(tree)) tree else { - fullyDefinedType(tree.tpe, "block", tree.span) + fullyDefinedType(tree.tpe, "block", tree.srcPos) var avoidingType = TypeOps.avoid(tree.tpe, localSyms) val ptDefined = isFullyDefined(pt, ForceDegree.none) if (ptDefined && !(avoidingType.widenExpr <:< pt)) avoidingType = pt @@ -1534,7 +1534,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case _ => if tree.isInline then checkInInlineContext("inline match", tree.srcPos) val sel1 = typedExpr(tree.selector) - val rawSelectorTpe = fullyDefinedType(sel1.tpe, "pattern selector", tree.span) + val rawSelectorTpe = fullyDefinedType(sel1.tpe, "pattern selector", tree.srcPos) val selType = rawSelectorTpe match case c: ConstantType if tree.isInline => c case otherTpe => otherTpe.widen diff --git a/tests/neg/i15311.check b/tests/neg/i15311.check new file mode 100644 index 000000000000..2974f53fe526 --- /dev/null +++ b/tests/neg/i15311.check @@ -0,0 +1,32 @@ +-- Error: tests/neg/i15311.scala:16:4 ---------------------------------------------------------------------------------- +16 |def test = // error + |^ + |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. + |A recurring operation is (inner to outer): + | + | check fully defined food.T + | check fully defined food.T + | check fully defined food.T + | check fully defined food.T + | check fully defined food.T + | check fully defined food.T + | check fully defined food.T + | check fully defined food.T + | check fully defined food.T + | check fully defined food.T + | ... + | + | check fully defined food.T + | check fully defined food.T + | check fully defined food.T + | check fully defined food.T + | check fully defined food.T + | check fully defined food.T + | check fully defined food.T + | check fully defined food.T + | check fully defined food.T + | check fully defined Template[food.T] +17 | eat(ham) +18 | eat(food.self) diff --git a/tests/neg/i15311.scala b/tests/neg/i15311.scala new file mode 100644 index 000000000000..10cebfe12408 --- /dev/null +++ b/tests/neg/i15311.scala @@ -0,0 +1,18 @@ +trait Template[+T <: Template[T]]: + type Clone <: T { type Clone = Template.this.Clone } + val self :Clone + +type Food = Template[_] + +class Ham extends Template[Ham]: + type Clone = Ham + val self = this + +def eat[F <: Template[F]](food :F) :F = food.self.self + +val ham = new Ham +val food :Food = ham + +def test = // error + eat(ham) + eat(food.self)