How does EK9 detect multi-level circular inheritance chains?
← Type Hierarchy Constraints · Ref: Q603
EK9 traverses the entire inheritance chain during type hierarchy checks and detects circular references at any depth, not just direct self-references.
TWO-LEVEL CYCLE
A extends B, B extends A creates a cycle of length 2. The compiler walks A's parent chain and finds it leads back to A.
THREE-LEVEL CYCLE
A extends B, B extends C, C extends A creates a cycle of length 3. Even though no class directly extends itself, the transitive chain A -> B -> C -> A is detected.
DEEP CHAINS
The compiler checks arbitrarily deep chains. A cycle buried five levels deep is caught just as reliably as a direct self-reference. The traversal is O(n) in the depth of the hierarchy.
HOW DETECTION WORKS
During Phase 5 (TYPE_HIERARCHY_CHECKS), the compiler walks each type's supertype chain. If it encounters the starting type again, it reports E05020. This is a standard cycle detection algorithm applied to the inheritance graph.
WHAT TO DO
Draw out the inheritance relationships to visualize the cycle. Identify which class should be the root. Break the cycle by removing one extends clause. Consider composition instead.
See Q602 for circular hierarchy basics. See Q610 for composition-based refactoring. See Q315 for inheritance depth quality checks.
Example
defines module qa.typehierarchy.chaindetection defines class //CORRECT: Five-level linear hierarchy (no cycles) Animal as open describe() <- rtn as String: "animal" default operator ? Mammal extends Animal as open override describe() <- rtn as String: "mammal" default operator ? Carnivore extends Mammal as open override describe() <- rtn as String: "carnivore" default operator ? Canine extends Carnivore as open override describe() <- rtn as String: "canine" default operator ? Wolf extends Canine override describe() <- rtn as String: "wolf" default operator ? defines function testDeepLinearChain() //Five levels deep: Wolf -> Canine -> Carnivore -> Mammal -> Animal wolf <- Wolf() require wolf? require wolf.describe() == "wolf" //Polymorphic use through the hierarchy animals <- List() of Animal animals += Animal() animals += Mammal() animals += Carnivore() animals += Canine() animals += Wolf() for creature in animals require creature?
Common mistakes
E50060 — String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details.
Incorrect:
require wolf.describe().toUpperCase() == "wolf"
Correct:
require wolf.describe() == "wolf"
E05120 — At every level of the five-level hierarchy, the override keyword is required when replacing the parent describe method. Omitting it triggers shadowing detection. See ek9 -h E05120 for details.
Incorrect:
describe()
Correct:
override describe()
Other ways to ask this
- Can EK9 detect indirect circular inheritance?
- What about three-way circular class hierarchies?
- Does EK9 check deep circular inheritance chains?
Coming from another language?
Java: detects circular inheritance at all depths (identical behavior). C#: detects cycles in class hierarchy. C++: compilation error for circular inheritance at any depth. Kotlin: compile error, same as Java. Python: raises TypeError when MRO cannot be computed. EK9: E05020 fires at any cycle depth, detected during TYPE_HIERARCHY_CHECKS phase.
Keywords: multi-level, transitive, inherit, hierarchy, cycle, circular, detection, deep, chain, type, E05020