diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index 8abbb81bae85..ea96d9dcd5d9 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -184,11 +184,6 @@ object Config { */ final val LogPendingFindMemberThreshold = 9 - /** Maximal number of outstanding recursive calls to findMember before backing out - * when findMemberLimit is set. - */ - final val PendingFindMemberLimit = LogPendingFindMemberThreshold * 4 - /** When in IDE, turn StaleSymbol errors into warnings instead of crashing */ final val ignoreStaleInIDE = true } diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 8b39883d51c0..d06794fcdf46 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -63,7 +63,7 @@ class ScalaSettings extends Settings.SettingGroup { val Xhelp = BooleanSetting("-X", "Print a synopsis of advanced options.") val XnoForwarders = BooleanSetting("-Xno-forwarders", "Do not generate static forwarders in mirror classes.") val XminImplicitSearchDepth = IntSetting("-Xmin-implicit-search-depth", "Set number of levels of implicit searches undertaken before checking for divergence.", 5) - val xmaxInlines = IntSetting("-Xmax-inlines", "Maximal number of successive inlines", 32) + val XmaxInlines = IntSetting("-Xmax-inlines", "Maximal number of successive inlines", 32) val XmaxClassfileName = IntSetting("-Xmax-classfile-name", "Maximum filename length for generated classes", 255, 72 to 255) val Xmigration = VersionSetting("-Xmigration", "Warn about constructs whose behavior may have changed since version.") val Xprint = PhasesSetting("-Xprint", "Print out program after") diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 0443f346476e..54993ce45cf6 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -332,21 +332,8 @@ object Denotations { } /** Handle merge conflict by throwing a `MergeError` exception */ - private def mergeConflict(tp1: Type, tp2: Type, that: Denotation)(implicit ctx: Context): Type = { - def showSymbol(sym: Symbol): String = if (sym.exists) sym.showLocated else "[unknown]" - def showType(tp: Type) = tp match { - case ClassInfo(_, cls, _, _, _) => cls.showLocated - case bounds: TypeBounds => i"type bounds $bounds" - case _ => tp.show - } - val msg = - s"""cannot merge - | ${showSymbol(this.symbol)} of type ${showType(tp1)} and - | ${showSymbol(that.symbol)} of type ${showType(tp2)} - """ - if (true) throw new MergeError(msg, tp1, tp2) - else throw new Error(msg) // flip condition for debugging - } + private def mergeConflict(tp1: Type, tp2: Type, that: Denotation)(implicit ctx: Context): Type = + throw new MergeError(this.symbol, that.symbol, tp1, tp2, NoPrefix) /** Merge parameter names of lambda types. If names in corresponding positions match, keep them, * otherwise generate new synthetic names. @@ -537,8 +524,7 @@ object Denotations { else if (pre.widen.classSymbol.is(Scala2x) || ctx.scala2Mode) info1 // follow Scala2 linearization - // compare with way merge is performed in SymDenotation#computeMembersNamed - else - throw new MergeError(s"${ex.getMessage} as members of type ${pre.show}", ex.tp1, ex.tp2) + else throw new MergeError(ex.sym1, ex.sym2, ex.tp1, ex.tp2, pre) } new JointRefDenotation(sym, jointInfo, denot1.validFor & denot2.validFor) } @@ -1136,21 +1122,15 @@ object Denotations { def doubleDefError(denot1: Denotation, denot2: Denotation, pre: Type = NoPrefix)(implicit ctx: Context): Nothing = { val sym1 = denot1.symbol val sym2 = denot2.symbol - def fromWhere = if (pre == NoPrefix) "" else i"\nwhen seen as members of $pre" - val msg = - if (denot1.isTerm) - i"""cannot merge - | $sym1: ${sym1.info} and - | $sym2: ${sym2.info}; - |they are both defined in ${sym1.owner} but have matching signatures - | ${denot1.info} and - | ${denot2.info}$fromWhere""" - else - i"""cannot merge - | $sym1 ${denot1.info} - | $sym2 ${denot2.info} - |they are conflicting definitions$fromWhere""" - throw new MergeError(msg, denot2.info, denot2.info) + if (denot1.isTerm) + throw new MergeError(sym1, sym2, sym1.info, sym2.info, pre) { + override def addendum(implicit ctx: Context) = + i""" + |they are both defined in ${sym1.owner} but have matching signatures + | ${denot1.info} and + | ${denot2.info}${super.addendum}""" + } + else throw new MergeError(sym1, sym2, denot1.info, denot2.info, pre) } // --- Overloaded denotations and predenotations ------------------------------------------------- diff --git a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala index 83188406d6a1..75732475ac73 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala @@ -168,7 +168,7 @@ class TypeApplications(val self: Type) extends AnyVal { * any type parameter that is-rebound by the refinement. */ final def typeParams(implicit ctx: Context): List[TypeParamInfo] = /*>|>*/ track("typeParams") /*<|<*/ { - self match { + try self match { case self: TypeRef => val tsym = self.symbol if (tsym.isClass) tsym.typeParams @@ -193,6 +193,9 @@ class TypeApplications(val self: Type) extends AnyVal { case _ => Nil } + catch { + case ex: Throwable => handleRecursive("type parameters of", self.show, ex) + } } /** If `self` is a higher-kinded type, its type parameters, otherwise Nil */ diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index d2e122b7bfe8..52c39ab473cc 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -121,7 +121,11 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { protected def isSubType(tp1: Type, tp2: Type, a: ApproxState): Boolean = { val saved = approx this.approx = a - try recur(tp1, tp2) finally this.approx = saved + try recur(tp1, tp2) + catch { + case ex: Throwable => handleRecursive("subtype", i"$tp1 <:< $tp2", ex, weight = 2) + } + finally this.approx = saved } protected def isSubType(tp1: Type, tp2: Type): Boolean = isSubType(tp1, tp2, NoApprox) @@ -161,7 +165,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { try { pendingSubTypes += p firstTry - } finally { + } + finally { pendingSubTypes -= p } } diff --git a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala new file mode 100644 index 000000000000..3028ebc04a2d --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala @@ -0,0 +1,147 @@ +package dotty.tools +package dotc +package core + +import util.common._ +import Types._ +import Symbols._ +import Flags._ +import Names._ +import Contexts._ +import SymDenotations._ +import Denotations._ +import Decorators._ +import reporting.diagnostic.Message +import reporting.diagnostic.messages._ +import ast.untpd +import config.Printers.cyclicErrors + +class TypeError(msg: String) extends Exception(msg) { + def this() = this("") + def toMessage(implicit ctx: Context): Message = getMessage +} + +class MalformedType(pre: Type, denot: Denotation, absMembers: Set[Name]) extends TypeError { + override def toMessage(implicit ctx: Context): Message = + i"malformed type: $pre is not a legal prefix for $denot because it contains abstract type member${if (absMembers.size == 1) "" else "s"} ${absMembers.mkString(", ")}" +} + +class MissingType(pre: Type, name: Name) extends TypeError { + private def otherReason(pre: Type)(implicit ctx: Context): String = pre match { + case pre: ThisType if pre.cls.givenSelfType.exists => + i"\nor the self type of $pre might not contain all transitive dependencies" + case _ => "" + } + + override def toMessage(implicit ctx: Context): Message = { + if (ctx.debug) printStackTrace() + i"""cannot resolve reference to type $pre.$name + |the classfile defining the type might be missing from the classpath${otherReason(pre)}""" + } +} + +class RecursionOverflow(val op: String, details: => String, previous: Throwable, val weight: Int) extends TypeError { + + def explanation = s"$op $details" + + private def recursions: List[RecursionOverflow] = { + val nested = previous match { + case previous: RecursionOverflow => previous.recursions + case _ => Nil + } + this :: nested + } + + def opsString(rs: List[RecursionOverflow])(implicit ctx: 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(implicit ctx: Context): Message = { + val mostCommon = recursions.groupBy(_.op).toList.maxBy(_._2.map(_.weight).sum)._2.reverse + s"""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): + |${opsString(mostCommon)}""".stripMargin + } + + override def fillInStackTrace(): Throwable = this + override def getStackTrace() = previous.getStackTrace() +} + +object handleRecursive { + def apply(op: String, details: => String, exc: Throwable, weight: Int = 1): Nothing = exc match { + case _: RecursionOverflow => + throw new RecursionOverflow(op, details, exc, weight) + case _ => + var e = exc + while (e != null && !e.isInstanceOf[StackOverflowError]) e = e.getCause + if (e != null) throw new RecursionOverflow(op, details, e, weight) + else throw exc + } +} + +class CyclicReference private (val denot: SymDenotation) extends TypeError { + + override def toMessage(implicit ctx: Context) = { + + def errorMsg(cx: Context): Message = + if (cx.mode is Mode.InferringReturnType) { + cx.tree match { + case tree: untpd.DefDef if !tree.tpt.typeOpt.exists => + OverloadedOrRecursiveMethodNeedsResultType(tree.name) + case tree: untpd.ValDef if !tree.tpt.typeOpt.exists => + RecursiveValueNeedsResultType(tree.name) + case _ => + errorMsg(cx.outer) + } + } + else CyclicReferenceInvolving(denot) + + val cycleSym = denot.symbol + if (cycleSym.is(Implicit, butNot = Method) && cycleSym.owner.isTerm) + CyclicReferenceInvolvingImplicit(cycleSym) + else + errorMsg(ctx) + } +} + +object CyclicReference { + def apply(denot: SymDenotation)(implicit ctx: Context): CyclicReference = { + val ex = new CyclicReference(denot) + if (!(ctx.mode is Mode.CheckCyclic)) { + cyclicErrors.println(ex.getMessage) + for (elem <- ex.getStackTrace take 200) + cyclicErrors.println(elem.toString) + } + ex + } +} + +class MergeError(val sym1: Symbol, val sym2: Symbol, val tp1: Type, val tp2: Type, prefix: Type) extends TypeError { + + private def showSymbol(sym: Symbol)(implicit ctx: Context): String = + if (sym.exists) sym.showLocated else "[unknown]" + + private def showType(tp: Type)(implicit ctx: Context) = tp match { + case ClassInfo(_, cls, _, _, _) => cls.showLocated + case _ => tp.show + } + + protected def addendum(implicit ctx: Context) = + if (prefix `eq` NoPrefix) "" else i"\nas members of type $prefix" + + override def toMessage(implicit ctx: Context): Message = { + if (ctx.debug) printStackTrace() + i"""cannot merge + | ${showSymbol(sym1)} of type ${showType(tp1)} and + | ${showSymbol(sym2)} of type ${showType(tp2)}$addendum + """ + } +} diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 1ff312dae475..05fd66b54282 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -326,10 +326,4 @@ trait TypeOps { this: Context => // TODO: Make standalone object. object TypeOps { @sharable var track = false // !!!DEBUG - - /** When a property with this key is set in a context, it limits the number - * of recursive member searches. If the limit is reached, findMember returns - * NoDenotation. - */ - val findMemberLimit = new Property.Key[Unit] } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 183bd53c287b..c70db6489198 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -20,7 +20,6 @@ import util.Positions.{Position, NoPosition} import util.Stats._ import util.{DotClass, SimpleIdentitySet} import reporting.diagnostic.Message -import reporting.diagnostic.messages.CyclicReferenceInvolving import ast.tpd._ import ast.TreeTypeMap import printing.Texts._ @@ -32,10 +31,9 @@ import Uniques._ import collection.{mutable, Seq} import config.Config import annotation.tailrec -import Flags.FlagSet import language.implicitConversions import scala.util.hashing.{ MurmurHash3 => hashing } -import config.Printers.{core, typr, cyclicErrors} +import config.Printers.{core, typr} import java.lang.ref.WeakReference object Types { @@ -665,19 +663,22 @@ object Types { } val recCount = ctx.findMemberCount - if (recCount >= Config.LogPendingFindMemberThreshold) { - if (ctx.property(TypeOps.findMemberLimit).isDefined && - ctx.findMemberCount > Config.PendingFindMemberLimit) - return NoDenotation + if (recCount >= Config.LogPendingFindMemberThreshold) ctx.pendingMemberSearches = name :: ctx.pendingMemberSearches - } ctx.findMemberCount = recCount + 1 - //assert(ctx.findMemberCount < 20) try go(this) catch { case ex: Throwable => - core.println(i"findMember exception for $this member $name, pre = $pre") - throw ex // DEBUG + core.println(s"findMember exception for $this member $name, pre = $pre, recCount = $recCount") + + def showPrefixSafely(pre: Type)(implicit ctx: Context): String = pre.stripTypeVar match { + case pre: TermRef => i"${pre.termSymbol.name}." + case pre: TypeRef => i"${pre.typeSymbol.name}#" + case pre: TypeProxy => showPrefixSafely(pre.underlying) + case _ => if (pre.typeSymbol.exists) i"${pre.typeSymbol.name}#" else "." + } + + handleRecursive("find-member", i"${showPrefixSafely(pre)}$name", ex) } finally { if (recCount >= Config.LogPendingFindMemberThreshold) @@ -4553,45 +4554,6 @@ object Types { def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean = true } - // ----- Exceptions ------------------------------------------------------------- - - class TypeError(msg: String) extends Exception(msg) - - class MalformedType(pre: Type, denot: Denotation, absMembers: Set[Name]) - extends TypeError( - s"malformed type: $pre is not a legal prefix for $denot because it contains abstract type member${if (absMembers.size == 1) "" else "s"} ${absMembers.mkString(", ")}") - - class MissingType(pre: Type, name: Name)(implicit ctx: Context) extends TypeError( - i"""cannot resolve reference to type $pre.$name - |the classfile defining the type might be missing from the classpath${otherReason(pre)}""") { - if (ctx.debug) printStackTrace() - } - - private def otherReason(pre: Type)(implicit ctx: Context): String = pre match { - case pre: ThisType if pre.cls.givenSelfType.exists => - i"\nor the self type of $pre might not contain all transitive dependencies" - case _ => "" - } - - class CyclicReference private (val denot: SymDenotation) - extends TypeError(s"cyclic reference involving $denot") { - def toMessage(implicit ctx: Context) = CyclicReferenceInvolving(denot) - } - - object CyclicReference { - def apply(denot: SymDenotation)(implicit ctx: Context): CyclicReference = { - val ex = new CyclicReference(denot) - if (!(ctx.mode is Mode.CheckCyclic)) { - cyclicErrors.println(ex.getMessage) - for (elem <- ex.getStackTrace take 200) - cyclicErrors.println(elem.toString) - } - ex - } - } - - class MergeError(msg: String, val tp1: Type, val tp2: Type) extends TypeError(msg) - // ----- Debug --------------------------------------------------------- @sharable var debugTrace = false diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index f3a1218f69aa..9a0e9aab968e 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2014,7 +2014,16 @@ object Parsers { val from = termIdentOrWildcard() if (from.name != nme.WILDCARD && in.token == ARROW) atPos(startOffset(from), in.skipToken()) { - Thicket(from, termIdentOrWildcard()) + val start = in.offset + val to = termIdentOrWildcard() + val toWithPos = + if (to.name == nme.ERROR) + // error identifiers don't consume any characters, so atPos(start)(id) wouldn't set a position. + // Some testcases would then fail in Positioned.checkPos. Set a position anyway! + atPos(start, start, in.lastOffset)(to) + else + to + Thicket(from, toWithPos) } else from } diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/Message.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/Message.scala index 09d7ae9751be..74b4e1fe0194 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/Message.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/Message.scala @@ -65,6 +65,12 @@ abstract class Message(val errorId: ErrorMessageID) { self => val kind = self.kind val explanation = self.explanation } + + def append(suffix: => String): Message = new Message(errorId) { + val msg = self.msg ++ suffix + val kind = self.kind + val explanation = self.explanation + } } /** An extended message keeps the contained message from being evaluated, while diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 3bcf99e8b3cc..e26fe556510b 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -14,7 +14,6 @@ import Symbols._ import NameOps._ import NameKinds.DefaultGetterName import typer.Inliner -import typer.ErrorReporting.cyclicErrorMsg import transform.ValueClasses import transform.SymUtils._ import dotty.tools.io.File @@ -243,10 +242,10 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder val ancestorTypes0 = try linearizedAncestorTypes(cinfo) catch { - case ex: CyclicReference => + case ex: TypeError => // See neg/i1750a for an example where a cyclic error can arise. // The root cause in this example is an illegal "override" of an inner trait - ctx.error(cyclicErrorMsg(ex), csym.pos) + ctx.error(ex.toMessage, csym.pos) defn.ObjectType :: Nil } if (ValueClasses.isDerivedValueClass(csym)) { diff --git a/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala b/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala index 9d84a2adb66c..5c3dea9cb79e 100644 --- a/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala @@ -46,22 +46,27 @@ abstract class MacroTransform extends Phase { flatten(trees.mapconserve(transformStat(_))) } - override def transform(tree: Tree)(implicit ctx: Context): Tree = { - tree match { - case EmptyValDef => + override def transform(tree: Tree)(implicit ctx: Context): Tree = + try + tree match { + case EmptyValDef => + tree + case _: PackageDef | _: MemberDef => + super.transform(tree)(localCtx(tree)) + case impl @ Template(constr, parents, self, _) => + cpy.Template(tree)( + transformSub(constr), + transform(parents)(ctx.superCallContext), + transformSelf(self), + transformStats(impl.body, tree.symbol)) + case _ => + super.transform(tree) + } + catch { + case ex: TypeError => + ctx.error(ex.toMessage, tree.pos) tree - case _: PackageDef | _: MemberDef => - super.transform(tree)(localCtx(tree)) - case impl @ Template(constr, parents, self, _) => - cpy.Template(tree)( - transformSub(constr), - transform(parents)(ctx.superCallContext), - transformSelf(self), - transformStats(impl.body, tree.symbol)) - case _ => - super.transform(tree) } - } def transformSelf(vd: ValDef)(implicit ctx: Context) = cpy.ValDef(vd)(tpt = transform(vd.tpt)) diff --git a/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala b/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala index 868c48b0c0ae..8e1981c85e07 100644 --- a/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala +++ b/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala @@ -157,42 +157,56 @@ class MegaPhase(val miniPhases: Array[MiniPhase]) extends Phase { /** Transform node using all phases in this group that have idxInGroup >= start */ def transformNode(tree: Tree, start: Int)(implicit ctx: Context): Tree = { - def goNamed(tree: Tree, start: Int) = tree match { - case tree: Ident => goIdent(tree, start) - case tree: Select => goSelect(tree, start) - case tree: ValDef => goValDef(tree, start) - case tree: DefDef => goDefDef(tree, start) - case tree: TypeDef => goTypeDef(tree, start) - case tree: Bind => goBind(tree, start) - case _ => goOther(tree, start) - } - def goUnnamed(tree: Tree, start: Int) = tree match { - case tree: Apply => goApply(tree, start) - case tree: TypeTree => goTypeTree(tree, start) - case tree: Thicket => - cpy.Thicket(tree)(tree.trees.mapConserve(transformNode(_, start))) - case tree: This => goThis(tree, start) - case tree: Literal => goLiteral(tree, start) - case tree: Block => goBlock(tree, start) - case tree: TypeApply => goTypeApply(tree, start) - case tree: If => goIf(tree, start) - case tree: New => goNew(tree, start) - case tree: Typed => goTyped(tree, start) - case tree: CaseDef => goCaseDef(tree, start) - case tree: Closure => goClosure(tree, start) - case tree: Assign => goAssign(tree, start) - case tree: SeqLiteral => goSeqLiteral(tree, start) - case tree: Super => goSuper(tree, start) - case tree: Template => goTemplate(tree, start) - case tree: Match => goMatch(tree, start) - case tree: UnApply => goUnApply(tree, start) - case tree: PackageDef => goPackageDef(tree, start) - case tree: Try => goTry(tree, start) - case tree: Inlined => goInlined(tree, start) - case tree: Return => goReturn(tree, start) - case tree: Alternative => goAlternative(tree, start) - case tree => goOther(tree, start) - } + def goNamed(tree: Tree, start: Int) = + try + tree match { + case tree: Ident => goIdent(tree, start) + case tree: Select => goSelect(tree, start) + case tree: ValDef => goValDef(tree, start) + case tree: DefDef => goDefDef(tree, start) + case tree: TypeDef => goTypeDef(tree, start) + case tree: Bind => goBind(tree, start) + case _ => goOther(tree, start) + } + catch { + case ex: TypeError => + ctx.error(ex.toMessage, tree.pos) + tree + } + def goUnnamed(tree: Tree, start: Int) = + try + tree match { + case tree: Apply => goApply(tree, start) + case tree: TypeTree => goTypeTree(tree, start) + case tree: Thicket => + cpy.Thicket(tree)(tree.trees.mapConserve(transformNode(_, start))) + case tree: This => goThis(tree, start) + case tree: Literal => goLiteral(tree, start) + case tree: Block => goBlock(tree, start) + case tree: TypeApply => goTypeApply(tree, start) + case tree: If => goIf(tree, start) + case tree: New => goNew(tree, start) + case tree: Typed => goTyped(tree, start) + case tree: CaseDef => goCaseDef(tree, start) + case tree: Closure => goClosure(tree, start) + case tree: Assign => goAssign(tree, start) + case tree: SeqLiteral => goSeqLiteral(tree, start) + case tree: Super => goSuper(tree, start) + case tree: Template => goTemplate(tree, start) + case tree: Match => goMatch(tree, start) + case tree: UnApply => goUnApply(tree, start) + case tree: PackageDef => goPackageDef(tree, start) + case tree: Try => goTry(tree, start) + case tree: Inlined => goInlined(tree, start) + case tree: Return => goReturn(tree, start) + case tree: Alternative => goAlternative(tree, start) + case tree => goOther(tree, start) + } + catch { + case ex: TypeError => + ctx.error(ex.toMessage, tree.pos) + tree + } if (tree.isInstanceOf[NameTree]) goNamed(tree, start) else goUnnamed(tree, start) } diff --git a/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala index 57858600dad6..2f166891f353 100644 --- a/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala +++ b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala @@ -6,7 +6,6 @@ import Flags._, Symbols._, Contexts._, Types._, Scopes._, Decorators._ import util.HashSet import collection.mutable import collection.immutable.BitSet -import typer.ErrorReporting.cyclicErrorMsg import scala.annotation.tailrec /** A module that can produce a kind of iterator (`Cursor`), @@ -133,10 +132,10 @@ object OverridingPairs { } } catch { - case ex: CyclicReference => + case ex: TypeError => // See neg/i1750a for an example where a cyclic error can arise. // The root cause in this example is an illegal "override" of an inner trait - ctx.error(cyclicErrorMsg(ex), base.pos) + ctx.error(ex.toMessage, base.pos) } } else { curEntry = curEntry.prev diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index ad4885f14669..2ad44d9cb3da 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -6,7 +6,7 @@ import scala.collection.mutable import core._ import typer.Checking import Types._, Contexts._, Names._, Flags._, DenotTransformers._ -import SymDenotations._, StdNames._, Annotations._, Trees._ +import SymDenotations._, StdNames._, Annotations._, Trees._, Scopes._ import Decorators._ import Symbols._, SymUtils._ import reporting.diagnostic.messages.{ImportRenamedTwice, NotAMember, SuperCallsNotAllowedInline} @@ -283,6 +283,11 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase case tpe => tpe } ) + case tree: AndTypeTree => + // Ideally, this should be done by Typer, but we run into cyclic references + // when trying to typecheck self types which are intersections. + Checking.checkNonCyclicInherited(tree.tpe, tree.left.tpe :: tree.right.tpe :: Nil, EmptyScope, tree.pos) + super.transform(tree) case Import(expr, selectors) => val exprTpe = expr.tpe val seen = mutable.Set.empty[Name] diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 7c5d0894a18a..37d45fd3d45d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -13,6 +13,7 @@ import Symbols._ import Trees._ import TreeInfo._ import ProtoTypes._ +import Scopes._ import CheckRealizable._ import ErrorReporting.errorTree @@ -221,7 +222,11 @@ object Checking { // global symbols when doing the cyclicity check. def isInteresting(prefix: Type): Boolean = prefix.stripTypeVar match { case NoPrefix => true - case prefix: ThisType => sym.owner.isClass && prefix.cls.isContainedIn(sym.owner) + case prefix: ThisType => + sym.owner.isClass && ( + prefix.cls.isContainedIn(sym.owner) // sym reachable through outer references + || sym.owner.isContainedIn(prefix.cls) // sym reachable through member references + ) case prefix: NamedType => (!sym.is(Private) && prefix.derivesFrom(sym.owner)) || (!prefix.symbol.isStaticOwner && isInteresting(prefix.prefix)) @@ -231,6 +236,7 @@ object Checking { case _: RefinedOrRecType | _: AppliedType => true case _ => false } + if (isInteresting(pre)) { val pre1 = this(pre, false, false) if (locked.contains(tp) || tp.symbol.infoOrCompleter.isInstanceOf[NoCompleter]) @@ -321,6 +327,33 @@ object Checking { checkTree((), refinement) } + /** Check type members inherited from different `parents` of `joint` type for cycles, + * unless a type with the same name aleadry appears in `decls`. + * @return true iff no cycles were detected + */ + def checkNonCyclicInherited(joint: Type, parents: List[Type], decls: Scope, pos: Position)(implicit ctx: Context): Unit = { + def qualifies(sym: Symbol) = sym.name.isTypeName && !sym.is(Private) + val abstractTypeNames = + for (parent <- parents; mbr <- parent.abstractTypeMembers if qualifies(mbr.symbol)) + yield mbr.name.asTypeName + + for (name <- abstractTypeNames) + try { + val mbr = joint.member(name) + mbr.info match { + case bounds: TypeBounds => + !checkNonCyclic(mbr.symbol, bounds, reportErrors = true).isError + case _ => + true + } + } + catch { + case ex: RecursionOverflow => + ctx.error(em"cyclic reference involving type $name", pos) + false + } + } + /** Check that symbol's definition is well-formed. */ def checkWellFormed(sym: Symbol)(implicit ctx: Context): Unit = { def fail(msg: Message) = ctx.error(msg, sym.pos) @@ -520,6 +553,9 @@ trait Checking { def checkNonCyclic(sym: Symbol, info: TypeBounds, reportErrors: Boolean)(implicit ctx: Context): Type = Checking.checkNonCyclic(sym, info, reportErrors) + def checkNonCyclicInherited(joint: Type, parents: List[Type], decls: Scope, pos: Position)(implicit ctx: Context): Unit = + Checking.checkNonCyclicInherited(joint, parents, decls, pos) + /** Check that Java statics and packages can only be used in selections. */ def checkValue(tree: Tree, proto: Type)(implicit ctx: Context): tree.type = { @@ -906,6 +942,7 @@ trait ReChecking extends Checking { trait NoChecking extends ReChecking { import tpd._ override def checkNonCyclic(sym: Symbol, info: TypeBounds, reportErrors: Boolean)(implicit ctx: Context): Type = info + override def checkNonCyclicInherited(joint: Type, parents: List[Type], decls: Scope, pos: Position)(implicit ctx: Context): Unit = () override def checkValue(tree: Tree, proto: Type)(implicit ctx: Context): tree.type = tree override def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = () override def checkClassType(tp: Type, pos: Position, traitReq: Boolean, stablePrefixReq: Boolean)(implicit ctx: Context): Type = tp diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 6e03384e31e1..79799c9013a1 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -23,26 +23,6 @@ object ErrorReporting { ErrorType(msg) } - def cyclicErrorMsg(ex: CyclicReference)(implicit ctx: Context) = { - val cycleSym = ex.denot.symbol - def errorMsg(msg: Message, cx: Context): Message = - if (cx.mode is Mode.InferringReturnType) { - cx.tree match { - case tree: untpd.DefDef if !tree.tpt.typeOpt.exists => - OverloadedOrRecursiveMethodNeedsResultType(tree.name) - case tree: untpd.ValDef if !tree.tpt.typeOpt.exists => - RecursiveValueNeedsResultType(tree.name) - case _ => - errorMsg(msg, cx.outer) - } - } else msg - - if (cycleSym.is(Implicit, butNot = Method) && cycleSym.owner.isTerm) - CyclicReferenceInvolvingImplicit(cycleSym) - else - errorMsg(ex.toMessage, ctx) - } - def wrongNumberOfTypeArgs(fntpe: Type, expectedArgs: List[ParamInfo], actual: List[untpd.Tree], pos: Position)(implicit ctx: Context) = errorType(WrongNumberOfTypeArgs(fntpe, expectedArgs, actual)(ctx), pos) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 49a507e0994c..ae4eb968d21d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -257,13 +257,13 @@ object Inliner { * and body that replace it. */ def inlineCall(tree: Tree, pt: Type)(implicit ctx: Context): Tree = - if (enclosingInlineds.length < ctx.settings.xmaxInlines.value) { + if (enclosingInlineds.length < ctx.settings.XmaxInlines.value) { val body = bodyToInline(tree.symbol) // can typecheck the tree and thereby produce errors if (ctx.reporter.hasErrors) tree else new Inliner(tree, body).inlined(pt) } else errorTree( tree, - i"""|Maximal number of successive inlines (${ctx.settings.xmaxInlines.value}) exceeded, + i"""|Maximal number of successive inlines (${ctx.settings.XmaxInlines.value}) exceeded, |Maybe this is caused by a recursive inline method? |You can use -Xmax:inlines to change the limit.""" ) diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 628c069d99d5..af7dbdaed390 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -1614,7 +1614,7 @@ class RefChecks extends MiniPhase { thisPhase => } catch { case ex: TypeError => if (settings.debug) ex.printStackTrace() - unit.error(tree.pos, ex.getMessage()) + unit.error(tree.pos, ex.toMssage) tree } finally { localTyper = savedLocalTyper diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 24ed19112fb6..1978e02f1fa0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1481,7 +1481,13 @@ class Typer extends Namer val seenParents = mutable.Set[Symbol]() def typedParent(tree: untpd.Tree): Tree = { - var result = if (tree.isType) typedType(tree)(superCtx) else typedExpr(tree)(superCtx) + @tailrec + def isTreeType(t: untpd.Tree): Boolean = t match { + case _: untpd.Function => true + case untpd.Parens(t1) => isTreeType(t1) + case _ => tree.isType + } + var result = if (isTreeType(tree)) typedType(tree)(superCtx) else typedExpr(tree)(superCtx) val psym = result.tpe.typeSymbol if (seenParents.contains(psym)) ctx.error(i"$psym is extended twice", tree.pos) seenParents += psym @@ -1544,6 +1550,8 @@ class Typer extends Namer cls, isRequired, cdef.pos) } + checkNonCyclicInherited(cls.thisType, cls.classParents, cls.info.decls, cdef.pos) + // check value class constraints checkDerivedValueClass(cls, body1) @@ -1855,8 +1863,7 @@ class Typer extends Namer assertPositioned(tree) try adapt(typedUnadapted(tree, pt, locked), pt, locked) catch { - case ex: CyclicReference => errorTree(tree, cyclicErrorMsg(ex)) - case ex: TypeError => errorTree(tree, ex.getMessage) + case ex: TypeError => errorTree(tree, ex.toMessage) } } diff --git a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala index 0ba36c2ac306..ee98321dcc78 100644 --- a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala +++ b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala @@ -82,21 +82,25 @@ class VarianceChecker()(implicit ctx: 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) { - if (status.isDefined) status - else tp match { - case tp: TypeRef => - val sym = tp.symbol - if (sym.variance != 0 && base.isContainedIn(sym.owner)) checkVarianceOfSymbol(sym) - else if (sym.isAliasType) this(status, sym.info.bounds.hi) - else foldOver(status, tp) - case tp: MethodOrPoly => - this(status, tp.resultType) // params will be checked in their TypeDef or ValDef nodes. - case AnnotatedType(_, annot) if annot.symbol == defn.UncheckedVarianceAnnot => - status - //case tp: ClassInfo => - // ??? not clear what to do here yet. presumably, it's all checked at local typedefs - case _ => - foldOver(status, tp) + try + if (status.isDefined) status + else tp match { + case tp: TypeRef => + val sym = tp.symbol + if (sym.variance != 0 && base.isContainedIn(sym.owner)) checkVarianceOfSymbol(sym) + else if (sym.isAliasType) this(status, sym.info.bounds.hi) + else foldOver(status, tp) + case tp: MethodOrPoly => + this(status, tp.resultType) // params will be checked in their TypeDef or ValDef nodes. + case AnnotatedType(_, annot) if annot.symbol == defn.UncheckedVarianceAnnot => + status + //case tp: ClassInfo => + // ??? not clear what to do here yet. presumably, it's all checked at local typedefs + case _ => + foldOver(status, tp) + } + catch { + case ex: Throwable => handleRecursive("variance check of", tp.show, ex) } } diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index aaa8ac216b5b..03906caae63d 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -187,6 +187,7 @@ class CompilationTests extends ParallelTesting { compileFile("tests/neg-custom-args/noimports.scala", defaultOptions.and("-Yno-imports")) + compileFile("tests/neg-custom-args/noimports2.scala", defaultOptions.and("-Yno-imports")) + compileFile("tests/neg-custom-args/i3882.scala", allowDeepSubtypes) + + compileFile("tests/neg-custom-args/i4372.scala", allowDeepSubtypes) + compileFile("tests/neg-custom-args/i1754.scala", allowDeepSubtypes) + compileFilesInDir("tests/neg-custom-args/isInstanceOf", allowDeepSubtypes and "-Xfatal-warnings") + compileFile("tests/neg-custom-args/i3627.scala", allowDeepSubtypes) diff --git a/tests/neg-custom-args/i4372.scala b/tests/neg-custom-args/i4372.scala new file mode 100644 index 000000000000..e6e75061bb02 --- /dev/null +++ b/tests/neg-custom-args/i4372.scala @@ -0,0 +1,3 @@ +object i4372 { + class X[A >: X[_ <: X[_]] <: X[A]] // error: too deep +} diff --git a/tests/neg/i4368.scala b/tests/neg/i4368.scala new file mode 100644 index 000000000000..c5c0f5223d95 --- /dev/null +++ b/tests/neg/i4368.scala @@ -0,0 +1,173 @@ +object Test1 { + trait X { + type A = B + type B + } + trait Y { + type A + type B = A + } + trait Z extends X with Y // error: cyclic +} + +object Test2 { + trait W { + type A + type B + } + trait X { z: W => + type A = z.B + type B + } + trait Y { z: W => + type A + type B = z.A + } + trait Z extends X with Y // error: cyclic +} + +object Test3 { + trait W { + type A + type B + } + trait X { z: W => + type A = z.B + type B + } + trait Y { z: W => + type A + type B = z.A + } + + object App { + type Z = X with Y + val z: Z = z + val a: z.A = a // error: too deep + } +} + +object Test4 { + trait X[F[_]] { + protected type A = F[B] + protected type B + } + trait Y[F[_]] { + protected type A + protected type B = F[A] + } + + trait Fix[F[_]] extends X[F] with Y[F] { + type Result = A // error: too deep + } +} + +object Test5 { + trait X { + type A = B + type B + } + trait Y { + type A + type B = A + } + + object App { + type Z = X & Y + val z: Z = z + val a: z.A = a // error: too deep + } +} + +object Test6 { + trait W { type T <: W; val t: T } + trait X { + type A = b.T + val a : A = b.t + type B <: W + val b : B + } + trait Y { + type A <: W + val a : A + type B = a.T + val b = a.t + } + trait Z extends X with Y // error: cyclic +} + +object Test7 { + class Fix[F[_]] { + class Foo { type R >: F[T] <: F[T] } // error: cyclic + type T = F[Foo#R] + } + + object App { + type Nat = Fix[Option]#T + } +} +object Test8 { + + class A { + type T = B#U // error: cyclic + } + + class B { + type U = A#T + } +} +object Test9 { + trait W { + type A + } + trait X extends W { + type A = B + type B + } + trait Y extends W { + type A + type B = A + } + + trait Foo[X <: W, Y <: W] { + type Z = X & Y + val z: Z + val a: z.A + } + + trait Boo { + val f: Foo[X, Y] + } + + trait Baz extends Boo { + val a = f.a // error: member search too deep + // this should be a cyclic error, but it goes undetected + // scalac reports a volatility error, but the dotty equivalent (checkRealizable) + // is checked too late. + } +} +object i4369 { + trait X { self => + type R <: Z + type Z >: X { type R = self.R; type Z = self.R } + } + class Foo extends X { type R = Foo; type Z = Foo } // error: too deep +} +object i4370 { + class Foo { type R = A } // error: cyclic + type A = List[Foo#R] +} +object i4371 { + class Foo { type A = Boo#B } // error: cyclic + class Boo { type B = Foo#A } +} +object i318 { + trait Y { + type A <: { type T >: B } + type B >: { type T >: A } + } + + val y: Y = ??? + val a: y.A = ??? // error: too deep + val b: y.B = a // error: too deep +} \ No newline at end of file diff --git a/tests/neg/parser-stability-23.scala b/tests/neg/parser-stability-23.scala new file mode 100644 index 000000000000..b8db4d937f25 --- /dev/null +++ b/tests/neg/parser-stability-23.scala @@ -0,0 +1,3 @@ +object i0 { + import Ordering.{ implicitly => } (true: Boolean) match { case _: i1 => true } // error // error +} diff --git a/tests/neg/parser-stability-25.scala b/tests/neg/parser-stability-25.scala new file mode 100644 index 000000000000..987ae79eb167 --- /dev/null +++ b/tests/neg/parser-stability-25.scala @@ -0,0 +1,11 @@ +class A extends (Int => i1) // error +class B extends (Int => this) // error +trait C { + val bar: Int => this // error +} + +// Test that function types ending in SIP-23 singleton types are understood correctly. + +class D extends (Int => 1) { + def apply(x: Int) = 2 // error +} diff --git a/tests/neg/parser-stability-26.scala b/tests/neg/parser-stability-26.scala new file mode 100644 index 000000000000..dd2c00d5c5b9 --- /dev/null +++ b/tests/neg/parser-stability-26.scala @@ -0,0 +1,2 @@ +// Test that function types ending in SIP-23 singleton types are understood correctly. +class E extends (Int => 1) // error diff --git a/tests/pos/singleton-fun-types.scala b/tests/pos/singleton-fun-types.scala new file mode 100644 index 000000000000..dd2f8525c9c4 --- /dev/null +++ b/tests/pos/singleton-fun-types.scala @@ -0,0 +1,4 @@ +trait C extends (Int => 1) +class D extends (Int => 1) { + def apply(x: Int) = 1 +} diff --git a/tests/run/returning.scala b/tests/run/returning.scala new file mode 100644 index 000000000000..f0af05e2ba14 --- /dev/null +++ b/tests/run/returning.scala @@ -0,0 +1,53 @@ +package scala.util.control { + +object NonLocalReturns { + + class ReturnThrowable[T] extends ControlThrowable { + private var myResult: T = _ + def throwReturn(result: T): Nothing = { + myResult = result + throw this + } + def result: T = myResult + } + + def throwReturn[T](result: T)(implicit returner: ReturnThrowable[T]): Nothing = + returner.throwReturn(result) + + def returning[T](op: implicit ReturnThrowable[T] => T): T = { + val returner = new ReturnThrowable[T] + try op(returner) + catch { + case ex: ReturnThrowable[_] => + if (ex `eq` returner) ex.result.asInstanceOf[T] else throw ex + } + } +} +} + +object Test extends App { + + import scala.util.control.NonLocalReturns._ + import scala.collection.mutable.ListBuffer + + def has(xs: List[Int], elem: Int) = + returning { + for (x <- xs) + if (x == elem) throwReturn(true) + false + } + + def takeUntil(xs: List[Int], elem: Int) = + returning { + var buf = new ListBuffer[Int] + for (x <- xs) + yield { + if (x == elem) throwReturn(buf.toList) + buf += x + x + } + } + + assert(has(List(1, 2, 3), 2)) + assert(takeUntil(List(1, 2, 3), 3) == List(1, 2)) +} \ No newline at end of file