1mod coverage;
2mod cse;
3mod independence;
4mod intent;
5#[cfg(feature = "runtime")]
6mod law;
7mod module_effects;
8mod naming;
9mod perf;
10mod verify;
11mod verify_effects;
12
13use crate::ast::{
14 Expr, Literal, Pattern, SourceSpan, Spanned, TopLevel, TypeDef, VerifyBlock, VerifyKind,
15};
16
17#[derive(Debug, Clone)]
20pub enum VerifyCaseOutcome {
21 Pass,
22 Skipped,
23 SkippedAfterBaseFail,
30 Mismatch {
31 expected: String,
32 actual: String,
33 },
34 RuntimeError {
35 error: String,
36 },
37 UnexpectedErr {
38 err_repr: String,
39 },
40}
41
42#[derive(Debug, Clone)]
43pub struct VerifyCaseResult {
44 pub outcome: VerifyCaseOutcome,
45 pub span: Option<SourceSpan>,
46 pub case_expr: String,
47 pub case_index: usize,
48 pub case_total: usize,
49 pub law_context: Option<VerifyLawContext>,
50 pub from_hostile: bool,
57 pub hostile_profile: Option<String>,
64}
65
66#[derive(Debug, Clone)]
67pub struct VerifyLawContext {
68 pub givens: Vec<(String, String)>, pub law_expr: String,
70}
71
72pub struct VerifyResult {
73 pub fn_name: String,
74 pub block_label: String, pub passed: usize,
76 pub failed: usize,
77 pub skipped: usize,
78 pub case_results: Vec<VerifyCaseResult>,
79 pub failures: Vec<(String, String, String)>, }
82
83pub struct ModuleCheckFindings {
84 pub errors: Vec<CheckFinding>,
85 pub warnings: Vec<CheckFinding>,
86}
87
88pub(crate) type FnSigSummary = (Vec<crate::types::Type>, crate::types::Type, Vec<String>);
89pub(crate) type FnSigMap = std::collections::HashMap<String, FnSigSummary>;
90
91#[derive(Debug, Clone, PartialEq, Eq)]
92pub struct FindingSpan {
93 pub line: usize,
94 pub col: usize,
95 pub len: usize,
96 pub label: String,
97}
98
99#[derive(Debug, Clone, PartialEq, Eq)]
100pub struct CheckFinding {
101 pub line: usize,
102 pub module: Option<String>,
103 pub file: Option<String>,
104 pub fn_name: Option<String>,
105 pub message: String,
106 pub extra_spans: Vec<FindingSpan>,
107}
108
109fn module_name_for_items(items: &[TopLevel]) -> Option<String> {
110 items.iter().find_map(|item| {
111 if let TopLevel::Module(m) = item {
112 Some(m.name.clone())
113 } else {
114 None
115 }
116 })
117}
118
119fn dotted_name(expr: &Spanned<Expr>) -> Option<String> {
120 match &expr.node {
121 Expr::Ident(name) => Some(name.clone()),
122 Expr::Attr(base, field) => {
123 let mut prefix = dotted_name(base)?;
124 prefix.push('.');
125 prefix.push_str(field);
126 Some(prefix)
127 }
128 _ => None,
129 }
130}
131
132fn normalize_constructor_tag(path: &str) -> Option<String> {
133 let mut parts = path.split('.').collect::<Vec<_>>();
134 if parts.len() < 2 {
135 return None;
136 }
137 let variant = parts.pop()?;
138 let type_name = parts.pop()?;
139 Some(crate::visibility::member_key(type_name, variant))
140}
141
142fn constructor_tag_from_pattern(pattern: &Pattern) -> Option<String> {
143 match pattern {
144 Pattern::Constructor(path, _) => normalize_constructor_tag(path),
145 _ => None,
146 }
147}
148
149fn constructor_tag_from_expr(expr: &Spanned<Expr>) -> Option<String> {
150 match &expr.node {
151 Expr::Attr(_, _) => normalize_constructor_tag(&dotted_name(expr)?),
152 Expr::FnCall(callee, _) => normalize_constructor_tag(&dotted_name(callee)?),
153 Expr::Constructor(name, _) => normalize_constructor_tag(name),
154 _ => None,
155 }
156}
157
158fn expr_is_result_err_case(expr: &Spanned<Expr>) -> bool {
159 match &expr.node {
160 Expr::FnCall(callee, _) => dotted_name(callee)
161 .and_then(|path| normalize_constructor_tag(&path))
162 .is_some_and(|tag| tag == "Result.Err"),
163 Expr::Constructor(name, _) => {
164 normalize_constructor_tag(name).is_some_and(|tag| tag == "Result.Err")
165 }
166 _ => false,
167 }
168}
169
170fn expr_is_result_ok_case(expr: &Spanned<Expr>) -> bool {
171 match &expr.node {
172 Expr::FnCall(callee, _) => dotted_name(callee)
173 .and_then(|path| normalize_constructor_tag(&path))
174 .is_some_and(|tag| tag == "Result.Ok"),
175 Expr::Constructor(name, _) => {
176 normalize_constructor_tag(name).is_some_and(|tag| tag == "Result.Ok")
177 }
178 _ => false,
179 }
180}
181
182fn expr_is_option_none_case(expr: &Spanned<Expr>) -> bool {
183 match &expr.node {
184 Expr::Attr(_, _) => dotted_name(expr)
185 .and_then(|path| normalize_constructor_tag(&path))
186 .is_some_and(|tag| tag == "Option.None"),
187 Expr::Constructor(name, None) => {
188 normalize_constructor_tag(name).is_some_and(|tag| tag == "Option.None")
189 }
190 _ => false,
191 }
192}
193
194fn expr_is_option_some_case(expr: &Spanned<Expr>) -> bool {
195 match &expr.node {
196 Expr::FnCall(callee, _) => dotted_name(callee)
197 .and_then(|path| normalize_constructor_tag(&path))
198 .is_some_and(|tag| tag == "Option.Some"),
199 Expr::Constructor(name, _) => {
200 normalize_constructor_tag(name).is_some_and(|tag| tag == "Option.Some")
201 }
202 _ => false,
203 }
204}
205
206fn expr_is_bool_case(expr: &Spanned<Expr>, expected: bool) -> bool {
207 matches!(&expr.node, Expr::Literal(Literal::Bool(value)) if *value == expected)
208}
209
210fn expr_is_empty_list_case(expr: &Spanned<Expr>) -> bool {
211 matches!(&expr.node, Expr::List(items) if items.is_empty())
212}
213
214fn expr_is_non_empty_list_case(expr: &Spanned<Expr>) -> bool {
215 matches!(&expr.node, Expr::List(items) if !items.is_empty())
216}
217
218fn expr_is_empty_string_case(expr: &Spanned<Expr>) -> bool {
219 matches!(&expr.node, Expr::Literal(Literal::Str(value)) if value.is_empty())
220}
221
222fn expr_is_int_literal_case(expr: &Spanned<Expr>, expected: i64) -> bool {
223 matches!(&expr.node, Expr::Literal(Literal::Int(value)) if *value == expected)
224}
225
226fn verify_cases_block_is_well_formed(block: &VerifyBlock) -> bool {
227 matches!(block.kind, VerifyKind::Cases)
228 && !block.cases.is_empty()
229 && block.cases.iter().all(|(left, right)| {
230 verify_case_calls_target(left, &block.fn_name)
231 && !verify_case_calls_target(right, &block.fn_name)
232 })
233}
234
235fn local_sum_type_constructors(items: &[TopLevel], type_name: &str) -> Option<Vec<String>> {
236 items.iter().find_map(|item| match item {
237 TopLevel::TypeDef(TypeDef::Sum { name, variants, .. }) if name == type_name => Some(
238 variants
239 .iter()
240 .map(|variant| crate::visibility::member_key(name, &variant.name))
241 .collect(),
242 ),
243 _ => None,
244 })
245}
246
247fn callee_is_target(callee: &Spanned<Expr>, fn_name: &str) -> bool {
248 matches!(&callee.node, Expr::Ident(name) if name == fn_name)
249}
250
251use verify::collect_target_call_args;
253use verify::verify_case_calls_target;
254
255pub use coverage::{collect_verify_coverage_warnings, collect_verify_coverage_warnings_in};
257pub use cse::{collect_cse_warnings, collect_cse_warnings_in};
258pub use independence::{collect_independence_warnings, collect_independence_warnings_in};
259pub use intent::{
260 check_module_intent, check_module_intent_with_sigs, check_module_intent_with_sigs_in,
261 index_decisions,
262};
263#[cfg(feature = "runtime")]
264pub use law::{collect_verify_law_dependency_warnings, collect_verify_law_dependency_warnings_in};
265pub use module_effects::{collect_module_effects_warnings, collect_module_effects_warnings_in};
266pub use naming::{collect_naming_warnings, collect_naming_warnings_in};
267pub use perf::{collect_perf_warnings, collect_perf_warnings_in};
268pub use verify::{expr_to_str, merge_verify_blocks};
269pub use verify_effects::{
270 collect_plain_cases_effectful_warnings, collect_plain_cases_effectful_warnings_in,
271};