-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Changes from all commits
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 |
---|---|---|
|
@@ -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 | ||
|
@@ -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. | ||
*/ | ||
|
@@ -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 | ||
|
@@ -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): | ||
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. 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 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. It should probably be the same logic as in RefChecks, yes. 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. 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 | ||
|
@@ -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 | ||
|
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() | ||
} |
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") | ||
} |
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 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?
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 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.