Skip to main content

aver/checker/
mod.rs

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