-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refine overriding pairs in RefChecks #12982
Changes from 7 commits
fa6a257
e008f6e
8138eb4
6a90e5e
de523b3
8c405ac
a784915
c238f82
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -323,6 +323,14 @@ object RefChecks { | |
overrideErrorMsg("no longer has compatible type"), | ||
(if (member.owner == clazz) member else clazz).srcPos)) | ||
|
||
/** Do types of term members `member` and `other` as seen from `self` match? | ||
* If not we treat them as not a real override and don't issue certain | ||
* error messages. Also, bridges are not generated in this case. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not skip all error messages instead of only certain ones? Currently it looks like we still emit overrideAccessError and overrideTargetNameError but if these methods are not true override then I don't see why we need to. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In fact it's just the comment that is wrong. All errors go to overrideError which does the check whether it's a true override. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In that case, why not immediately check for false overrides at the top of checkOverride before doing any of the checks? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because the false overrides check is expensive and override checks are common while override errors are rare. |
||
* Type members are always assumed to match. | ||
*/ | ||
def trueMatch: Boolean = | ||
member.isType || memberTp(self).matches(otherTp(self)) | ||
|
||
def emitOverrideError(fullmsg: Message) = | ||
if (!(hasErrors && member.is(Synthetic) && member.is(Module))) { | ||
// suppress errors relating toi synthetic companion objects if other override | ||
|
@@ -333,7 +341,7 @@ object RefChecks { | |
} | ||
|
||
def overrideError(msg: String, compareTypes: Boolean = false) = | ||
if (noErrorType) | ||
if trueMatch && noErrorType then | ||
emitOverrideError(overrideErrorMsg(msg, compareTypes)) | ||
|
||
def autoOverride(sym: Symbol) = | ||
|
@@ -360,24 +368,6 @@ object RefChecks { | |
|
||
//Console.println(infoString(member) + " overrides " + infoString(other) + " in " + clazz);//DEBUG | ||
|
||
// return if we already checked this combination elsewhere | ||
if (member.owner != clazz) { | ||
def deferredCheck = member.is(Deferred) || !other.is(Deferred) | ||
def subOther(s: Symbol) = s derivesFrom other.owner | ||
def subMember(s: Symbol) = s derivesFrom member.owner | ||
|
||
if (subOther(member.owner) && deferredCheck) | ||
//Console.println(infoString(member) + " shadows1 " + infoString(other) " in " + clazz);//DEBUG | ||
return | ||
val parentSymbols = clazz.info.parents.map(_.typeSymbol) | ||
if (parentSymbols exists (p => subOther(p) && subMember(p) && deferredCheck)) | ||
//Console.println(infoString(member) + " shadows2 " + infoString(other) + " in " + clazz);//DEBUG | ||
return | ||
if (parentSymbols forall (p => subOther(p) == subMember(p))) | ||
//Console.println(infoString(member) + " shadows " + infoString(other) + " in " + clazz);//DEBUG | ||
return | ||
} | ||
|
||
/* Is the intersection between given two lists of overridden symbols empty? */ | ||
def intersectionIsEmpty(syms1: Iterator[Symbol], syms2: Iterator[Symbol]) = { | ||
val set2 = syms2.toSet | ||
|
@@ -412,7 +402,7 @@ object RefChecks { | |
overrideError("cannot be used here - class definitions cannot be overridden") | ||
else if (!other.is(Deferred) && member.isClass) | ||
overrideError("cannot be used here - classes can only override abstract types") | ||
else if (other.isEffectivelyFinal) // (1.2) | ||
else if other.isEffectivelyFinal then // (1.2) | ||
overrideError(i"cannot override final member ${other.showLocated}") | ||
else if (member.is(ExtensionMethod) && !other.is(ExtensionMethod)) // (1.3) | ||
overrideError("is an extension method, cannot override a normal method") | ||
|
@@ -433,9 +423,11 @@ object RefChecks { | |
member.setFlag(Override) | ||
else if (member.isType && self.memberInfo(member) =:= self.memberInfo(other)) | ||
() // OK, don't complain about type aliases which are equal | ||
else if (member.owner != clazz && other.owner != clazz && | ||
!(other.owner derivesFrom member.owner)) | ||
emitOverrideError( | ||
else if member.owner != clazz | ||
&& other.owner != clazz | ||
&& !other.owner.derivesFrom(member.owner) | ||
then | ||
overrideError( | ||
s"$clazz inherits conflicting members:\n " | ||
+ infoStringWithLocation(other) + " and\n " + infoStringWithLocation(member) | ||
+ "\n(Note: this can be resolved by declaring an override in " + clazz + ".)") | ||
|
@@ -496,25 +488,51 @@ object RefChecks { | |
}*/ | ||
} | ||
|
||
val opc = new OverridingPairs.Cursor(clazz): | ||
|
||
/** We declare a match if either we have a full match including matching names | ||
* or we have a loose match with different target name but the types are the same. | ||
* This leaves two possible sorts of discrepancies to be reported as errors | ||
* in `checkOveride`: | ||
* | ||
* - matching names, target names, and signatures but different types | ||
* - matching names and types, but different target names | ||
*/ | ||
override def matches(sym1: Symbol, sym2: Symbol): Boolean = | ||
!(sym1.owner.is(JavaDefined, butNot = Trait) && sym2.owner.is(JavaDefined, butNot = Trait)) && // javac already handles these checks | ||
(sym1.isType || { | ||
val sd1 = sym1.asSeenFrom(clazz.thisType) | ||
val sd2 = sym2.asSeenFrom(clazz.thisType) | ||
sd1.matchesLoosely(sd2) | ||
/** We declare a match if either we have a full match including matching names | ||
* or we have a loose match with different target name but the types are the same. | ||
* This leaves two possible sorts of discrepancies to be reported as errors | ||
* in `checkOveride`: | ||
* | ||
* - matching names, target names, and signatures but different types | ||
* - matching names and types, but different target names | ||
*/ | ||
def considerMatching(sym1: Symbol, sym2: Symbol, self: Type): Boolean = | ||
if sym1.owner.is(JavaDefined, butNot = Trait) | ||
&& sym2.owner.is(JavaDefined, butNot = Trait) | ||
then false // javac already handles these checks | ||
else if sym1.isType then true | ||
else | ||
val sd1 = sym1.asSeenFrom(self) | ||
val sd2 = sym2.asSeenFrom(self) | ||
sd1.matchesLoosely(sd2) | ||
&& (sym1.hasTargetName(sym2.targetName) | ||
|| compatibleTypes(sym1, sd1.info, sym2, sd2.info)) | ||
}) | ||
|
||
val opc = new OverridingPairs.Cursor(clazz): | ||
override def matches(sym1: Symbol, sym2: Symbol): Boolean = | ||
considerMatching(sym1, sym2, self) | ||
|
||
private def inLinearizationOrder(sym1: Symbol, sym2: Symbol, parent: Symbol): Boolean = | ||
val owner1 = sym1.owner | ||
val owner2 = sym2.owner | ||
def precedesIn(bcs: List[ClassSymbol]): Boolean = (bcs: @unchecked) match | ||
case bc :: bcs1 => | ||
if owner1 eq bc then true | ||
else if owner2 eq bc then false | ||
else precedesIn(bcs1) | ||
case _ => | ||
false | ||
precedesIn(parent.asClass.baseClasses) | ||
|
||
// We can exclude pairs safely from checking only under two additional conditions | ||
// - their signatures also match in the parent class. | ||
// See neg/i12828.scala for an example where this matters. | ||
// - They overriding/overridden appear in linearization order. | ||
// See neg/i5094.scala for an example where this matters. | ||
override def canBeHandledByParent(sym1: Symbol, sym2: Symbol, parent: Symbol): Boolean = | ||
considerMatching(sym1, sym2, parent.thisType) | ||
.showing(i"already handled ${sym1.showLocated}: ${sym1.asSeenFrom(parent.thisType).signature}, ${sym2.showLocated}: ${sym2.asSeenFrom(parent.thisType).signature} = $result", refcheck) | ||
&& inLinearizationOrder(sym1, sym2, parent) | ||
end opc | ||
|
||
while opc.hasNext do | ||
|
@@ -528,13 +546,11 @@ object RefChecks { | |
// | ||
// class A { type T = B } | ||
// class B extends A { override type T } | ||
for | ||
dcl <- clazz.info.decls.iterator | ||
if dcl.is(Deferred) | ||
other <- dcl.allOverriddenSymbols | ||
if !other.is(Deferred) | ||
do | ||
checkOverride(dcl, other) | ||
for dcl <- clazz.info.decls.iterator do | ||
if dcl.is(Deferred) then | ||
for other <- dcl.allOverriddenSymbols do | ||
if !other.is(Deferred) then | ||
checkOverride(dcl, other) | ||
|
||
printMixinOverrideErrors() | ||
|
||
|
@@ -578,7 +594,8 @@ object RefChecks { | |
def isConcrete(sym: Symbol) = sym.exists && !sym.isOneOf(NotConcrete) | ||
clazz.nonPrivateMembersNamed(mbr.name) | ||
.filterWithPredicate( | ||
impl => isConcrete(impl.symbol) && mbrDenot.matchesLoosely(impl)) | ||
impl => isConcrete(impl.symbol) | ||
&& mbrDenot.matchesLoosely(impl, alwaysCompareParams = true)) | ||
.exists | ||
|
||
/** The term symbols in this class and its baseclasses that are | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
-- Error: tests/neg/i12828.scala:7:7 ----------------------------------------------------------------------------------- | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this file should be removed |
||
7 |object Baz extends Bar[Int] // error overriding foo: incompatible type | ||
| ^ | ||
| object creation impossible, since def foo(x: A): Unit in trait Foo is not defined | ||
| (Note that | ||
| parameter A in def foo(x: A): Unit in trait Foo does not match | ||
| parameter Int & String in def foo(x: A & String): Unit in trait Bar | ||
| ) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
-- Error: tests/neg/i12828.scala:7:7 ----------------------------------------------------------------------------------- | ||
7 |object Baz extends Bar[Int] // error: not implemented | ||
| ^ | ||
| object creation impossible, since def foo(x: A): Unit in trait Foo is not defined | ||
| (Note that | ||
| parameter A in def foo(x: A): Unit in trait Foo does not match | ||
| parameter Int & String in def foo(x: A & String): Unit in trait Bar | ||
| ) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
trait Foo[A]: | ||
def foo(x: A): Unit | ||
|
||
trait Bar[A] extends Foo[A]: | ||
def foo(x: A & String): Unit = println(x.toUpperCase) | ||
|
||
object Baz extends Bar[Int] // error: not implemented | ||
|
||
@main def run() = Baz.foo(42) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
abstract class Foo[A] { | ||
def foo(x: A): Unit | ||
} | ||
abstract class Bar[A] extends Foo[A] { | ||
def foo(x: A with String): Unit = println(x.toUpperCase) | ||
} | ||
object Baz extends Bar[Int] // error: not implemented (same as Scala 2) | ||
// Scala 2 gives: object creation impossible. Missing implementation for `foo` | ||
|
||
object Test { | ||
def main(args: Array[String]) = Baz.foo(42) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
trait A[X] { | ||
def foo(x: X): Unit = | ||
println("A.foo") | ||
} | ||
trait B[X] extends A[X] { | ||
def foo(x: Int): Unit = | ||
println("B.foo") | ||
} | ||
object C extends B[Int] // error: conflicting members | ||
// Scala 2: same | ||
|
||
object Test { | ||
def main(args: Array[String]) = { | ||
C.foo(1) | ||
val a: A[Int] = C | ||
a.foo(1) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be called
alwaysCompareTypes
since Type#matches can also check the result type if we're after erasure.