What are the return type and parameter type rules for method overrides?

← Override Mechanics · Ref: Q699

An override keeps the parent's PARAMETER types exactly, keeps its purity, and may NARROW its return type to a subtype (a covariant return).

RETURN TYPE: SAME OR NARROWER (E07440)

The return type of an override must be the parent's type or a subtype of it:

  Parent: duplicate() <- rtn as Formatter
  Child:  override duplicate() <- rtn as Formatter          // Correct
  Child:  override duplicate() <- rtn as DetailFormatter    // Correct: covariant
  Child:  override duplicate() <- rtn as Integer            // E07440

An unrelated return type is E07440 ('return types are incompatible (covariance required)'). Code holding a Formatter reference still gets a Formatter back, so the narrowed override is safe - and it is the override that runs.

PARAMETER TYPES: EXACTLY THE SAME (E05110)

Parameter types must match exactly:

  Parent: handle(input as String)
  Child:  override handle(input as String)    // Correct
  Child:  override handle(input as Integer)   // E05110

Different parameter types make a new overload, not an override, so 'override' on it is E05110.

PURITY IS KEPT (E05150, E05160)

A pure parent method requires a pure override (E05150). If the parent method is NOT pure, the override cannot add purity:

  Parent: compute()          // Not pure
  Child:  override compute() as pure  // E05160

Purity must be designed in at the base level.

WHY

Any code using the parent type works correctly with any descendant: it passes the same argument types, and it gets back something that IS the type it expects.

See Q570 for override basics. See Q573 for access modifier rules. See Q567 for purity chain inheritance.

Example

defines module qa.override.returntypecovariance

  defines class

    <?-
      Base class defining the method contract.
      Parameter types and purity set here; return types may be narrowed.
    -?>
    Formatter as open

      //Method with String return and String parameter
      formatItem()
        -> itemText as String
        <- formatted as String: `[${itemText}]`

      //Pure method: descendants must keep purity
      summarize() as pure
        <- rtn as String: "base summary"

      //Method with multiple parameters
      combine()
        ->
          firstPart as String
          secondPart as String
        <- combined as String: `${firstPart} ${secondPart}`

      //Method returning the base type
      duplicate()
        <- rtn as Formatter: Formatter()

      default operator ?

    <?-
      Correct overrides: parameter types and purity preserved exactly.
      A return type may be narrowed to a subtype (covariant return).
    -?>
    DetailFormatter extends Formatter

      //Correct: String -> String, same types
      override formatItem()
        -> itemText as String
        <- formatted as String: `<< ${itemText} >>`

      //Correct: pure override of pure method
      override summarize() as pure
        <- rtn as String: "detail summary"

      //Correct: (String, String) -> String, same types
      override combine()
        ->
          firstPart as String
          secondPart as String
        <- combined as String: `${firstPart} | ${secondPart}`

      //Correct: covariant return - DetailFormatter is a Formatter
      override duplicate()
        <- rtn as DetailFormatter: DetailFormatter()

      default operator ?

  defines program

    ReturnTypeCovarianceDemo()
      stdout <- Stdout()

      base <- Formatter()
      stdout.println(base.formatItem("item"))
      stdout.println(base.summarize())

      detail <- DetailFormatter()
      stdout.println(detail.formatItem("item"))
      stdout.println(detail.summarize())
      stdout.println(detail.combine(firstPart: "left", secondPart: "right"))

      //Through a Formatter reference, the covariant override still runs
      asBase as Formatter: DetailFormatter()
      stdout.println(`Duplicate: ${asBase.duplicate().summarize()}`)

Common mistakes

E05150 — If the parent method is pure, the override must remain pure; removing purity from an override triggers E05150. (The reverse - adding purity to an override of a non-pure method - is E05160.) See ek9 -h E05150 for details.

Incorrect:

override summarize()

Correct:

override summarize() as pure

E05110 — Parameter types in an override must match the parent exactly. Changing String to Integer means the method no longer overrides the parent and triggers E05110. See ek9 -h E05110 for details.

Incorrect:

override formatItem()
        -> itemText as Integer

Correct:

override formatItem()
        -> itemText as String

E07440 — The return type of an override must be the parent's type or a subtype of it (covariant). Integer is unrelated to String, so the override triggers E07440. See ek9 -h E07440 for details.

Incorrect:

override formatItem()
        -> itemText as String
        <- formatted as Integer: 42

Correct:

override formatItem()
        -> itemText as String
        <- formatted as String: `<< ${itemText} >>`
Other ways to ask this
  • What is E05160 cannot add purity in override?
  • What is E05110 parameter type mismatch in override?
  • What is E07440 incompatible return type in override?
  • Must override methods have identical signatures?

Coming from another language?

Java: covariant return types allowed, parameter types must match. Kotlin: covariant returns, exact params. C++: covariant returns. Rust: no inheritance, trait impls must match exactly. EK9: like Java - exact parameter types (E05110), covariant (narrowed) return types allowed, an unrelated return type is E07440.

Keywords: return, E05160, override, type, virtual, E05110, inherit, abstract, function, parameter, E07440, open, signature, covariance