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::sync::Arc<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::Spanned<crate::ast::Expr>) -> bool {
27    use crate::ast::Expr;
28    match &expr.node {
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.args.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 ListTake<T>(xs: seq<T>, n: int): seq<T> {
174  if n <= 0 then []
175  else if n >= |xs| then xs
176  else xs[..n]
177}
178
179function ListDrop<T>(xs: seq<T>, n: int): seq<T> {
180  if n <= 0 then xs
181  else if n >= |xs| then []
182  else xs[n..]
183}
184
185function MapGet<K, V>(m: map<K, V>, k: K): Option<V> {
186  if k in m then Some(m[k])
187  else None
188}
189
190// --- String/Char helpers (opaque stubs for verification) ---
191
192function IntToString(n: int): string
193function IntFromString(s: string): Result<int, string>
194function FloatToString(r: real): string
195function FloatFromString(s: string): Result<real, string>
196function StringCharAt(s: string, i: int): Option<string> {
197  if 0 <= i < |s| then Option.Some([s[i]]) else Option.None
198}
199function StringChars(s: string): seq<string> {
200  seq(|s|, (i: int) requires 0 <= i < |s| => [s[i]])
201}
202function StringJoin(sep: string, parts: seq<string>): string
203  decreases |parts|
204{
205  if |parts| == 0 then ""
206  else if |parts| == 1 then parts[0]
207  else parts[0] + sep + StringJoin(sep, parts[1..])
208}
209function CharToCode(c: string): int
210function CharFromCode(n: int): Option<string>
211function MapEntries<K, V>(m: map<K, V>): seq<(K, V)>
212function MapFromList<K, V>(entries: seq<(K, V)>): map<K, V>
213  decreases |entries|
214{
215  if |entries| == 0 then map[]
216  else MapFromList(entries[..|entries|-1])[entries[|entries|-1].0 := entries[|entries|-1].1]
217}
218function ByteToHex(b: int): Result<string, string>
219function ByteFromHex(s: string): Result<int, string>
220function ToString<T>(v: T): string
221"#;