From dbe86f5865cc8d5d6921dcbbb4ea4d6128dccac6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 1 Mar 2020 23:20:50 +0100 Subject: [PATCH] Give reasonable error messages when classfiles are not found Fixes #6501 Fixes #8405 Both tests exercise corner cases where a type seems to be present at first but then is missing. It took some effort to dig out the useful error message from excetiopn handling code. --- .../dotty/tools/dotc/core/Denotations.scala | 7 +++--- .../dotty/tools/dotc/core/TypeErrors.scala | 14 ++++++----- .../tools/dotc/core/tasty/TreePickler.scala | 2 ++ .../dotc/reporting/diagnostic/Message.scala | 2 +- tests/neg/i6501.scala | 24 +++++++++++++++++++ tests/neg/i8405.scala | 11 +++++++++ 6 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 tests/neg/i6501.scala create mode 100644 tests/neg/i8405.scala diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 7039fec0875b..e03ffd2741b5 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -832,9 +832,10 @@ object Denotations { private def updateValidity()(implicit ctx: Context): this.type = { assert( - ctx.runId >= validFor.runId || - ctx.settings.YtestPickler.value || // mixing test pickler with debug printing can travel back in time - symbol.is(Permanent), // Permanent symbols are valid in all runIds + ctx.runId >= validFor.runId + || ctx.settings.YtestPickler.value // mixing test pickler with debug printing can travel back in time + || ctx.mode.is(Mode.Printing) // no use to be picky when printing error messages + || symbol.isOneOf(ValidForeverFlags), s"denotation $this invalid in run ${ctx.runId}. ValidFor: $validFor") var d: SingleDenotation = this while ({ diff --git a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala index adf0f1a75583..20372063d249 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala @@ -17,12 +17,14 @@ import config.Printers.cyclicErrors class TypeError(msg: String) extends Exception(msg) { def this() = this("") - def toMessage(implicit ctx: Context): Message = super.getMessage + final def toMessage(implicit ctx: Context): Message = + produceMessage(using ctx.addMode(Mode.Printing)) + def produceMessage(using Context): Message = super.getMessage override def getMessage: String = super.getMessage } class MalformedType(pre: Type, denot: Denotation, absMembers: Set[Name]) extends TypeError { - override def toMessage(implicit ctx: Context): Message = + override def produceMessage(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(", ")}" } @@ -33,7 +35,7 @@ class MissingType(pre: Type, name: Name) extends TypeError { case _ => "" } - override def toMessage(implicit ctx: Context): Message = { + override def produceMessage(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)}""" @@ -67,7 +69,7 @@ class RecursionOverflow(val op: String, details: => String, val previous: Throwa (rs.map(_.explanation): List[String]).mkString("\n ", "\n| ", "") } - override def toMessage(implicit ctx: Context): Message = { + override def produceMessage(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? @@ -109,7 +111,7 @@ object handleRecursive { class CyclicReference private (val denot: SymDenotation) extends TypeError { var inImplicitSearch: Boolean = false - override def toMessage(implicit ctx: Context): Message = { + override def produceMessage(implicit ctx: Context): Message = { val cycleSym = denot.symbol // cycleSym.flags would try completing denot and would fail, but here we can use flagsUNSAFE to detect flags @@ -182,7 +184,7 @@ class MergeError(val sym1: Symbol, val sym2: Symbol, val tp1: Type, val tp2: Typ s"\nas members of $owner" } - override def toMessage(implicit ctx: Context): Message = { + override def produceMessage(implicit ctx: Context): Message = { if (ctx.debug) printStackTrace() i"""cannot merge | ${showSymbol(sym1)} of type ${showType(tp1)} and diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 0afaab9d757f..47df2955b211 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -606,6 +606,8 @@ class TreePickler(pickler: TastyPickler) { } } catch { + case ex: TypeError => + ctx.error(ex.toMessage, tree.sourcePos.focus) case ex: AssertionError => println(i"error when pickling tree $tree") throw ex diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/Message.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/Message.scala index 48c5deaaa9a2..b8eb546c1d24 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/Message.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/Message.scala @@ -124,7 +124,7 @@ class NoExplanation(val msg: String) extends Message(ErrorMessageID.NoExplanatio val explanation: String = "" val kind: String = "" - override def toString(): String = s"NoExplanation($msg)" + override def toString(): String = msg } /** The extractor for `NoExplanation` can be used to check whether any error diff --git a/tests/neg/i6501.scala b/tests/neg/i6501.scala new file mode 100644 index 000000000000..b73abd53624e --- /dev/null +++ b/tests/neg/i6501.scala @@ -0,0 +1,24 @@ +import scala.collection.immutable.HashMap + +trait MapImpl { + type Key + type Value + type Map + val lookup: Map => Key => Value +} +class HashMapImpl[K, V] extends MapImpl { + type Key = K + type Value = V + type Map = HashMap[K, V] + val lookup: Map => Key => Value = m => k => m(k) +} +object Foo { + val Server0: + (mImpl: MapImpl) => mImpl.Map => mImpl.Key => mImpl.Value + = mImpl => mImpl.lookup + val Client: + (server: (mImpl: MapImpl & {type Key = String} & {type Value = Int}) => mImpl.Map => String => Int) => Int = + // server => server(??? : (HashMapImpl[String, Int]))(???)("test lookup key") //works + // server => server(HashMapImpl[String, Int])(???)("") //works + server => server(???)(???)("test lookup key") // error +} \ No newline at end of file diff --git a/tests/neg/i8405.scala b/tests/neg/i8405.scala new file mode 100644 index 000000000000..4d337bc2a34f --- /dev/null +++ b/tests/neg/i8405.scala @@ -0,0 +1,11 @@ +class ActorRef +trait ActorEventBus { + type Subscriber = ActorRef +} +trait ManagedActorClassification { this: ActorEventBus => + def unsubscribe(subscriber: Subscriber): Unit = ??? +} +class ActorClassificationUnsubscriber(bus: ManagedActorClassification) { + val actor = ??? + bus.unsubscribe(actor) // error +} \ No newline at end of file