Skip to main content

aver/codegen/dafny/
mod.rs

1/// Aver → Dafny transpiler.
2///
3/// Generates a single `.dfy` file with:
4/// - `datatype` declarations for sum types
5/// - `datatype` wrappers for product types (records)
6/// - `function` definitions for pure functions
7/// - `lemma` stubs for verify-law blocks
8/// - `assert` examples for verify-cases blocks
9mod expr;
10mod toplevel;
11
12use crate::ast::{TopLevel, VerifyKind};
13use crate::codegen::{CodegenContext, ProjectOutput};
14
15/// Check if a function body uses the `?` (ErrorProp) operator.
16/// Such functions require early-return semantics that Dafny pure functions cannot express.
17fn body_uses_error_prop(body: &std::rc::Rc<crate::ast::FnBody>) -> bool {
18    match body.as_ref() {
19        crate::ast::FnBody::Block(stmts) => stmts.iter().any(|s| match s {
20            crate::ast::Stmt::Binding(_, _, expr) => expr_uses_error_prop(expr),
21            crate::ast::Stmt::Expr(expr) => expr_uses_error_prop(expr),
22        }),
23    }
24}
25
26fn expr_uses_error_prop(expr: &crate::ast::Expr) -> bool {
27    use crate::ast::Expr;
28    match expr {
29        Expr::ErrorProp(_) => true,
30        Expr::FnCall(f, args) => expr_uses_error_prop(f) || args.iter().any(expr_uses_error_prop),
31        Expr::BinOp(_, l, r) => expr_uses_error_prop(l) || expr_uses_error_prop(r),
32        Expr::Match { subject, arms, .. } => {
33            expr_uses_error_prop(subject) || arms.iter().any(|a| expr_uses_error_prop(&a.body))
34        }
35        Expr::Constructor(_, Some(arg)) => expr_uses_error_prop(arg),
36        Expr::List(elems) | Expr::Tuple(elems) => elems.iter().any(expr_uses_error_prop),
37        Expr::RecordCreate { fields, .. } => fields.iter().any(|(_, e)| expr_uses_error_prop(e)),
38        Expr::RecordUpdate { base, updates, .. } => {
39            expr_uses_error_prop(base) || updates.iter().any(|(_, e)| expr_uses_error_prop(e))
40        }
41        Expr::InterpolatedStr(parts) => parts.iter().any(|p| match p {
42            crate::ast::StrPart::Parsed(e) => expr_uses_error_prop(e),
43            _ => false,
44        }),
45        Expr::Attr(obj, _) => expr_uses_error_prop(obj),
46        Expr::TailCall(inner) => inner.1.iter().any(expr_uses_error_prop),
47        _ => false,
48    }
49}
50
51/// Transpile an Aver program into a Dafny project.
52pub fn transpile(ctx: &CodegenContext) -> ProjectOutput {
53    let mut sections: Vec<String> = Vec::new();
54
55    sections.push("// Generated by the Aver → Dafny transpiler".to_string());
56    sections.push("// Pure core logic only (effectful functions are omitted)\n".to_string());
57
58    // Prelude: Result/Option datatypes
59    sections.push(DAFNY_PRELUDE.to_string());
60
61    // Emit type definitions from dependent modules
62    for module in &ctx.modules {
63        for td in &module.type_defs {
64            if let Some(code) = toplevel::emit_type_def(td) {
65                sections.push(code);
66            }
67        }
68    }
69
70    // Emit type definitions from the main module
71    for td in &ctx.type_defs {
72        if let Some(code) = toplevel::emit_type_def(td) {
73            sections.push(code);
74        }
75    }
76
77    // Emit function definitions from dependent modules (pure only, no ? operator)
78    for module in &ctx.modules {
79        for fd in &module.fn_defs {
80            if fd.effects.is_empty() && !body_uses_error_prop(&fd.body) {
81                sections.push(toplevel::emit_fn_def(fd, ctx));
82            }
83        }
84    }
85
86    // Emit function definitions from the main module (pure only, no ? operator)
87    for item in &ctx.items {
88        if let TopLevel::FnDef(fd) = item
89            && fd.effects.is_empty()
90            && fd.name != "main"
91            && !body_uses_error_prop(&fd.body)
92        {
93            sections.push(toplevel::emit_fn_def(fd, ctx));
94        }
95    }
96
97    // Emit verify law blocks: sample assertions + universal lemma.
98    let mut law_counter: std::collections::HashMap<String, usize> =
99        std::collections::HashMap::new();
100    for item in &ctx.items {
101        if let TopLevel::Verify(vb) = item
102            && let VerifyKind::Law(law) = &vb.kind
103        {
104            let count = law_counter.entry(vb.fn_name.clone()).or_insert(0);
105            *count += 1;
106            let suffix = if *count > 1 {
107                format!("_{}", count)
108            } else {
109                String::new()
110            };
111
112            // 1. Sample assertions from the domain expansion (concrete smoke tests)
113            if !vb.cases.is_empty()
114                && let Some(code) = toplevel::emit_law_samples(vb, law, ctx, &suffix)
115            {
116                sections.push(code);
117            }
118
119            // 2. Universal lemma (when clause becomes requires)
120            sections.push(toplevel::emit_verify_law(vb, law, ctx));
121        }
122    }
123
124    let content = sections.join("\n");
125    let file_name = format!("{}.dfy", ctx.project_name);
126
127    ProjectOutput {
128        files: vec![(file_name, content)],
129    }
130}
131
132const DAFNY_PRELUDE: &str = r#"// --- Prelude: standard types and helpers ---
133
134datatype Result<T, E> = Ok(value: T) | Err(error: E)
135
136datatype Option<T> = None | Some(value: T)
137
138function ResultWithDefault<T, E>(r: Result<T, E>, d: T): T {
139  match r
140  case Ok(v) => v
141  case Err(_) => d
142}
143
144function OptionWithDefault<T>(o: Option<T>, d: T): T {
145  match o
146  case Some(v) => v
147  case None => d
148}
149
150function OptionToResult<T, E>(o: Option<T>, err: E): Result<T, E> {
151  match o
152  case Some(v) => Result.Ok(v)
153  case None => Result.Err(err)
154}
155
156function ListReverse<T>(xs: seq<T>): seq<T>
157  decreases |xs|
158{
159  if |xs| == 0 then []
160  else ListReverse(xs[1..]) + [xs[0]]
161}
162
163function ListHead<T>(xs: seq<T>): Option<T> {
164  if |xs| == 0 then None
165  else Some(xs[0])
166}
167
168function ListTail<T>(xs: seq<T>): seq<T> {
169  if |xs| == 0 then []
170  else xs[1..]
171}
172
173function MapGet<K, V>(m: map<K, V>, k: K): Option<V> {
174  if k in m then Some(m[k])
175  else None
176}
177
178// --- String/Char helpers (opaque stubs for verification) ---
179
180function IntToString(n: int): string
181function IntFromString(s: string): Result<int, string>
182function FloatToString(r: real): string
183function FloatFromString(s: string): Result<real, string>
184function StringCharAt(s: string, i: int): Option<string> {
185  if 0 <= i < |s| then Option.Some([s[i]]) else Option.None
186}
187function StringChars(s: string): seq<string> {
188  seq(|s|, (i: int) requires 0 <= i < |s| => [s[i]])
189}
190function StringJoin(sep: string, parts: seq<string>): string
191  decreases |parts|
192{
193  if |parts| == 0 then ""
194  else if |parts| == 1 then parts[0]
195  else parts[0] + sep + StringJoin(sep, parts[1..])
196}
197function CharToCode(c: string): int
198function CharFromCode(n: int): Option<string>
199function MapEntries<K, V>(m: map<K, V>): seq<(K, V)>
200function MapFromList<K, V>(entries: seq<(K, V)>): map<K, V>
201  decreases |entries|
202{
203  if |entries| == 0 then map[]
204  else MapFromList(entries[..|entries|-1])[entries[|entries|-1].0 := entries[|entries|-1].1]
205}
206function ByteToHex(b: int): Result<string, string>
207function ByteFromHex(s: string): Result<int, string>
208function ToString<T>(v: T): string
209"#;