Why does 'default operator' in a derived class fail when the base class has no operators?

← Override Mechanics · Ref: Q1314

A 'default operator' in a derived class generates operators that call the SUPER class's matching operator first, then combine with the derived fields. If the base class has no operators (no 'default operator' and no manual operators), there is nothing for the derived default to chain to, so EK9 raises E07190 - one missing operator at a time (e.g. <=>, ==, $, #?, ?).

Fix - choose one of two approaches:

APPROACH 1 (recommended): add 'default operator' to the base class so the super has operators to call. Then 'default operator' in the derived class chains cleanly.

  defines class
    Shape as open
      name as String: String()
      default operator    // base now has operators
    Circle extends Shape
      radius as Float: 0.0
      default operator    // chains to super's operators

APPROACH 2: implement the operators manually in the derived class instead of using 'default operator', writing each operator (?, $, <=>, etc.) explicitly.

Note that an aggregate with fields also needs 'override operator ?' or 'default operator ?' to define set/unset semantics, so adding 'default operator' to the base fixes both concerns at once.

See Q1069 for mixing default operator with a custom override. See Q577 for override operator basics.

Example

defines module qa.overridemechanics.defaultsuperoperator

  defines class

    //THE FIX: the base class defines 'default operator' so the super has
    //operators (<=>, ==, $, #?, ?) for the derived default to chain to.
    Shape as open
      name as String: String()

      Shape()
        -> name as String
        this.name :=: name

      default operator

    //The derived 'default operator' generates operators that call super's
    //matching operator first, then combine with the derived fields.
    Circle extends Shape
      radius as Float: 0.0

      Circle()
        ->
          name as String
          radius as Float
        super(name)
        this.radius :=: radius

      default operator

  defines program

    DefaultSuperOperatorDemo()
      stdout <- Stdout()

      c1 <- Circle("small", 1.0)
      c2 <- Circle("large", 5.0)

      stdout.println(`Circle set: ${c1?}`)
      stdout.println(`Circle: ${c1}`)
      stdout.println(`Equal: ${c1 == Circle("small", 1.0)}`)
      stdout.println(`Compare: ${c1 <=> c2}`)

Common mistakes

E07190 — A derived 'default operator' generates operators that call the super's matching operator first. When the base class (Shape) defines no operators, there is nothing for the derived default to chain to, so E07190 is raised once per missing operator (<=>, ==, $, #?, ?). Add 'default operator' to the base class so the super has operators to call, or implement the operators manually in the derived class instead of defaulting. See ek9 -h E07190 for details.

Incorrect:

Shape as open
  name as String: String()
  // no operators defined

Circle extends Shape
  radius as Float: 0.0
  default operator

Correct:

    Shape as open
      name as String: String()

      Shape()
        -> name as String
        this.name :=: name
Other ways to ask this
  • What triggers E07190 default operator missing in super?
  • Why can't I use default operator when my parent class defines no operators?
  • How do I fix 'default of operators requires super to have appropriate operator'?

Coming from another language?

Java: equals/hashCode/toString are inherited from Object, so a subclass's generated/IDE versions always have a super to call - no compile-time check ties them together. Kotlin data classes do not support inheritance of generated equals/hashCode at all (data classes are effectively final). EK9: 'default operator' explicitly chains to the super's operator and the compiler enforces (E07190) that the super actually defines the operator being defaulted, rather than silently producing inconsistent behaviour.

Keywords: operator, base, default, override, inherit, E07190, super, extends