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]