1use std::collections::{HashMap, HashSet};
2
3use intent_lang_syntax::ast::*;
4use z3::{Config, SatResult, Solver, with_z3_config};
5
6use crate::vcgen::{VcKind, VerificationCondition};
7
8#[derive(Debug, Clone)]
11pub enum VerifyResult {
12 Verified,
13 Failed { counterexample: String },
14 Unknown { reason: String },
15 Error { message: String },
16}
17
18#[derive(Debug, Clone)]
21struct FieldInfo {
22 sort: String,
23}
24
25struct TypeInfo {
26 structs: HashMap<String, HashMap<String, FieldInfo>>,
28 enums: HashSet<String>,
29}
30
31impl TypeInfo {
32 fn from_program(prog: &Program) -> Self {
33 let mut structs = HashMap::new();
34 let mut enums = HashSet::new();
35 for decl in &prog.declarations {
36 match &decl.node {
37 Declaration::Type(t) => {
38 let mut fields = HashMap::new();
39 for f in &t.fields {
40 fields.insert(
41 f.name.clone(),
42 FieldInfo {
43 sort: type_expr_to_smt(&f.ty),
44 },
45 );
46 }
47 structs.insert(t.name.clone(), fields);
48 }
49 Declaration::Enum(e) => {
50 enums.insert(e.name.clone());
51 }
52 _ => {}
53 }
54 }
55 TypeInfo { structs, enums }
56 }
57
58 fn field_sort(&self, struct_name: &str, field: &str) -> Option<&str> {
59 self.structs
60 .get(struct_name)
61 .and_then(|f| f.get(field))
62 .map(|fi| fi.sort.as_str())
63 }
64}
65
66fn type_expr_to_smt(te: &TypeExpr) -> String {
67 match te {
68 TypeExpr::Named(n) => match n.as_str() {
69 "Int" => "Int".to_string(),
70 "Bool" => "Bool".to_string(),
71 "String" => "String".to_string(),
72 other => other.to_string(),
73 },
74 TypeExpr::Qualified(module, name) => {
75 let full = format!("{module}_{name}");
77 match full.as_str() {
78 "Int" | "Bool" | "String" => full,
79 _ => full,
80 }
81 }
82 TypeExpr::Generic(name, args) => match name.as_str() {
83 "Seq" => format!("(Array Int {})", type_expr_to_smt(&args[0])),
84 "Set" => format!("(Array {} Bool)", type_expr_to_smt(&args[0])),
85 _ => name.clone(),
86 },
87 }
88}
89
90pub struct SmtEncoder {
93 lines: Vec<String>,
95 declared: HashSet<String>,
96 type_info: TypeInfo,
97 param_types: HashMap<String, String>,
99}
100
101impl SmtEncoder {
102 pub fn new(prog: &Program) -> Self {
103 Self {
104 lines: Vec::new(),
105 declared: HashSet::new(),
106 type_info: TypeInfo::from_program(prog),
107 param_types: HashMap::new(),
108 }
109 }
110
111 pub fn encode_vc(&mut self, vc: &VerificationCondition, prog: &Program) {
112 self.lines.clear();
113 self.declared.clear();
114 self.param_types.clear();
115
116 self.emit("(set-logic ALL)");
117
118 for decl in &prog.declarations {
120 if let Declaration::Enum(e) = &decl.node {
121 let variants: Vec<String> = e.variants.iter().map(|v| format!("({v})")).collect();
122 self.emit(&format!(
123 "(declare-datatype {} ({}))",
124 e.name,
125 variants.join(" ")
126 ));
127 self.declared.insert(e.name.clone());
128 }
129 }
130
131 for d in &vc.declarations {
133 match d {
134 crate::vcgen::SmtDecl::DeclareConst(name, ty) => {
135 let type_name = match ty {
136 TypeExpr::Named(n) => n.clone(),
137 TypeExpr::Qualified(m, n) => format!("{m}.{n}"),
138 TypeExpr::Generic(n, _) => n.clone(),
139 };
140
141 if self.type_info.structs.contains_key(&type_name) {
142 let field_entries: Vec<(String, String)> = self.type_info.structs
143 [&type_name]
144 .iter()
145 .map(|(fname, fi)| (fname.clone(), fi.sort.clone()))
146 .collect();
147 self.param_types.insert(name.clone(), type_name.clone());
148 for (field_name, sort) in &field_entries {
149 let const_name = format!("{name}_{field_name}");
150 self.declare_const(&const_name, sort);
151 self.declare_const(&format!("{const_name}_prime"), sort);
152 }
153 } else {
154 let sort = type_expr_to_smt(ty);
155 self.declare_const(name, &sort);
156 self.declare_const(&format!("{name}_prime"), &sort);
157 }
158 }
159 _ => {}
160 }
161 }
162
163 match vc.kind {
164 VcKind::Intent => {
165 for e in &vc.assumes {
167 let smt = self.expr_to_smt(e);
168 self.emit(&format!("(assert {smt})"));
169 }
170 if !vc.goals.is_empty() {
172 let goal_parts: Vec<String> =
173 vc.goals.iter().map(|e| self.expr_to_smt(e)).collect();
174 let conj = if goal_parts.len() == 1 {
175 goal_parts[0].clone()
176 } else {
177 format!("(and {})", goal_parts.join(" "))
178 };
179 self.emit(&format!("(assert (not {conj}))"));
180 }
181 }
182 VcKind::Theorem => {
183 if let Some(body) = vc.goals.first() {
186 let smt = self.expr_to_smt(body);
187 self.emit(&format!("(assert (not {smt}))"));
188 }
189 }
190 }
191
192 self.emit("(check-sat)");
193 }
196
197 pub fn get_output(&self) -> String {
198 self.lines.join("\n")
199 }
200
201 fn declare_const(&mut self, name: &str, sort: &str) {
202 if self.declared.insert(name.to_string()) {
203 self.emit(&format!("(declare-const {name} {sort})"));
204 }
205 }
206
207 fn emit(&mut self, line: &str) {
208 self.lines.push(line.to_string());
209 }
210
211 fn expr_to_smt(&mut self, expr: &Spanned<Expr>) -> String {
214 match &expr.node {
215 Expr::IntLit(v) => {
216 if *v < 0 {
217 format!("(- {})", -v)
218 } else {
219 v.to_string()
220 }
221 }
222 Expr::BoolLit(b) => b.to_string(),
223 Expr::StringLit(s) => format!("\"{s}\""),
224
225 Expr::Ident(name) => {
226 for decl_name in &self.type_info.enums {
228 if let Some(fields) = self.type_info.structs.get(decl_name) {
229 let _ = fields;
231 }
232 }
233 self.sanitize(name)
234 }
235
236 Expr::Prime(inner) => {
237 let base = self.expr_to_smt(inner);
238 format!("{base}_prime")
239 }
240
241 Expr::FieldAccess(base, field) => {
242 let b = self.expr_to_smt(base);
243 let name = format!("{b}_{field}");
244 if !self.declared.contains(&name) {
246 let sort = self
248 .resolve_field_sort(&b, field)
249 .unwrap_or_else(|| "Int".to_string());
250 self.declare_const(&name, &sort);
251 self.declare_const(&format!("{name}_prime"), &sort);
253 }
254 name
255 }
256
257 Expr::Index(base, idx) => {
258 let b = self.expr_to_smt(base);
259 let i = self.expr_to_smt(idx);
260 format!("(select {b} {i})")
261 }
262
263 Expr::BinOp(lhs, op, rhs) => {
264 let l = self.expr_to_smt(lhs);
265 let r = self.expr_to_smt(rhs);
266 match op {
267 BinOp::Add => format!("(+ {l} {r})"),
268 BinOp::Sub => format!("(- {l} {r})"),
269 BinOp::Mul => format!("(* {l} {r})"),
270 BinOp::Div => format!("(div {l} {r})"),
271 BinOp::Mod => format!("(mod {l} {r})"),
272 BinOp::Eq => format!("(= {l} {r})"),
273 BinOp::Neq => format!("(not (= {l} {r}))"),
274 BinOp::Lt => format!("(< {l} {r})"),
275 BinOp::Le => format!("(<= {l} {r})"),
276 BinOp::Gt => format!("(> {l} {r})"),
277 BinOp::Ge => format!("(>= {l} {r})"),
278 BinOp::And => format!("(and {l} {r})"),
279 BinOp::Or => format!("(or {l} {r})"),
280 BinOp::Implies => format!("(=> {l} {r})"),
281 }
282 }
283
284 Expr::UnaryOp(op, operand) => {
285 let o = self.expr_to_smt(operand);
286 match op {
287 UnaryOp::Not => format!("(not {o})"),
288 UnaryOp::Neg => format!("(- {o})"),
289 }
290 }
291
292 Expr::IfThenElse(c, t, e) => {
293 let sc = self.expr_to_smt(c);
294 let st = self.expr_to_smt(t);
295 let se = self.expr_to_smt(e);
296 format!("(ite {sc} {st} {se})")
297 }
298
299 Expr::Forall(vars, body) => {
300 let bindings: Vec<String> = vars
301 .iter()
302 .map(|v| {
303 let sort = type_expr_to_smt(&v.ty);
304 format!("({} {})", self.sanitize(&v.name), sort)
305 })
306 .collect();
307 let b = self.expr_to_smt(body);
308 format!("(forall ({}) {})", bindings.join(" "), b)
309 }
310
311 Expr::Exists(vars, body) => {
312 let bindings: Vec<String> = vars
313 .iter()
314 .map(|v| {
315 let sort = type_expr_to_smt(&v.ty);
316 format!("({} {})", self.sanitize(&v.name), sort)
317 })
318 .collect();
319 let b = self.expr_to_smt(body);
320 format!("(exists ({}) {})", bindings.join(" "), b)
321 }
322
323 Expr::Call(name, args) => {
324 if args.is_empty() {
325 self.sanitize(name)
326 } else {
327 let smt_args: Vec<String> = args.iter().map(|a| self.expr_to_smt(a)).collect();
328 format!("({} {})", self.sanitize(name), smt_args.join(" "))
329 }
330 }
331
332 Expr::Paren(inner) => self.expr_to_smt(inner),
333 }
334 }
335
336 fn resolve_field_sort(&self, base_var: &str, field: &str) -> Option<String> {
337 if let Some(type_name) = self.param_types.get(base_var) {
339 return self
340 .type_info
341 .field_sort(type_name, field)
342 .map(|s| s.to_string());
343 }
344 for (_, fields) in &self.type_info.structs {
347 for (fname, fi) in fields {
348 let compound = format!("{base_var}_{fname}");
349 if compound == format!("{base_var}_{field}") {
350 return Some(fi.sort.clone());
351 }
352 }
353 }
354 None
355 }
356
357 fn sanitize(&self, name: &str) -> String {
358 match name {
359 "and" | "or" | "not" | "true" | "false" | "ite" | "let" | "forall" | "exists" => {
360 format!("intent_{name}")
361 }
362 _ => name.to_string(),
363 }
364 }
365}
366
367fn smt_for_solver(smt_input: &str) -> String {
371 smt_input
372 .lines()
373 .filter(|line| {
374 let t = line.trim();
375 !t.starts_with("(check-sat)") && !t.starts_with("(get-model)")
376 })
377 .collect::<Vec<_>>()
378 .join("\n")
379}
380
381pub fn run_z3(smt_input: &str) -> VerifyResult {
382 let mut cfg = Config::new();
383 cfg.set_timeout_msec(5_000);
384 let smt = smt_for_solver(smt_input);
385
386 with_z3_config(&cfg, || {
387 let solver = Solver::new();
388 solver.from_string(smt.as_bytes());
389
390 match solver.check() {
391 SatResult::Unsat => VerifyResult::Verified,
392 SatResult::Sat => {
393 let counterexample = solver
394 .get_model()
395 .map(|model| parse_z3_model(&model.to_string()))
396 .unwrap_or_default();
397 VerifyResult::Failed { counterexample }
398 }
399 SatResult::Unknown => VerifyResult::Unknown {
400 reason: "Z3 returned unknown (timeout or undecidable fragment)".to_string(),
401 },
402 }
403 })
404}
405
406fn parse_z3_model(raw: &str) -> String {
409 let mut assignments = Vec::new();
410 let mut remaining = raw;
412 while let Some(pos) = remaining.find("define-fun ") {
413 remaining = &remaining[pos + 11..];
414 let name_end = remaining
416 .find(|c: char| c.is_whitespace())
417 .unwrap_or(remaining.len());
418 let name = &remaining[..name_end];
419 remaining = &remaining[name_end..];
420
421 if let Some(paren_pos) = remaining.find("()") {
423 remaining = &remaining[paren_pos + 2..].trim_start();
424 let sort_end = remaining
426 .find(|c: char| c.is_whitespace())
427 .unwrap_or(remaining.len());
428 remaining = &remaining[sort_end..].trim_start();
429 let value = extract_smt_value(remaining);
431
432 let display_name = name.replace('_', ".");
434 if !name.ends_with("_prime") {
436 assignments.push(format!("{display_name} = {value}"));
437 }
438 }
439 }
440
441 if assignments.is_empty() {
442 raw.to_string()
443 } else {
444 assignments.join(", ")
445 }
446}
447
448fn extract_smt_value(s: &str) -> String {
450 let s = s.trim();
451 if s.starts_with('(') {
452 if let Some(close) = find_matching_paren(s) {
454 let inner = &s[1..close].trim();
455 if inner.starts_with("- ") {
456 return format!("-{}", inner[2..].trim());
457 }
458 return inner.to_string();
459 }
460 s.chars().take(30).collect()
462 } else {
463 s.split(|c: char| c == ')' || c == '\n')
465 .next()
466 .unwrap_or(s)
467 .trim()
468 .to_string()
469 }
470}
471
472fn find_matching_paren(s: &str) -> Option<usize> {
473 let mut depth = 0;
474 for (i, c) in s.char_indices() {
475 match c {
476 '(' => depth += 1,
477 ')' => {
478 depth -= 1;
479 if depth == 0 {
480 return Some(i);
481 }
482 }
483 _ => {}
484 }
485 }
486 None
487}
488
489pub fn verify_vc(vc: &VerificationCondition, prog: &Program) -> VerifyResult {
491 if vc.kind == VcKind::Intent && vc.goals.is_empty() {
493 return VerifyResult::Verified;
494 }
495
496 let mut encoder = SmtEncoder::new(prog);
497 encoder.encode_vc(vc, prog);
498 let smt = encoder.get_output();
499 run_z3(&smt)
500}