Skip to content

Commit 3c79c1e

Browse files
authored
Fix override checking for Java methods with covariant array (#24408)
Fixes #24074 When resolving overridden methods 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`. --- It seems in Scala2, we use `Types#matches` (which does match in Scala3 as well), so Scala3 is more strict? https://github.com/scala/scala/blob/ae6ae4dd59cb90af62093cde52a292e5bd8bb7a8/src/reflect/scala/reflect/internal/Symbols.scala#L2461 https://github.com/scala/scala/blob/ae6ae4dd59cb90af62093cde52a292e5bd8bb7a8/src/reflect/scala/reflect/internal/Symbols.scala#L2445-L2452 https://github.com/scala/scala/blob/ae6ae4dd59cb90af62093cde52a292e5bd8bb7a8/src/reflect/scala/reflect/internal/Types.scala#L862-L874 --- Previous try #24377
2 parents 7eab684 + ed3f18d commit 3c79c1e

File tree

4 files changed

+48
-3
lines changed

4 files changed

+48
-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
@@ -994,7 +994,20 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
994994
(sym1 eq NullClass) && isNullable(tp2)
995995
}
996996
case tp1 @ AppliedType(tycon1, args1) =>
997-
compareAppliedType1(tp1, tycon1, args1)
997+
// Special case: Java arrays are covariant.
998+
// When checking overrides (frozenConstraint) of Java methods, allow B[] <: A[] if B <: A.
999+
def checkJavaArrayCovariance: Boolean = tp2 match {
1000+
case AppliedType(tycon2, arg2 :: Nil)
1001+
if frozenConstraint
1002+
&& tycon1.typeSymbol == defn.ArrayClass
1003+
&& tycon2.typeSymbol == defn.ArrayClass
1004+
&& args1.length == 1 =>
1005+
// Arrays are covariant in Java: B[] <: A[] if B <: A
1006+
isSubType(args1.head, arg2)
1007+
case _ => false
1008+
}
1009+
(checkJavaArrayCovariance && ctx.property(ComparingJavaMethods).isDefined)
1010+
|| compareAppliedType1(tp1, tycon1, args1)
9981011
case tp1: SingletonType =>
9991012
def comparePaths = tp2 match
10001013
case tp2: (TermRef | ThisType) =>
@@ -3357,6 +3370,13 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
33573370

33583371
object TypeComparer {
33593372

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

tests/pos/i24074/JavaPart.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
public class JavaPart {
2+
public interface A { }
3+
public interface B extends A {
4+
int onlyInB();
5+
}
6+
7+
public interface Lvl1 {
8+
A[] getData();
9+
}
10+
11+
public interface Lvl2 extends Lvl1 {
12+
@Override
13+
B[] getData();
14+
}
15+
16+
public interface Lvl3 extends Lvl2, Lvl1 { }
17+
}

tests/pos/i24074/Test.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
object Test:
2+
def test(lvl3: JavaPart.Lvl3): Unit =
3+
lvl3.getData.head.onlyInB()

0 commit comments

Comments
 (0)