Skip to content

Refine "can be handled by parent" condition when generting bridges #13092

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

Merged
merged 1 commit into from
Jul 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/Bridges.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ class Bridges(root: ClassSymbol, thisPhase: DenotTransformer)(using Context) {

override def exclude(sym: Symbol) =
!sym.isOneOf(MethodOrModule) || super.exclude(sym)

override def canBeHandledByParent(sym1: Symbol, sym2: Symbol, parent: Symbol): Boolean =
OverridingPairs.isOverridingPair(sym1, sym2, parent.thisType)
}

val site = root.thisType
Expand Down
68 changes: 64 additions & 4 deletions compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package transform

import core._
import Flags._, Symbols._, Contexts._, Scopes._, Decorators._, Types.Type
import NameKinds.DefaultGetterName
import collection.mutable
import collection.immutable.BitSet
import scala.annotation.tailrec
Expand All @@ -16,12 +17,12 @@ import scala.annotation.tailrec
* Adapted from the 2.9 version of OverridingPairs. The 2.10 version is IMO
* way too unwieldy to be maintained.
*/
object OverridingPairs {
object OverridingPairs:

/** The cursor class
* @param base the base class that contains the overriding pairs
*/
class Cursor(base: Symbol)(using Context) {
class Cursor(base: Symbol)(using Context):

private val self = base.thisType

Expand Down Expand Up @@ -165,5 +166,64 @@ object OverridingPairs {

nextOverriding()
next()
}
}
end Cursor

/** Is this `sym1` considered an override of `sym2` (or vice versa) if both are
* seen as members of `site`?
* 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.
* We leave out pairs of methods in Java classes under the assumption since these
* have already been checked and handled by javac.
* This leaves two possible sorts of discrepancies to be reported as errors
* in `RefChecks`:
*
* - matching names, target names, and signatures but different types
* - matching names and types, but different target names
*
* This method is used as a replacement of `matches` in some subclasses of
* OverridingPairs.
*/
def isOverridingPair(sym1: Symbol, sym2: Symbol, self: Type)(using Context): Boolean =
if sym1.owner.is(JavaDefined, butNot = Trait)
&& sym2.owner.is(JavaDefined, butNot = Trait)
then false // javac already handles these checks and inserts bridges
else if sym1.isType then true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this isn't new code, but it's weird that this overload of isOverridingPair simply allows all types to match whereas the other overload will check that the kinds match, could this be harmonized?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure. Maybe. But not in this PR. Somebody would have to take the time to experiment and figure this out. It's best to be conservative here since I am sure there are lots of code bases out there that we have not seen yet and that test this logic in interesting ways.

else
val sd1 = sym1.asSeenFrom(self)
val sd2 = sym2.asSeenFrom(self)
sd1.matchesLoosely(sd2)
&& (sym1.hasTargetName(sym2.targetName)
|| isOverridingPair(sym1, sd1.info, sym2, sd2.info))

/** Let `member` and `other` be members of some common class C with types
* `memberTp` and `otherTp` in C. Are the two symbols considered an overriding
* pair in C? We assume that names already match so we test only the types here.
* @param fallBack A function called if the initial test is false and
* `member` and `other` are term symbols.
*/
def isOverridingPair(member: Symbol, memberTp: Type, other: Symbol, otherTp: Type, fallBack: => Boolean = false)(using Context): Boolean =
if member.isType then // intersection of bounds to refined types must be nonempty
memberTp.bounds.hi.hasSameKindAs(otherTp.bounds.hi)
&& (
(memberTp frozen_<:< otherTp)
|| !member.owner.derivesFrom(other.owner)
&& {
// if member and other come from independent classes or traits, their
// bounds must have non-empty-intersection
val jointBounds = (memberTp.bounds & otherTp.bounds).bounds
jointBounds.lo frozen_<:< jointBounds.hi
}
)
else
// releaxed override check for explicit nulls if one of the symbols is Java defined,
// force `Null` being a subtype of reference types during override checking
val relaxedCtxForNulls =
if ctx.explicitNulls && (member.is(JavaDefined) || other.is(JavaDefined)) then
ctx.retractMode(Mode.SafeNulls)
else ctx
member.name.is(DefaultGetterName) // default getters are not checked for compatibility
|| memberTp.overrides(otherTp,
member.matchNullaryLoosely || other.matchNullaryLoosely || fallBack
)(using relaxedCtxForNulls)

end OverridingPairs
63 changes: 12 additions & 51 deletions compiler/src/dotty/tools/dotc/typer/RefChecks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import config.Printers.{checks, noPrinter}
import scala.util.{Try, Failure, Success}
import config.{ScalaVersion, NoScalaVersion}
import Decorators._
import OverridingPairs.isOverridingPair
import typer.ErrorReporting._
import config.Feature.{warnOnMigration, migrateTo3}
import config.Printers.refcheck
Expand Down Expand Up @@ -264,31 +265,6 @@ object RefChecks {
i"${if (showLocation) sym1.showLocated else sym1}$infoStr"
}

def compatibleTypes(member: Symbol, memberTp: Type, other: Symbol, otherTp: Type, fallBack: => Boolean = false): Boolean =
try
if (member.isType) // intersection of bounds to refined types must be nonempty
memberTp.bounds.hi.hasSameKindAs(otherTp.bounds.hi) &&
((memberTp frozen_<:< otherTp) ||
!member.owner.derivesFrom(other.owner) && {
// if member and other come from independent classes or traits, their
// bounds must have non-empty-intersection
val jointBounds = (memberTp.bounds & otherTp.bounds).bounds
jointBounds.lo frozen_<:< jointBounds.hi
})
else
// releaxed override check for explicit nulls if one of the symbols is Java defined,
// force `Null` being a subtype of reference types during override checking
val relaxedCtxForNulls =
if ctx.explicitNulls && (member.is(JavaDefined) || other.is(JavaDefined)) then
ctx.retractMode(Mode.SafeNulls)
else ctx
member.name.is(DefaultGetterName) // default getters are not checked for compatibility
|| memberTp.overrides(otherTp, member.matchNullaryLoosely || other.matchNullaryLoosely || fallBack)(using relaxedCtxForNulls)
catch case ex: MissingType =>
// can happen when called with upwardsSelf as qualifier of memberTp and otherTp,
// because in that case we might access types that are not members of the qualifier.
false

/* Check that all conditions for overriding `other` by `member`
* of class `clazz` are met.
*/
Expand Down Expand Up @@ -318,10 +294,15 @@ object RefChecks {
}

def compatTypes(memberTp: Type, otherTp: Type): Boolean =
compatibleTypes(member, memberTp, other, otherTp,
fallBack = warnOnMigration(
overrideErrorMsg("no longer has compatible type"),
(if (member.owner == clazz) member else clazz).srcPos))
try
isOverridingPair(member, memberTp, other, otherTp,
fallBack = warnOnMigration(
overrideErrorMsg("no longer has compatible type"),
(if (member.owner == clazz) member else clazz).srcPos))
catch case ex: MissingType =>
// can happen when called with upwardsSelf as qualifier of memberTp and otherTp,
// because in that case we might access types that are not members of the qualifier.
false

/** 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 override
Expand Down Expand Up @@ -488,29 +469,9 @@ object RefChecks {
}*/
}

/** 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):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PrepJSInterop also performs some overriding checks using Cursor, so I wonder if it should use a subclass of Cursor with the same logic we're now using here in RefChecks, or perhaps the checking logic in PrepJSInterop should be moved to RefChecks? /cc @sjrd

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should probably be the same logic as in RefChecks, yes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I let you experiment with it. But I am going to merge this as is, since I do not think I have the time to look at all code that considers overriding pairs.

override def matches(sym1: Symbol, sym2: Symbol): Boolean =
considerMatching(sym1, sym2, self)
isOverridingPair(sym1, sym2, self)

private def inLinearizationOrder(sym1: Symbol, sym2: Symbol, parent: Symbol): Boolean =
val owner1 = sym1.owner
Expand All @@ -530,7 +491,7 @@ object RefChecks {
// - 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)
isOverridingPair(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
Expand Down
4 changes: 3 additions & 1 deletion tests/run/i1240.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ object Test {
// In Java, this gives an error like this:
// methods foo(A) from C[D] and foo(String) from C[D] are inherited with the same signature
// But the analogous example with `b1` compiles OK in Java.
assert(b2.foo(new D) == "T foo")
assert(b2.foo(new D) == "D foo")
// Here we get "D foo" since a bridge method for foo(x: D) was inserted
// in the anonymous class of b2.
}
}

Expand Down
38 changes: 38 additions & 0 deletions tests/run/i13087.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import scala.collection.mutable.Builder

class DDD[S,T,A]

trait NN[S, T, A, K[_], +D <: DDD[Set[S],T,K[A]]]
class NNN[S, T, K[_], A] extends NN[S, T, A, K, DDD[Set[S],T,K[A]]]

object NN {
def newBuilder[S, T, A, K[_]]:
NNbuilder[S, T, K, A, DDD[Set[S],T,K[A]], NN[S,T,A,K,?], Unit] =
new NNNbuilder[S, T, K, A]
}

// Remove the type parameter E, hardcoding in E := Unit, and the issue
// goes away.
trait NNbuilder
[S, T, K[_], A, +D <: DDD[Set[S],T,K[A]], +N <: NN[S,T,A,K,D], E]
extends Builder[Unit, N] {
def clear(): Unit = throw new UnsupportedOperationException()
final def addOne(builder: E): this.type = this
}

// Unfold this class defn, and the issue goes away
abstract class AbstractNNNbuilder
[S, T, K[_], A, +D <: DDD[Set[S],T,K[A]], +N <: NN[S,T,A,K,D], E]
extends NNbuilder[S,T,K,A,D,N,E]

class NNNbuilder[S, T, K[_], A] extends AbstractNNNbuilder[
S, T, K, A, DDD[Set[S], T, K[A]], NNN[S, T, K, A], Unit
] {
override def result(): NNN[S, T, K, A] = new NNN[S, T, K, A]
}

@main def Test: Unit = {
val builder = NN.newBuilder[String, Char, Int, Set]
builder += ()
builder.result()
}
17 changes: 17 additions & 0 deletions tests/run/i13087a.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
trait SimpleTrait[T] {
def myMethod(t: T): Int
def doIt(t: T): Unit = {
myMethod(t) // java.lang.AbstractMethodError: Method BadClass.myMethod(Ljava/lang/Object;)I is abstract
}
}

abstract class SimpleClass[T] extends SimpleTrait[T] {
def myMethod(t: String): Int = 5
}

class BadClass extends SimpleClass[String]

object Test {
def main(args: Array[String]): Unit =
(new BadClass).doIt("foobar")
}