E07187: '<?' and '>?' select by '<=>', which is unset for two values of different run-time types: they cannot be used on an open or abstract class, record or component, or a trait, unless its sameness is hierarchy-wide - make the type closed, write '<=>' by hand on its abstract base or trait, or compare with '<=>' and handle unset yourself

Category: E07xxx Full resolution · Reported in phase: FULL_RESOLUTION · Classification: COALESCING_COMPARISON_ACROSS_RUNTIME_TYPES · All error codes

What went wrong

'<?' and '>?' select one of their two operands by comparing them with '<=>'. That only works when every two set values of the type have an ordering. Here the operands' type is an open class, an abstract class, an open or abstract record or component, or a trait, so its values can be of DIFFERENT run-time types - a Base and a Kid that extends it, say - and two values of different run-time types are never the same or in order: their '<=>' is UNSET. There is then no lesser or greater value to select.

The result cannot be made unset either. An unset value cannot be built for a type of your own: 'T()' may give a SET value, may not exist or be private, the type may be abstract or a trait, and it would run your code. So the operators are refused for such a type at compile time.

The one exception is hierarchy-wide sameness: a '<=>' written by hand on an abstract base or a trait, over what every sub-type provides (an 'area()' for shapes, say). That '<=>' orders every pair of values, whatever their run-time types, so the operators can always choose.

In a generic, the body cannot know what 'T' will be, so the check is made where 'T' is given its type: making a 'Keeper' of Base, or 'lesserOf() of Base', is the error when the generic uses one of these operators on its 'T' and Base is such a type.

Closed classes, closed records and closed components, enumerations, constrained types and the built-in types are never affected.

Worked examples

The [EXAMPLE] section below shows '<?' on an open class, then the fix - the class made closed, so both values are always of one run-time type.

How to fix it

  1. If nothing needs to extend the type, make it closed: remove 'as open'.
  2. If the hierarchy should be ordered as a whole, write '<=>' by hand on its abstract base or trait, over what every sub-type provides (with a hand-written '#?' beside it, E07184). Every pair of values then has an order.
  3. Otherwise compare with '<=>' yourself and decide what to do when the result is unset:
//Unset when first and second are of different run-time types: keep first then
comparison <- first <=> second
lesser <- comparison? and comparison > 0 <- second : first
  1. In a generic, parameterise it with a closed type, or with a type whose hierarchy has a hand-written '<=>' on its abstract base or trait.

Common causes

  • 'as open' added to a class that nothing extends yet
  • An abstract base with 'default operator': a defaulted sameness is per-type, it compares each type with itself only
  • A generic min/max helper parameterised with the base of a hierarchy

See also

  • E07182: hierarchy-wide sameness (written by hand on an abstract base or trait) is sealed
  • E07184: '#?' goes with the sameness
  • E07620: the type has no '<=>' at all

Examples

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

Fails with E07187: '<?' and '>?' select by '<=>', which is unset for two values of different run-time types: they cannot be used on an open or abstract class, record or component, or a trait, unless its sameness is hierarchy-wide - make the type closed, write '<=>' by hand on its abstract base or trait, or compare with '<=>' and handle unset yourself

#!ek9
defines module error.e07187.bad

  defines class

    Base as open
      tag as String: String()

      Base()
        -> initial as String
        tag :=? initial

      default operator

    Kid extends Base
      Kid()
        -> initial as String
        super(initial)

  defines program

    Lesser()
      stdout <- Stdout()
      first as Base: Base("a")
      second as Base: Kid("b")

      //A Base and a Kid are different run-time types: their '<=>' is unset, so there is no lesser one
      lesser <- first <? second
      stdout.println($lesser)

Fixed: Make the class closed, so both values are always of one run-time type

#!ek9
defines module error.e07187.good

  defines class

    //Closed: nothing extends Base, so two Base values always compare
    Base
      tag as String: String()

      Base()
        -> initial as String
        tag := initial

      default operator

  defines program

    Lesser()
      stdout <- Stdout()
      first <- Base("a")
      second <- Base("b")

      lesser <- first <? second
      stdout.println($lesser)

Fixed: Keep the hierarchy open, compare with '<=>' and decide what an unset comparison means

#!ek9
defines module error.e07187.good.compare

  defines class

    Base as open
      tag as String: String()

      Base()
        -> initial as String
        tag := initial

      default operator

    Kid extends Base
      Kid()
        -> initial as String
        super(initial)

  defines program

    Lesser()
      stdout <- Stdout()
      first as Base: Base("a")
      second as Base: Kid("b")

      //Unset when first and second are of different run-time types: keep first then
      comparison <- first <=> second
      lesser <- comparison? and comparison > 0 <- second : first
      stdout.println($lesser)

Questions and answers

These Q&A entries show E07187 and its fix: