soma.cell

E0090: guard_effect

E0090 guard_effect — a guard that calls a handler, think() or writes a slot.

A compile-time error of soma check, the checker of the Soma programming language (soma explain E0090).

The rule

A guard is a pure condition over the locals of the handler that calls transition(); calling a handler, think(), or writing a slot inside it is refused, because the guard would run on every transition unseen by the termination, cost and invariant analyses. Compute the value in the handler before transition() (let limit = limit_for(customer)) and test that local in the guard.

A program that triggers it

wrong.cell

cell Loans {
    memory {
        limits: Map<String, Int> [persistent]
    }
    state loan {
        initial: requested
        requested -> approved { guard { amount <= limit_for(customer) } }
        requested -> rejected
    }
    on limit_for(customer: String) {
        return limits.get(customer) ?? 0
    }
    on approve_loan(id: String, customer: String, amount: Int) {
        transition(id, "approved")
    }
    on reject_loan(id: String) { transition(id, "rejected") }
}

What soma check prints:

$ soma check wrong.cell
error: the guard of requested -> approved calls the handler `limit_for` — a guard is a condition over the calling handler's locals: it cannot call handlers, think(), or write slots (it would run on every transition(), unseen by termination, cost and invariant proofs); compute the value in the handler before transition() and test that local in the guard
  --> wrong.cell:7:41
  |
7 |         requested -> approved { guard { amount <= limit_for(customer) } }
  |                                         ^
  = code: E0090
✗ 1 error(s), 0 warning(s).

The fix field of soma check --json: a guard is a condition over the calling handler's locals: it cannot call handlers, think(), or write slots (it would run on every transition(), unseen by termination, cost and invariant proofs); compute the value in the handler before transition() and test that local in the guard

On line 7, requested -> approved { guard { amount <= limit_for(customer) } } becomes requested -> approved { guard { amount <= limit } }; 1 line is added after line 13.

wrong.cell → right.cell

--- wrong.cell
+++ right.cell
@@ -5,5 +5,5 @@
     state loan {
         initial: requested
-        requested -> approved { guard { amount <= limit_for(customer) } }
+        requested -> approved { guard { amount <= limit } }
         requested -> rejected
     }
@@ -12,4 +12,5 @@
     }
     on approve_loan(id: String, customer: String, amount: Int) {
+        let limit = limit_for(customer)
         transition(id, "approved")
     }

With the fix, soma check accepts the program:

$ soma check right.cell
✓ All checks passed.