E07238: a type that adds fields, concrete or abstract, must declare its own '$', '$$' and ':=:', and its own sameness ('<=>' or '==', with '#?'), where it would inherit one: the inherited operator was written for the super's fields and cannot know these - use 'default operator' for each (or the bare 'default operator') or write one
What went wrong
This type adds fields of its own but would INHERIT '$', '$$', ':=:' or its sameness ('<=>' or '==', with '#?') from a type above it. That operator was written for the super's fields: it cannot know the ones this type adds. So the new fields would be silently missing from the string ('$') and the JSON ('$$'), a copy (':=:') would copy only half of the object, and two values that differ only in the new fields would be the same: '==' true, '<=>' 0, equal '#?'. A Leaf that adds 'name' below a Base with '$' and its own 'id' would print as Base(id=1), with 'name' silently missing.
This is the same rule E07235 applies to '?', extended to the operators whose inherited version would lose data. For SAMENESS it applies to a concrete AND to an abstract type that has per-type sameness: below an ordered super (one with '<=>') it declares its own '<=>', below an equatable-only super (one with '==' and no '<=>') its own '=='. Nothing at run time catches the inherited one - both values are the same run-time type. It does not apply where the sameness was WRITTEN BY HAND on an abstract base or a trait: that is hierarchy-wide sameness (every Shape by area), and a sub-type may not change it (E07182). Fields a sub-type adds below a hierarchy-wide sameness are not compared on purpose: a Card whose trait orders by 'rank()' has Card(5, "hearts") == Card(5, "spades"). One sameness per type; if you want another view (by suit as well, 'aces high'), put it into the trait's ordering or write a comparator function and 'sort by' it (see ek9 -q 2280).
An abstract type that adds fields therefore has two choices, and they differ:
- 'default operator' (or 'default operator <=>' / '==') is PER-TYPE sameness. It compares this type's fields and the super's, and each sub-type that adds fields declares its own again.
- A '<=>' or '==' written BY HAND on the abstract type is HIERARCHY-WIDE sameness. It orders every pair of values in the hierarchy, whatever their run-time types, and is sealed: no sub-type may declare its own (E07182), so fields a sub-type adds later are not compared.
It follows the super CLASS chain only. A concrete '$' inherited from a TRAIT is not checked: a trait has no fields, so its '$' is a deliberate representation (often built on an abstract method such as 'label()'), not a rendering of fields it could miss.
A copy (':=:') is polymorphic: each type that declares ':=:' also overrides the inherited ':=:(Base)', so a copy through a Base-typed reference runs the object's own copy. It copies in full between objects of the same class, and throws an Exception naming the classes when the source is a different class (a Middle into a Leaf, or a sub-type that adds fields).
Worked examples
Compilable examples demonstrating this error and the correct fix:
- ek9 -q 116 (What does 'default operator' generate?)
- ek9 -q 1314 (Why does 'default operator' in a derived class fail when the base class lacks operators?)
- ek9 -q 2248 (Why were two of my subclass objects equal when only the added field was different?)
- ek9 -q 2280 (Why is my Card equal to another Card of the same rank but a different suit?)
How to fix it
Declare the operator on the type that adds the fields (good.ek9 in the [EXAMPLE] section below). Generated on that Leaf, '$' prints 'Leaf(Base(id=1), name=leaf)': the generated '$' calls the NEAREST '$' above it (the super's, or one further up when the types in between add no fields) and then renders this type's own fields. Write the operator by hand instead when the fields must not all be shown or copied (good_written.ek9 below: it overrides Base's '$' and shows the name only).
For sameness the fix is one line (good_sameness.ek9 below): 'default operator <=>' below an ordered super, or 'default operator ==' below an equatable-only one. It compares the super's sameness first, then this type's own fields, and generates '#?' with it. A hand-written '<=>' or '==' needs a hand-written '#?' (E07184).
The bare 'default operator' (no operator named) declares all of them at once, with the same kind of sameness as the super.
Common causes
- A field added to a sub-type that had none, so it used to inherit '$' safely
- 'default operator ?' added for E07235 without the matching '$', '$$' or ':=:'
- A copy (':=:') declared on the root of a hierarchy and not on its sub-types
- 'default operator <=>' declared on the root of a hierarchy and not on the sub-types that add fields, an abstract one in between included
See also
- E07235: a type with fields must define its own '?'
- E07185: an abstract '$', '$$' or '#?' on a type with fields
- E07190: 'default operator' with no such operator above it
- E07182: hierarchy-wide sameness (written by hand on an abstract base or trait) is sealed
- E07186: below an ordered super a sub-type's own sameness is '<=>'
Examples
Every example is compiled by the EK9 compiler: the first fails exactly as captioned, the others fix it.
Fails with E07238: a type that adds fields, concrete or abstract, must declare its own '$', '$$' and ':=:', and its own sameness ('<=>' or '==', with '#?'), where it would inherit one: the inherited operator was written for the super's fields and cannot know these - use 'default operator' for each (or the bare 'default operator') or write one
#!ek9 defines module error.e07238.bad defines record //A record root with '$$' and ':=:'. Reading as open celsius as Float: Float() default operator ? default operator $$ default operator :=: //Adds a field, declares '$$' only: it would inherit Reading's ':=:'. Located extends Reading place as String: String() default operator ? default operator $$ defines class //A class root with every one of the three. Base as open id as Integer: Integer() default operator ? default operator $ default operator $$ default operator :=: //Adds a field and declares only '?': it would inherit all three. Leaf extends Base name as String: String() default operator ? //No fields of its own: needs nothing. Middle extends Base as open Middle() super() //Adds a field below the field-less middle: the nearest '$' is still Base's. Deeper extends Middle weight as Integer: Integer() default operator ? default operator $$ default operator :=: //A field-less root whose '$' is abstract. Shape as abstract operator $ as pure abstract <- rtn as String? //Adds a field but declares no '$': below an abstract one the field would be left out of every sub-type's '$'. Named extends Shape as abstract label as String: String() default operator ? //An ordered root: the bare 'default operator' gives it '<=>' over score, and '#?'. Ordered as open score as Integer: Integer() default operator //Adds a field and declares every operator but its sameness: it would inherit Ordered's '<=>' and '#?'. Ranked extends Ordered name as String: String() default operator ? default operator $ default operator $$ default operator :=: defines component Store as open size as Integer: Integer() default operator ? default operator $ FileStore extends Store path as String: String() default operator ?
Fixed: Declare the operator where the fields are added
#!ek9 defines module error.e07238.good defines class Base as open id as Integer: Integer() default operator ? default operator $ //Adds a field, so it declares its own '$' over it Leaf extends Base name as String: String() default operator ? default operator $
Fixed: Declare the sameness where the fields are added
#!ek9 defines module error.e07238.good.sameness defines class Ordered as open score as Integer: Integer() default operator //Adds a field, so it declares its own '<=>' (and so '#?'): Ordered's first, then the name Ranked extends Ordered name as String: String() default operator ? default operator $ default operator $$ default operator :=: default operator <=>
Fixed: Or write the operator by hand when not every field should be shown
#!ek9 defines module error.e07238.good.written defines class Base as open id as Integer: Integer() default operator ? default operator $ Leaf extends Base name as String: String() default operator ? //Shows the name only, by choice override operator $ as pure <- rtn as String: `Leaf ${name}`
Questions and answers
These Q&A entries show E07238 and its fix: