-
Notifications
You must be signed in to change notification settings - Fork 0
laws
pannous edited this page Sep 30, 2026
·
1 revision
square(x) := x*x
law square(-x) == square(x)
A law states a property once. Its assurance rises without touching the source.
| Level | What happens | Where |
|---|---|---|
| stated |
law <expr> is split from the program, bound to the first user function it calls, and free variables get the kinds of the parameters they feed (x:float → Float, otherwise Int) |
law::separate_laws |
| asserted | debug builds (cfg!(debug_assertions)): each law call like square(x) is matched against concrete program calls square(3), and the instance is evaluated. A violation replaces the result with Error("law … violated: counterexample x=3")
|
law::assert_laws, called from wasm_emitter::eval
|
| tested |
PROPERTY_TRIALS (64) deterministic inputs: edge cases 0, ±1, ±2, i64::MAX, i64::MIN and ±3037000500 (the first square past i64::MAX) first, then xorshift values in ±1000. Each instance runs through the full parse → wasm → Node path (eval_parsed) |
law::property_test |
| proved | pure integer functions and the law are exported to Lean 4 as unbounded Int and checked |
law::lean::prove |
law::verify(code) runs tested → proved and returns a LawReport per law.
CLI: warp verify file.wasp (or inline code) prints the reports and exits 1 if any law is violated.
- Warp Int promotes to arbitrary precision:
square(3037000500)is9223372037000250000. It is exported as LeanInt, so Proved means proved for Warp's mathematical integer semantics. Explicitas i64values still wrap, but casts are not exported yet. - Definitions use
def f (x : Int) : Int := …. The Lean term covers+ - * %,^nwith a literal nonnegative exponent, unary minus,?:,if then else, and comparisons (lifted to 0/1 inside terms).%usesInt.tmod, matching signed truncating remainder. Warp/yields a Float, so it isn't exported. - The law becomes
theorem f_law (vars : Int) : prop := by try simp only [defs]; all_goals first | (t; done) | …. Every tactic is wrapped in; donebecausesimpcan rewrite a goal without closing it and still count as success. - Tactics are core Lean only:
rfl decide ac_rfl grind simp, plus a small checked helper for0 ≤ x*x; no Mathlib is required. Unlike finiteBitVec, arbitraryIntcannot be exhaustively decided. A law that generated tests do not refute and Lean cannot prove therefore remainsTested/Unknownrather than producing a spurious finite-word counterexample. - Results are cached by the hash of the Lean source in
target/lean/law_<hash>.{lean,result}. This is the "proof result recorded" step until the semantic artifact exists. - Not exported, so the law stays at the Tested level: recursive functions (Lean would need termination proofs), Float parameters,
/, and anything outside the term subset.
-
Proved: Lean closed the goal. -
FAILED … counterexample x=1: a property test instance evaluated to false. -
Tested (reason): every generated instance held, but Lean could not prove it or export it.
Counterexamples come from property tests evaluated in wasm. Lean either proves the universal integer statement or returns Unknown.
- Laws caught a real bug: in
:=functions,x:floatparameters are compiled as Int, sohalf(x:float) := x/2; half(1.0)returns 0. See the ignored testtest_law_float_parameters_are_tested. The cause is thatUserFunctionDef.paramskeeps only defaults, not types, andinfer_function_return_kindassumes Int.
- Attach laws to
FunctionDeclinsrc/semantic/once that IR exists. Persist the verdict in the Wasp-serialized artifact instead oftarget/lean. - Use an SMT backend (z3 is installed) as a faster prover for linear and bitvector laws.
- Asserted mode checks concrete call sites at compile time. It doesn't yet check values computed at runtime inside the WASM module.
- Lawful lifting: broadcasting is allowed only where functor laws are Proved.