From c91ee595dcd1911500b2acd3d79dadd3ca19f1cf Mon Sep 17 00:00:00 2001 From: Yoonjae Jeon Date: Fri, 7 Nov 2025 17:55:59 +0900 Subject: [PATCH 1/2] Emit unchecked warning for abstract type args in non-final types --- .../tools/dotc/transform/TypeTestsCasts.scala | 44 ++++++++++--------- tests/neg/i24322.scala | 13 ++++++ tests/pos/i24322.scala | 10 +++++ 3 files changed, 47 insertions(+), 20 deletions(-) create mode 100644 tests/neg/i24322.scala create mode 100644 tests/pos/i24322.scala diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index a8c8ec8ce1d8..4a3a1c3fecb6 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -144,26 +144,30 @@ object TypeTestsCasts { case _ => recur(defn.AnyType, tpT) } case tpe @ AppliedType(tycon, targs) if !trustTypeApplication => - X.widenDealias match { - case OrType(tp1, tp2) => - // This case is required to retrofit type inference, - // which cut constraints in the following two cases: - // - T1 <:< T2 | T3 - // - T1 & T2 <:< T3 - // See TypeComparer#either - recur(tp1, P) && recur(tp2, P) - case tpX: FlexibleType => - recur(tpX.underlying, P) - case x => - // always false test warnings are emitted elsewhere - // provablyDisjoint wants fully applied types as input; because we're in the middle of erasure, we sometimes get raw types here - val xApplied = - val tparams = x.typeParams - if tparams.isEmpty then x else x.appliedTo(tparams.map(_ => WildcardType)) - TypeComparer.provablyDisjoint(xApplied, tpe.derivedAppliedType(tycon, targs.map(_ => WildcardType))) - || typeArgsDeterminable(X, tpe) - ||| i"its type arguments can't be determined from $X" - } + val abstractArgs = targs.filter(isAbstract) + if abstractArgs.nonEmpty && !tycon.classSymbol.is(Final) then + i"type arguments $abstractArgs refer to abstract types," + else + X.widenDealias match { + case OrType(tp1, tp2) => + // This case is required to retrofit type inference, + // which cut constraints in the following two cases: + // - T1 <:< T2 | T3 + // - T1 & T2 <:< T3 + // See TypeComparer#either + recur(tp1, P) && recur(tp2, P) + case tpX: FlexibleType => + recur(tpX.underlying, P) + case x => + // always false test warnings are emitted elsewhere + // provablyDisjoint wants fully applied types as input; because we're in the middle of erasure, we sometimes get raw types here + val xApplied = + val tparams = x.typeParams + if tparams.isEmpty then x else x.appliedTo(tparams.map(_ => WildcardType)) + TypeComparer.provablyDisjoint(xApplied, tpe.derivedAppliedType(tycon, targs.map(_ => WildcardType))) + || typeArgsDeterminable(X, tpe) + ||| i"its type arguments can't be determined from $X" + } 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) diff --git a/tests/neg/i24322.scala b/tests/neg/i24322.scala new file mode 100644 index 000000000000..61566bc114df --- /dev/null +++ b/tests/neg/i24322.scala @@ -0,0 +1,13 @@ +trait A[+T]: + def x: T +trait B[+T] extends A[T]: + def y: T + +object Troll extends A[Int] with B[Any]: + def x: Int = 0 + def y: Any = "" + +def f[T](a: A[T]): T = a match { + case b: B[T] => b.y // error + case _ => a.x +} diff --git a/tests/pos/i24322.scala b/tests/pos/i24322.scala new file mode 100644 index 000000000000..fa91cd2adac5 --- /dev/null +++ b/tests/pos/i24322.scala @@ -0,0 +1,10 @@ +trait A[+T]: + def x: T +final class B[+T] extends A[T]: + def x: T = ??? + def y: T = ??? + +def f[T](a: A[T]): T = a match { + case b: B[T] => b.y // compiles without warning + case _ => a.x +} From 18370f55b21cf99f9949ad4813f65a4b65a2449b Mon Sep 17 00:00:00 2001 From: Yoonjae Jeon Date: Fri, 7 Nov 2025 18:11:29 +0900 Subject: [PATCH 2/2] handle unchecked annotations --- compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index 4a3a1c3fecb6..05116c9e03d9 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -144,7 +144,7 @@ object TypeTestsCasts { case _ => recur(defn.AnyType, tpT) } case tpe @ AppliedType(tycon, targs) if !trustTypeApplication => - val abstractArgs = targs.filter(isAbstract) + val abstractArgs = targs.filter(arg => isAbstract(arg) && !arg.isInstanceOf[WildcardType] ) if abstractArgs.nonEmpty && !tycon.classSymbol.is(Final) then i"type arguments $abstractArgs refer to abstract types," else