Skip to content

Commit e0a8785

Browse files
committed
Test total and partial Aeneas contract semantics
Pin the execution guarantees independently of the specification macro. Add kernel-checked examples for successful returns, named assumptions, tuple results, generic modeling providers and dependent ghosts. Total contracts reject panic and divergence even with postcondition True. Partial contracts permit divergence but reject panic, wrong successful results and decoder-rejected outputs. Keep Rust None and Err payloads distinct from decoding rejection and execution failure. gherrit-pr-id: Gmokoduvrhwmesqj7moerqxqv6jjz63e5 Agent-authored-by: AI agent acting on joshlf's behalf
1 parent 7c36edd commit e0a8785

1 file changed

Lines changed: 111 additions & 0 deletions

File tree

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/- Copyright 2026 The Fuchsia Authors
2+
3+
Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
4+
<LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
5+
license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
6+
This file may not be copied, modified, or distributed except according to
7+
those terms. -/
8+
9+
module
10+
public import ModelPrelude
11+
@[expose] public section
12+
13+
/-!
14+
These kernel-checked examples explain Aeneas's native total and partial
15+
contracts independently of our spec macro. They pin successful results, tuple
16+
postconditions, panic rejection, and divergence behavior. The independently
17+
written theorem types prevent a change in syntax from silently changing the
18+
claim. Total contracts require successful execution even for a True
19+
postcondition. Partial contracts may diverge, but still reject panic.
20+
-/
21+
open Aeneas Aeneas.Std AeneasSpecs
22+
namespace ContractTests
23+
24+
-- Omitted requirements mean True; a successful result is still required.
25+
theorem identity_spec (x : Nat) :
26+
Result.ok x
27+
⦃ ret => ret = x ⦄ := by
28+
exact WP.spec.ret rfl
29+
30+
-- Named requirements are available without introductions in the proof body.
31+
theorem transitive_spec (x y z : Nat)
32+
(hxy : x = y)
33+
(hyz : y = z) :
34+
Result.ok x
35+
⦃ ret => ret = z ⦄ := by
36+
exact WP.spec.ret (hxy.trans hyz)
37+
38+
-- Reuse Aeneas's tuple-aware binders for returns and mutable post-states.
39+
theorem tuple_spec (x : Nat) :
40+
Result.ok (x, x + 1)
41+
⦃ ret x' => ret = x ∧ x' = x + 1 ⦄ := by
42+
simp
43+
44+
theorem diverging_spec :
45+
(Result.div : Result Nat)
46+
⦃ _ => False ⦄div := by
47+
exact WP.dspec.div _
48+
49+
theorem partial_identity_spec (x : Nat) :
50+
Result.ok x
51+
⦃ ret => ret = x ⦄div := by
52+
exact WP.dspec.ret rfl
53+
54+
-- These independently written types pin the exported theorem types and outcomes.
55+
example : ∀ x : Nat, WP.spec (Result.ok x) (fun ret => ret = x) := identity_spec
56+
example : ∀ x y z : Nat, x = y → y = z →
57+
WP.spec (Result.ok x) (fun ret => ret = z) := transitive_spec
58+
example : ∀ x : Nat, WP.spec (Result.ok (x, x + 1))
59+
(fun (ret, x') => ret = x ∧ x' = x + 1) := tuple_spec
60+
example : WP.dspec (Result.div : Result Nat) (fun _ => False) := diverging_spec
61+
example : ∀ x : Nat, WP.dspec (Result.ok x) (fun ret => ret = x) := partial_identity_spec
62+
63+
theorem panic_rejected :
64+
¬ WP.spec (Result.fail Error.panic : Result Nat) (fun _ => True) := by simp
65+
66+
theorem divergence_rejected :
67+
¬ WP.spec (Result.div : Result Nat) (fun _ => True) := by simp
68+
69+
-- Partial correctness must reject panic and incorrect successful returns too.
70+
theorem partial_panic_rejected :
71+
¬ WP.dspec (Result.fail Error.panic : Result Nat) (fun _ => True) := by simp
72+
73+
theorem partial_wrong_return_rejected :
74+
¬ WP.dspec (Result.ok 0) (fun ret : Nat => ret = 1) := by simp
75+
76+
-- A generic identity shares one explicit dictionary and the same witness on
77+
-- input and output, even when the dictionary rejects some representations.
78+
theorem modeled_identity {Raw : Type} (provider : RustModel Raw) (raw : Raw)
79+
(value : provider.Model) (h : provider.decode raw = some value) :
80+
WP.spec (Result.ok raw) (fun out =>
81+
∃ decoded, provider.decode out = some decoded ∧ decoded = value) :=
82+
WP.spec.ret ⟨value, h, rfl⟩
83+
84+
-- A dependent ghost retains its exact domain in mathematical input scope.
85+
theorem modeled_position {Raw : Type} (provider : RustModel Raw)
86+
(length : provider.Model → Nat) (raw : Raw) (value : provider.Model)
87+
(h : provider.decode raw = some value) (position : Fin (length value)) :
88+
WP.spec (Result.ok raw) (fun out =>
89+
∃ decoded, provider.decode out = some decoded ∧ position.val < length decoded) :=
90+
WP.spec.ret ⟨value, h, position.isLt⟩
91+
92+
-- An accepted execution cannot return a decoder-rejected representation, even
93+
-- when its authored postcondition contributes no information.
94+
theorem rejected_output_rejected {Raw : Type} (provider : RustModel Raw)
95+
(raw : Raw) (h : provider.decode raw = none) :
96+
¬ WP.spec (Result.ok raw) (fun out =>
97+
∃ decoded, provider.decode out = some decoded ∧ True) := by
98+
simp [WP.spec_ok, h]
99+
100+
theorem partial_rejected_output_rejected {Raw : Type} (provider : RustModel Raw)
101+
(raw : Raw) (h : provider.decode raw = none) :
102+
¬ WP.dspec (Result.ok raw) (fun out =>
103+
∃ decoded, provider.decode out = some decoded ∧ True) := by
104+
simp [h]
105+
106+
-- Rust absence and Rust errors remain successful decoded constructor cases.
107+
example : (@modelOption Usize (modelUScalar .Usize)).decode none = some none := rfl
108+
example : (@modelRustResult Usize Unit (modelUScalar .Usize) modelUnit).decode
109+
(.Err ()) = some (.Err ()) := rfl
110+
111+
end ContractTests

0 commit comments

Comments
 (0)