From 21261c07bb4bdf8aeb493a7fc1893e75c1085344 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 27 May 2024 13:09:29 +0200 Subject: [PATCH 1/4] Explain unresolvable references better We run into problems when referring to a member of a self type of a class that it not also a member of the class from outside via an asSeenFrom. One example is in 11226.scala where we see: ```scala trait ManagedActorClassification { this: ActorEventBus => def unsubscribe(subscriber: Subscriber): Unit } class Unsubscriber(bus: ManagedActorClassification) { def test(a: ActorRef): Unit = bus.unsubscribe(a) // error } ``` The problem is that `unsubscribe` refers to the type `Subscriber` which is not resolvable as a member of `bus`. one idea could be to rule out type signatures like `unsubscribe`, similar how we rule out public signatures referring to private members. But this could rule out existing valid programs. For instance, the `unsubscribe` signature is unproblematic if it gets only called with prefixes that inherit `ActorEventBus`. You could say that the problem was instead that the type of `bus` was not specific enough. In the long term, maybe restructing the signature is the right move. But for now, we just try to give better error messages in the case of existing failures. Fixes #11226 --- .../dotty/tools/dotc/core/TypeErrors.scala | 29 ++++++++++++++----- .../dotty/tools/dotc/reporting/messages.scala | 12 +++++++- tests/neg/i11226.check | 6 ++++ tests/neg/i11226.scala | 14 +++++++++ tests/neg/i11226a.check | 12 ++++++++ tests/neg/i11226a.scala | 13 +++++++++ tests/neg/i16407.check | 12 ++++---- tests/pos/i11226b.scala | 11 +++++++ 8 files changed, 94 insertions(+), 15 deletions(-) create mode 100644 tests/neg/i11226.check create mode 100644 tests/neg/i11226.scala create mode 100644 tests/neg/i11226a.check create mode 100644 tests/neg/i11226a.scala create mode 100644 tests/pos/i11226b.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala index 5b19fe0e7bdd..79d1cecbd6be 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala @@ -57,17 +57,30 @@ end TypeError class MalformedType(pre: Type, denot: Denotation, absMembers: Set[Name])(using Context) extends TypeError: def toMessage(using Context) = em"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)(using Context) extends TypeError: - private def otherReason(pre: Type)(using 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 MissingType(val pre: Type, val name: Name)(using Context) extends TypeError: + + def reason(using Context): String = + def missingClassFile = + "The classfile defining the type might be missing from the classpath" + val cls = pre.classSymbol + val givenSelf = cls match + case cls: ClassSymbol => cls.givenSelfType + case _ => NoType + pre match + case pre: ThisType if pre.cls.givenSelfType.exists => + i"""$missingClassFile + |or the self type of $pre might not contain all transitive dependencies""" + case _ if givenSelf.exists && givenSelf.member(name).exists => + i"""$name exists as a member of the self type $givenSelf of $cls + |but it cannot be referenced from a scope that does not extend that ${ctx.printer.kindString(cls)}""" + case _ => + missingClassFile + override def toMessage(using Context): Message = if ctx.debug then printStackTrace() - em"""cannot resolve reference to type $pre.$name - |the classfile defining the type might be missing from the classpath${otherReason(pre)}""" + em"""Cannot resolve reference to type $pre.$name. + |$reason.""" end MissingType class RecursionOverflow(val op: String, details: => String, val previous: Throwable, val weight: Int)(using Context) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index ceb8ecbc8e03..9558981be800 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -301,6 +301,7 @@ class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tre // these are usually easier to analyze. We exclude F-bounds since these would // lead to a recursive infinite expansion. object reported extends TypeMap, IdentityCaptRefMap: + var notes: String = "" def setVariance(v: Int) = variance = v val constraint = mapCtx.typerState.constraint var fbounded = false @@ -318,6 +319,15 @@ class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tre case tp: LazyRef => fbounded = true tp + case tp @ TypeRef(pre, _) => + if pre != NoPrefix && !pre.member(tp.name).exists then + notes ++= + i""" + | + |Note that I could not resolve reference $tp. + |${MissingType(pre, tp.name).reason} + """ + mapOver(tp) case _ => mapOver(tp) @@ -329,7 +339,7 @@ class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tre else (found1, expected1) val (foundStr, expectedStr) = Formatting.typeDiff(found2, expected2) i"""|Found: $foundStr - |Required: $expectedStr""" + |Required: $expectedStr${reported.notes}""" end msg override def msgPostscript(using Context) = diff --git a/tests/neg/i11226.check b/tests/neg/i11226.check new file mode 100644 index 000000000000..90ed2d6a8ebe --- /dev/null +++ b/tests/neg/i11226.check @@ -0,0 +1,6 @@ +-- Error: tests/neg/i11226.scala:13:36 --------------------------------------------------------------------------------- +13 | def test(a: ActorRef): Unit = bus.unsubscribe(a) // error + | ^ + | Cannot resolve reference to type (Unsubscriber.this.bus : ManagedActorClassification).Subscriber. + | Subscriber exists as a member of the self type ActorEventBus of trait ManagedActorClassification + | but it cannot be referenced from a scope that does not extend that trait. diff --git a/tests/neg/i11226.scala b/tests/neg/i11226.scala new file mode 100644 index 000000000000..34c6eb78fd2d --- /dev/null +++ b/tests/neg/i11226.scala @@ -0,0 +1,14 @@ +trait ActorRef + +trait ActorEventBus { + type Subscriber = ActorRef +} + +trait ManagedActorClassification { this: ActorEventBus => + def unsubscribe(subscriber: Subscriber, from: Any): Unit + def unsubscribe(subscriber: Subscriber): Unit +} + +class Unsubscriber(bus: ManagedActorClassification) { + def test(a: ActorRef): Unit = bus.unsubscribe(a) // error +} \ No newline at end of file diff --git a/tests/neg/i11226a.check b/tests/neg/i11226a.check new file mode 100644 index 000000000000..871973264677 --- /dev/null +++ b/tests/neg/i11226a.check @@ -0,0 +1,12 @@ +-- [E007] Type Mismatch Error: tests/neg/i11226a.scala:12:48 ----------------------------------------------------------- +12 | def test(a: ActorRef): Unit = bus.unsubscribe(a) // error + | ^ + | Found: (a : ActorRef) + | Required: Unsubscriber.this.bus.Subscriber + | + | Note that I could not resolve reference Unsubscriber.this.bus.Subscriber. + | Subscriber exists as a member of the self type ActorEventBus of trait ManagedActorClassification + | but it cannot be referenced from a scope that does not extend that trait + | + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i11226a.scala b/tests/neg/i11226a.scala new file mode 100644 index 000000000000..f30530c5a58e --- /dev/null +++ b/tests/neg/i11226a.scala @@ -0,0 +1,13 @@ +trait ActorRef + +trait ActorEventBus { + type Subscriber = ActorRef +} + +trait ManagedActorClassification { this: ActorEventBus => + def unsubscribe(subscriber: Subscriber): Unit +} + +class Unsubscriber(bus: ManagedActorClassification) { + def test(a: ActorRef): Unit = bus.unsubscribe(a) // error +} \ No newline at end of file diff --git a/tests/neg/i16407.check b/tests/neg/i16407.check index 5c6bd19ca8c1..481d70e83ce3 100644 --- a/tests/neg/i16407.check +++ b/tests/neg/i16407.check @@ -1,12 +1,12 @@ -- Error: tests/neg/i16407.scala:2:2 ----------------------------------------------------------------------------------- 2 | f(g()) // error // error | ^ - | cannot resolve reference to type (X.this : Y & X).A - | the classfile defining the type might be missing from the classpath - | or the self type of (X.this : Y & X) might not contain all transitive dependencies + | Cannot resolve reference to type (X.this : Y & X).A. + | The classfile defining the type might be missing from the classpath + | or the self type of (X.this : Y & X) might not contain all transitive dependencies. -- Error: tests/neg/i16407.scala:2:4 ----------------------------------------------------------------------------------- 2 | f(g()) // error // error | ^ - | cannot resolve reference to type (X.this : Y & X).A - | the classfile defining the type might be missing from the classpath - | or the self type of (X.this : Y & X) might not contain all transitive dependencies + | Cannot resolve reference to type (X.this : Y & X).A. + | The classfile defining the type might be missing from the classpath + | or the self type of (X.this : Y & X) might not contain all transitive dependencies. diff --git a/tests/pos/i11226b.scala b/tests/pos/i11226b.scala new file mode 100644 index 000000000000..4074cbfc4b2e --- /dev/null +++ b/tests/pos/i11226b.scala @@ -0,0 +1,11 @@ +trait A { + class T() +} +trait B { + this: A => + def f(a: Int = 0): Any +} +trait C extends B { + this: A => + def f(t: T): Any +} \ No newline at end of file From 0b9ee335fa488458966fcbe8e2ce1e7601f49546 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 27 May 2024 15:53:37 +0200 Subject: [PATCH 2/4] Add test to bestEffortCompilation blacklist --- compiler/test/dotc/neg-best-effort-pickling.blacklist | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/test/dotc/neg-best-effort-pickling.blacklist b/compiler/test/dotc/neg-best-effort-pickling.blacklist index 1c8421b44539..2daf32509ed1 100644 --- a/compiler/test/dotc/neg-best-effort-pickling.blacklist +++ b/compiler/test/dotc/neg-best-effort-pickling.blacklist @@ -14,6 +14,7 @@ i17121.scala illegal-match-types.scala i13780-1.scala i20317a.scala +i11226.scala # semantic db generation fails in the first compilation i1642.scala From 66bba46f143efa3bb44202c956292ba82ce4b4a1 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 28 May 2024 16:25:31 +0200 Subject: [PATCH 3/4] Update compiler/src/dotty/tools/dotc/core/TypeErrors.scala Co-authored-by: Guillaume Martres --- compiler/src/dotty/tools/dotc/core/TypeErrors.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala index 79d1cecbd6be..11e313c47932 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala @@ -72,7 +72,7 @@ class MissingType(val pre: Type, val name: Name)(using Context) extends TypeErro |or the self type of $pre might not contain all transitive dependencies""" case _ if givenSelf.exists && givenSelf.member(name).exists => i"""$name exists as a member of the self type $givenSelf of $cls - |but it cannot be referenced from a scope that does not extend that ${ctx.printer.kindString(cls)}""" + |but it cannot be called on a receiver whose type does not extend $cls""" case _ => missingClassFile From e1ce6b99781ee6daeb363ee4b14b91dc5ba1ea69 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 28 May 2024 18:29:49 +0200 Subject: [PATCH 4/4] Update check files --- tests/neg/i11226.check | 6 +++--- tests/neg/i11226a.check | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/neg/i11226.check b/tests/neg/i11226.check index 90ed2d6a8ebe..571f54326808 100644 --- a/tests/neg/i11226.check +++ b/tests/neg/i11226.check @@ -1,6 +1,6 @@ -- Error: tests/neg/i11226.scala:13:36 --------------------------------------------------------------------------------- 13 | def test(a: ActorRef): Unit = bus.unsubscribe(a) // error | ^ - | Cannot resolve reference to type (Unsubscriber.this.bus : ManagedActorClassification).Subscriber. - | Subscriber exists as a member of the self type ActorEventBus of trait ManagedActorClassification - | but it cannot be referenced from a scope that does not extend that trait. + | Cannot resolve reference to type (Unsubscriber.this.bus : ManagedActorClassification).Subscriber. + | Subscriber exists as a member of the self type ActorEventBus of trait ManagedActorClassification + | but it cannot be called on a receiver whose type does not extend trait ManagedActorClassification. diff --git a/tests/neg/i11226a.check b/tests/neg/i11226a.check index 871973264677..ecb0760dd01c 100644 --- a/tests/neg/i11226a.check +++ b/tests/neg/i11226a.check @@ -1,12 +1,12 @@ -- [E007] Type Mismatch Error: tests/neg/i11226a.scala:12:48 ----------------------------------------------------------- 12 | def test(a: ActorRef): Unit = bus.unsubscribe(a) // error | ^ - | Found: (a : ActorRef) - | Required: Unsubscriber.this.bus.Subscriber + | Found: (a : ActorRef) + | Required: Unsubscriber.this.bus.Subscriber | - | Note that I could not resolve reference Unsubscriber.this.bus.Subscriber. - | Subscriber exists as a member of the self type ActorEventBus of trait ManagedActorClassification - | but it cannot be referenced from a scope that does not extend that trait - | + | Note that I could not resolve reference Unsubscriber.this.bus.Subscriber. + | Subscriber exists as a member of the self type ActorEventBus of trait ManagedActorClassification + | but it cannot be called on a receiver whose type does not extend trait ManagedActorClassification + | | | longer explanation available when compiling with `-explain`