E05280: dispatcher handler can never be reached - no concrete type in the program routes to it

Category: E05xxx Explicit type symbol definition · Reported in phase: PRE_IR_CHECKS · Classification: DISPATCHER_HANDLER_NEVER_REACHED · All error codes

What went wrong

A dispatcher handler can never be reached. No concrete type anywhere in the program routes to it, so the method is dead code: it compiles, it looks like it runs, and it never does.

Worked examples

Compilable examples demonstrating this error and the correct fix:

  • ek9 -q 1378 (How do I dispatch over a mix of classes and functions?)
  • ek9 -q 1377 (Can a dispatcher method dispatch on function types?)

Why this is not a local mistake

Whether a handler is reachable is NOT a property of the dispatcher on its own. It depends on which types elsewhere in the program extend or implement the handler's parameter type.

That means this error can appear in a file you did not edit. Deleting the last class that implements a trait, removing an 'extends' during a refactor, or narrowing a hierarchy can all leave a handler in a different file with nothing that can reach it. Without this check the handler would simply stop being called, with no diagnostic anywhere.

How reachability is decided

A handler is reachable when it WINS for at least one concrete type that can arrive at the dispatcher.

  • Only CONCRETE types count. A value never has an abstract type at runtime, so a handler for an abstract class, a trait or an abstract function is reachable only if something concrete extends or implements it.
  • WINNING matters, not merely matching. A handler that always loses to a more specific one is just as dead.

This is the same decision the compiler makes when it builds the dispatch table, so if this error fires, no dispatch entry would have been emitted for that handler.

How to fix it

  1. If the handler is still wanted, give it something to dispatch on - declare a concrete type that extends or implements its parameter type
  2. If a refactor removed the last implementor, restore it, or point the handler at a type that still has one
  3. If the handler is genuinely obsolete, delete it
  4. If the handler is shadowed by a more specific one, widen or remove the competing handler

Common causes

  • A hierarchy refactor removed the last concrete implementor of the handler's parameter type
  • A handler for an abstract function type where nothing declares 'extends <that function>' - EK9 function conformance is nominal, so a matching signature alone is NOT conformance
  • A handler for a trait that no concrete type implements
  • A handler for a parameterised generic function (for example 'Consumer of Integer') with no conforming dynamic function anywhere
  • A handler shadowed by a more specific handler that always wins

See also

  • E05210: dispatcher handler type not in base type's hierarchy
  • E05230: dispatcher has ambiguous handler resolution
  • E05260: dispatcher on sealed type must handle all permitted types

Examples

Every example is compiled by the EK9 compiler: the first fails exactly as captioned, the others fix it.

Fails with E05280: dispatcher handler can never be reached - no concrete type in the program routes to it

#!ek9
defines module error.e05280.bad

  defines trait

    <?-
      Nothing implements this trait, so no value can ever have it.
    -?>
    Orphaned
      describe() as pure
        <- rtn as String: "orphaned"

    <?-
      Control: Adopted IS implemented by a concrete class below.
    -?>
    Adopted
      describe() as pure
        <- rtn as String: "adopted"

  defines class

    <?-
      Nothing extends this abstract class.
    -?>
    OrphanedBase as abstract
      default operator ?

    ConcreteAdopter with trait of Adopted
      default operator ?

    <?-
      Every handler here is declared against a type with no concrete form, so none can ever be
      selected. The 'Any' base means these are NOT hierarchy errors - every type is under Any -
      which is exactly why this needed its own check.
    -?>
    UnreachableHandlers

      handle() as dispatcher
        -> item as Any
        <- rtn as String: "unreachable-set fallback"

      handle()
        -> item as OrphanedBase
        <- rtn as String: "nothing extends OrphanedBase"

      handle()
        -> item as OrphanedFunction
        <- rtn as String: "nothing extends OrphanedFunction"

      handle()
        -> item as Orphaned
        <- rtn as String: "no concrete type implements Orphaned"

      handle()
        -> item as Supplier of String
        <- rtn as String: "nothing in this program conforms to Supplier of String"

    <?-
      Control class: every handler below IS reachable, so none of them may report.
      If this class ever starts reporting, the check has become over-eager.
    -?>
    ReachableHandlers

      handle()
        -> item as AdoptedFunction
        <- rtn as String: "ExtendedFunction extends AdoptedFunction"

      handle()
        -> item as Adopted
        <- rtn as String: "ConcreteAdopter implements Adopted"

      handle() as dispatcher
        -> item as Any
        <- rtn as String: "reachable-set fallback"

      handle()
        -> item as ConcreteAdopter
        <- rtn as String: "a concrete type is its own instantiation"

      handle()
        -> item as Integer
        <- rtn as String: "built-in concrete type"

    <?-
      The same rule under a NAMED base. MidAbstract is under Shape, so this is not an E05210
      hierarchy error - but nothing extends MidAbstract, so the handler is still dead.
    -?>
    Shape as open
      default operator ?

    ConcreteShape extends Shape
      default operator ?

    MidAbstract extends Shape as abstract
      default operator ?

    NamedBaseHandlers

      render()
        -> s as ConcreteShape
        <- rtn as String: "reachable"

      render()
        -> s as MidAbstract
        <- rtn as String: "nothing extends MidAbstract"

      render() as dispatcher
        -> s as Shape
        <- rtn as String: "named-base fallback"

  defines function

    <?-
      Nothing extends this abstract function. Function conformance in EK9 is nominal, so a
      matching signature elsewhere would NOT make this reachable.
    -?>
    OrphanedFunction() as pure abstract
      -> inputText as String
      <- outputText as String?

    <?-
      Control: ExtendedFunction is concrete and extends AdoptedFunction.
    -?>
    AdoptedFunction() as pure abstract
      -> inputText as String
      <- outputText as String?

    ExtendedFunction() extends AdoptedFunction as pure
      -> inputText as String
      <- outputText as String: inputText

Fixed: Keep only handlers some concrete type in the program can reach; delete the dead ones

#!ek9
defines module error.e05280.good

  defines trait

    Adopted
      describe() as pure
        <- rtn as String: "adopted"

  defines class

    ConcreteAdopter with trait of Adopted
      default operator ?

    Shape as open
      default operator ?

    ConcreteShape extends Shape
      default operator ?

    ReachableHandlers
      handle()
        -> item as Adopted
        <- rtn as String: "ConcreteAdopter implements Adopted"

      handle() as dispatcher
        -> item as Any
        <- rtn as String: "fallback"

      handle()
        -> item as Integer
        <- rtn as String: "built-in concrete type"

    NamedBaseHandlers
      render()
        -> shape as ConcreteShape
        <- rtn as String: "reachable"

      render() as dispatcher
        -> shape as Shape
        <- rtn as String: "named-base fallback"

Questions and answers

These Q&A entries show E05280 and its fix: