diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index a79b1bd940f3..3d8f50178280 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -1764,11 +1764,11 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling case _ => false /** Does type `tp1` have a member with name `name` whose normalized type is a subtype of - * the normalized type of the refinement `tp2`? + * the normalized type of the refinement of `tp2`? * Normalization is as follows: If `tp2` contains a skolem to its refinement type, * rebase both itself and the member info of `tp` on a freshly created skolem type. */ - protected def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean = + def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean = trace(i"hasMatchingMember($tp1 . $name :? ${tp2.refinedInfo}), mbr: ${tp1.member(name).info}", subtyping) { def qualifies(m: SingleDenotation): Boolean = @@ -2750,6 +2750,9 @@ object TypeComparer { def matchesType(tp1: Type, tp2: Type, relaxed: Boolean)(using Context): Boolean = comparing(_.matchesType(tp1, tp2, relaxed)) + def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType)(using Context): Boolean = + comparing(_.hasMatchingMember(name, tp1, tp2)) + def matchingMethodParams(tp1: MethodType, tp2: MethodType)(using Context): Boolean = comparing(_.matchingMethodParams(tp1, tp2)) diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index 0b880afdd995..ae36606836ed 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -150,7 +150,7 @@ object TypeTestsCasts { case AndType(tp1, tp2) => recur(X, tp1) && recur(X, tp2) case OrType(tp1, tp2) => recur(X, tp1) && recur(X, tp2) case AnnotatedType(t, _) => recur(X, t) - case _: RefinedType => false + case tp2: RefinedType => recur(X, tp2.parent) && TypeComparer.hasMatchingMember(tp2.refinedName, X, tp2) case _ => true }) diff --git a/tests/neg-custom-args/isInstanceOf/refined-types.scala b/tests/neg-custom-args/isInstanceOf/refined-types.scala new file mode 100644 index 000000000000..dfcc3e343099 --- /dev/null +++ b/tests/neg-custom-args/isInstanceOf/refined-types.scala @@ -0,0 +1,22 @@ +class A +class B extends A +type AA = A { type T = Int } +type BA = B { type T = Int } +type AL = A { type T >: Int } +type BL = B { type T >: Int } +type AU = A { type T <: Int } +type BU = B { type T <: Int } + +def aa(x: AA) = x.isInstanceOf[BA] // was: the type test for BA cannot be checked at runtime +def al(x: AL) = x.isInstanceOf[BL] // was: the type test for BL cannot be checked at runtime +def au(x: AU) = x.isInstanceOf[BU] // was: the type test for BU cannot be checked at runtime + +// an alias leaves nothing unchecked when type testing against one bound: +def bl(x: AA) = x.isInstanceOf[BL] // was: the type test for BL cannot be checked at runtime +def bu(x: AA) = x.isInstanceOf[BU] // was: the type test for BU cannot be checked at runtime + +// but static knowledge of only one bound makes checking against an alias unchecked: +def al_ba(x: AL) = x.isInstanceOf[BA] // error: the type test for BA cannot be checked at runtime +def au_ba(x: AU) = x.isInstanceOf[BA] // error: the type test for BA cannot be checked at runtime +def al_bu(x: AL) = x.isInstanceOf[BU] // error: the type test for BU cannot be checked at runtime +def au_bl(x: AU) = x.isInstanceOf[BL] // error: the type test for BL cannot be checked at runtime