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 NEAREST matching operator above it first - the super's, or one further up when the classes in between add no fields and declare none - 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 naming the first operator it cannot chain to (<=>).

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. Likewise a derived class that ADDS fields declares its own '$', '$$' and ':=:' rather than inheriting the base's, which cannot know the new fields (E07238) - the bare 'default operator' in Circle does that.

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 only 'operator ?' and nothing else, there is nothing for the derived default to chain to, so the compiler reports E07190 for the first missing operator it needs (<=>). 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:

        this.name :=: name

      default operator ?

Correct:

        this.name :=: name

      default operator

E07238 — Circle adds a field (radius) but declares only '?', so it would INHERIT Shape's '$', '$$' and ':=:' - operators written for Shape's fields, which cannot know about radius: the string and JSON would silently leave it out and a copy would copy only half the object. A type that adds fields declares its own '$', '$$' and ':=:' (as it must declare '?', E07235); the bare 'default operator' declares them all, each calling Shape's first. See ek9 -h E07238 for details.

Incorrect:

        this.radius :=: radius

      default operator ?

Correct:

        this.radius :=: radius

      default operator
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: E07238, inherit, default, extends, super, E07190, operator, override, base