E07184: '#?' goes with the type's sameness: defaulted with a defaulted one, hand-written with a hand-written one

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

What went wrong

'#?' does not go with the type's sameness. Equal values must have equal hashes, or Set, Dict and uniq treat them as different:

  • hand-written '<=>' or '==' needs a HAND-WRITTEN '#?' over the same fields (a defaulted '#?' hashes every field, so equal values would hash differently);
  • defaulted sameness gets its '#?' generated - a hand-written one may disagree;
  • a type that inherits its sameness (and declares none of its own) inherits its '#?' too;
  • a '#?' on a type with no sameness at all has nothing to agree with;
  • where the sameness is declared, '#?' must have a body there - an abstract '#?' beside '<=>' or '==' is an error on every type, a field-less abstract root included: every sub-type of an abstract root inherits the sameness, so none of them may declare the '#?' that abstract one asks for, and no sub-type could be written.

EK9 codifies Java's equals/hashCode/compareTo contract (Effective Java items 10, 11 and 14) as a compile error: a type defines its SAMENESS once - '<=>' if it is ordered, '==' if it is equatable only - and every other comparison is generated from it, so two ways of saying "equal" cannot disagree.

Per-type sameness - a '==' or '<=>' generated or hand-written on a type that is not hierarchy-wide - compares objects of the same run-time type: between two DIFFERENT run-time types (a Leaf and a Middle) '==' is false and '<=>' is unset. Declare the sameness by hand on an ABSTRACT root or a trait when objects of different classes must compare (hierarchy-wide sameness, E07182).

Worked examples

Compilable examples demonstrating this error and the correct fix:

  • ek9 -q 1402 (How do I define equality and ordering for my own type?)

How to fix it

  1. Hand-written sameness: write '#?' over exactly the fields it compares (in the [EXAMPLE] section below, an Account compared by 'id' hashes 'id' alone)
  2. Defaulted sameness: remove the hand-written '#?'.
  3. A hash with no sameness: give the type '==' (hashable but not sortable is an equatable-only type), or remove the '#?'.
  4. An abstract '#?' beside the sameness: give it a body over what the sameness compares (an abstract root writes it over its own abstract accessors), or generate both - the '#?' with a generated '<=>' (or '==').

Common causes

  • 'default operator #?' beside a hand-written '<=>'
  • A '#?' written only so the type can be used with uniq
  • 'operator #? as pure abstract' on the abstract root that declares '<=>'

See also

  • E07181: comparisons generated from the sameness
  • E07182: sameness declared again below an abstract base or trait

Examples

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

Fails with E07184: '#?' goes with the type's sameness: defaulted with a defaulted one, hand-written with a hand-written one

#!ek9
defines module error.e07184.bad

  defines trait

    //A trait with a concrete '==' and an abstract '#?'.
    Coded
      code() as pure abstract
        <- rtn as String?

      operator == as pure
        -> other as Coded
        <- rtn as Boolean: code() == other.code()

      operator #? as pure
        <- rtn as Integer?

  defines class

    //A field-less abstract root: hand-written '<=>' over an abstract accessor, abstract '#?'.
    Shape as abstract
      area() as pure abstract
        <- rtn as Float?

      operator <=> as pure
        -> other as Shape
        <- rtn as Integer: area() <=> other.area()

      default operator ?

      operator #? as pure abstract
        <- rtn as Integer?

    //A field-less abstract root with DEFAULTED sameness and an abstract '#?'.
    Marker as abstract
      default operator <=>

      default operator ?

      operator #? as pure abstract
        <- rtn as Integer?

Fixed: Hash what it compares

#!ek9
defines module error.e07184.good

  defines class

    Account
      id as Integer?
      balance as Integer?

      default private Account()

      Account()
        ->
          identifier as Integer
          amount as Integer

        this.id: identifier
        this.balance: amount

      identity() as pure
        <- rtn as Integer: id

      //Hand-written sameness...
      operator <=> as pure
        -> other as Account
        <- rtn as Integer: id <=> other.identity()

      //...goes with a hand-written '#?' that hashes what it compares

      override operator ? as pure
        <- rtn as Boolean: id? and balance?

      operator #? as pure
        <- rtn as Integer: #?id

Questions and answers

These Q&A entries show E07184 and its fix: