How do sealed classes with allow only work in EK9?

← Type Hierarchy Constraints · Ref: Q679

EK9 supports sealed class hierarchies using 'allow only'. A sealed class declares which types are permitted to extend it. This creates a closed set of subtypes, enabling exhaustive pattern matching.

ALLOW ONLY SYNTAX

The parent class lists permitted subtypes:

  Shape allow only Circle, Rectangle as open

Only Circle and Rectangle may extend Shape. Any other class attempting to extend Shape is rejected.

OPEN REQUIREMENT (E05270)

The sealed class must be declared 'as open' (via the 'allow only' syntax which implies openness). Without it, no class can extend it at all.

CONSTRUCTOR DELEGATION (E05050)

When child classes call super(), the super() call must be the FIRST statement in the constructor body. Code before super() triggers E05050.

OVERRIDE CORRECTNESS (E05100)

Child classes can override parent methods. Using 'override' on a method that does not exist in the parent triggers E05100.

BENEFITS

Sealed hierarchies enable dispatchers to check exhaustiveness. If a dispatcher handles Shape, the compiler knows only Circle and Rectangle exist.

See Q607 for sealed class requirements. See Q609 for sealed across modules. See Q611 for hierarchy validation summary. See Q602 for circular hierarchy detection.

Example

defines module qa.typehierarchy.sealedallowonly

  defines class

    <?-
      Sealed base: only Circle and Square may extend.
      The 'allow only' creates a closed set of subtypes.
    -?>
    Shape allow only Circle, Square as open
      shapeName <- String()

      Shape()
        -> shapeName as String
        this.shapeName: shapeName

      label()
        <- rtn as String: shapeName

      default operator ?

    <?-
      Permitted subtype: Circle.
      Must call super() as FIRST statement.
    -?>
    Circle extends Shape
      radius <- Float()

      Circle()
        -> radius as Float
        super("circle")
        this.radius: radius

      override label()
        <- rtn as String: `circle r=${radius}`

      default operator ?

    <?-
      Permitted subtype: Square.
      Also calls super() first.
    -?>
    Square extends Shape
      sideLength <- Float()

      Square()
        -> sideLength as Float
        super("square")
        this.sideLength: sideLength

      override label()
        <- rtn as String: `square s=${sideLength}`

      default operator ?

  defines class

    <?-
      Renderer uses dispatcher to handle each sealed subtype.
      Dispatchers are class methods, not standalone functions.
    -?>
    ShapeRenderer

      describe() as dispatcher
        -> shape as Shape
        <- description as String: shape.label()

      describe()
        -> shape as Circle
        <- description as String: `Circular: ${shape.label()}`

      describe()
        -> shape as Square
        <- description as String: `Rectangular: ${shape.label()}`

      default operator ?

  defines program

    SealedAllowOnlyDemo()
      stdout <- Stdout()

      renderer <- ShapeRenderer()

      shapes <- List() of Shape
      shapes += Circle(5.0)
      shapes += Square(4.0)

      for shape in shapes
        stdout.println(renderer.describe(shape))

Common mistakes

E05050 — When a child class calls super(), the super() call must be the first statement in the constructor body. Placing code before super() triggers E05050. See ek9 -h E05050 for details.

Incorrect:

Circle()
        -> radius as Float
        this.radius: radius
        super("circle")

Correct:

Circle()
        -> radius as Float
        super("circle")
        this.radius: radius

E05110 — Using 'override' on a method that does not exist in the parent type triggers E05110. The method name must match an actual parent method. See ek9 -h E05110 for details.

Incorrect:

override nonExistentMethod()
        <- rtn as String: "oops"

Correct:

      override label()
        <- rtn as String: `circle r=${radius}`

E05270 — A sealed class using 'allow only' must be declared 'as open' so that the permitted subtypes can extend it. Without 'as open', no class can extend it at all, which contradicts the 'allow only' declaration and triggers E05270. See ek9 -h E05270 for details.

Incorrect:

Shape allow only Circle, Square

Correct:

Shape allow only Circle, Square as open
Other ways to ask this
  • What is E05050 constructor delegation must be first?
  • What is E05100 nothing to override?
  • What is E05270 sealed class must be open?
  • How does 'allow only' restrict which classes can extend a base?

Coming from another language?

Java: sealed classes with 'permits' (Java 17+). Kotlin: sealed classes with 'sealed' keyword. Rust: enum variants (algebraic data types). Python: no sealed classes. Go: no sealed types. EK9: 'allow only' on class declaration, compiler enforces the closed set.

Keywords: E05270, E05100, type, only, circular, class, hierarchy, permitted, closed, sealed, E05050, allow, inherit, exhaustive