E81068: the 'operator close' of an open or abstract type can never call a method a subtype may override, nor hand out 'this' - a subtype's own properties are closed before this body runs

Category: E81xxx-E83xxx Test configuration, execution and coverage · Reported in phase: PRE_IR_CHECKS · Classification: CLOSE_OPERATOR_USES_OVERRIDABLE · All error codes

What went wrong

The 'operator close' of an OPEN or ABSTRACT type calls a method a subtype may override, or hands out 'this'. A subtype's close closes the subtype's own properties first and then the super's close runs, so a method the subtype overrides, called from the super's close, works on resources that are already closed - a write after close, or a flush that is lost.

It is the same rule E08061 applies to the constructor of an open or abstract type, at the other end of the object's life: there the subtype's properties are not set yet, here they are already closed.

Worked examples

Compilable examples demonstrating this error and the correct fix:

  • ek9 -q 1860 (Which of my types must declare their own operator close, and what may that close do?)
  • ek9 -q 1396 (Why can't my constructor call a method before every property is set?)

How to fix it

Call only what no subtype can replace: a PRIVATE helper, or the methods of the properties this type itself holds (the [EXAMPLE] section below: an open base whose close calls a private 'tidy()', and one whose close writes to the 'Out' property it holds). If a subtype needs work done when it closes, it does that work in its own close; its generated close then closes its own properties and calls this close.

Common causes

  • A "template method" close in a base class that calls a hook subtypes override.
  • Passing 'this' to a logger or a registry from a base class's close.
  • Calling super.someMethod() from an open type's close - the super's method can itself call an override.

See also

  • E81068: An open type's operator close calls an overridable method (this error)
  • E08061: The object is used before it is fully constructed
  • E81067: A type with a closeable property must declare its own operator close
  • E81066: A trait's operator close must be abstract

Examples

Every example is compiled by the EK9 compiler: the first fails exactly as captioned, the others fix it.

Fails with E81068: the 'operator close' of an open or abstract type can never call a method a subtype may override, nor hand out 'this' - a subtype's own properties are closed before this body runs

#!ek9
defines module error.e81068.bad

  defines class

    Out
      name as String?

      default private Out()

      Out()
        -> label as String
        name: label

      write() as pure
        -> text as String
        require text?

      default operator ?

      operator close as pure
        require name?

    <?- BAD: an open type's close calls a method a subtype may override -?>
    FlushingBase as open
      label as String?

      FlushingBase()
        label: "base"

      flush() as pure
        require label?

      default operator ?

      operator close as pure
        flush()

    <?- BAD: the same call written through 'this' -?>
    ThisFlushingBase as open
      label as String?

      ThisFlushingBase()
        label: "base"

      flush() as pure
        require label?

      default operator ?

      operator close as pure
        this.flush()

    <?- BAD: an abstract type's close calls its own abstract method -?>
    AbstractBase as abstract
      label as String?

      AbstractBase()
        label: "abstract"

      describe() as pure abstract
        <- rtn as String?

      default operator ?

      operator close as pure
        text <- describe()
        require text?

    <?- BAD: an open type's close hands out 'this' -?>
    ReportingBase as open
      label as String?

      ReportingBase()
        label: "reporting"

      default operator ?

      operator close as pure
        report(this)

    <?- BAD: a call to the super's method from an open type's close - the super's method can call back into an override -?>
    ChainedBase is FlushingBase as open
      out as Out?

      ChainedBase()
        super()
        out: Out("chained")

      default operator ?

      override operator close as pure
        super.flush()

    <?- VALID: a private helper cannot be overridden -?>
    PrivateHelperBase as open
      label as String?

      PrivateHelperBase()
        label: "private"

      private tidy() as pure
        require label?

      default operator ?

      operator close as pure
        tidy()

    <?- VALID: calling methods of a property the type holds, and closing it -?>
    HolderBase as open
      out as Out?

      HolderBase()
        out: Out("held")

      default operator ?

      operator close as pure
        out.write("final")

    <?- VALID: an overriding close chaining to the super's close -?>
    HolderSub is HolderBase as open
      extra as Out?

      HolderSub()
        super()
        extra: Out("extra")

      default operator ?

      default operator close

    <?- VALID: a closed type has no subtype, so its close may call its own methods and use 'this' -?>
    ClosedType
      label as String?

      ClosedType()
        label: "closed"

      flush() as pure
        require label?

      default operator ?

      operator close as pure
        flush()
        report(this)

  defines function

    report() as pure
      -> item as Any
      require item?

Fixed: In an open type, close only calls private helpers and what its properties hold

#!ek9
defines module error.e81068.good

  defines class

    Out
      name as String?

      default private Out()

      Out()
        -> label as String
        name: label

      write() as pure
        -> text as String
        require text?

      default operator ?

      operator close as pure
        require name?

    PrivateHelperBase as open
      label as String?

      PrivateHelperBase()
        label: "private"

      private tidy() as pure
        require label?

      default operator ?

      operator close as pure
        tidy()

    HolderBase as open
      out as Out?

      HolderBase()
        out: Out("held")

      default operator ?

      operator close as pure
        out.write("final")

Questions and answers

These Q&A entries show E81068 and its fix: