E07235: aggregate with properties/fields requires 'override operator ?' (custom) or 'default operator ?' (auto-generated) to define set/unset semantics
What went wrong
Your aggregate (class/record/component) has properties/fields but does not define its own '?' (isSet) operator. This breaks EK9's tri-state semantics.
Worked examples
Compilable examples demonstrating this error and the correct fix:
- ek9 -q 116 (What does 'default operator' generate?)
- ek9 -q 96 (How do operators work in EK9?)
- ek9 -q 98 (How do record operators work in EK9?)
- ek9 -q 120 (How do I sort a collection in EK9?)
- ek9 -q 122 (How do I use collect as to aggregate stream results in EK9?)
Quick fix
Add ONE of these among the operators of your class/record/component (operators come after the fields and methods):
- 'default operator ?' - generated: true only when EVERY field is set (good.ek9 in the [EXAMPLE] section below).
- 'override operator ? as pure' returning a Boolean you compute - your own rule, for example only the mandatory fields (good_custom.ek9 below).
Choose 'default' for auto-generation, or 'override' if you need custom logic.
Why this is required
EK9's type system is built on three states: ABSENT, UNSET, and SET. The '?' operator is how code determines if an object is in a "meaningful, usable state". Without a proper '?' implementation that knows about your fields, guards, safe access, and conditional assignment will give incorrect results.
All types inherit from 'Any', which has a '?' operator - but Any's implementation knows nothing about YOUR fields. You must override it.
Why this is critical
The tri-state model permeates ALL of EK9. Each of these asks your type's '?':
- A GUARD ('if person <- getPerson()') enters its block only when person? is true.
- A LOOP GUARD ('while item <- first() then ...') skips the loop when item? is false.
- ASSIGN-IF-UNSET ('result :=? fallback') assigns only when result? is false.
- COALESCING ('person ?: guest') gives person when person? is true, else guest.
Without your own '?' that inspects your fields, ALL of these give wrong results.
The problem in detail
The failing source in the [EXAMPLE] section below (bad.ek9) is a Person with a name and an age and no '?' of its own. A guard on a Person would use Any's '?', which knows nothing about 'name' or 'age': a Person with both set could fail the guard, OR one with both unset could pass it. Either way: BUG.
[SOLUTION 1: AUTO-GENERATED (Recommended)] Use 'default operator ?' for the generated implementation (good.ek9 below): it returns true when name? AND age? are both true (every field must be set).
[SOLUTION 2: CUSTOM IMPLEMENTATION] Use 'override operator ? as pure' for specific semantics (good_custom.ek9 below): there only the name is mandatory, so a Person with a name and no age still counts as set.
[SOLUTION 3: WITH INHERITANCE] A sub-type that adds fields must also define its own '?' to include them (good_inherited.ek9 below): Employee's generated '?' checks super? AND employeeId? AND every other field it declares.
Types with no fields
A class, component, service or dynamic class with NO fields at all (counting inherited ones) never needs this: the compiler gives it a '?' that returns true - present means set. So only the sub-type that ADDS a field below such a chain must define its own (good_fieldless.ek9 below: Marker has no field and needs nothing; Counted adds one and declares 'default operator ?').
Records are not in that list: a concrete record must have a field (E07237), so every concrete record defines its own '?'.
Common patterns
- "All fields set" (auto): default operator ?
- "Any field set": override operator ? returning field1? or field2? or ...
- "Required fields set": override operator ? checking only mandatory fields
- "Delegate to key field": override operator ? returning keyField?
How to fix it
Add ONE of these to your aggregate, among its operators (after all fields and methods):
- 'default operator ?' - the compiler generates it from your fields.
- 'override operator ? as pure' with '<- rtn as Boolean:' and your own logic.
Use 'default' when "every field set = object set" is what you want. Use 'override' when you need custom logic (e.g., only required fields matter, or a partial object should still count as set).
Placement
'default operator ?' and 'override operator ?' are operators, so they go where every operator goes. An aggregate's body has a fixed order: properties first, then constructors and methods, then operators. Any other order is an error (E01086). Within the operators the order is free, and the generated '?' covers every property of the type wherever it sits. The [EXAMPLE] below puts the operator after the properties.
Design rationale
EK9 requires explicit definition of "set" semantics rather than guessing. This:
- Eliminates ambiguity about object validity
- Makes guard behavior predictable and correct
- Enables compile-time verification of null safety
- Removes the need for null checks: a value is tested for set, never for null
See also
Examples
Every example is compiled by the EK9 compiler: the first fails exactly as captioned, the others fix it.
Fails with E07235: aggregate with properties/fields requires 'override operator ?' (custom) or 'default operator ?' (auto-generated) to define set/unset semantics
#!ek9 defines module error.e07235.bad defines class Person name <- String() age <- Integer()
Fixed: Generate it: set only when every field is set
#!ek9 defines module error.e07235.good defines class Person name <- String() age <- Integer() default operator ?
Fixed: Write it: only the name is required
#!ek9 defines module error.e07235.good.custom defines class Person name <- String() age <- Integer() override operator ? as pure <- rtn as Boolean: name?
Fixed: A sub-type that adds fields declares its own
#!ek9 defines module error.e07235.good.inherited defines class Person as open name <- String() default operator ? Employee extends Person employeeId <- String() default operator ?
Fixed: A type with no fields needs none; the sub-type that adds one does
#!ek9 defines module error.e07235.good.fieldless defines class Marker as open mark() <- rtn as String: "marked" Counted extends Marker count <- Integer() default operator ?
Questions and answers
These Q&A entries show E07235 and its fix:
- Q96: How do operators work in EK9?
- Q98: How do record operators work in EK9?
- Q116: What does 'default operator' generate?
- Q120: How do I sort a collection in EK9?
- Q122: How do I use collect as to aggregate stream results in EK9?
- Q1095: Use a Coordinate record as a Dict key.
- Q1228: Find the cheaper Product from two optional product records.