Skip to content

Commit 3a1f9f6

Browse files
committed
Fix #7597: Refine checks whether a deferred term member is implemented
The check for a concrete class used to be simply that its `abstractTermMembers` are empty. However, i7597.scala shows that this is not enough. The problem is that abstractTermMembers is based on signatures. It will not include a member as long as there is a concrete member with the same signature. But as #7597 shows it's possible for a concrete member to have the same signature as an abstract one but still not have a matching type.
1 parent 5374d91 commit 3a1f9f6

File tree

6 files changed

+59
-27
lines changed

6 files changed

+59
-27
lines changed

compiler/src/dotty/tools/dotc/core/SymDenotations.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2377,7 +2377,7 @@ object SymDenotations {
23772377
/** is the cache valid in current run at given phase? */
23782378
def isValidAt(phase: Phase)(implicit ctx: Context): Boolean
23792379

2380-
/** Render invalid this cache and all cache that depend on it */
2380+
/** Render invalid this cache and all caches that depend on it */
23812381
def invalidate(): Unit
23822382
}
23832383

compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import core._
66
import Contexts._, Types._, Symbols._, Names._, Decorators._, ProtoTypes._
77
import Flags._
88
import NameKinds.FlatName
9-
import config.Printers.implicits
9+
import config.Printers.{implicits, implicitsDetailed}
1010
import util.Spans.Span
1111
import ast.{untpd, tpd}
1212
import Implicits.{hasExtMethod, Candidate}
@@ -94,7 +94,7 @@ trait ImportSuggestions:
9494
def rootsIn(ref: TermRef)(using Context): List[TermRef] =
9595
if seen.contains(ref) then Nil
9696
else
97-
implicits.println(i"search for suggestions in ${ref.symbol.fullName}")
97+
implicitsDetailed.println(i"search for suggestions in ${ref.symbol.fullName}")
9898
seen += ref
9999
ref :: rootsStrictlyIn(ref)
100100

compiler/src/dotty/tools/dotc/typer/RefChecks.scala

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ object RefChecks {
169169
* TODO This still needs to be cleaned up; the current version is a straight port of what was there
170170
* before, but it looks too complicated and method bodies are far too large.
171171
*/
172-
private def checkAllOverrides(clazz: Symbol)(implicit ctx: Context): Unit = {
172+
private def checkAllOverrides(clazz: ClassSymbol)(implicit ctx: Context): Unit = {
173173
val self = clazz.thisType
174174
val upwardsSelf = upwardsThisType(clazz)
175175
var hasErrors = false
@@ -470,33 +470,52 @@ object RefChecks {
470470
}
471471
}
472472

473-
def ignoreDeferred(member: SingleDenotation) =
474-
member.isType || {
475-
val mbr = member.symbol
476-
mbr.isSuperAccessor || // not yet synthesized
477-
ShortcutImplicits.isImplicitShortcut(mbr) || // only synthesized when referenced, see Note in ShortcutImplicits
478-
mbr.is(JavaDefined) && hasJavaErasedOverriding(mbr)
479-
}
473+
def ignoreDeferred(mbr: Symbol) =
474+
mbr.isType
475+
|| mbr.isSuperAccessor // not yet synthesized
476+
|| ShortcutImplicits.isImplicitShortcut(mbr) // only synthesized when referenced, see Note in ShortcutImplicits
477+
|| mbr.is(JavaDefined) && hasJavaErasedOverriding(mbr)
478+
479+
def isImplemented(mbr: Symbol) =
480+
val mbrType = clazz.thisType.memberInfo(mbr)
481+
def (sym: Symbol).isConcrete = sym.exists && !sym.is(Deferred)
482+
clazz.nonPrivateMembersNamed(mbr.name)
483+
.filterWithPredicate(
484+
impl => impl.symbol.isConcrete && mbrType.matchesLoosely(impl.info))
485+
.exists
486+
487+
/** The term symbols in this class and its baseclasses that are
488+
* abstract in this class. We can't use memberNames for that since
489+
* a concrete member might have the same signature as an abstract
490+
* member in a base class, yet might not override it.
491+
*/
492+
def missingTermSymbols: List[Symbol] =
493+
val buf = new mutable.ListBuffer[Symbol]
494+
for bc <- clazz.baseClasses
495+
sym <- bc.info.decls.toList
496+
if sym.is(DeferredTerm) && !isImplemented(sym) && !ignoreDeferred(sym)
497+
do buf += sym
498+
buf.toList
480499

481500
// 2. Check that only abstract classes have deferred members
482501
def checkNoAbstractMembers(): Unit = {
483502
// Avoid spurious duplicates: first gather any missing members.
484-
val missing = clazz.thisType.abstractTermMembers.filterNot(ignoreDeferred)
503+
val missing = missingTermSymbols
485504
// Group missing members by the name of the underlying symbol,
486505
// to consolidate getters and setters.
487-
val grouped = missing.groupBy(_.symbol.underlyingSymbol.name)
506+
val grouped = missing.groupBy(_.underlyingSymbol.name)
488507

489508
val missingMethods = grouped.toList flatMap {
490509
case (name, syms) =>
491-
val withoutSetters = syms filterNot (_.symbol.isSetter)
510+
val withoutSetters = syms filterNot (_.isSetter)
492511
if (withoutSetters.nonEmpty) withoutSetters else syms
493512
}
494513

495514
def stubImplementations: List[String] = {
496515
// Grouping missing methods by the declaring class
497-
val regrouped = missingMethods.groupBy(_.symbol.owner).toList
498-
def membersStrings(members: List[SingleDenotation]) =
499-
members.sortBy(_.symbol.name.toString).map(_.showDcl + " = ???")
516+
val regrouped = missingMethods.groupBy(_.owner).toList
517+
def membersStrings(members: List[Symbol]) =
518+
members.sortBy(_.name.toString).map(_.showDcl + " = ???")
500519

501520
if (regrouped.tail.isEmpty)
502521
membersStrings(regrouped.head._2)
@@ -520,22 +539,21 @@ object RefChecks {
520539
}
521540

522541
for (member <- missing) {
523-
val memberSym = member.symbol
524542
def undefined(msg: String) =
525543
abstractClassError(false, s"${member.showDcl} is not defined $msg")
526-
val underlying = memberSym.underlyingSymbol
544+
val underlying = member.underlyingSymbol
527545

528546
// Give a specific error message for abstract vars based on why it fails:
529547
// It could be unimplemented, have only one accessor, or be uninitialized.
530548
if (underlying.is(Mutable)) {
531549
val isMultiple = grouped.getOrElse(underlying.name(ctx), Nil).size > 1
532550

533551
// If both getter and setter are missing, squelch the setter error.
534-
if (memberSym.isSetter && isMultiple) ()
552+
if (member.isSetter && isMultiple) ()
535553
else undefined(
536-
if (memberSym.isSetter) "\n(Note that an abstract var requires a setter in addition to the getter)"
537-
else if (memberSym.isGetter && !isMultiple) "\n(Note that an abstract var requires a getter in addition to the setter)"
538-
else err.abstractVarMessage(memberSym))
554+
if (member.isSetter) "\n(Note that an abstract var requires a setter in addition to the getter)"
555+
else if (member.isGetter && !isMultiple) "\n(Note that an abstract var requires a getter in addition to the setter)"
556+
else err.abstractVarMessage(member))
539557
}
540558
else if (underlying.is(Method)) {
541559
// If there is a concrete method whose name matches the unimplemented
@@ -961,7 +979,7 @@ class RefChecks extends MiniPhase { thisPhase =>
961979
}
962980

963981
override def transformTemplate(tree: Template)(implicit ctx: Context): Tree = try {
964-
val cls = ctx.owner
982+
val cls = ctx.owner.asClass
965983
checkOverloadedRestrictions(cls)
966984
checkParents(cls)
967985
if (cls.is(Trait)) tree.parents.foreach(checkParentPrefix(cls, _))

tests/explicit-nulls/neg/override-java-object-arg.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import javax.management.{Notification, NotificationEmitter, NotificationListener
77
class Foo {
88

99
def bar(): Unit = {
10-
val listener = new NotificationListener() {
10+
val listener = new NotificationListener() { // error: object creation impossible
1111
override def handleNotification(n: Notification|Null, emitter: Object): Unit = { // error: method handleNotification overrides nothing
1212
}
1313
}
@@ -17,7 +17,7 @@ class Foo {
1717
}
1818
}
1919

20-
val listener3 = new NotificationListener() {
20+
val listener3 = new NotificationListener() { // error: object creation impossible
2121
override def handleNotification(n: Notification, emitter: Object|Null): Unit = { // error: method handleNotification overrides nothing
2222
}
2323
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
case class Foo1(erased x: Int) // error // error
1+
case class Foo1(erased x: Int) // error

tests/neg/i7597.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
object Test extends App {
2+
def foo[S <: String]: String => Int =
3+
new (String => Int) { def apply(s: S): Int = 0 } // error
4+
5+
trait Fn[A, B] {
6+
def apply(x: A): B
7+
}
8+
9+
class C[S <: String] extends Fn[String, Int] { // error
10+
def apply(s: S): Int = 0
11+
}
12+
13+
foo("")
14+
}

0 commit comments

Comments
 (0)