1mod coverage;
2mod intent;
3mod law;
4mod verify;
5
6use crate::ast::{Expr, Literal, Pattern, SourceSpan, TopLevel, TypeDef, VerifyBlock, VerifyKind};
7
8#[derive(Debug, Clone)]
11pub enum VerifyCaseOutcome {
12 Pass,
13 Skipped,
14 Mismatch { expected: String, actual: String },
15 RuntimeError { error: String },
16 UnexpectedErr { err_repr: String },
17}
18
19#[derive(Debug, Clone)]
20pub struct VerifyCaseResult {
21 pub outcome: VerifyCaseOutcome,
22 pub span: Option<SourceSpan>,
23 pub case_expr: String,
24 pub case_index: usize,
25 pub case_total: usize,
26 pub law_context: Option<VerifyLawContext>,
27}
28
29#[derive(Debug, Clone)]
30pub struct VerifyLawContext {
31 pub givens: Vec<(String, String)>, pub law_expr: String,
33}
34
35pub struct VerifyResult {
36 pub fn_name: String,
37 pub block_label: String, pub passed: usize,
39 pub failed: usize,
40 pub skipped: usize,
41 pub case_results: Vec<VerifyCaseResult>,
42 pub failures: Vec<(String, String, String)>, }
45
46pub struct ModuleCheckFindings {
47 pub errors: Vec<CheckFinding>,
48 pub warnings: Vec<CheckFinding>,
49}
50
51pub(crate) type FnSigSummary = (Vec<crate::types::Type>, crate::types::Type, Vec<String>);
52pub(crate) type FnSigMap = std::collections::HashMap<String, FnSigSummary>;
53
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct CheckFinding {
56 pub line: usize,
57 pub module: Option<String>,
58 pub file: Option<String>,
59 pub message: String,
60}
61
62fn module_name_for_items(items: &[TopLevel]) -> Option<String> {
63 items.iter().find_map(|item| {
64 if let TopLevel::Module(m) = item {
65 Some(m.name.clone())
66 } else {
67 None
68 }
69 })
70}
71
72fn dotted_name(expr: &Expr) -> Option<String> {
73 match expr {
74 Expr::Ident(name) => Some(name.clone()),
75 Expr::Attr(base, field) => {
76 let mut prefix = dotted_name(base)?;
77 prefix.push('.');
78 prefix.push_str(field);
79 Some(prefix)
80 }
81 _ => None,
82 }
83}
84
85fn normalize_constructor_tag(path: &str) -> Option<String> {
86 let mut parts = path.split('.').collect::<Vec<_>>();
87 if parts.len() < 2 {
88 return None;
89 }
90 let variant = parts.pop()?;
91 let type_name = parts.pop()?;
92 Some(format!("{}.{}", type_name, variant))
93}
94
95fn constructor_tag_from_pattern(pattern: &Pattern) -> Option<String> {
96 match pattern {
97 Pattern::Constructor(path, _) => normalize_constructor_tag(path),
98 _ => None,
99 }
100}
101
102fn constructor_tag_from_expr(expr: &Expr) -> Option<String> {
103 match expr {
104 Expr::Attr(_, _) => normalize_constructor_tag(&dotted_name(expr)?),
105 Expr::FnCall(callee, _) => normalize_constructor_tag(&dotted_name(callee)?),
106 Expr::Constructor(name, _) => normalize_constructor_tag(name),
107 _ => None,
108 }
109}
110
111fn expr_is_result_err_case(expr: &Expr) -> bool {
112 match expr {
113 Expr::FnCall(callee, _) => dotted_name(callee)
114 .and_then(|path| normalize_constructor_tag(&path))
115 .is_some_and(|tag| tag == "Result.Err"),
116 Expr::Constructor(name, _) => {
117 normalize_constructor_tag(name).is_some_and(|tag| tag == "Result.Err")
118 }
119 _ => false,
120 }
121}
122
123fn expr_is_result_ok_case(expr: &Expr) -> bool {
124 match expr {
125 Expr::FnCall(callee, _) => dotted_name(callee)
126 .and_then(|path| normalize_constructor_tag(&path))
127 .is_some_and(|tag| tag == "Result.Ok"),
128 Expr::Constructor(name, _) => {
129 normalize_constructor_tag(name).is_some_and(|tag| tag == "Result.Ok")
130 }
131 _ => false,
132 }
133}
134
135fn expr_is_option_none_case(expr: &Expr) -> bool {
136 match expr {
137 Expr::Attr(_, _) => dotted_name(expr)
138 .and_then(|path| normalize_constructor_tag(&path))
139 .is_some_and(|tag| tag == "Option.None"),
140 Expr::Constructor(name, None) => {
141 normalize_constructor_tag(name).is_some_and(|tag| tag == "Option.None")
142 }
143 _ => false,
144 }
145}
146
147fn expr_is_option_some_case(expr: &Expr) -> bool {
148 match expr {
149 Expr::FnCall(callee, _) => dotted_name(callee)
150 .and_then(|path| normalize_constructor_tag(&path))
151 .is_some_and(|tag| tag == "Option.Some"),
152 Expr::Constructor(name, _) => {
153 normalize_constructor_tag(name).is_some_and(|tag| tag == "Option.Some")
154 }
155 _ => false,
156 }
157}
158
159fn expr_is_bool_case(expr: &Expr, expected: bool) -> bool {
160 matches!(expr, Expr::Literal(Literal::Bool(value)) if *value == expected)
161}
162
163fn expr_is_empty_list_case(expr: &Expr) -> bool {
164 matches!(expr, Expr::List(items) if items.is_empty())
165}
166
167fn expr_is_non_empty_list_case(expr: &Expr) -> bool {
168 matches!(expr, Expr::List(items) if !items.is_empty())
169}
170
171fn expr_is_empty_string_case(expr: &Expr) -> bool {
172 matches!(expr, Expr::Literal(Literal::Str(value)) if value.is_empty())
173}
174
175fn expr_is_int_literal_case(expr: &Expr, expected: i64) -> bool {
176 matches!(expr, Expr::Literal(Literal::Int(value)) if *value == expected)
177}
178
179fn verify_cases_block_is_well_formed(block: &VerifyBlock) -> bool {
180 matches!(block.kind, VerifyKind::Cases)
181 && !block.cases.is_empty()
182 && block.cases.iter().all(|(left, right)| {
183 verify_case_calls_target(left, &block.fn_name)
184 && !verify_case_calls_target(right, &block.fn_name)
185 })
186}
187
188fn local_sum_type_constructors(items: &[TopLevel], type_name: &str) -> Option<Vec<String>> {
189 items.iter().find_map(|item| match item {
190 TopLevel::TypeDef(TypeDef::Sum { name, variants, .. }) if name == type_name => Some(
191 variants
192 .iter()
193 .map(|variant| format!("{name}.{}", variant.name))
194 .collect(),
195 ),
196 _ => None,
197 })
198}
199
200fn callee_is_target(callee: &Expr, fn_name: &str) -> bool {
201 matches!(callee, Expr::Ident(name) if name == fn_name)
202}
203
204use verify::collect_target_call_args;
206use verify::verify_case_calls_target;
207
208pub use coverage::{collect_verify_coverage_warnings, collect_verify_coverage_warnings_in};
210pub use intent::{
211 check_module_intent, check_module_intent_with_sigs, check_module_intent_with_sigs_in,
212 index_decisions,
213};
214pub use law::{collect_verify_law_dependency_warnings, collect_verify_law_dependency_warnings_in};
215pub use verify::{expr_to_str, merge_verify_blocks, run_verify};