This is an automated email from the ASF dual-hosted git repository.

He-Pin pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko.git


The following commit(s) were added to refs/heads/main by this push:
     new b936e5e1a8 fix: enable strict-unsealed-patmat lint and fix 
exhaustiveness warnings (#3330)
b936e5e1a8 is described below

commit b936e5e1a8ca59e39c840791ebbea824c540ce92
Author: He-Pin(kerr) <[email protected]>
AuthorDate: Tue Jul 14 16:11:41 2026 +0800

    fix: enable strict-unsealed-patmat lint and fix exhaustiveness warnings 
(#3330)
    
    * fix: enable strict-unsealed-patmat lint and fix exhaustiveness warnings
    
    Motivation:
    The strict-unsealed-patmat lint was disabled globally in #264 to unblock
    the build, leaving many pattern matches on non-sealed traits unchecked.
    Issue #265 tracks re-enabling this lint.
    
    Modification:
    Remove the -Xlint:-strict-unsealed-patmat silencing flag from
    PekkoDisciplinePlugin for Scala 2.13. Fix all resulting exhaustiveness
    warnings across the codebase:
    - Seal Command traits where all subtypes are in the same file
    - Add @nowarn annotations where traits are intentionally non-sealed
      (cross-file protocols in tutorials)
    - Add catch-all cases for matches on OptionVal and other extractor-only
      types (Collections, stream operators)
    
    Result:
    The strict-unsealed-patmat lint is now active for Scala 2.13, catching
    future pattern match exhaustiveness issues at compile time.
    
    Tests:
    sbt "docs / Test / compile" — success, no exhaustiveness errors
    
    References:
    Fixes #265
    
    * fix: add catch-all case for StageActorRefBenchmark getStageActor match
    
    Motivation:
    The strict-unsealed-patmat lint enabled in the previous commit caught
    a non-exhaustive pattern match in bench-jmh's StageActorRefBenchmark
    that was missed in the initial pass.
    
    Modification:
    Add a catch-all case to the getStageActor partial function in
    StageActorSink.preStart to handle unexpected messages.
    
    Result:
    bench-jmh compiles without exhaustiveness warnings under
    strict-unsealed-patmat.
    
    Tests:
    sbt "bench-jmh / Compile / compile" — success
    
    References:
    Refs #265
---
 .../apache/pekko/actor/typed/delivery/TestProducerWithAsk.scala   | 2 +-
 .../pekko/actor/typed/delivery/TestProducerWorkPulling.scala      | 2 +-
 .../apache/pekko/actor/typed/eventstream/EventStreamDocSpec.scala | 3 +++
 actor/src/main/scala/org/apache/pekko/util/Collections.scala      | 1 +
 .../pekko/actor/typed/delivery/ReliableDeliveryBenchmark.scala    | 8 ++++----
 .../scala/org/apache/pekko/stream/StageActorRefBenchmark.scala    | 1 +
 .../pekko/cluster/sharding/typed/ShardedDaemonProcessSpec.scala   | 2 +-
 .../pekko/cluster/sharding/typed/ShardingCompileOnlySpec.scala    | 2 +-
 .../apache/pekko/cluster/sharding/ShardRegionDataTypesSpec.scala  | 2 ++
 .../scala/docs/actor/typed/CoordinatedActorShutdownSpec.scala     | 2 +-
 .../test/scala/docs/actor/typed/SharedMutableStateDocSpec.scala   | 2 +-
 docs/src/test/scala/typed/tutorial_4/DeviceGroup.scala            | 3 +++
 docs/src/test/scala/typed/tutorial_5/DeviceGroup.scala            | 2 ++
 docs/src/test/scala/typed/tutorial_5/DeviceGroupQuery.scala       | 2 ++
 .../pekko/persistence/typed/ReplicatedEventPublishingSpec.scala   | 2 +-
 .../pekko/persistence/typed/internal/RecoveryPermitterSpec.scala  | 2 +-
 project/PekkoDisciplinePlugin.scala                               | 3 +--
 .../org/apache/pekko/stream/impl/UnfoldResourceSourceAsync.scala  | 1 +
 .../scala/org/apache/pekko/stream/impl/fusing/DropRepeated.scala  | 1 +
 .../pekko/stream/impl/fusing/GroupedAdjacentByWeighted.scala      | 2 ++
 .../main/scala/org/apache/pekko/stream/impl/fusing/Switch.scala   | 2 ++
 21 files changed, 33 insertions(+), 14 deletions(-)

diff --git 
a/actor-typed-tests/src/test/scala/org/apache/pekko/actor/typed/delivery/TestProducerWithAsk.scala
 
b/actor-typed-tests/src/test/scala/org/apache/pekko/actor/typed/delivery/TestProducerWithAsk.scala
index f028d45414..6de4c51f46 100644
--- 
a/actor-typed-tests/src/test/scala/org/apache/pekko/actor/typed/delivery/TestProducerWithAsk.scala
+++ 
b/actor-typed-tests/src/test/scala/org/apache/pekko/actor/typed/delivery/TestProducerWithAsk.scala
@@ -26,7 +26,7 @@ import pekko.util.Timeout
 
 object TestProducerWithAsk {
 
-  trait Command
+  sealed trait Command
   final case class RequestNext(askTo: 
ActorRef[ProducerController.MessageWithConfirmation[TestConsumer.Job]])
       extends Command
   private case object Tick extends Command
diff --git 
a/actor-typed-tests/src/test/scala/org/apache/pekko/actor/typed/delivery/TestProducerWorkPulling.scala
 
b/actor-typed-tests/src/test/scala/org/apache/pekko/actor/typed/delivery/TestProducerWorkPulling.scala
index 384f7c4a95..b545d911a7 100644
--- 
a/actor-typed-tests/src/test/scala/org/apache/pekko/actor/typed/delivery/TestProducerWorkPulling.scala
+++ 
b/actor-typed-tests/src/test/scala/org/apache/pekko/actor/typed/delivery/TestProducerWorkPulling.scala
@@ -22,7 +22,7 @@ import pekko.actor.typed.scaladsl.Behaviors
 
 object TestProducerWorkPulling {
 
-  trait Command
+  sealed trait Command
   final case class RequestNext(sendTo: ActorRef[TestConsumer.Job]) extends 
Command
   private case object Tick extends Command
 
diff --git 
a/actor-typed-tests/src/test/scala/org/apache/pekko/actor/typed/eventstream/EventStreamDocSpec.scala
 
b/actor-typed-tests/src/test/scala/org/apache/pekko/actor/typed/eventstream/EventStreamDocSpec.scala
index 2bac08ea45..49894ffbc0 100644
--- 
a/actor-typed-tests/src/test/scala/org/apache/pekko/actor/typed/eventstream/EventStreamDocSpec.scala
+++ 
b/actor-typed-tests/src/test/scala/org/apache/pekko/actor/typed/eventstream/EventStreamDocSpec.scala
@@ -82,6 +82,9 @@ object EventStreamDocSpec {
                 case UnhandledMessage(message, sender, recipient) =>
                   context.log.info("UnhandledMessage received from sender ({}) 
to recipient ({}) with message: {}",
                     sender.path.name, recipient.path.name, message.toString)
+
+                case other =>
+                  context.log.warn("Unexpected AllDeadLetters: {}", other)
               }
               Behaviors.same
           }
diff --git a/actor/src/main/scala/org/apache/pekko/util/Collections.scala 
b/actor/src/main/scala/org/apache/pekko/util/Collections.scala
index 9311c3dadc..cf49d1e0e5 100644
--- a/actor/src/main/scala/org/apache/pekko/util/Collections.scala
+++ b/actor/src/main/scala/org/apache/pekko/util/Collections.scala
@@ -39,6 +39,7 @@ private[pekko] object Collections {
         pf.applyOrElse(t, NotApplied) match {
           case _: NotApplied.type => // do nothing
           case r: R @unchecked    => builder += r
+          case _                  => // unreachable, NotApplied handled above, 
everything else is R
         }
       })
       builder.result()
diff --git 
a/bench-jmh/src/main/scala/org/apache/pekko/actor/typed/delivery/ReliableDeliveryBenchmark.scala
 
b/bench-jmh/src/main/scala/org/apache/pekko/actor/typed/delivery/ReliableDeliveryBenchmark.scala
index da331671e3..c6f86884fd 100644
--- 
a/bench-jmh/src/main/scala/org/apache/pekko/actor/typed/delivery/ReliableDeliveryBenchmark.scala
+++ 
b/bench-jmh/src/main/scala/org/apache/pekko/actor/typed/delivery/ReliableDeliveryBenchmark.scala
@@ -36,7 +36,7 @@ import pekko.actor.typed.scaladsl.Behaviors
 import com.typesafe.config.ConfigFactory
 
 object Producer {
-  trait Command
+  sealed trait Command
 
   case object Run extends Command
   private case class WrappedRequestNext(r: 
ProducerController.RequestNext[Consumer.Command]) extends Command
@@ -83,7 +83,7 @@ object Producer {
 }
 
 object Consumer {
-  trait Command
+  sealed trait Command
 
   case object TheMessage extends Command
 
@@ -111,7 +111,7 @@ object Consumer {
 }
 
 object WorkPullingProducer {
-  trait Command
+  sealed trait Command
 
   case object Run extends Command
   private case class WrappedRequestNext(r: 
WorkPullingProducerController.RequestNext[Consumer.Command]) extends Command
@@ -148,7 +148,7 @@ object WorkPullingProducer {
 
 object Guardian {
 
-  trait Command
+  sealed trait Command
   final case class RunPointToPoint(id: String, numberOfMessages: Int, useAsk: 
Boolean, replyTo: ActorRef[Done])
       extends Command
   final case class RunWorkPulling(id: String, numberOfMessages: Int, workers: 
Int, replyTo: ActorRef[Done])
diff --git 
a/bench-jmh/src/main/scala/org/apache/pekko/stream/StageActorRefBenchmark.scala 
b/bench-jmh/src/main/scala/org/apache/pekko/stream/StageActorRefBenchmark.scala
index 007253e275..67b0f21ce5 100644
--- 
a/bench-jmh/src/main/scala/org/apache/pekko/stream/StageActorRefBenchmark.scala
+++ 
b/bench-jmh/src/main/scala/org/apache/pekko/stream/StageActorRefBenchmark.scala
@@ -79,6 +79,7 @@ object StageActorRefBenchmark {
         override def preStart(): Unit = {
           control.init(getStageActor {
             case (_, CountDown) => control.countDown()
+            case _              => // unexpected message
           }.ref)
           pull(in)
         }
diff --git 
a/cluster-sharding-typed/src/multi-jvm/scala/org/apache/pekko/cluster/sharding/typed/ShardedDaemonProcessSpec.scala
 
b/cluster-sharding-typed/src/multi-jvm/scala/org/apache/pekko/cluster/sharding/typed/ShardedDaemonProcessSpec.scala
index af0fb05aa9..5d952fdc56 100644
--- 
a/cluster-sharding-typed/src/multi-jvm/scala/org/apache/pekko/cluster/sharding/typed/ShardedDaemonProcessSpec.scala
+++ 
b/cluster-sharding-typed/src/multi-jvm/scala/org/apache/pekko/cluster/sharding/typed/ShardedDaemonProcessSpec.scala
@@ -43,7 +43,7 @@ object ShardedDaemonProcessSpec extends MultiNodeConfig {
   case class ProcessActorEvent(id: Int, event: Any) extends CborSerializable
 
   object ProcessActor {
-    trait Command
+    sealed trait Command
     case object Stop extends Command
 
     def apply(id: Int): Behavior[Command] = Behaviors.setup { ctx =>
diff --git 
a/cluster-sharding-typed/src/test/scala/docs/org/apache/pekko/cluster/sharding/typed/ShardingCompileOnlySpec.scala
 
b/cluster-sharding-typed/src/test/scala/docs/org/apache/pekko/cluster/sharding/typed/ShardingCompileOnlySpec.scala
index a02cff3cad..56e08cdaa2 100644
--- 
a/cluster-sharding-typed/src/test/scala/docs/org/apache/pekko/cluster/sharding/typed/ShardingCompileOnlySpec.scala
+++ 
b/cluster-sharding-typed/src/test/scala/docs/org/apache/pekko/cluster/sharding/typed/ShardingCompileOnlySpec.scala
@@ -161,7 +161,7 @@ object ShardingCompileOnlySpec {
 
     // a sharded counter that sends responses to another sharded actor
     object Counter {
-      trait Command
+      sealed trait Command
       case object Increment extends Command
       final case class GetValue(replyToEntityId: String) extends Command
       val TypeKey: EntityTypeKey[Command] = 
EntityTypeKey[Command]("example-sharded-counter")
diff --git 
a/cluster-sharding/src/test/scala/org/apache/pekko/cluster/sharding/ShardRegionDataTypesSpec.scala
 
b/cluster-sharding/src/test/scala/org/apache/pekko/cluster/sharding/ShardRegionDataTypesSpec.scala
index 47ef090e58..e46d52022f 100644
--- 
a/cluster-sharding/src/test/scala/org/apache/pekko/cluster/sharding/ShardRegionDataTypesSpec.scala
+++ 
b/cluster-sharding/src/test/scala/org/apache/pekko/cluster/sharding/ShardRegionDataTypesSpec.scala
@@ -58,6 +58,7 @@ class ShardRegionDataTypesSpec extends AnyWordSpec with 
Matchers {
       s match {
         case ShardRegionStats(stats) =>
           stats shouldBe Map("s1" -> 1)
+        case _ => fail("Should match ShardRegionStats")
       }
     }
 
@@ -103,6 +104,7 @@ class ShardRegionDataTypesSpec extends AnyWordSpec with 
Matchers {
       state match {
         case CurrentShardRegionState(shards) =>
           shards shouldBe Set(ShardState("s1", Set("e1")))
+        case _ => fail("Should match CurrentShardRegionState")
       }
     }
 
diff --git 
a/docs/src/test/scala/docs/actor/typed/CoordinatedActorShutdownSpec.scala 
b/docs/src/test/scala/docs/actor/typed/CoordinatedActorShutdownSpec.scala
index 233e2a310a..85a89ceb31 100644
--- a/docs/src/test/scala/docs/actor/typed/CoordinatedActorShutdownSpec.scala
+++ b/docs/src/test/scala/docs/actor/typed/CoordinatedActorShutdownSpec.scala
@@ -28,7 +28,7 @@ class CoordinatedActorShutdownSpec {
   // #coordinated-shutdown-addTask
   object MyActor {
 
-    trait Messages
+    sealed trait Messages
     case class Stop(replyTo: ActorRef[Done]) extends Messages
 
     def behavior: Behavior[Messages] =
diff --git 
a/docs/src/test/scala/docs/actor/typed/SharedMutableStateDocSpec.scala 
b/docs/src/test/scala/docs/actor/typed/SharedMutableStateDocSpec.scala
index 18380cf643..54ec248c32 100644
--- a/docs/src/test/scala/docs/actor/typed/SharedMutableStateDocSpec.scala
+++ b/docs/src/test/scala/docs/actor/typed/SharedMutableStateDocSpec.scala
@@ -27,7 +27,7 @@ class SharedMutableStateDocSpec {
   def expensiveCalculation(): Future[String] = ???
 
   object MyActor {
-    trait Command
+    sealed trait Command
     case class Message(msg: String, replyTo: ActorRef[Any]) extends Command
     case class UpdateState(newState: String) extends Command
 
diff --git a/docs/src/test/scala/typed/tutorial_4/DeviceGroup.scala 
b/docs/src/test/scala/typed/tutorial_4/DeviceGroup.scala
index 82f192727e..5feb660207 100644
--- a/docs/src/test/scala/typed/tutorial_4/DeviceGroup.scala
+++ b/docs/src/test/scala/typed/tutorial_4/DeviceGroup.scala
@@ -13,6 +13,8 @@
 
 package typed.tutorial_4
 
+import scala.annotation.nowarn
+
 import org.apache.pekko.actor.typed.ActorRef
 import org.apache.pekko.actor.typed.Behavior
 import org.apache.pekko.actor.typed.PostStop
@@ -47,6 +49,7 @@ class DeviceGroup(context: ActorContext[DeviceGroup.Command], 
groupId: String)
 
   context.log.info("DeviceGroup {} started", groupId)
 
+  @nowarn("msg=match may not be exhaustive")
   override def onMessage(msg: Command): Behavior[Command] =
     msg match {
       case trackMsg @ RequestTrackDevice(`groupId`, deviceId, replyTo) =>
diff --git a/docs/src/test/scala/typed/tutorial_5/DeviceGroup.scala 
b/docs/src/test/scala/typed/tutorial_5/DeviceGroup.scala
index 9e2da39da6..bb7183d269 100644
--- a/docs/src/test/scala/typed/tutorial_5/DeviceGroup.scala
+++ b/docs/src/test/scala/typed/tutorial_5/DeviceGroup.scala
@@ -13,6 +13,7 @@
 
 package typed.tutorial_5
 
+import scala.annotation.nowarn
 import scala.concurrent.duration._
 import org.apache.pekko
 import pekko.actor.typed.ActorRef
@@ -51,6 +52,7 @@ class DeviceGroup(context: ActorContext[DeviceGroup.Command], 
groupId: String)
 
   context.log.info("DeviceGroup {} started", groupId)
 
+  @nowarn("msg=match may not be exhaustive")
   override def onMessage(msg: Command): Behavior[Command] =
     msg match {
       // #query-added
diff --git a/docs/src/test/scala/typed/tutorial_5/DeviceGroupQuery.scala 
b/docs/src/test/scala/typed/tutorial_5/DeviceGroupQuery.scala
index cb9bf48360..fb37dfb9cb 100644
--- a/docs/src/test/scala/typed/tutorial_5/DeviceGroupQuery.scala
+++ b/docs/src/test/scala/typed/tutorial_5/DeviceGroupQuery.scala
@@ -13,6 +13,7 @@
 
 package typed.tutorial_5
 
+import scala.annotation.nowarn
 import scala.concurrent.duration._
 
 import org.apache.pekko
@@ -85,6 +86,7 @@ class DeviceGroupQuery(
 
   // #query-outline
   // #query-state
+  @nowarn("msg=match may not be exhaustive")
   override def onMessage(msg: Command): Behavior[Command] =
     msg match {
       case WrappedRespondTemperature(response) => 
onRespondTemperature(response)
diff --git 
a/persistence-typed-tests/src/test/scala/org/apache/pekko/persistence/typed/ReplicatedEventPublishingSpec.scala
 
b/persistence-typed-tests/src/test/scala/org/apache/pekko/persistence/typed/ReplicatedEventPublishingSpec.scala
index 024ab6ddcd..63ee71f37c 100644
--- 
a/persistence-typed-tests/src/test/scala/org/apache/pekko/persistence/typed/ReplicatedEventPublishingSpec.scala
+++ 
b/persistence-typed-tests/src/test/scala/org/apache/pekko/persistence/typed/ReplicatedEventPublishingSpec.scala
@@ -34,7 +34,7 @@ object ReplicatedEventPublishingSpec {
   val EntityType = "EventPublishingSpec"
 
   object MyReplicatedBehavior {
-    trait Command
+    sealed trait Command
     case class Add(text: String, replyTo: ActorRef[Done]) extends Command
     case class Get(replyTo: ActorRef[Set[String]]) extends Command
     case object Stop extends Command
diff --git 
a/persistence-typed/src/test/scala/org/apache/pekko/persistence/typed/internal/RecoveryPermitterSpec.scala
 
b/persistence-typed/src/test/scala/org/apache/pekko/persistence/typed/internal/RecoveryPermitterSpec.scala
index 6eae8d0fd4..cb23605966 100644
--- 
a/persistence-typed/src/test/scala/org/apache/pekko/persistence/typed/internal/RecoveryPermitterSpec.scala
+++ 
b/persistence-typed/src/test/scala/org/apache/pekko/persistence/typed/internal/RecoveryPermitterSpec.scala
@@ -45,7 +45,7 @@ object RecoveryPermitterSpec {
 
   object EventState extends State
 
-  trait Command
+  sealed trait Command
 
   case object StopActor extends Command
 
diff --git a/project/PekkoDisciplinePlugin.scala 
b/project/PekkoDisciplinePlugin.scala
index 48212954c2..7f04f6729a 100644
--- a/project/PekkoDisciplinePlugin.scala
+++ b/project/PekkoDisciplinePlugin.scala
@@ -140,8 +140,7 @@ object PekkoDisciplinePlugin extends AutoPlugin {
                 "-Ywarn-nullary-override",
                 "-Ywarn-nullary-unit",
                 "-Ypartial-unification",
-                "-Yno-adapted-args") ++ Set(
-                "-Xlint:-strict-unsealed-patmat")
+                "-Yno-adapted-args")
             case Some((2, 12)) =>
               disciplineScalacOptions
             case _ =>
diff --git 
a/stream/src/main/scala/org/apache/pekko/stream/impl/UnfoldResourceSourceAsync.scala
 
b/stream/src/main/scala/org/apache/pekko/stream/impl/UnfoldResourceSourceAsync.scala
index fff8b10a58..87cad18f34 100644
--- 
a/stream/src/main/scala/org/apache/pekko/stream/impl/UnfoldResourceSourceAsync.scala
+++ 
b/stream/src/main/scala/org/apache/pekko/stream/impl/UnfoldResourceSourceAsync.scala
@@ -117,6 +117,7 @@ import pekko.util.OptionVal
         // we got a pull but there is no open resource, we are either
         // currently creating/restarting then the read will be triggered when 
creating the
         // resource completes, or shutting down and then the pull does not 
matter anyway
+        case _ => // unreachable, OptionVal.Some and OptionVal.None cover all 
cases
       }
 
       override def postStop(): Unit = maybeResource match {
diff --git 
a/stream/src/main/scala/org/apache/pekko/stream/impl/fusing/DropRepeated.scala 
b/stream/src/main/scala/org/apache/pekko/stream/impl/fusing/DropRepeated.scala
index 222d55b3a5..d3530f29a6 100644
--- 
a/stream/src/main/scala/org/apache/pekko/stream/impl/fusing/DropRepeated.scala
+++ 
b/stream/src/main/scala/org/apache/pekko/stream/impl/fusing/DropRepeated.scala
@@ -65,6 +65,7 @@ private[pekko] final class DropRepeated[T](predicate: (T, T) 
=> Boolean) extends
           case OptionVal.None =>
             last = OptionVal.Some(elem)
             push(out, last.get)
+          case _ => // unreachable, OptionVal.Some and OptionVal.None cover 
all cases
         }
       }
 
diff --git 
a/stream/src/main/scala/org/apache/pekko/stream/impl/fusing/GroupedAdjacentByWeighted.scala
 
b/stream/src/main/scala/org/apache/pekko/stream/impl/fusing/GroupedAdjacentByWeighted.scala
index 5bed5f5f2c..1216c42e6b 100644
--- 
a/stream/src/main/scala/org/apache/pekko/stream/impl/fusing/GroupedAdjacentByWeighted.scala
+++ 
b/stream/src/main/scala/org/apache/pekko/stream/impl/fusing/GroupedAdjacentByWeighted.scala
@@ -149,6 +149,7 @@ private[pekko] final case class 
GroupedAdjacentByWeighted[T, R](
           } else {
             pendingGroup = OptionVal.Some(group)
           }
+        case _ => // unreachable, OptionVal.Some and OptionVal.None cover all 
cases
       }
 
       private def tryPullIfNeeded(): Unit = pendingGroup match {
@@ -182,6 +183,7 @@ private[pekko] final case class 
GroupedAdjacentByWeighted[T, R](
             } else {
               completeStage()
             }
+          case _ => // unreachable
         }
       }
 
diff --git 
a/stream/src/main/scala/org/apache/pekko/stream/impl/fusing/Switch.scala 
b/stream/src/main/scala/org/apache/pekko/stream/impl/fusing/Switch.scala
index 1bcd953ec6..52a81c6faa 100644
--- a/stream/src/main/scala/org/apache/pekko/stream/impl/fusing/Switch.scala
+++ b/stream/src/main/scala/org/apache/pekko/stream/impl/fusing/Switch.scala
@@ -79,6 +79,7 @@ import pekko.util.OptionVal
               else removeCurrentSource(completeIfClosed = true)
             }
           case OptionVal.None =>
+          case _              => // unreachable
         }
       }
 
@@ -123,6 +124,7 @@ import pekko.util.OptionVal
           case OptionVal.Some(src) =>
             src.cancel()
           case OptionVal.None =>
+          case _              => // unreachable
         }
       }
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to