E07235: aggregate with properties/fields requires 'override operator ?' (custom) or 'default operator ?' (auto-generated) to define set/unset semantics

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

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 '?':

  1. A GUARD ('if person <- getPerson()') enters its block only when person? is true.
  2. A LOOP GUARD ('while item <- first() then ...') skips the loop when item? is false.
  3. ASSIGN-IF-UNSET ('result :=? fallback') assigns only when result? is false.
  4. 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):

  1. 'default operator ?' - the compiler generates it from your fields.
  2. '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

  • E07180: default operator missing prerequisite in same class
  • E07190: default operator missing in super class
  • E07200: property type missing required operator
  • E07237: a concrete record must have a field

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: