Skip to content

Commit

Permalink
Explain unresolvable references better
Browse files Browse the repository at this point in the history
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
  • Loading branch information
odersky committed May 28, 2024
1 parent 9b5ab2e commit 21261c0
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 15 deletions.
29 changes: 21 additions & 8 deletions compiler/src/dotty/tools/dotc/core/TypeErrors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 11 additions & 1 deletion compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)

Expand All @@ -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) =
Expand Down
6 changes: 6 additions & 0 deletions tests/neg/i11226.check
Original file line number Diff line number Diff line change
@@ -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.
14 changes: 14 additions & 0 deletions tests/neg/i11226.scala
Original file line number Diff line number Diff line change
@@ -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
}
12 changes: 12 additions & 0 deletions tests/neg/i11226a.check
Original file line number Diff line number Diff line change
@@ -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`
13 changes: 13 additions & 0 deletions tests/neg/i11226a.scala
Original file line number Diff line number Diff line change
@@ -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
}
12 changes: 6 additions & 6 deletions tests/neg/i16407.check
Original file line number Diff line number Diff line change
@@ -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.
11 changes: 11 additions & 0 deletions tests/pos/i11226b.scala
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 21261c0

Please sign in to comment.