Do traits, records, and components also check for circular hierarchies?

← Type Hierarchy Constraints · Ref: Q604

E05020 applies to ALL EK9 construct types that support inheritance or implementation relationships, not just classes.

TRAITS

Traits can extend other traits, forming hierarchies. Circular trait chains (TraitA extends TraitB extends TraitA) are detected and rejected with E05020.

RECORDS

Records that extend other records are subject to the same cycle detection. A record extending another record that extends the first is a compile error.

COMPONENTS

Components participate in hierarchies through trait implementation. Circular dependencies between components are detected.

CLASSES WITH TRAITS

Even when cycles involve a mix of classes and traits (a class implementing a trait that requires extending a class that circularly depends on the original), the compiler detects the cycle.

UNIFORM ENFORCEMENT

The type hierarchy checker runs the same cycle detection algorithm across all construct types. This means you get the same error (E05020) regardless of whether the cycle involves classes, traits, records, or components.

See Q602 for circular hierarchy basics. See Q106 for trait hierarchies. See Q97 for records vs classes.

Example

defines module qa.typehierarchy.allconstructs

  defines trait

    //CORRECT: Linear trait hierarchy
    Describable
      describe() as abstract
        <- rtn as String?

    Displayable with trait of Describable
      display() as abstract
        <- rtn as String?

  defines record

    //CORRECT: Simple record (records are closed by default)
    Point
      xPos as Float: 0.0
      yPos as Float: 0.0

      Point()
        ->
          xPos as Float
          yPos as Float
        this.xPos: xPos
        this.yPos: yPos

      operator $ as pure
        <- rtn as String: `(${xPos}, ${yPos})`

      default operator ?

  defines class

    //Class implementing trait hierarchy correctly
    Widget with trait of Displayable
      label <- "widget"

      Widget()
        -> label as String
        this.label: label

      override describe()
        <- rtn as String: label

      override display()
        <- rtn as String: `[${label}]`

      default operator ?

  defines function

    testCorrectHierarchies()
      point <- Point(3.0, 4.0)
      require point?
      require $point == "(3.0, 4.0)"

      widget <- Widget("button")
      require widget?
      require widget.describe() == "button"
      require widget.display() == "[button]"

Common mistakes

E50060 — String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details.

Incorrect:

require widget.describe().toUpperCase() == "button"

Correct:

require widget.describe() == "button"

E05120 — When implementing abstract trait methods like describe from Describable, the override keyword is required to show explicit intent. See ek9 -h E05120 for details.

Incorrect:

describe()

Correct:

override describe()
Other ways to ask this
  • Does EK9 check circular inheritance on traits?
  • Can records have circular hierarchies in EK9?
  • Is E05020 only for classes or all construct types?

Coming from another language?

Java: circular check on classes and interfaces. C#: circular check on classes, interfaces, and structs. Kotlin: circular check on all types including sealed. Rust: no class inheritance; trait coherence rules prevent cycles. Go: interfaces are structural, no circular hierarchy concept. EK9: uniform E05020 check across classes, traits, records, and components.

Keywords: inherit, hierarchy, uniform, record, trait, component, circular, construct, type, E05020, cycle