Skip to content

Commit 5e43e34

Browse files
committed
Fix override checking for Java methods with covariant array
Fixes #24074 When resolving overloads from Java interfaces, treat arrays as covariant. This fixes incorrect method selection when multiple Java interfaces override methods with array return types. Previously, in the example below, we get the compilation error: ``` value foo is not a member of org.test.Test2.A lvl3.foo.head.foo() ``` ```java public class JavaPart { public interface A { } public interface B extends A { int onlyInB(); } public interface Lvl1 { A[] getData(); } public interface Lvl2 extends Lvl1 { @OverRide B[] getData(); } public interface Lvl3 extends Lvl2, Lvl1 { } } ``` ```scala def test(lvl3: JavaPart.Lvl3): Unit = lvl3.getData.head.onlyInB() ``` because `Denotations#mergeSingleDenot` creates a `JointRefDenotation` for `Lvl1.getData: A[]` and `Lvl2.getData: B[]`, with a return type of `JArray[A] & JArray[B]` (since the compiler doesn't recognize that `Lvl2.getData` overrides `Lvl1.getData`). And because `JArray` isn't recognized as covariant, `JArray[A & B] <: JArray[A] & JArray[B]` cannot be derived. Consequently, `lvl3.getData.head` returns a value typed as `A` instead of neither `B` nor `A & B`, which fails to resolve the method `onlyInB`.
1 parent e29c66d commit 5e43e34

File tree

2 files changed

+28
-3
lines changed

2 files changed

+28
-3
lines changed

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -480,9 +480,14 @@ object Denotations {
480480

481481
val matchLoosely = sym1.matchNullaryLoosely || sym2.matchNullaryLoosely
482482

483-
if symScore <= 0 && info2.overrides(info1, matchLoosely, checkClassInfo = false) then
483+
val compareCtx =
484+
if sym1.is(JavaDefined) && sym2.is(JavaDefined) then
485+
ctx.withProperty(TypeComparer.ComparingJavaMethods, Some(()))
486+
else ctx
487+
488+
if symScore <= 0 && info2.overrides(info1, matchLoosely, checkClassInfo = false)(using compareCtx) then
484489
denot2
485-
else if symScore >= 0 && info1.overrides(info2, matchLoosely, checkClassInfo = false) then
490+
else if symScore >= 0 && info1.overrides(info2, matchLoosely, checkClassInfo = false)(using compareCtx) then
486491
denot1
487492
else
488493
val jointInfo = infoMeet(info1, info2, safeIntersection)

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -993,7 +993,20 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
993993
(sym1 eq NullClass) && isNullable(tp2)
994994
}
995995
case tp1 @ AppliedType(tycon1, args1) =>
996-
compareAppliedType1(tp1, tycon1, args1)
996+
// Special case: Java arrays are covariant.
997+
// When checking overrides (frozenConstraint) of Java methods, allow B[] <: A[] if B <: A.
998+
def checkJavaArrayCovariance: Boolean = tp2 match {
999+
case AppliedType(tycon2, arg2 :: Nil)
1000+
if frozenConstraint
1001+
&& tycon1.typeSymbol == defn.ArrayClass
1002+
&& tycon2.typeSymbol == defn.ArrayClass
1003+
&& args1.length == 1 =>
1004+
// Arrays are covariant in Java: B[] <: A[] if B <: A
1005+
isSubType(args1.head, arg2)
1006+
case _ => false
1007+
}
1008+
(ctx.property(ComparingJavaMethods).isDefined && checkJavaArrayCovariance)
1009+
|| compareAppliedType1(tp1, tycon1, args1)
9971010
case tp1: SingletonType =>
9981011
def comparePaths = tp2 match
9991012
case tp2: (TermRef | ThisType) =>
@@ -3356,6 +3369,13 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
33563369

33573370
object TypeComparer {
33583371

3372+
import util.Property
3373+
3374+
/** A property key to indicate we're comparing Java-defined methods.
3375+
* When it is set, arrays are treated as covariant for override checking.
3376+
*/
3377+
val ComparingJavaMethods = new Property.Key[Unit]
3378+
33593379
/** A richer compare result, returned by `testSubType` and `test`. */
33603380
enum CompareResult:
33613381
case OK, OKwithGADTUsed, OKwithOpaquesUsed

0 commit comments

Comments
 (0)