Question: Can records extend other records in EK9?

How do I inherit one record from another in EK9?

How do I override operators on a child record and call the parent's operator?

What does super.<=>(other) mean on a record operator?

Answer:
Yes. Records in EK9 follow the same closed-by-default + 'as open' opt-in rule as classes. Mark a base record 'as open' to allow another record to extend it. The child record uses 'extends', calls 'super(...)' in its constructor, and overrides operators with the mandatory 'override' keyword.

THE AS OPEN OPT-IN
Without 'as open' on the base, attempting to extend triggers E05030 (not open to be extended) — same rule as classes. Records are NOT open just because they are records; they are closed by default like every other genus (see Q101).

CHILD-OF-RECORD STRUCTURE
A child record:
- declares 'extends ParentRecord' (or 'is ParentRecord')
- calls 'super(arg, ...)' from its constructor to initialise the inherited fields
- overrides parent operators with 'override operator <symbol> as pure' (override is mandatory; see Q102, Q570)
- can call 'super.<symbol>(...)' inside an override to compose parent behaviour with child fields
- typically declares 'default operator ?' so its own fields gain set/unset semantics

FIELD INHERITANCE — PUBLIC AND VISIBLE
Record fields are public (Q900, Q1060). When a child record extends a parent record, the parent's public fields remain public on instances of the child. Direct access works the same way:
  child.parentField    OK — public, inherited
  child.childField     OK — public, declared

OPERATOR OVERRIDE CHAINING WITH super.<op>(...)
The canonical pattern for the comparator <=> on a child record is: compute the parent's result first, then refine with the child's own fields. The same pattern applies to $ (string) and #? (hashcode):
  override operator <=> as pure
    -> other as ChildRecord
    <- rtn as Integer: super.<=>(other)
    if rtn == 0
      rtn: childField1 <=> other.childField1
    if rtn == 0
      rtn: childField2 <=> other.childField2

For $ the convention is to append the child's representation:
  override operator $ as pure
    <- rtn as String: `${super.$()} ChildRecord(${childField1}, ${childField2})`

For #? the convention is to xor with the child's hashes:
  override operator #? as pure
    <- rtn as Integer: super.#?() xor #?childField1 xor #?childField2

MUTABILITY IS PRESERVED ACROSS THE HIERARCHY
Like all EK9 records, inherited fields are mutable. Mutability is gated by 'as pure' on the calling function/method, not on the type, so the parent-child pair preserves the Liskov Substitution Principle (Q97).

WHY OPERATOR OVERRIDE BUT NOT METHOD OVERRIDE?
Records cannot have methods at all — only constructors and operators (Q97, E07290). So inheritance on records is about extending fields and operators, never adding behaviour as methods. If a chain needs behaviour, switch the construct to a class.

See Q97 for class-vs-record overview. See Q101 for closed-by-default rationale. See Q102 for 'as open' on all genera. See Q570 for override mechanics. See Q900 for the public/private field rule. See Q876 for record basics. See Q98 for record operators. See Q116 for default operator. See Q241 for mutation operators.

defines module qa.classesandoop.recordinheritance

  defines record

    <?-
      Base record marked 'as open' so SimpleRecord may extend it.
      Without 'as open' the compiler rejects the extension (E05030).
    -?>
    BaseRecord as open
      anything <- 90

      BaseRecord() as pure
        -> anything as Integer
        this.anything :=: anything

      default operator <=>

      default operator ?

      default operator $
      default operator #?

    <?-
      Child record extending BaseRecord.
      - 'extends BaseRecord' inherits the public 'anything' field.
      - 'super(anyValue)' initialises the inherited field via the parent constructor.
      - 'override operator <=>' is mandatory (E05120 otherwise).
      - 'super.<=>(other)' composes the parent comparison with the child's fields.
      - 'default operator ?' synthesises set/unset semantics across all fields.
    -?>
    SimpleRecord extends BaseRecord
      field1 <- 0
      count as Integer?
      check as Integer: 1

      SimpleRecord() as pure
        count :=? 0

      SimpleRecord() as pure
        ->
          anyValue as Integer
          field1 as Integer
          count as Integer
          check as Integer

        super(anyValue)
        this.field1 :=: field1
        //'count' is declared uninitialised ('count as Integer?'), so first assignment in a pure constructor uses the guarded ':=?' (':=' reassignment is disallowed in 'pure'; ':=:' deep-copy requires an already-initialised target).
        this.count :=? count
        this.check :=: check

      override operator <=> as pure
        -> other as SimpleRecord
        <- rtn as Integer: super.<=>(other)

        if rtn == 0
          rtn: field1 <=> other.field1
        if rtn == 0
          rtn: count <=> other.count
        if rtn == 0
          rtn: check <=> other.check

      default operator ?

      override operator $ as pure
        <- rtn as String: `${super.$()} SimpleRecord(${field1}, ${count}, ${check})`

      override operator #? as pure
        <- rtn as Integer: super.#?() xor #?field1 xor #?count xor #?check

  defines program

    RecordInheritanceDemo()
      stdout <- Stdout()

      // 4-argument constructor needs named args (E11062 otherwise)
      r1 <- SimpleRecord(anyValue: 10, field1: 1, count: 2, check: 3)

      // Inherited public field 'anything' is directly accessible
      stdout.println(`r1.anything: ${r1.anything}`)
      stdout.println(`r1.field1:   ${r1.field1}`)
      stdout.println(`r1.check:    ${r1.check}`)

      // The overridden $ operator composes parent + child output
      stdout.println(`r1: ${r1}`)

      // The overridden <=> operator chains super.<=> with child fields
      r2 <- SimpleRecord(anyValue: 10, field1: 1, count: 2, check: 3)
      r3 <- SimpleRecord(anyValue: 10, field1: 1, count: 2, check: 4)
      stdout.println(`r1 <=> r2: ${r1 <=> r2}`)
      stdout.println(`r1 <=> r3: ${r1 <=> r3}`)

      // Inherited field is mutable — mutability is gated by 'as pure' on the
      // calling function, not on the type. Inside this (non-pure) program we
      // may freely modify any inherited or declared field.
      r1.anything: 999
      stdout.println(`After mutation r1.anything: ${r1.anything}`)

Java: records (Java 16+) are implicitly final — record-to-record inheritance is impossible. Kotlin: data classes cannot be open/inherited; the language explicitly disallows it. Rust: structs cannot inherit at all (no inheritance, period). Go: struct embedding is composition, not inheritance. Swift: structs are value types and cannot be subclassed. EK9 is unusual in allowing record-to-record inheritance via the same 'as open' opt-in mechanism as classes. This deliberately keeps records as data carriers while permitting layered data hierarchies — useful for event-sourcing, DTO families, and value-type taxonomies where a base shape is extended with extra fields and operator refinements. The override-mandatory rule (E05120) makes the inheritance explicit and prevents accidental shadowing.

#?
$
<=>
BaseRecord
as open
comparator
data class hierarchy
extends
inheritance
open
operator
override
record
subclass
subrecord
super
value type hierarchy

record inheritance
as open record
operator override super
record extends

Explanation: Without 'as open' on the base, SimpleRecord cannot extend BaseRecord. EK9 records are closed-by-default just like classes. The fix is to add 'as open' to the base record. See ek9 -h E05030 for details.

Explanation: When a child record re-declares an operator that exists on the parent, the 'override' keyword is mandatory. Omitting it triggers E05120. This applies to <=>, $, #?, and any operator inherited from the parent. See ek9 -h E05120 for details.

Explanation: A class cannot extend a record because they are different construct genera. Classes extend classes; records extend records. The compiler rejects cross-genus inheritance. See ek9 -h E50010 for details.

Explanation: Records cannot have methods — only constructors and operators. Even a child record cannot add a method that didn't exist on the parent. If you need behaviour, switch the chain to classes. See ek9 -h E07290 for details.

Query the effective API of a child record at file:line to see declared + inherited fields with source attribution, plus operator overrides with the parent-method anchor for chain navigation.


Question: I got E08252 NESTED_ENTER_DIFFERENT_LOCK — what does 'lock-order cycle' mean and how do I fix it?

EK9 says my locks form a cycle — what now?

Two MutexLocks deadlock at compile time — how to restructure?

How to fix nested enter different lock identity?

Answer:
E08252 fires when EK9's deadlock detector finds two or more places in your program that acquire the same set of MutexLocks in OPPOSITE orders, forming a cycle. At runtime, two threads running these paths concurrently can deadlock — each holds one lock and waits for the other.

WHAT THE ERROR MEANS
The analyzer builds a workspace-wide precedence graph: every time your code does `lockA.enter(...)` with `lockB.enter(...)` nested inside, an edge lockA → lockB is recorded. A CYCLE in this graph (e.g. lockA → lockB AND lockB → lockA somewhere) is a deadlock waiting to happen. The error message names the participating locks and renders the cycle path: 'lockA → lockB → lockA'.

NOT TRIGGERED BY SINGLE-DIRECTION NESTING (PROVABLY-ORDERABLE LOCKS ONLY)
If only ONE function nests lockB inside lockA (and no other code ever does the reverse), there is no cycle — that code is safe, PROVIDED the two locks are provably orderable: different types, or different field declarations. E08252 only fires when at least two places contradict each other.

IMPORTANT CAVEAT: this 'single direction is safe' rule does NOT extend to two locks of the SAME type on caller- or runtime-determined objects — two different same-type MutexLock parameters, or the same lock field on two different objects (the from.getLock()/to.getLock() bank transfer, two instances of one class, dining-philosophers forks). Those are interchangeable at the call site, so a single static direction can instantiate as both orders at runtime; EK9 rejects them even with no reverse site — that is E08255 (UNPROVABLE_LOCK_ORDER), not E08252. The fix is the same: one lock over an owning record.

THREE WAYS TO FIX

1. UNIFY UNDER ONE LOCK
If both resources genuinely need atomic protection together, put them in a record and guard with ONE MutexLock. The MutexKey body can mutate every field of the record atomically. This is the canonical fix when joint atomicity is required (e.g. account-to-account transfer).

  defines record
    Balances
      source as Integer: 0
      target as Integer: 0
      default operator ?

  defines component
    TransferService
      accountLock as MutexLock of Balances: MutexLock(Balances())

      transfer()
        -> amount as Integer
        key <- (amount) is MutexKey of Balances as function
          lockedItem.source: lockedItem.source - amount
          lockedItem.target: lockedItem.target + amount
        require accountLock.enter(key)

2. SEQUENTIAL — NEVER NESTED
If the two concerns don't need joint atomicity, acquire locks one at a time. Release the first before taking the second. Same code path, two enters, no nesting.

  require lockA.enter(keyA)   //completes and releases
  require lockB.enter(keyB)   //independent operation

3. PICK A SINGLE OWNER FOR EACH CONCERN
If two methods on the same component want different locks, give each method exactly one lock. Different concerns → different methods → different locks. No method ever holds both.

WHY EK9 DETECTS THIS
Lock-order cycle deadlocks are notoriously hard to reproduce — they only manifest under specific thread interleavings. Catching them at compile time eliminates a whole class of production bugs. The precedence-graph approach is the same model the Linux kernel uses at runtime (lockdep); EK9 brings it forward to compile time thanks to the closed-world type system.

See Q158 for MutexLock basics (which shows the canonical LockableAddressSet pattern). See Q1299 for the cross-thread variant (E08253). See Q1300 for multi-lock design patterns.

defines module qa.concurrency.deadlock.cycle

  defines record

    Balances
      source as Integer: 100
      target as Integer: 0
      default operator ?

  defines component

    //Canonical fix: ONE MutexLock guards a record containing both
    //resources. The MutexKey body mutates both fields atomically;
    //no second lock exists, so no cycle can form.
    TransferService
      accountLock as MutexLock of Balances: MutexLock(Balances())

      transfer()
        ->
          amount as Integer

        transferKey <- (amount) is MutexKey of Balances as function
          //Both fields mutated atomically under one lock.
          lockedItem.source: lockedItem.source - amount
          lockedItem.target: lockedItem.target + amount

        require accountLock.enter(transferKey)

      default operator ?

  defines program

    LockOrderCycleFix()
      stdout <- Stdout()
      service <- TransferService()
      service.transfer(25)
      stdout.println("transfer complete — no deadlock possible")

Java: deadlocks discovered at runtime via thread dumps and stress testing. Tools like FindBugs/SpotBugs flag some cases but can't see polymorphic dispatch or call chains. Go: deadlock detector in the runtime fires only when ALL goroutines block — partial deadlocks slip through. Rust: lock-order checked by convention; std library has no compile-time detection. EK9: workspace-wide static cycle detection on the lock precedence graph; refuses to compile if a cycle exists across any combination of call paths.

E08252 MutexLock atomic concurrent cycle deadlock enter fix lock mutex nested order race restructure transfer


Question: I got E08253 CROSS_THREAD_SAME_LOCK — what is the thread boundary issue and how do I fix it?

How do I fix E08253 cross-thread deadlock?

Same lock across thread boundary error in EK9

Why does | async cause deadlock with MutexLock?

Signals handler cannot reacquire same lock

Answer:
E08253 fires when the SAME MutexLock is acquired on TWO DIFFERENT threads with the outer hold still in scope. Even though MutexLock is reentrant on the SAME thread (re-entering legally), it is NOT reentrant across threads — the second thread blocks waiting for the first to release. If the first thread is itself waiting on the second to complete (very common with async dispatch), they deadlock.

WHERE THE THREAD BOUNDARY COMES FROM
EK9 creates a thread boundary at three syntactic points:
  1. TCP.accept(handler) — accept loop dispatches the handler on a new connection thread.
  2. Signals.register(name, handler) — signal dispatcher invokes the handler from its dispatch thread.
  3. Stream pipelines with `| async` — the async stage dispatches each element to a thread-pool worker.
Anywhere else (regular function calls, the `| call` stream stage) stays on the same thread.

THE COMMON REFACTORING TRAP
The deadlock often appears after refactoring. A method holds a lock, pushes some work into a helper, the helper grows, eventually someone 'speeds things up' by adding `| async`. The async worker captures a reference to the lock and tries to enter it — by then no one remembers the outer hold is still active. EK9 catches this through the substitution chain even when the helper is many call frames deep.

THREE WAYS TO FIX

1. CHANGE `| async` TO `| call` IF THREAD POOLING ISN'T NEEDED
The `| call` stream stage runs each element on the same thread synchronously. Same lock reacquisition is reentrant and legal:

  outerKey <- (sharedLock) is MutexKey of Integer as function
    cat workers | call | map with toLine > stdout   //SAFE — same thread
  require sharedLock.enter(outerKey)

2. MOVE THE ENTER() OUT OF THE ASYNC BOUNDARY
If you genuinely need async parallelism, don't have the worker reacquire the same lock. Do the locked work BEFORE dispatching to async, then pass the immutable result through the pipeline.

  preparedItems as List of Item: List()
  key <- (preparedItems) is MutexKey of Integer as function
    preparedItems += extractSnapshot()   //all locked work here
  require sharedLock.enter(key)

  cat preparedItems | async with processItem   //no lock needed across boundary

3. USE A DIFFERENT LOCK PER WORKER
If the async workers must protect shared state, give each worker its OWN lock (different MutexLock for that concern), and never hold the outer lock when the workers run. The outer pipeline coordinates without the contended lock.

WHY EK9 DETECTS THIS
Classic Java code that uses ExecutorService.submit() with a synchronized block on the same lock will hang at runtime. The bug is often only visible under load. EK9 makes the thread-boundary visible in the syntax — `| async` is the keyword — and propagates lock identity through call chains and substitution, so the compiler catches the reacquisition even when the worker is several refactoring layers away from the outer hold.

See Q158 for MutexLock basics (which shows the canonical LockableAddressSet pattern). See Q159 for stream pipelines with async. See Q1298 for the same-thread lock-order cycle (E08252). See Q1300 for multi-lock design patterns.

defines module qa.concurrency.cross.thread.fix

  defines function

    asyncWorkerFn() as abstract
      <- rtn as Boolean?

    toLine() as pure
      -> ok as Boolean
      <- rtn as String: $ok

    //Canonical fix: do the locked work BEFORE the async boundary; the
    //resulting snapshot is immutable enough to pass through the pipeline
    //without needing the lock. Workers run on different threads but never
    //touch the outer hold.
    crossThreadSafe()
      -> sharedLock as MutexLock of Integer

      stdout <- Stdout()
      snapshot as Integer: Integer()

      readKey <- (snapshot) is MutexKey of Integer as function
        snapshot :=: lockedItem
      require sharedLock.enter(readKey)

      results <- [ true, false, true ]
      cat results | map with toLine > stdout

Java: ExecutorService.submit() with a synchronized block on the same monitor — deadlocks at runtime, often only under load. Goroutines / channels — easy to spawn but lock interactions are the developer's responsibility. Python asyncio — explicit await; lock acquisition across awaits can deadlock with no compile-time warning. EK9: thread boundaries are syntactic (`| async`, TCP.accept, Signals.register); the compiler tracks the lock identity through any depth of call chain and refuses to compile if the same lock would be acquired on a different thread while still held.

E08253 MutexLock TCP async boundary call concurrency cross-thread deadlock fix handler reentrant refactor signal thread

worker <- (sharedLock) is asyncWorkerFn as function
        innerKey <- () is MutexKey of Integer as function
          stdout <- Stdout()
          stdout.println(lockedItem)
        rtn: sharedLock.enter(innerKey)
      outerKey <- (worker) is MutexKey of Integer as function
        stdout <- Stdout()
        workers <- [ worker ]
        cat workers | async | map with toLine > stdout
      require sharedLock.enter(outerKey)

Explanation: The canonical fix does all locked work BEFORE the async boundary and passes immutable data through the pipeline. The incorrect form captures `sharedLock` into a worker and calls `sharedLock.enter(innerKey)` inside it, while `cat workers | async` dispatches that worker to a thread-pool thread and the outer still holds the lock via `sharedLock.enter(outerKey)`. MutexLock is reentrant only on the holding thread, so the worker blocks cross-thread and — if the caller waits on the pipeline — both deadlock. Use `| call` (same thread) if you must reacquire, or keep the lock off the async workers entirely. See ek9 -h E08253.


Question: How do I use multiple MutexLocks correctly in EK9 — when do I need one big lock vs many small ones?

Best practices for multiple locks in EK9

How to coordinate multiple locks safely?

Answer:
EK9 supports multiple MutexLocks — but you have to design them so the compiler can prove the program is deadlock-free. There are three idiomatic patterns; pick based on whether the resources need atomic coordination together.

PATTERN 1: ONE LOCK PER INDEPENDENT CONCERN
Use multiple locks when two parts of your state are independent — no operation ever needs both. Each method touches exactly one lock; locks are never nested across concerns.

  defines component
    InventorySystem
      orderLock as MutexLock of Integer: MutexLock(Integer(0))
      inventoryLock as MutexLock of Integer: MutexLock(Integer(0))

      placeOrder() ... require orderLock.enter(orderKey)
      adjustInventory() ... require inventoryLock.enter(invKey)

The deadlock detector sees no cross-lock nesting — no precedence-graph edges, no possible cycle. This is the cleanest pattern when your domain naturally separates.

PATTERN 2: SEQUENTIAL ACQUISITION (RELEASE THEN ACQUIRE)
When one function needs two locks at different times in its body, acquire and release each in turn — never hold both at once. The MutexKey callback completes (and releases) BEFORE the next enter() begins.

  fn()
    ->
      lockA as MutexLock of Integer
      lockB as MutexLock of String

    keyA <- () is MutexKey of Integer as function
      //work with lockA
    keyB <- () is MutexKey of String as function
      //work with lockB

    require lockA.enter(keyA)   //completes and releases
    require lockB.enter(keyB)   //independent of lockA

No overlap = no cycle. Works as long as the two operations are truly independent (no shared invariant that needs to span both critical sections).

PATTERN 3: ONE LOCK OVER A UNIFIED RECORD
When two resources MUST be mutated atomically together — the classic 'transfer money from A to B' case — wrap them in a record and protect with a single MutexLock. The MutexKey body mutates all fields atomically; you never need two locks.

  defines record
    AccountPair
      sourceBalance as Integer: 100
      targetBalance as Integer: 0
      default operator ?

  defines component
    TransferService
      accountLock as MutexLock of AccountPair: MutexLock(AccountPair())

      transfer()
        -> amount as Integer
        key <- (amount) is MutexKey of AccountPair as function
          lockedItem.sourceBalance: lockedItem.sourceBalance - amount
          lockedItem.targetBalance: lockedItem.targetBalance + amount
        require accountLock.enter(key)

Granularity trade-off: this serialises every transfer through one lock. If contention is real, decompose differently — per-transaction-id locks, optimistic concurrency with version numbers, or partition by account-range. But measure first; coarse locking is fine for most workloads.

WHAT TO AVOID
The pattern EK9 deliberately refuses to compile: nest one lock inside another's MutexKey body in a way that creates a cycle elsewhere in the program. Example to AVOID:

  transferAtoB() ... lockA.enter(... lockB.enter(...))   //order A → B
  transferBtoA() ... lockB.enter(... lockA.enter(...))   //order B → A — cycle!

The two functions together form a lock-order cycle: T1 running transferAtoB while T2 runs transferBtoA can deadlock. EK9 fires E08252.

SAME-TYPE LOCKS ON DIFFERENT OBJECTS (E08255) — REJECTED EVEN WITHOUT A REVERSE SITE
A subtler trap: nesting two locks of the SAME type whose objects are caller- or runtime-determined — the same lock field on two different objects (from.getLock() then to.getLock(), two instances of one class, dining-philosophers forks, collection elements), or two different same-type MutexLock parameters. Because the objects are interchangeable at the call site, a SINGLE static direction can instantiate as both orders at runtime — so EK9 rejects it with E08255 (UNPROVABLE_LOCK_ORDER) even when no opposite-order site exists. There is no 'always lock the lower-address one first' escape; the only fix is Pattern 3 (one lock over an owning record).

DURABLE OR CROSS-INSTANCE STATE IS A TRANSACTION CONCERN, NOT A MUTEX CONCERN
If the data you are trying to coordinate is durable (database rows) or shared across processes/instances, an in-memory MutexLock cannot protect it at all — that is a transaction concern (use the database's transaction / optimistic-locking facilities), not something a lock can solve.

The acquisition-order convention pattern ('always lockA before lockB') is NOT trusted by the compiler. Even if your team is disciplined, the convention can break under refactoring pressure. The compiler wants the constraint enforced by code structure (one of the three patterns above), not by developer discipline.

WHEN TO USE WHICH
- Independent concerns, no joint atomicity → Pattern 1.
- Two operations in one method but no overlapping atomicity → Pattern 2.
- Joint atomicity required (one resource depends on another being valid) → Pattern 3.

See Q158 for MutexLock basics (which shows the canonical LockableAddressSet pattern). See Q1298 for E08252 cycle errors. See Q1299 for E08253 cross-thread errors.

defines module qa.concurrency.multi.lock.patterns

  defines record

    //Pattern 3 demo: unified record under one lock for joint atomicity.
    AccountPair
      sourceBalance as Integer: 100
      targetBalance as Integer: 0
      default operator ?

  defines component

    TransferService
      accountLock as MutexLock of AccountPair: MutexLock(AccountPair())

      transfer()
        -> amount as Integer

        transferKey <- (amount) is MutexKey of AccountPair as function
          lockedItem.sourceBalance: lockedItem.sourceBalance - amount
          lockedItem.targetBalance: lockedItem.targetBalance + amount

        require accountLock.enter(transferKey)

      default operator ?

    //Pattern 1 demo: two locks for two independent concerns. Neither
    //method ever holds both — no precedence-graph edges, no cycle.
    OrderInventory
      orderLock as MutexLock of Integer: MutexLock(Integer(0))
      inventoryLock as MutexLock of Integer: MutexLock(Integer(0))

      placeOrder()
        ->
          orderId as Integer
        orderKey <- (orderId) is MutexKey of Integer as function
          lockedItem :=: lockedItem + orderId
        require orderLock.enter(orderKey)

      adjustInventory()
        ->
          delta as Integer
        invKey <- (delta) is MutexKey of Integer as function
          lockedItem :=: lockedItem + delta
        require inventoryLock.enter(invKey)

      default operator ?

  defines program

    MultiLockPatterns()
      stdout <- Stdout()

      //Pattern 3: joint atomicity through one lock.
      service <- TransferService()
      service.transfer(25)
      stdout.println("Pattern 3: transfer complete")

      //Pattern 1: independent concerns, two locks, no nesting.
      orders <- OrderInventory()
      orders.placeOrder(42)
      orders.adjustInventory(-1)
      stdout.println("Pattern 1: independent operations complete")

Java: developers manage lock ordering by convention (e.g. always grab the lower-hashcode lock first). Tools like FindBugs/SpotBugs detect a subset; most acquisition-order bugs are caught only at runtime or via stress testing. Go / Rust: same convention-driven discipline; no language-level prevention. EK9: three structural patterns that the compiler can verify; the convention-driven approach is rejected because it doesn't survive refactoring.

MutexLock atomic best coarse concurrent deadlock design fine grained locks multiple mutex pattern practice structure transfer

multi-lock design
lock patterns
deadlock avoidance


Question: Why can't I sort or group a stream from Stdin, UDP or a TCP connection?

What triggers E10050 BUFFERING_REQUIRES_BOUNDED?

Why does sort fail on an unbounded stream source in EK9?

How do I sort/group/tail/uniq a stream from a socket or Stdin?

Answer:
Buffering operations (sort, group, tail, uniq) must see or accumulate over ALL of the input before producing output, so they require a BOUNDED (finite) source. Stdin, UDP and TCPConnection are UNBOUNDED — infinite IO streams with no defined end — so buffering one would consume memory without bound and never emit. EK9 rejects this at compile time (E10050).

ERROR
  stdin <- Stdin()
  cat stdin | sort > stdout        // ERROR: E10050

FIX WITH head N (head bounds the stream)
  cat stdin | head 1000 | sort > stdout   // OK

FIX WITH bounded windows
  while active
    batch <- cat udpConnection | head 100 | collect as List of Packet
    cat batch | sort > processor

STREAMING OPERATIONS ARE FINE ON UNBOUNDED SOURCES
  cat stdin | filter by validLine | map with toEntry > stdout   // OK
  cat udpConnection | map by handler > stdout                   // OK

head N is the mechanism that converts an unbounded stream to a bounded one.

defines module qa.streams.unboundedbuffer

  defines function

    nonEmpty() as pure
      -> line as String
      <- rtn as Boolean: length line > 0

  defines program

    UnboundedStreamDemo()
      stdin <- Stdin()
      stdout <- Stdout()

      //Streaming operations (filter/map) compose freely with an unbounded Stdin source.
      cat stdin | filter by nonEmpty > stdout

      //To sort/group/tail/uniq, 'head N' must bound the unbounded stream first.
      first10Sorted <- List() of String
      cat stdin | head 10 | sort > first10Sorted

      stdout.println(`Captured: ${length first10Sorted}`)

Java: Stream.sorted() on an infinite stream hangs at runtime, no compile check. Python: sorted(infinite_generator) hangs. Rust: collecting an infinite iterator to sort hangs. EK9: the compiler proves at compile time that buffering an unbounded source is impossible and requires 'head N' to bound it first.

E10050 Stdin TCP UDP backpressure bounded group head sort stream tail unbounded uniq

Explanation: Buffering operators like sort need a bounded source; stdin is unbounded, so add 'head N' before sort. See ek9 -h E10050 for details.


Question: Why does EK9 reject a single deeply-nested coalescing expression?

What triggers E11013 EXCESSIVE_EXPRESSION_COMPLEXITY?

How do I fix an expression that is too complex in EK9?

Why must I extract intermediate variables from nested coalescing?

Answer:
EK9 measures expression complexity independently of method complexity. Nested coalescing operators (?:, ??, <?, >?, <=?, >=?) carry an exponential 3^(depth-1) penalty, so three nested levels reach 17 points and exceed the 15-point threshold, raising E11013. The fix is to extract named intermediate variables (step1<-..., step2<-...), which flattens the depth to 1 each while producing an identical result.

See Q311 for quality checks.

defines module qa.quality.expression.complexity

  defines function

    //THE FIX: extract intermediate named variables instead of one nested expression.
    //Each coalescing operator is now at depth 1, so total cost is 1 + 2 + 2 = 5 (well under 15).
    computeFallback() as pure
      ->
        a as Integer
        b as Integer
        c as Integer
        d as Integer
      <-
        result as Integer?
      step1 <- a ?: b
      step2 <- step1 <? c
      result: step2 >? d

  defines program

    ExpressionComplexityDemo()
      stdout <- Stdout()
      result <- computeFallback(a: Integer(), b: 2, c: 3, d: 4)
      stdout.println(`Result is ${result}`)

Java: no expression-level complexity analysis; nested ternaries compile silently. Kotlin/Swift: chained ?:/?? unlimited, no linter rule for coalescing depth. EK9: compile-time error forcing extraction into readable intermediate variables.

E11013 coalescing complexity expression extract intermediate quality variable

expression complexity
E11013
intermediate variables

Explanation: The single nested expression costs 1*9 + 2*3 + 2*1 = 17 points (3^(depth-1) per level), exceeding the 15-point expression threshold. Extracting each coalescing step into its own named variable drops every expression to depth 1 (1 + 2 + 2 = 5 points total), so none exceeds the threshold while the computed result is identical.


Question: Why does EK9 reject a call nested inside a throw expression?

What triggers E11070 NESTED_CALL_IN_THROW?

Why can't I throw the result of a call chain directly?

How do I throw an exception produced by a factory call in EK9?

Answer:
EK9 rejects a call nested inside a throw (E11070) when the thrown expression is itself a call applied to the result of another call (a higher-order chain such as throw getFactory("std")("oops")). The exception construction is hidden behind that chain. Extract each step into a local variable and throw the variable, so the exception is materialised and the throw site stays readable. The two valid forms are a direct constructor call (throw Exception(reason)) or a previously-declared variable (throw ex).

See Q311 for quality checks.

defines module qa.quality.throw.nested.call

  defines function

    ExceptionFactory() as pure abstract
      -> reason as String
      <- builtException as Exception?

    StandardExceptionFactory() is ExceptionFactory as pure
      -> reason as String
      <- builtException as Exception: Exception(reason)

    getExceptionFactory()
      -> kind as String
      <- chosen as ExceptionFactory: StandardExceptionFactory
      if kind == "alt"
        chosen: StandardExceptionFactory

    //FIX: extract the factory selection and the exception into local
    //variables, then throw the variable. No call is nested in another
    //call at the throw site.
    raiseFailure()
      -> kind as String
      factory <- getExceptionFactory(kind)
      ex <- factory("operation failed")
      throw ex

  defines program

    NestedCallInThrowDemo()
      stdout <- Stdout()
      try
        raiseFailure("std")
      catch
        -> caught as Exception
        stdout.println(caught.reason())

Java: throwing the result of a factory chain is unchecked by the compiler. Python/Go: no detection. EK9: compile-time error (E11070) requiring the thrown exception to be a direct constructor call or a previously-declared variable, never a call applied to a call.

E11070 call exception extract nested quality throw

nested call in throw
E11070
exception extraction

Explanation: Throwing a call applied to another call's result hides the exception construction at the throw site — extract the factory and exception into local variables and throw the variable. See ek9 -h E11070 for details.


Question: How do I fix E11043 when a method injects too many distinct types?

What triggers E11043 EXCESSIVE_INJECTION_TYPES_PER_METHOD?

Why does EK9 reject a method that injects more than 4 distinct types?

How do I group method-local '!' injections behind a facade?

Answer:
A single method may inject at most 4 distinct types via the method-local '!' suffix. Injecting a 5th distinct type triggers E11043 (EXCESSIVE_INJECTION_TYPES_PER_METHOD) - the method has too many collaborators to understand or test.

Fix: group the related dependencies behind ONE dedicated facade component. Register the facade in the application, then inject the single facade type with '!'. The method now has one collaborator.

See Q203 for application wiring. See Q960 for the per-component injection-field limit (E11040). See Q311 for quality checks.

defines module qa.quality.injection.types

  defines component

    Repository as abstract
      findAll() as abstract
        <- rtn as List of String?

      default operator ?

    InMemoryRepo extends Repository
      override findAll()
        <- rtn <- List() of String
        rtn += "order-1"

      default operator ?

    Logger as abstract
      log() as abstract
        -> message as String

      default operator ?

    ConsoleLogger extends Logger
      override log()
        -> message as String
        stdout <- Stdout()
        stdout.println(message)

      default operator ?

    Mailer as abstract
      send() as abstract
        -> message as String

      default operator ?

    SmtpMailer extends Mailer
      override send()
        -> message as String
        stdout <- Stdout()
        stdout.println(`sending: ${message}`)

      default operator ?

    //The facade groups the three collaborators behind ONE injectable type.
    //A method injects only the facade, keeping distinct injected types low.
    OrderFacade as abstract
      placeOrder() as abstract
        <- rtn as String?

      default operator ?

    DefaultOrderFacade extends OrderFacade
      repo as Repository!
      logger as Logger!
      mailer as Mailer!

      override placeOrder()
        <- rtn as String: "placed"
        orders <- repo.findAll()
        logger.log(`Found ${length orders} orders`)
        mailer.send("order confirmation")

      default operator ?

  defines application

    OrderApp
      register InMemoryRepo() as Repository
      register ConsoleLogger() as Logger
      register SmtpMailer() as Mailer
      register DefaultOrderFacade() as OrderFacade

  defines program

    //CORRECT: the method injects ONE facade type, not five distinct types.
    FacadeDemo() with application of OrderApp
      stdout <- Stdout()

      facade as OrderFacade!

      result <- facade.placeOrder()
      stdout.println(`Order ${result} via single facade injection`)

Java: Spring allows unlimited @Autowired locals; over-injection detected only by SonarQube/ArchUnit if configured. C#/.NET: Seemann recommends a low collaborator count but it is advisory. Go: Wire has no per-function limit, caught only in review. EK9: compiler-enforced limit of 4 distinct injected types per method, fixed by a facade component.

E11043 application component facade injection method quality register

facade component
E11043
method-local injection


Question: Why does EK9 reject two service methods that share the same HTTP path?

What triggers E02070 duplicate service path/operation?

Why can't two GET methods map to the same URI in EK9?

How do I fix a duplicated service endpoint path in EK9?

Answer:
Each HTTP verb plus URI path must map to exactly one handler method. When two methods on a service share the same verb and structurally identical path, EK9 raises E02070 at compile time - there is no way to know which handler should run for that URL.

Path parameters are compared STRUCTURALLY, not by name: :/{id} and :/{userId} are the SAME path, so renaming a path variable does NOT resolve the conflict. The fix is to make the paths genuinely distinct (a different literal segment, an extra segment, or a different HTTP verb).

See Q684 for valid URI path rules. See Q946 for path-variable-to-parameter matching.

defines module qa.webdeep.duplicatepath

  defines constant

    JSON_CONTENT_TYPE <- "application/json"
    ENGLISH_LANGUAGE <- "en"

  defines service

    <?-
      Service demonstrating distinct endpoints that avoid E02070.
      Each HTTP verb plus path combination is unique. Note that
      :/{id} and :/{name}/profile differ structurally (extra segment),
      so they are NOT duplicates even though both contain a path variable.
    -?>
    UserService :/users open

      //GET /users/{id} — lookup by identifier
      findById() as GET for :/{id}
        -> id as String
        <- response as HTTPResponse: (capturedId: id) with trait HTTPResponse
          override content()
            <- rtn as String: `{"id": "${capturedId}"}`
          override status() as pure
            <- rtn as Integer: 200
          override contentType() as pure
            <- rtn as String: JSON_CONTENT_TYPE
          override cacheControl() as pure
            <- rtn as String: "no-cache"
          override contentLanguage() as pure
            <- rtn as String: ENGLISH_LANGUAGE
          default operator ?

      //GET /users/{name}/profile — distinct path (extra literal segment),
      //so no E02070 conflict with findById even though both have a variable.
      byName() as GET for :/{name}/profile
        -> name as String
        <- response as HTTPResponse: (capturedName: name) with trait HTTPResponse
          override content()
            <- rtn as String: `{"name": "${capturedName}"}`
          override status() as pure
            <- rtn as Integer: 200
          override contentType() as pure
            <- rtn as String: JSON_CONTENT_TYPE
          override cacheControl() as pure
            <- rtn as String: "max-age=60"
          override contentLanguage() as pure
            <- rtn as String: ENGLISH_LANGUAGE
          default operator ?

  defines application

    UserApp
      register UserService()

  defines program

    DuplicateServicePathDemo()
      stdout <- Stdout()
      stdout.println("Distinct service endpoints (no E02070):")
      stdout.println("  GET /users/{id}")
      stdout.println("  GET /users/{name}/profile")

Java Spring: two @GetMapping("/x") methods compile fine and only collide at startup with an AmbiguousMappingException - a runtime failure. Python Flask: a duplicate @app.route raises only when the app boots. Go: last route silently wins. EK9: the duplicate endpoint is a compile-time error (E02070), so the ambiguous routing can never ship.

E02070 GET URI duplicate endpoint http path route service webservice

duplicate service path
E02070
service routing

Explanation: Two GET methods both mapping to :/{id} and :/{nick} are the SAME endpoint because path variables match structurally, not by name - so EK9 cannot pick a handler and raises E02070. Make the paths genuinely distinct (add a literal segment such as :/{name}/profile, or use a different HTTP verb). See ek9 -h E02070 for details.


Question: Why can't a local construct share a name with a referenced (imported) type in EK9?

What triggers E03010 CONSTRUCT_REFERENCE_CONFLICT?

Why does importing List then defining a local class List fail to compile?

How do I resolve a name collision between a references import and a local type?

Answer:
EK9 refuses to pick a winner when a locally-defined construct (class, record, trait, function) shares its name with a type brought in via a 'references' block. Both the import and the local definition want the same short name, so any later use of that name is ambiguous - the compiler rejects it immediately with E03010 rather than silently choosing one.

Fix: rename the local construct to a distinct name (e.g. ItemList instead of List). The referenced type keeps its short name, the local type gets its own, and every use resolves to exactly one symbol. Alternatively, drop the reference and use the fully qualified 'module::Type' form inline when the imported type is needed.

See Q726 for references block syntax. See Q832 for the design rationale.

defines module qa.syntaxrules.referencecollision

  //'List' is imported via references and keeps that short name.
  references
    org.ek9.lang::List

  //THE FIX: the local construct is named distinctly (ItemList), so it does
  //NOT collide with the referenced 'List'. Naming this class 'List' would
  //trigger E03010 CONSTRUCT_REFERENCE_CONFLICT.
  defines class

    ItemList
      items as List of String?

      ItemList()
        items: List() of String

      ItemList()
        -> initial as String
        items: List() of String
        items += initial

      add()
        -> item as String
        items += item

      size()
        <- rtn as Integer: length items

      default operator ?

  defines program

    ReferenceCollisionDemo()
      stdout <- Stdout()

      //'List' resolves unambiguously to the referenced org.ek9.lang::List.
      basket <- ItemList("apple")
      basket.add("pear")

      stdout.println(`Basket holds ${basket.size()} items`)

Java: wildcard imports (import java.util.*) silently shadow local types; the conflict surfaces only when two imports expose the same name. Python: 'from module import *' overwrites local names with no warning. C#: 'using' directives let a local type silently win over an imported one. EK9: neither the reference nor the local takes priority - the collision is a compile-time error (E03010) and the developer must choose a distinct name.

E03010 collision conflict import local name references rename syntax

reference vs local collision
E03010
construct naming

references
  org.ek9.lang::List
defines class
  List

Explanation: Importing 'List' via references and then defining a local class also named 'List' makes the short name 'List' ambiguous - neither the import nor the local definition takes priority, so EK9 raises E03010. Rename the local construct (ItemList) so each name resolves to exactly one symbol, or drop the reference and use the fully qualified org.ek9.lang::List inline. See ek9 -h E03010 for details.


Question: Why does EK9 reject the same symbol listed twice in a references block?

What triggers E03020 conflicting references in EK9?

Why can't I import the same type twice in EK9?

How do I fix a duplicate reference in an EK9 references block?

Answer:
Each symbol may appear at most once in a module's references block. Listing the same module.path::Symbol twice (or two references that resolve to the same short name) triggers E03020 - conflicting references - because the second entry is redundant and usually signals a copy-paste mistake.

The fix is simple: keep one reference and delete the duplicate. If two genuinely different modules each export a type with the same short name, you cannot reference both; keep the more frequently used one in the references block and use the fully qualified module.path::Symbol form inline for the other.

See Q726 for module reference syntax. See Q851 for why references need the :: qualifier.

defines module qa.syntax.duplicate.reference

  //THE FIX: each symbol appears in the references block exactly once.
  //Listing 'org.ek9.lang::List' twice would trigger E03020 (conflicting references).
  references
    org.ek9.lang::List

  defines program

    DuplicateReferenceDemo()
      stdout <- Stdout()

      //Short-name access via the single, non-duplicated reference.
      names <- List() of String
      names += "Alice"
      names += "Bob"
      stdout.println(`Names count: ${length names}`)

Java: duplicate import statements compile and are at most an IDE/checkstyle warning. Python: a duplicate import silently re-binds the name with no diagnostic. Go: an unused import is an error but a redundant duplicate is deduplicated by tooling. Rust: a duplicate 'use' is an error (E0252/E0254). EK9: a duplicate references entry is a hard compile-time error (E03020) caught in the REFERENCE_CHECKS phase, so the redundancy can never reach a build.

E03020 conflict duplicate import module qualifier reference references syntax

duplicate references
E03020
references block

references
    org.ek9.lang::List
    org.ek9.lang::List

Explanation: The same symbol 'List' is listed twice in the references block, so EK9 reports E03020 - the second entry conflicts with the first and is redundant. List every referenced symbol exactly once; if two different modules export the same short name, keep one in the references block and use the fully qualified module.path::Symbol form inline for the other. See ek9 -h E03020 for details.


Question: Why does my references statement fail with E03030 reference does not resolve?

Why can't the compiler find the type named in my references block?

How do I fix a references entry that points at a module or type that does not exist?

Answer:
E03030 means a 'references' entry uses a fully-qualified name (module.path::SymbolName) whose module path or symbol name the compiler cannot find. The format is valid (it has the '::' separator, so it is not E01010), but nothing in the compile path actually resolves to it.

COMMON CAUSES
1. Typo in the symbol name: org.ek9.lang::Lst instead of ::List.
2. Typo in the module path: org.ek9.lng::List (missing 'a').
3. The defining module is not in the compile path (companion file missing, dependency not added).
4. The type lives in a different module than the one you named.

HOW TO FIX
Verify the exact module path and symbol name against the defining module's 'defines' sections, and make sure that module compiles and is part of the same workspace/compile path. Once the name matches a real exported symbol, the reference resolves and the short name becomes usable.

DISTINCTION FROM RELATED ERRORS
E01010 is a FORMAT error (the '::' qualifier is missing entirely). E03030 is a RESOLUTION error (the format is fine but the target does not exist at import time). E50001 is the same idea at USE time for a bare identifier rather than a references entry.

See Q726 for references syntax. See Q851 for the module qualifier rule.

defines module qa.ref.consumer.resolve

  //THE FIX: the references entry names a real, exported symbol on a real
  //module path, so it resolves at REFERENCE_CHECKS time and the short
  //name 'List' becomes usable. A typo such as 'Lst' or a wrong module
  //path (org.ek9.lng) would fail to resolve and raise E03030.
  references
    org.ek9.lang::List

  defines program

    ReferenceResolveDemo()
      stdout <- Stdout()

      names <- List() of String
      names += "World"
      stdout.println(`Greeting list size: ${length names}`)

Java: a wrong import (import com.example.Lst) is a compile error 'cannot find symbol' / 'package does not exist'. Python: a bad 'from module import Name' raises ImportError at runtime, not compile time. Go: an unresolved import path fails the build. Rust: 'use std::collections::Vc' for a missing item is a compile error E0432. EK9: a references entry that does not resolve is caught at compile time in REFERENCE_CHECKS as E03030.

E03030 REFERENCE_CHECKS import module qualifier reference resolve syntax typo

references resolution
E03030
module imports

Explanation: The reference 'org.ek9.lang::Lst' is well-formed (it has the '::' qualifier) but no symbol named 'Lst' exists in module 'org.ek9.lang' - the real exported symbol is 'List'. Because the name does not resolve at import time, REFERENCE_CHECKS raises E03030. Fix the typo so the symbol name matches a real exported symbol, and ensure the defining module is in the compile path. See ek9 -h E03030 for details.


Question: Why does EK9 reject 'of TypeName' on a non-generic type like String?

What triggers E04070 when using 'of' on a type?

Why can't I write String() of Integer in EK9?

How do I know which types accept an 'of' clause in EK9?

Answer:
The 'of' clause supplies type arguments and is only valid on generic (template) types - those declared with 'of type T'. Applying 'of SomeType' to a non-generic type such as String, Integer or any plain class raises E04070 because the type has a fixed structure and has no type parameter to fill.

GENERIC TYPES (accept 'of')
Built-in: List of T, Dict of (K, V), Optional of T, Result of (OK, ERR).
User-defined: any class declared 'Container of type T'.

NON-GENERIC TYPES (reject 'of')
String, Integer, Float, Boolean, Character and any plain class without an 'of type T' declaration. These need no type argument, so an 'of' clause is meaningless.

FIX
Drop the 'of' clause for a non-generic type (just String()), or, if you need a parameterized container, use a generic type such as List() of String.

The opposite mistake - omitting 'of' on a generic type - raises E04080, and supplying the wrong number of type arguments raises E06020.

See Q194 for defining generic classes. See Q198 for built-in generics.

defines module qa.generics.nongeneric

  defines program

    NonGenericOfDemo()
      stdout <- Stdout()

      // === CORRECT: 'of' on a GENERIC type ===
      // List is declared 'of type T', so it accepts a type argument.

      names <- List() of String
      names += "Alice"
      names += "Bob"
      stdout.println(`List of String has ${length names} items`)

      // === CORRECT: a non-generic type needs NO 'of' clause ===
      // String has a fixed structure; constructing it takes no type argument.

      greeting <- String("hello")
      stdout.println(`Plain String: ${greeting}`)

      // === WRONG (commented out): 'of' on a non-generic type ===
      // bad <- String() of Integer   // ERROR E04070: String is not generic

      stdout.println("Use 'of' only on generic types like List, Dict, Optional")

Java: String<Integer> is a compile error too, but only because String is not declared generic - the message is 'type String does not take parameters'. C#: same, 'non-generic type cannot be used with type arguments'. Kotlin/Swift: angle-bracket arguments on a non-generic type are rejected by the parser/type checker. EK9: the 'of' clause is rejected at the EXPLICIT_TYPE_SYMBOL_DEFINITION phase with E04070, stating the type is not 'template/generic' in nature.

E04070 List String generic non-generic of parameter parameterize template type

Explanation: String is not a generic/template type, so it has no type parameter to fill - applying 'of Integer' to it raises E04070. Either drop the 'of' clause for the non-generic type (just String()), or use a genuinely generic type such as List() of String when you need a parameterized container. See ek9 -h E04070 for details.


Question: Why does EK9 reject a generic type named without 'of' in a declaration?

What triggers E04080 TEMPLATE_TYPE_REQUIRES_PARAMETERIZATION?

Why can't I declare a parameter or variable just 'as List' in EK9?

How do I specify the element type when declaring a generic in EK9?

Answer:
When a generic type such as List, Dict, Optional or Result appears in a type position - a parameter declaration, a return-value declaration, or a field/variable type - it must be fully parameterized with an 'of' clause. Naming the bare generic (e.g. 'as List') gives the compiler no element type to work with, so it raises E04080 (Template/Generic requires parameterization). The fix is to state the element type: 'as List of String', 'as Dict of (String, Integer)', 'as Optional of MyClass', or 'as Result of (Integer, String)'. This is distinct from E06010, which fires when a bare generic is constructed in an expression such as 'items <- List()'.

See Q194 for generic classes. See Q198 for built-in generics.

defines module qa.generics.parameterization

  defines function

    //THE FIX: every generic in a type position carries an 'of' clause.
    //The parameter and the return value are both fully parameterized.
    headCount() as pure
      -> names as List of String
      <- rtn as Integer: length names

    //Dict in a parameter position needs both key and value types.
    lookupAge() as pure
      ->
        ages as Dict of (String, Integer)
        who as String
      <- rtn as Integer: ages.getOrDefault(who, Integer())

  defines program

    GenericParameterizationDemo()
      stdout <- Stdout()

      names <- List() of String
      names += "Alice"
      names += "Bob"
      stdout.println(`Head count: ${headCount(names)}`)

      ages <- {"Alice": 30, "Bob": 25}
      stdout.println(`Age of Alice: ${lookupAge(ages, "Alice")}`)

Java: 'List items' (raw type) compiles with only an unchecked warning, deferring failures to runtime ClassCastException. Kotlin/Swift: a bare generic in a type position is a compile error, matching EK9. Python: collections are untyped, so element type is never declared. EK9: a generic in any declaration position MUST be parameterized at compile time (E04080), there is no raw-type escape hatch.

Dict E04080 List Optional declaration generic of parameterization parameterized raw-type type-parameter

generic parameterization
E04080
of clause

headCount()
  -> names as List
  <- rtn as Integer: length names

Explanation: Declaring a parameter as the bare generic 'List' gives the compiler no element type, so it raises E04080. A generic named in a type position (parameter, return value, or field) must be fully parameterized: add an 'of' clause such as 'as List of String'. See ek9 -h E04080 for details.


Question: Why can't I call this() or super() inside a regular method?

What triggers E05060 this()/super() outside a constructor?

Why does EK9 reject super() in a normal method?

How do I call a parent member from a method without super()?

Answer:
this() and super() are constructor delegation CALLS. They are only valid as the first statement inside a constructor body, where they delegate to another constructor of the same class (this(...)) or to the parent constructor (super(...)). Using them as a call anywhere else - inside a regular method, a property initializer, or a function - raises E05060.

EK9 distinguishes the CALL syntax from the member-ACCESS syntax:
  this()  = call another constructor of this class (constructors only)
  super() = call the parent constructor (constructors only)
  this.x  = access a member of this instance (anywhere in the class)
  super.x = access a parent member (anywhere in the class)

If you wanted to read or assign a member from inside a method, use the DOT form (this.field or super.method()). If you genuinely wanted to delegate, move the call into a constructor as its first statement.

See Q580 for this() delegation. See Q581 for super() delegation. See Q582 for delegation order (E05050).

defines module qa.constructor.delegationscope

  defines class

    Animal as open
      species <- String()

      Animal()
        -> species as String
        this.species: species

      species() as pure
        <- rtn as String: species

      default operator ?

    //Pet uses super() correctly - only inside a constructor, as the first statement.
    Pet extends Animal
      petName <- String()

      Pet()
        -> petName as String
        super("Unknown")
        this.petName: petName

      //CORRECT: a regular method uses DOT access (this.petName), never the
      //delegation CALL super(...). Methods cannot re-run a constructor.
      rename()
        -> newName as String
        this.petName: newName

      petName() as pure
        <- rtn as String: petName

      default operator ?

  defines program

    DelegationScopeDemo()
      stdout <- Stdout()

      pet <- Pet("Buddy")
      stdout.println(`${pet.petName()} is a ${pet.species()}`)

      pet.rename("Max")
      stdout.println(`Renamed to ${pet.petName()}`)

Java: this()/super() are also constructor-only and the compiler rejects them in methods, but the error is generic. Kotlin: super.method() is the access form and there is no this() call in methods. C#: this()/base() are constructor-initializer-only. EK9: compile-time error E05060 with an explicit hint to switch from CALL syntax () to ACCESS syntax (.), or move the delegation into a constructor.

E05060 access call constructor delegation dot method super this

constructor delegation
this/super outside constructor
E05060

rename()
        -> newName as String
        super("Reset")
        this.petName: newName

Explanation: super(...) is a constructor delegation CALL and may only appear as the first statement of a constructor, so calling it inside the rename() method raises E05060. A regular method cannot re-run the parent constructor. To touch members from a method use the DOT access form (this.petName or super.someMethod()); to delegate, move the call into a constructor. See ek9 -h E05060 for details.


Question: Why can't I instantiate a program or application with a constructor call in EK9?

What triggers E05200 INCOMPATIBLE_GENUS_CONSTRUCTOR?

Why does 'app <- MyApplication()' fail to compile in EK9?

How do I start a program and its application without calling 'new'?

Answer:
Programs and applications are not ordinary objects, so they cannot be created with a constructor call. Writing 'app <- MyApplication()' or 'prog <- MyProgram()' raises E05200 (INCOMPATIBLE_GENUS_CONSTRUCTOR): the PROGRAM, GENERAL_APPLICATION and SERVICE_APPLICATION genus types reject local constructor use.

A program is the operating-system entry point; the runtime starts it. An application is the dependency-injection blueprint; it is brought to life by a program that declares 'with application of AppName'. You never construct either by hand.

The correct pattern is to define the application with its 'register' statements, then bind a program to it with 'with application of', and let injection ('!') resolve the registered components. No constructor call is needed or allowed for the program or the application itself.

See Q231 for program-application linking. See Q672 for the 'with application of' requirement.

defines module qa.di.program.app.construction

  defines component

    OrderService as abstract
      process() as abstract
        <- summary as String?

      default operator ?

    StandardOrderService is OrderService
      override process()
        <- summary as String: "order processed"

      default operator ?

  defines application

    //The application is the dependency-injection blueprint.
    //It is never constructed by hand; a program brings it to life.
    OrderApp
      register StandardOrderService() as OrderService

  defines program

    //CORRECT: bind the program to the application with 'with application of'.
    //No constructor call is made on the program or the application.
    RunDemo() with application of OrderApp
      stdout <- Stdout()

      service as OrderService!
      stdout.println(service.process())

Java Spring: you can technically 'new' a @Configuration or @SpringBootApplication class, and an ApplicationContext is built by SpringApplication.run() - misuse is caught (if at all) at runtime. C#/.NET: Program.cs is conventionally a static entry; nothing stops you constructing a Startup. Go: main() is the entry point and wiring is manual code. EK9: the compiler models program and application as distinct genus types and rejects any constructor call on them (E05200), so the entry point and DI blueprint can never be misused as plain objects.

E05200
application
constructor
entry point
genus
injection
instantiate
program
register
with application of

program and application genus
E05200
with application of binding

RunDemo()
      app <- OrderApp()
      service as OrderService!
      service.process()

Explanation: An application (and a program) has a special genus and cannot be created with a constructor call, so 'app <- OrderApp()' triggers E05200 INCOMPATIBLE_GENUS_CONSTRUCTOR. The application is a dependency-injection blueprint, not an object you build: bind the program to it with 'with application of OrderApp' and let '!' inject the registered components. See ek9 -h E05200 for details.


Question: Why does EK9 reject an application that has no register statements?

What triggers E07160 implementation must be provided on an application?

Why can't I declare an empty application block in EK9?

How do I fix 'definition of application: implementation must be provided'?

Answer:
An application is a dependency-injection registry: its whole purpose is to bind concrete components to the abstract types that programs inject. An empty application body has no purpose and is almost always an incomplete definition, so EK9 raises E07160 (implementation must be provided). Unlike a method, an application cannot be marked 'as abstract' - it must contain at least one register statement (or a block statement). The fix is to add the register lines that wire your components.

  defines application
    GreetApp
      register ConsoleGreeter() as Greeter

See Q1113 for wiring several components. See Q231 for linking a program to an application.

defines module qa.di.application.body

  defines component

    Greeter as abstract
      greet() as abstract
        -> name as String
        <- message as String?

      default operator ?

    ConsoleGreeter is Greeter
      override greet()
        -> name as String
        <- message as String: `Hello, ${name}!`

      default operator ?

  defines application

    //CORRECT: the application body provides at least one register statement,
    //so it is a meaningful DI registry (not an empty body -> E07160).
    GreetApp
      register ConsoleGreeter() as Greeter

  defines program

    ApplicationBodyDemo() with application of GreetApp
      stdout <- Stdout()
      greeter as Greeter!
      stdout.println(greeter.greet("World"))

Spring: an empty @Configuration class compiles silently and fails only at runtime when a bean is missing. Guice: an empty AbstractModule.configure() compiles and defers the error to injector creation. .NET: an empty service-collection setup compiles and surfaces missing-service errors at first resolve. EK9: an empty application is a compile-time error (E07160) - the registry must declare its bindings up front.

DI E07160 application body empty implementation register registry wiring

defines application
E07160
application body

Explanation: An application with no register or block statements has an empty body. An application cannot be abstract and a registry with no bindings is meaningless, so EK9 raises E07160. Add at least one register statement that binds a concrete component to its abstract type. See ek9 -h E07160 for details.


Question: Why does 'default operator' in a derived class fail when the base class has no operators?

What triggers E07190 default operator missing in super?

Why can't I use default operator when my parent class defines no operators?

How do I fix 'default of operators requires super to have appropriate operator'?

Answer:
A 'default operator' in a derived class generates operators that call the SUPER class's matching operator first, then combine with the derived fields. If the base class has no operators (no 'default operator' and no manual operators), there is nothing for the derived default to chain to, so EK9 raises E07190 - one missing operator at a time (e.g. <=>, ==, $, #?, ?).

Fix - choose one of two approaches:

APPROACH 1 (recommended): add 'default operator' to the base class so the super has operators to call. Then 'default operator' in the derived class chains cleanly.
  defines class
    Shape as open
      name as String: String()
      default operator    // base now has operators
    Circle extends Shape
      radius as Float: 0.0
      default operator    // chains to super's operators

APPROACH 2: implement the operators manually in the derived class instead of using 'default operator', writing each operator (?, $, <=>, etc.) explicitly.

Note that an aggregate with fields also needs 'override operator ?' or 'default operator ?' to define set/unset semantics, so adding 'default operator' to the base fixes both concerns at once.

See Q1069 for mixing default operator with a custom override. See Q577 for override operator basics.

defines module qa.overridemechanics.defaultsuperoperator

  defines class

    //THE FIX: the base class defines 'default operator' so the super has
    //operators (<=>, ==, $, #?, ?) for the derived default to chain to.
    Shape as open
      name as String: String()

      Shape()
        -> name as String
        this.name :=: name

      default operator

    //The derived 'default operator' generates operators that call super's
    //matching operator first, then combine with the derived fields.
    Circle extends Shape
      radius as Float: 0.0

      Circle()
        ->
          name as String
          radius as Float
        super(name)
        this.radius :=: radius

      default operator

  defines program

    DefaultSuperOperatorDemo()
      stdout <- Stdout()

      c1 <- Circle("small", 1.0)
      c2 <- Circle("large", 5.0)

      stdout.println(`Circle set: ${c1?}`)
      stdout.println(`Circle: ${c1}`)
      stdout.println(`Equal: ${c1 == Circle("small", 1.0)}`)
      stdout.println(`Compare: ${c1 <=> c2}`)

Java: equals/hashCode/toString are inherited from Object, so a subclass's generated/IDE versions always have a super to call - no compile-time check ties them together. Kotlin data classes do not support inheritance of generated equals/hashCode at all (data classes are effectively final). EK9: 'default operator' explicitly chains to the super's operator and the compiler enforces (E07190) that the super actually defines the operator being defaulted, rather than silently producing inconsistent behaviour.

E07190 base default extends inherit operator override super

default operator requires super operator

Shape as open
  name as String: String()
  // no operators defined

Circle extends Shape
  radius as Float: 0.0
  default operator

Explanation: A derived 'default operator' generates operators that call the super's matching operator first. When the base class (Shape) defines no operators, there is nothing for the derived default to chain to, so E07190 is raised once per missing operator (<=>, ==, $, #?, ?). Add 'default operator' to the base class so the super has operators to call, or implement the operators manually in the derived class instead of defaulting. See ek9 -h E07190 for details.


Question: Why does 'default operator' fail when a property's type has no operators?

What triggers E07200 PROPERTY_TYPE_MISSING_OPERATOR?

Why does my class with 'default operator' say a field type lacks an operator?

How do I use default operator when a property is a custom class?

Answer:
'default operator' generates field-by-field implementations by calling the SAME operator on each property's type. So if a class uses 'default operator', EVERY property type must support the operators being generated (==, <>, <=>, $, #?, ? and so on). When a property's type is a custom class or record that defines no operators, the generated code has nothing to call and compilation fails with E07200.

Two fixes:
1. Give the property's type its own operators - the simplest is to add 'default operator' to that type too, so it gains the full field-by-field set.
2. Or implement the outer operators manually instead of using 'default', e.g. 'override operator ? as pure' returning a hand-written expression.

Built-in types (Integer, Float, String, Boolean, Date, Time, DateTime, Duration, Money) already have all operators, so properties of those types never trigger E07200.

See Q949 for the full default operator rules. See Q245 for implementing operators by hand.

defines module qa.operators.propertytypeoperator

  defines class

    <?-
      The property TYPE must itself support the operators that 'default'
      generates. Coordinate gets 'default operator' so it gains the full
      field-by-field set (==, <>, <=>, $, #?, ?), satisfying Place below.
    -?>
    Coordinate
      x <- Integer()
      y <- Integer()

      Coordinate() as pure
        ->
          x as Integer
          y as Integer
        this.x :=: x
        this.y :=: y

      default operator

    <?-
      Place uses 'default operator'. Because its property 'where' is a
      Coordinate that now supports all operators, the generated
      field-by-field code compiles cleanly - no E07200.
    -?>
    Place
      name <- String()
      where <- Coordinate()

      Place() as pure
        ->
          name as String
          where as Coordinate
        this.name :=: name
        this.where :=: where

      default operator

  defines program

    PropertyTypeOperatorDemo()
      stdout <- Stdout()

      origin <- Coordinate(0, 0)
      target <- Coordinate(3, 4)

      home <- Place("home", origin)
      away <- Place("away", target)

      stdout.println(`home == away: ${home == away}`)
      stdout.println(`home: ${home}`)
      stdout.println(`home set: ${home?}`)

Java: IDE-generated equals/hashCode silently compiles even if a field type has no usable equals (falling back to Object identity) - a latent bug. Kotlin data class / Rust derive(Eq, Hash) / Python dataclass all require contained types to themselves support the derived behaviour, but the failure modes differ (Rust errors at derive, Java does not). EK9 makes the requirement explicit at compile time: every property type must provide the operator that 'default' generates, or E07200 is raised.

E07200 E07235 auto default field generate missing operator property type

default operator
E07200
property type operators

Coordinate
  x <- Integer()
  y <- Integer()
  // no operators

Place
  where <- Coordinate()
  default operator

Explanation: 'default operator' on Place generates field-by-field code that calls operators (such as $ and ?) on the type of every property. The property 'where' is a Coordinate, but Coordinate defines no operators, so the generated implementation has nothing to call and E07200 is raised for the missing operator. The fix is to give Coordinate its own operators - adding 'default operator' to Coordinate is the simplest, or implement Place's operators manually instead of using 'default'. See ek9 -h E07200 for details.


Question: Why can't a helper method in an EK9 service be marked 'protected'?

What triggers E07240 METHOD_MODIFIER_PROTECTED_IN_SERVICE?

How do I hide an internal helper method inside an EK9 service?

Why does EK9 reject 'protected' on a non-web service method?

Answer:
A non-web method inside a 'defines service' construct cannot be marked 'protected'. Services are entry points, not extensible classes, so the 'protected' concept (granting access to subclasses) has no meaning for them and EK9 raises E07240. Use 'private' for an internal helper that should not be exposed as a route, or leave the method public (the default) if it is a web endpoint. Web methods carry an HTTP mapping such as 'as GET for :/path'; plain helper methods do not and so must be 'private' or public, never 'protected'.

See Q199 for REST GET endpoints. See Q201 for HTTP responses.

defines module qa.web.serviceprotected

  defines service

    Greeting :/greeting open

      hello() as GET for :/hello
        <- response as HTTPResponse?

        message <- buildMessage()
        response: (message) with trait HTTPResponse
          override content()
            <- rtn as String: message
          override status() as pure
            <- rtn as Integer: 200
          override contentType() as pure
            <- rtn as String: "text/plain"
          override cacheControl() as pure
            <- rtn as String: "no-store"
          override contentLanguage() as pure
            <- rtn as String: "en"
          default operator ?

      //CORRECT: an internal helper uses 'private', never 'protected'.
      //'protected' on a non-web service method raises E07240.
      private buildMessage()
        <- rtn as String: "Hello from the EK9 service"

  defines application

    GreetingApp
      register Greeting()

  defines program

    ServiceProtectedDemo()
      stdout <- Stdout()

      stdout.println("Service registered with application")
      stdout.println("GET /greeting/hello returns a greeting")
      stdout.println("Internal helper is private, not protected")

Java (JAX-RS/Spring): a resource class is a normal class, so 'protected' helper methods compile fine and are governed only by inheritance. Kotlin/C#: same - controllers are ordinary classes with full visibility ranges. EK9: services are dedicated entry-point constructs, not inheritable classes, so 'protected' is rejected at compile time (E07240); internal helpers use 'private' instead.

E07240 helper method modifier private protected rest service visibility web

service visibility
E07240
protected in service

protected buildMessage()
  <- rtn as String: "internal"

Explanation: Marking a non-web service method 'protected' triggers E07240, because a service is an entry point - not an extensible class - so subclass-oriented 'protected' visibility is meaningless. Use 'private' for an internal helper (or leave it public). See ek9 -h E07240 for details.


Question: Why can't I mark a method 'protected' in a closed class?

What triggers E07260 METHOD_MODIFIER_PROTECTED_IN_CLOSED_CLASS?

Why does EK9 reject 'protected' on a method in a non-open class?

When is the 'protected' access modifier valid on a class method?

Answer:
The 'protected' access modifier only makes sense for inheritance: it grants access to subclasses while hiding the member from outside code. A class that is closed (the EK9 default) can never have subclasses, so 'protected' would be meaningless there. EK9 rejects it at compile time with E07260.

THE FIX
There are two correct paths depending on intent:
  1. If you DO intend the class to be extended, mark the class 'as open'. Then 'protected' is valid because subclasses exist to use it.
  2. If no inheritance is intended, use 'private' instead. A private method is fully encapsulated and needs no open class.

WHY EK9 IS STRICT
In Java you can write 'protected' in a final class and it silently behaves like package-private nonsense; the modifier is dead. EK9 eliminates that dead, misleading code: 'protected' must match a real inheritance relationship or it does not compile.

See Q102 for 'as open'. See Q101 for closed-by-default. See Q573 for override access modifier rules.

defines module qa.oop.protected.open

  defines class

    //CORRECT: the class is 'as open', so 'protected' is meaningful -
    //subclasses can call validate() while outside code cannot.
    Account as open
      balance as Float: 0.0

      Account()
        -> opening as Float
        this.balance: opening

      //Protected: visible to subclasses, hidden from the outside world.
      protected validate() as pure
        <- rtn as Boolean: balance >= 0.0

      describe()
        <- rtn as String: `Account balance ${balance}`

      default operator ?

    //Subclass exercises the protected method - the reason 'protected' exists.
    SavingsAccount extends Account
      SavingsAccount()
        -> opening as Float
        super(opening)

      isHealthy() as pure
        <- rtn as Boolean: validate()

      default operator ?

  defines program

    ProtectedRequiresOpenDemo()
      stdout <- Stdout()

      savings <- SavingsAccount(100.0)
      stdout.println(savings.describe())
      stdout.println(`Healthy: ${savings.isHealthy()}`)

Java: 'protected' compiles even on a 'final' class, where it can never be exercised by a subclass - the modifier is silently dead code. Kotlin: 'protected' is allowed on a final class with only a warning at most. C#: 'protected' on a sealed class is permitted but useless. EK9: compile-time error E07260 - 'protected' requires the class to be 'open' (or abstract, which is implicitly open), otherwise use 'private'.

E07260 access class closed inheritance method modifier open private protected subclass visibility

protected modifier
E07260
open class
access modifiers

    Account as open
      balance as Float: 0.0

Account
  protected validate() as pure
    <- rtn as Boolean: true

Explanation: 'protected' grants access to subclasses, but a closed class (the EK9 default) can never be extended, so the modifier is meaningless and triggers E07260. Either mark the class 'as open' so subclasses exist to use the protected method, or use 'private' if no inheritance is intended. See ek9 -h E07260 for details.


Question: Why does EK9 say a return is not possible when all branches throw?

Why can't my function return a value if every if/else branch throws?

How do I fix 'return not possible, as instructions only result in an Exception'?

Answer:
EK9 performs flow analysis on functions and methods that declare a return variable (<- rtn). If every path through the body ends in throw, the declared return can never be assigned or reached, so the compiler raises E07380 ('return not possible, as instructions only result in an Exception'). This usually means a fallible function was written so that BOTH the failure branch and the success branch throw - leaving no normal path that produces a value.

The fix is to keep at least one non-throwing path that assigns the return variable. Throw only for the genuinely exceptional case (typically guarded by a condition), then fall through to assign and return the normal result. A conditional throw is fine; an unconditional throw on every branch is not.

See Q933 for unreachable code after throw (E07370). See Q139 for try/catch versus Result for fallible work.

defines module qa.controlflow.unreachable.return

  defines function

    //FIX: only the failure case throws. The normal path falls through,
    //assigns the return variable, and is reachable - so E07380 is avoided.
    acceptAmount() as pure
      -> amount as Integer
      <-
        rtn as String?
      if amount <= 0
        throw Exception(`bad amount: ${amount}`)
      rtn: `accepted ${amount}`

  defines program

    UnreachableReturnDemo()
      stdout <- Stdout()
      stderr <- Stderr()

      //Normal path: a value is produced and returned.
      stdout.println(acceptAmount(42))

      //Failure path: the conditional throw fires and is caught.
      try
        stdout.println(acceptAmount(-1))
      catch
        -> ex as Exception
        stderr.println(`Caught: ${ex}`)

Java: a method whose every branch throws compiles fine - the missing return is simply never required, and dead-code-after-throw is only caught when literally unreachable statements follow. Kotlin/Scala: such a method infers the bottom type 'Nothing' and is accepted silently. Go: returning is optional after a panic, no diagnostic. EK9: because a declared return variable is a contract, a body that can only throw makes that contract unsatisfiable, so the compiler rejects it at compile time (E07380) rather than shipping a function that promises a value it can never deliver.

E07380
all paths
analysis
exception
flow
return
throw
unreachable

unreachable return
all paths throw
E07380

if amount <= 0
  throw Exception(`bad amount: ${amount}`)
else
  throw Exception("unexpected")
rtn: `accepted ${amount}`

Explanation: When both the if and the else branch throw, every path through the function ends in an Exception, so the declared return variable can never be reached or assigned - the function can only ever throw. EK9 raises E07380 because the declared return is then a promise the body cannot keep. Remove the unconditional throw on the success branch (throw only for the failure case) so a normal path remains that assigns the return. See ek9 -h E07380 for details.


Question: Why does EK9 reject an operator that takes a parameter but has no return declaration?

What triggers E07400 RETURNING_MISSING on an operator?

Why must my '+' operator declare a '<- rtn' return value?

How do I fix 'returning variable and type missing' in EK9?

Answer:
Operators with value semantics (such as +, -, *, /, <=>, $, #?) MUST produce a value, so EK9 requires a return declaration in their signature. Omitting the '<- rtn as Type: ...' line raises E07400 (RETURNING_MISSING): the operator promises a result but never declares one, so any expression using it would have nothing to evaluate to.

EK9 has no return keyword - the return value is a named variable declared with '<-' in the signature. The compiler then enforces that all paths give it a value. To fix E07400, add the return declaration:

  operator + as pure
    -> other as Vector
    <- rtn as Vector: Vector(x + other.x)

Note this is different from mutating operators like ++ and -- which must NOT declare a return (that would raise RETURN_VALUE_NOT_SUPPORTED).

See Q1001 for the general '<- rtn' return mechanism.

defines module qa.functionsandmethods.operatorreturn

  defines class

    Vector
      x as Integer: Integer()

      Vector() as pure
        -> initial as Integer
        x :=? initial

      //CORRECT: a value-producing operator declares its return with '<- rtn'.
      //Omitting this line would raise E07400 RETURNING_MISSING.
      operator + as pure
        -> other as Vector
        <- rtn as Vector: Vector(x + other.x)

      operator $ as pure
        <- rtn as String: `Vector(${x})`

      override operator ? as pure
        <- rtn as Boolean: x?

  defines program

    OperatorReturnDemo()
      stdout <- Stdout()

      a <- Vector(3)
      b <- Vector(4)
      sum <- a + b
      stdout.println(`Sum is ${sum}`)

Java/Kotlin: operator overloads are just methods whose return type is part of the signature; forgetting to return compiles only if the declared return type is void, otherwise it is a normal type error. C++: an operator+ with a non-void return that never returns is undefined behaviour, often only a warning. EK9: the missing return is a hard compile-time error (E07400) at type-definition time, because the return value is a declared variable the compiler can see is absent.

E07400 RETURNING_MISSING declaration missing operator return rtn value

operator return declaration
E07400
missing return

Explanation: A value-producing operator like '+' must declare what it returns, but this signature has only an incoming parameter and no '<- rtn as Type' line, so EK9 raises E07400 - the operator promises a result it never produces. Add the return declaration '<- rtn as Vector: ...' so all paths supply a value. (Mutating operators such as ++ and -- are the opposite: they must NOT declare a return.) See ek9 -h E07400 for details.


Question: Why can't I declare 'operator !=' on my EK9 type?

What triggers E07640 BAD_NOT_EQUAL_OPERATOR?

How do I implement not-equal on a custom EK9 class?

Why does EK9 reject '!=' as an operator name?

Answer:
EK9's not-equal operator is '<>' (mathematical notation), not the C-family '!='. When you declare an operator on a type you must name it '<>'. Declaring 'operator != as pure' triggers E07640 (BAD_NOT_EQUAL_OPERATOR) because '!=' is not a valid EK9 operator symbol for a declaration.

Replace 'operator !=' with 'operator <>'. The operator must be pure, take exactly one argument of the same type, and return Boolean. EK9 chose '<>' because it is the mathematical not-equal form (the same as Pascal, SQL and BASIC) and composes naturally with '<' and '>'.

See Q238 for the complete fixed operator set. See Q239 for comparison and ordering operators.

defines module qa.operators.notequal

  defines class

    Score
      points <- Integer()

      Score() as pure
        -> points as Integer
        this.points :=: points

      operator == as pure
        -> other as Score
        <- rtn as Boolean: points == other.points

      //THE FIX: EK9's not-equal operator is '<>', NOT '!='.
      //Declaring 'operator !=' would trigger E07640.
      operator <> as pure
        -> other as Score
        <- rtn as Boolean: points <> other.points

      operator $ as pure
        <- rtn as String: $points

      override operator ? as pure
        <- rtn as Boolean: points?

  defines program

    NotEqualOperatorDemo()
      stdout <- Stdout()

      a <- Score(10)
      b <- Score(20)

      stdout.println(`Equal: ${a == b}`)
      stdout.println(`Not equal: ${a <> b}`)

Java/C++/Python/Rust/JavaScript: not-equal is '!=' and (where overloading exists) you implement it under that symbol or as a negated equals. EK9: the only not-equal operator symbol is '<>', and the compiler rejects a '!=' declaration at compile time (E07640, phase EXPLICIT_TYPE_SYMBOL_DEFINITION) rather than silently accepting an alternate spelling.

!= <> E07640 comparison declaration equal inequality neq not operator

not-equal operator
E07640
operator declaration

operator != as pure
  -> other as Score
  <- rtn as Boolean: points <> other.points

Explanation: EK9 uses the mathematical not-equal symbol '<>', not the C-family '!='. Declaring 'operator !=' is not a valid EK9 operator name and triggers E07640. Rename the declaration to 'operator <>' (pure, one argument, returns Boolean). See ek9 -h E07640 for details.


Question: Why can't I declare 'operator not' on a type in EK9?

What triggers E07650 when defining a logical NOT operator?

How do I implement boolean negation on a custom type in EK9?

Why does EK9 reject 'operator not' and want '~' instead?

Answer:
EK9 has no 'not' operator and no '!' logical-NOT. The single logical-NOT (and bitwise complement) operator is '~'. When you declare 'operator not' on a class, record, or trait the compiler raises E07650 (BAD_NOT_OPERATOR) and tells you to use '~'. The same applies at call sites: write '~value', never '!value' or 'not value' for negation. ('not' IS a valid keyword, but only as the prefix of 'not contains' / 'not in' membership tests, not as a standalone logical-NOT.)

Define the operator with the '~' symbol; on a custom type it must be pure, take no arguments, and return the SAME type (so 'Switch' returns a negated 'Switch'). On the built-in Boolean, '~' yields the negated Boolean directly.

See Q244 for boolean and bitwise operators. See Q903 for operators that do NOT exist in EK9.

defines module qa.operators.notistilde

  defines class

    //THE FIX: the logical-NOT operator is declared with '~', not 'not' and not '!'.
    //It is pure, takes no arguments, and returns a Boolean here.
    Switch
      active as Boolean: Boolean()

      Switch() as pure
        -> initial as Boolean
        active :=? initial

      operator ~ as pure
        <- rtn as Switch: Switch(~ active)

      operator $ as pure
        <- rtn as String: $ active

      override operator ? as pure
        <- rtn as Boolean: active?

  defines program

    NotOperatorDemo()
      stdout <- Stdout()

      on <- Switch(true)
      off <- ~ on

      stdout.println(`Switch on: ${on}`)
      stdout.println(`Negated:   ${off}`)

Java/C/C++/JavaScript/Rust: '!' is logical NOT. Python: 'not' keyword is logical NOT. Kotlin: '!' plus an overloadable 'not()' operator function. EK9: neither '!' nor 'not' is a logical-NOT operator; '~' is the single negation operator, enforced at compile time both when DEFINING the operator (E07650) and at call sites.

E07650 boolean complement logical negation not operator tilde

logical NOT operator
E07650
tilde negation

operator not as pure
  <- rtn as Switch: Switch(~ active)

Explanation: EK9 has no 'not' operator and no '!' logical-NOT; declaring 'operator not' on a type raises E07650. Rename the operator to '~', which is EK9's single logical-NOT and bitwise-complement operator. At call sites use '~value' too, never '!value'. See ek9 -h E07650 for details.


Question: Why does EK9 reject some operators inside a service definition?

What triggers E07670 unsupported service operator?

Which operators are allowed inside an EK9 service?

Why can't I use operator * in an EK9 REST service?

Answer:
An EK9 service maps operators to HTTP verbs, so only the operators that have a clear CRUD meaning are permitted. The supported set is +, +=, -, -=, :^:, :~: and ?. Using any other operator (for example * or /) inside a service raises E07670 because there is no HTTP verb to map it to.

The semantic mapping is: += and + create (POST), -= and - remove (DELETE), :^: replaces (PUT), :~: merges (PATCH), and ? checks whether the service is set. Arithmetic operators like * have no place at the HTTP boundary.

Fix: keep the service surface limited to the supported operators, and move any computational behaviour (such as multiplication) into a plain helper class or function that the service handler calls.

See Q200 for CRUD operator mapping. See Q660 for path parameters with operators.

defines module qa.web.unsupported.operator

  defines service

    //CORRECT: only the supported, HTTP-mappable operators appear here.
    //Any arithmetic (such as multiplication) lives in a helper, not the service.
    Items :/items open

      // GET /items — list all items
      listAll() :/
        <- response as HTTPResponse: () with trait HTTPResponse
          override content()
            <- rtn as String: `["item1", "item2"]`
          override status() as pure
            <- rtn as Integer: 200
          override contentType() as pure
            <- rtn as String: "application/json"
          override cacheControl() as pure
            <- rtn as String: "no-cache"
          override contentLanguage() as pure
            <- rtn as String: "en"
          default operator ?

      // POST /items — add new item (supported operator, maps to create)
      operator += :/
        -> content as String :=: CONTENT
        <- response as HTTPResponse: () with trait HTTPResponse
          override content()
            <- rtn as String: `{"created": true}`
          override status() as pure
            <- rtn as Integer: 201
          override contentType() as pure
            <- rtn as String: "application/json"
          override cacheControl() as pure
            <- rtn as String: "no-store"
          override contentLanguage() as pure
            <- rtn as String: "en"
          default operator ?

  defines class

    //Arithmetic belongs in a plain helper, never in the service surface.
    Calculator
      multiply() as pure
        ->
          a as Integer
          b as Integer
        <-
          rtn as Integer: a * b

  defines application

    ItemsApp
      register Items()

  defines program

    UnsupportedServiceOperatorDemo()
      stdout <- Stdout()
      calc <- Calculator()
      stdout.println("Service uses only supported operators: +, +=, -, -=, :^:, :~:, ?")
      stdout.println(`Arithmetic stays in a helper: 6 * 7 = ${calc.multiply(6, 7)}`)

Java: Spring/JAX-RS use method annotations (@GetMapping, @PostMapping); an arbitrary method is allowed and only meaningful at runtime. Python Flask/FastAPI: any function can be a route, no restriction on its name or shape. Go/Rust: handlers are ordinary functions wired to verbs by the router. EK9: the service surface is restricted at compile time to the operators that map to HTTP verbs (+, +=, -, -=, :^:, :~:, ?); anything else is rejected with E07670 so business arithmetic never leaks into the transport layer.

E07670 crud http operator rest service unsupported verb webservice

unsupported service operator
E07670
service operator mapping

operator * :/{id}
  -> id as String
  <- response as HTTPResponse: ...

Explanation: The * operator has no HTTP-verb meaning, so it cannot appear inside a service and triggers E07670. Only +, +=, -, -=, :^:, :~: and ? are supported because each maps to a CRUD HTTP verb. Use a supported operator (for example += for POST) for the endpoint and move any arithmetic into a helper class. See ek9 -h E07670 for details.


Question: Why does EK9 reject an HTTP access marker (:=: PATH) on a service operation's returning parameter?

What triggers E07690 HTTP access verb not supported in this context?

Why can't I put :=: PATH or :=: HEADER on a service return value in EK9?

Where are HTTP access markers (PATH, HEADER, QUERY, CONTENT) allowed in an EK9 service?

Answer:
An HTTP access marker (:=: PATH, :=: HEADER, :=: QUERY, :=: REQUEST, :=: CONTENT, :=: CONTEXT) tells EK9 where to source an INCOMING value from the HTTP request. It is therefore only valid on the INCOMING parameters of a service operation. Putting one on a returning parameter, an aggregate property, or a plain block variable makes no sense - there is nothing to bind from the request - so EK9 raises E07690 (HTTP access verb not supported in this context) at compile time.

The fix is to bind request data only on the incoming parameter list and let the returning parameter be an ordinary HTTPResponse. The HTTP verb of the operation (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) describes the operation; the access marker describes where each incoming argument comes from.

See Q658 for path-parameter binding. See Q202 for request parameter binding.

defines module qa.webdeep.httpaccessmarker

  defines constant

    JSON_CONTENT_TYPE <- "application/json"
    ENGLISH_LANGUAGE <- "en"

  defines service

    <?-
      An HTTP access marker (:=: PATH here) is valid ONLY on an incoming
      parameter, where it binds a value from the request. The returning
      parameter is a plain HTTPResponse with NO access marker - adding one
      there would trigger E07690.
    -?>
    Items :/items open

      //CORRECT: :=: PATH on the incoming 'id' only; the return is a plain HTTPResponse.
      byId() as GET for :/{id}
        ->
          id as String :=: PATH
        <-
          response as HTTPResponse: (capturedId: id) with trait HTTPResponse
            override content()
              <- rtn as String: `{"id": "${capturedId}"}`
            override status() as pure
              <- rtn as Integer: 200
            override contentType() as pure
              <- rtn as String: JSON_CONTENT_TYPE
            override cacheControl() as pure
              <- rtn as String: "no-cache"
            override contentLanguage() as pure
              <- rtn as String: ENGLISH_LANGUAGE
            default operator ?

  defines application

    ItemsApp
      register Items()

  defines program

    HttpAccessMarkerDemo()
      stdout <- Stdout()
      stdout.println("Service registered (no E07690):")
      stdout.println("  GET /items/{id} binds {id} via :=: PATH on the incoming parameter")
      stdout.println("  the returning HTTPResponse carries no access marker")

Java Spring: @PathVariable / @RequestHeader can only annotate handler parameters; placing them elsewhere is simply ignored or a runtime wiring error. JAX-RS @PathParam on a return is meaningless and silently dropped. EK9 instead rejects a misplaced access marker at compile time with E07690, so request-binding can only ever describe an incoming value.

CONTENT E07690 HEADER PATH QUERY access binding http marker returning service webservice

HTTP access marker placement
E07690
service request binding

byId() as GET for :/{id}
  ->
    id as String :=: PATH
  <-
    response as HTTPResponse :=: PATH

Explanation: An HTTP access marker (:=: PATH) sources an INCOMING value from the request, so it is only valid on an incoming parameter. Placing it on the returning parameter (or a property/local) has nothing to bind and triggers E07690. Remove the marker from the return and keep it only on the incoming argument. See ek9 -h E07690 for details.