E07188: a '<=>' must order every two set values of its type: it cannot return an unset Integer, nor build its result on a '<=>' (a field's, a super's, a function's) that answers unset for two set values
What went wrong
A '<=>' written by hand can leave its result UNSET for two values of its own type that are both SET. The type then has no order between those two values: a sort over a list of them would depend on the order the list arrived in, and '<?' or '>?' would hand back whichever operand happened to come second.
EK9 checks this when the operator is declared, because the compiler knows every type that can reach it. A '<=>' must order every two SET values of its type. It is reported when the Integer it returns can come from
- an unset Integer: 'Integer()', typically the 'else Integer()' of a '<=>' that only compares values of one unit;
- a '<=>' that is itself partial: the '<=>' of Money (defined only within one currency) or Dimension (only within one unit suffix), the hand-written '<=>' of another type that is partial, or a method or function whose result comes from one of these;
- the '<=>' of a type whose values can be of DIFFERENT run-time types: an open or abstract class, record or component, or a trait, whose sameness is per-type, not hierarchy-wide (one written by hand on an abstract base or trait). Two values of different run-time types are never the same, so their '<=>' is unset. This holds whether or not a sub-type exists yet: another module can add one.
A sub-type that overrides a '<=>' with one that can answer unset is reported on the sub-type's own '<=>', not on the super's.
The check looks at where the returned Integer can get its value, not at the path the code takes: a partial source is reported even when a test guards it. A path that never assigns a returning variable declared with no value is E08050. A '<=>' that is generated ('default operator <=>') is never reported here: over a field that is partial (Money, Dimension, or an open or abstract type as above) it is withdrawn, and a use of it is E07620.
Worked examples
The [EXAMPLE] section below shows a Length that returns an unset Integer for two lengths in different units, and a Priced that compares Money values directly, then the fixes - compare the unit first and the amount second, and compare a Money's currency (its '#>') before its amount (its '#<').
How to fix it
- Make the result total: compare the part that groups the values (the unit, the currency) first, then the part that orders within a group. 'rtn: unit <=> other.unit' and then 'if rtn == 0' compare the amounts.
- For Money compare '#>' (the currency, a String) and then '#<' (the amount, a Float). For a Dimension the same two prefix operators give the suffix and the value.
- Never write 'Integer()' as the result of a '<=>'. Declare the result with no value ('<- rtn as Integer?') and let E08050 prove that every path assigns it.
- For a field of an open or abstract type: put the ordering on an abstract base or a trait, written by hand over what every sub-type provides ('area() <=> other.area()'), so it is hierarchy-wide sameness; or make the field's type closed (remove 'as open'); or compare a part of the field that every run-time type has ('item.size()').
- If the type should not be ordered at all, declare 'default operator ==' and compare with a comparator function.
Common causes
- 'else Integer()' on a '<=>' that compares only values of the same unit, kind or currency
- Comparing two Money or Dimension fields with '<=>' directly
- A '<=>' that calls a helper whose result is unset for some pair of values
- Comparing a field whose type is open (or abstract with a defaulted '<=>'): a value of the type and one of a sub-type compare unset
See also
- E07620: a use of an ordering that is not available (a defaulted '<=>' over a Money, Dimension or open-typed field is withdrawn)
- E07187: '<?' and '>?' over a type whose values can be of different run-time types
- E07200: 'default operator <=>' written out over a field that is not totally ordered
- E08050: a returning variable that is not assigned on every path
- E07181: comparisons generated from the sameness
Examples
Every example is compiled by the EK9 compiler: the first fails exactly as captioned, the others fix it.
Fails with E07188: a '<=>' must order every two set values of its type: it cannot return an unset Integer, nor build its result on a '<=>' (a field's, a super's, a function's) that answers unset for two set values
#!ek9 defines module error.e07188.bad defines class Length amount as Integer: Integer() unit as String: String() Length() -> amount as Integer unit as String this.amount: amount this.unit: unit measure() as pure <- rtn as String: unit quantity() as pure <- rtn as Integer: amount //Two lengths in different units are set, but this '<=>' gives them no order: it returns an unset Integer operator <=> as pure -> other as Length <- rtn as Integer: unit == other.measure() <- amount <=> other.quantity() else Integer() default operator ? default operator $ default operator $$ operator #? as pure <- rtn as Integer: #?amount + #?unit default operator :=: Priced name as String: String() price as Money: Money() Priced() -> name as String price as Money this.name: name this.price: price //Money orders only within one currency: 10 GBP <=> 5 USD is unset, and so is this '<=>' operator <=> as pure -> other as Priced <- rtn as Integer: price <=> other.price default operator ? default operator $ default operator $$ operator #? as pure <- rtn as Integer: #?price default operator :=:
Fixed: Order every two set values: compare the unit or currency first, then the amount
#!ek9 defines module error.e07188.good defines class Length amount as Integer: Integer() unit as String: String() Length() -> amount as Integer unit as String this.amount: amount this.unit: unit measure() as pure <- rtn as String: unit quantity() as pure <- rtn as Integer: amount //Every pair of lengths is ordered: by unit first, then by amount - no path returns an unset Integer operator <=> as pure -> other as Length <- rtn as Integer: unit <=> other.measure() if rtn == 0 rtn: amount <=> other.quantity() default operator ? default operator $ default operator $$ operator #? as pure <- rtn as Integer: #?amount + #?unit default operator :=: Priced name as String: String() price as Money: Money() Priced() -> name as String price as Money this.name: name this.price: price //The currency (String) is compared first, then the amount (Float): both orders are total operator <=> as pure -> other as Priced <- rtn as Integer: #> price <=> #> other.price if rtn == 0 rtn: #< price <=> #< other.price default operator ? default operator $ default operator $$ operator #? as pure <- rtn as Integer: #?price default operator :=:
Questions and answers
These Q&A entries show E07188 and its fix: