How do sealed dispatchers enforce exhaustive handler coverage?
← Dispatcher Validation · Ref: Q618
When a dispatcher's parameter type is a sealed type (one with 'allow only'), the compiler requires handlers for ALL permitted concrete types. Missing a handler raises E05260.
EXHAUSTIVE CHECKING
The compiler cross-references the 'allow only' list with the set of dispatcher handlers. Every concrete type in the list must have a matching handler (either exact or through a supertype).
COMPILE-TIME SAFETY
When you add a new type to the 'allow only' list, EVERY dispatcher operating on that type fails to compile until a handler is added. This guarantees all dispatch points are updated when the type hierarchy grows.
UNSEALED DISPATCHERS
Dispatchers on unsealed types (without 'allow only') do NOT require exhaustive handlers. The entry method acts as a catch-all fallback for unhandled types.
ABSTRACT TYPES
Abstract types in the hierarchy do not need handlers because they cannot be instantiated at runtime. Only concrete types require handlers.
BOTH CLASSES AND TRAITS
Exhaustive checking works for both sealed classes (allow only on class) and sealed traits (allow only on trait). The mechanism is identical.
See Q299 for sealed trait dispatchers. See Q302 for sealed class dispatchers. See Q614 for handler type hierarchy. See Q607 for sealed class requirements.
Example
defines module qa.dispatchervalidation.exhaustive defines trait //Sealed trait: requires exhaustive dispatch Command allow only CreateCommand, UpdateCommand, DeleteCommand execute() as abstract <- rtn as String? defines class CreateCommand with trait of Command override execute() <- rtn as String: "created" UpdateCommand with trait of Command override execute() <- rtn as String: "updated" DeleteCommand with trait of Command override execute() <- rtn as String: "deleted" //CORRECT: exhaustive dispatcher handles ALL permitted types CommandProcessor process() as dispatcher -> cmd as Command <- rtn as String: cmd.execute() process() -> cmd as CreateCommand <- rtn as String: "Processing create: " + cmd.execute() process() -> cmd as UpdateCommand <- rtn as String: "Processing update: " + cmd.execute() process() -> cmd as DeleteCommand <- rtn as String: "Processing delete: " + cmd.execute() defines function testExhaustiveDispatch() processor <- CommandProcessor() createCmd <- CreateCommand() updateCmd <- UpdateCommand() deleteCmd <- DeleteCommand() require processor.process(createCmd) == "Processing create: created" require processor.process(updateCmd) == "Processing update: updated" require processor.process(deleteCmd) == "Processing delete: deleted"
Common mistakes
E50060 — String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details.
Incorrect:
require processor.process(createCmd).toUpperCase() == "Processing create: created"
Correct:
require processor.process(createCmd) == "Processing create: created"
E50001 — The dispatcher entry takes Command. A handler for String is outside the Command hierarchy. All handler parameter types must be subtypes of the entry parameter type. See ek9 -h E50001 for details.
Incorrect:
process()
-> item as String
Correct:
process()
-> cmd as CreateCommand
E05260 — The sealed trait Command allows only CreateCommand, UpdateCommand, and DeleteCommand. Removing the DeleteCommand handler leaves the dispatcher non-exhaustive. All permitted types must have handlers. See ek9 -h E05260 for details.
Incorrect:
process()
-> cmd as CreateCommand
<- rtn as String: "Processing create: " + cmd.execute()
process()
-> cmd as UpdateCommand
<- rtn as String: "Processing update: " + cmd.execute()
Correct:
process()
-> cmd as CreateCommand
<- rtn as String: "Processing create: " + cmd.execute()
process()
-> cmd as UpdateCommand
<- rtn as String: "Processing update: " + cmd.execute()
process()
-> cmd as DeleteCommand
<- rtn as String: "Processing delete: " + cmd.execute()
Other ways to ask this
- What is E05260 for dispatchers?
- Must I handle all types in a sealed dispatcher?
- How does EK9 enforce exhaustive dispatch on sealed types?
Coming from another language?
Java: exhaustive switch on sealed types (Java 21+). Kotlin: exhaustive when on sealed classes/interfaces. Rust: exhaustive match on enums. Scala: exhaustive match on sealed traits. TypeScript: exhaustive switch with never type. EK9: E05260 enforces exhaustive dispatch handler coverage on sealed types.
Keywords: exhaustive, E05260, validate, dispatch, visitor, closed, only, complete, allow, handler, sealed, ambiguity, coverage