diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 834e6ae15dfe..03152ab7e291 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -480,9 +480,14 @@ object Denotations { val matchLoosely = sym1.matchNullaryLoosely || sym2.matchNullaryLoosely - if symScore <= 0 && info2.overrides(info1, matchLoosely, checkClassInfo = false) then + val compareCtx = + if sym1.is(JavaDefined) && sym2.is(JavaDefined) then + ctx.withProperty(TypeComparer.ComparingJavaMethods, Some(())) + else ctx + + if symScore <= 0 && info2.overrides(info1, matchLoosely, checkClassInfo = false)(using compareCtx) then denot2 - else if symScore >= 0 && info1.overrides(info2, matchLoosely, checkClassInfo = false) then + else if symScore >= 0 && info1.overrides(info2, matchLoosely, checkClassInfo = false)(using compareCtx) then denot1 else val jointInfo = infoMeet(info1, info2, safeIntersection) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 77fdc24a01cc..2288c9feb059 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -993,7 +993,20 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling (sym1 eq NullClass) && isNullable(tp2) } case tp1 @ AppliedType(tycon1, args1) => - compareAppliedType1(tp1, tycon1, args1) + // Special case: Java arrays are covariant. + // When checking overrides (frozenConstraint) of Java methods, allow B[] <: A[] if B <: A. + def checkJavaArrayCovariance: Boolean = tp2 match { + case AppliedType(tycon2, arg2 :: Nil) + if frozenConstraint + && tycon1.typeSymbol == defn.ArrayClass + && tycon2.typeSymbol == defn.ArrayClass + && args1.length == 1 => + // Arrays are covariant in Java: B[] <: A[] if B <: A + isSubType(args1.head, arg2) + case _ => false + } + (ctx.property(ComparingJavaMethods).isDefined && checkJavaArrayCovariance) + || compareAppliedType1(tp1, tycon1, args1) case tp1: SingletonType => def comparePaths = tp2 match case tp2: (TermRef | ThisType) => @@ -3356,6 +3369,13 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling object TypeComparer { + import util.Property + + /** A property key to indicate we're comparing Java-defined methods. + * When it is set, arrays are treated as covariant for override checking. + */ + val ComparingJavaMethods = new Property.Key[Unit] + /** A richer compare result, returned by `testSubType` and `test`. */ enum CompareResult: case OK, OKwithGADTUsed, OKwithOpaquesUsed diff --git a/tests/pos/i24074/JavaPart.java b/tests/pos/i24074/JavaPart.java new file mode 100644 index 000000000000..1a28b7e411c2 --- /dev/null +++ b/tests/pos/i24074/JavaPart.java @@ -0,0 +1,17 @@ +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 { } +} diff --git a/tests/pos/i24074/Test.scala b/tests/pos/i24074/Test.scala new file mode 100644 index 000000000000..7fd5a10330e6 --- /dev/null +++ b/tests/pos/i24074/Test.scala @@ -0,0 +1,3 @@ +object Test: + def test(lvl3: JavaPart.Lvl3): Unit = + lvl3.getData.head.onlyInB()