#![cfg_attr(coverage_nightly, coverage(off))]
use crate::cli::handlers::work_contract::{EvidenceType, FalsificationResult};
use anyhow::Result;
use std::path::Path;
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub(crate) fn count_lean_sorry_in_source(source: &str) -> usize {
let mut count = 0;
let mut in_block_comment = 0i32;
for line in source.lines() {
let trimmed = line.trim();
if trimmed.starts_with("--") {
continue;
}
let cleaned = strip_lean_block_comments(trimmed, &mut in_block_comment);
if in_block_comment > 0 {
continue;
}
if contains_sorry_word_boundary(&cleaned) {
count += 1;
}
}
count
}
fn strip_lean_block_comments(line: &str, depth: &mut i32) -> String {
let bytes = line.as_bytes();
let mut result = String::with_capacity(line.len());
let mut i = 0;
while i < bytes.len() {
if i + 1 < bytes.len() && bytes[i] == b'/' && bytes[i + 1] == b'-' {
*depth += 1;
i += 2;
continue;
}
if i + 1 < bytes.len() && bytes[i] == b'-' && bytes[i + 1] == b'/' && *depth > 0 {
*depth -= 1;
i += 2;
continue;
}
if *depth == 0 {
result.push(bytes[i] as char);
}
i += 1;
}
result
}
fn contains_sorry_word_boundary(line: &str) -> bool {
let bytes = line.as_bytes();
let sorry = b"sorry";
let mut pos = 0;
while pos + sorry.len() <= bytes.len() {
if let Some(idx) = line[pos..].find("sorry") {
let abs_idx = pos + idx;
let before_ok = abs_idx == 0
|| !(bytes[abs_idx - 1].is_ascii_alphanumeric() || bytes[abs_idx - 1] == b'_');
let after_ok = abs_idx + sorry.len() >= bytes.len()
|| !(bytes[abs_idx + sorry.len()].is_ascii_alphanumeric()
|| bytes[abs_idx + sorry.len()] == b'_');
if before_ok && after_ok {
return true;
}
pos = abs_idx + 1;
} else {
break;
}
}
false
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub(crate) fn test_formal_proof_verification(
project_path: &Path,
max_sorry_count: usize,
) -> Result<FalsificationResult> {
print!("Scanning .lean files for sorry... ");
let mut total_sorry = 0usize;
let mut sorry_files = Vec::new();
for entry in walkdir::WalkDir::new(project_path)
.into_iter()
.filter_map(|e| e.ok())
{
let path = entry.path();
if path.extension().is_some_and(|ext| ext == "lean") {
if let Ok(content) = std::fs::read_to_string(path) {
let count = count_lean_sorry_in_source(&content);
if count > 0 {
total_sorry += count;
sorry_files.push(path.to_path_buf());
}
}
}
}
if sorry_files.is_empty() && total_sorry == 0 {
return Ok(FalsificationResult::passed(
"No .lean files with sorry found".to_string(),
));
}
if total_sorry <= max_sorry_count {
Ok(FalsificationResult::passed(format!(
"{} sorry occurrence(s) within threshold (max: {})",
total_sorry, max_sorry_count
)))
} else {
Ok(FalsificationResult::failed(
format!(
"{} sorry occurrence(s) exceed threshold (max: {}), in: {}",
total_sorry,
max_sorry_count,
sorry_files
.iter()
.map(|p| p.display().to_string())
.collect::<Vec<_>>()
.join(", ")
),
EvidenceType::FileList(sorry_files),
))
}
}
#[cfg(test)]
mod formal_checks_tests {
use super::*;
#[test]
fn test_count_lean_sorry_empty_source_returns_zero() {
assert_eq!(count_lean_sorry_in_source(""), 0);
}
#[test]
fn test_count_lean_sorry_no_occurrences_returns_zero() {
let src = "def foo : Nat := 42\ndef bar : Nat := 99\n";
assert_eq!(count_lean_sorry_in_source(src), 0);
}
#[test]
fn test_count_lean_sorry_basic_occurrence() {
let src = "theorem t : True := sorry\n";
assert_eq!(count_lean_sorry_in_source(src), 1);
}
#[test]
fn test_count_lean_sorry_multiple_occurrences() {
let src = "theorem t1 : True := sorry\ntheorem t2 : True := sorry\n";
assert_eq!(count_lean_sorry_in_source(src), 2);
}
#[test]
fn test_count_lean_sorry_skips_line_comments() {
let src = "-- This contains sorry but is a comment\ntheorem t : True := sorry\n";
assert_eq!(count_lean_sorry_in_source(src), 1);
}
#[test]
fn test_count_lean_sorry_word_boundary_excludes_identifiers() {
let src = "def sorry_helper : Nat := 42\ndef presorry : Nat := 0\n";
assert_eq!(count_lean_sorry_in_source(src), 0);
}
#[test]
fn test_count_lean_sorry_inside_block_comment_skipped() {
let src = "/- sorry inside comment -/\ndef foo : Nat := 42\n";
assert_eq!(count_lean_sorry_in_source(src), 0);
}
#[test]
fn test_count_lean_sorry_around_block_comment() {
let src = "theorem a : True := sorry /- now in block\nstill in block -/ done\n";
let count = count_lean_sorry_in_source(src);
assert_eq!(
count, 0,
"lines that open block comments are dropped entirely"
);
}
#[test]
fn test_count_lean_sorry_after_closed_block_comment_counts() {
let src = "theorem a : True := /- comment -/ sorry\n";
let count = count_lean_sorry_in_source(src);
assert_eq!(count, 1);
}
#[test]
fn test_strip_lean_block_comments_no_comment_returns_original() {
let mut depth = 0;
let result = strip_lean_block_comments("def foo : Nat := 42", &mut depth);
assert_eq!(result, "def foo : Nat := 42");
assert_eq!(depth, 0);
}
#[test]
fn test_strip_lean_block_comments_inline_block_stripped() {
let mut depth = 0;
let result = strip_lean_block_comments("a /- hidden -/ b", &mut depth);
assert!(!result.contains("hidden"));
assert!(result.contains('a'));
assert!(result.contains('b'));
assert_eq!(depth, 0, "balanced block returns to depth 0");
}
#[test]
fn test_strip_lean_block_comments_unclosed_block_increments_depth() {
let mut depth = 0;
let _ = strip_lean_block_comments("before /- start", &mut depth);
assert_eq!(depth, 1, "unclosed block leaves depth at 1");
}
#[test]
fn test_strip_lean_block_comments_close_decrements_depth() {
let mut depth = 1;
let _ = strip_lean_block_comments("end -/ rest", &mut depth);
assert_eq!(depth, 0);
}
#[test]
fn test_contains_sorry_word_boundary_standalone_true() {
assert!(contains_sorry_word_boundary("theorem t := sorry"));
assert!(contains_sorry_word_boundary("sorry"));
assert!(contains_sorry_word_boundary(" sorry "));
}
#[test]
fn test_contains_sorry_word_boundary_in_identifier_false() {
assert!(!contains_sorry_word_boundary("sorrysame"));
assert!(!contains_sorry_word_boundary("presorry"));
assert!(!contains_sorry_word_boundary("sorry_helper"));
assert!(!contains_sorry_word_boundary("_sorry"));
}
#[test]
fn test_contains_sorry_word_boundary_no_sorry_false() {
assert!(!contains_sorry_word_boundary("def foo := 42"));
assert!(!contains_sorry_word_boundary(""));
}
#[test]
fn test_test_formal_proof_verification_no_lean_files_passes() {
let tmp = tempfile::tempdir().unwrap();
let result = test_formal_proof_verification(tmp.path(), 0).unwrap();
let _ = result;
}
#[test]
fn test_test_formal_proof_verification_lean_file_no_sorry_passes() {
let tmp = tempfile::tempdir().unwrap();
std::fs::write(tmp.path().join("a.lean"), "def foo : Nat := 42\n").unwrap();
let _ = test_formal_proof_verification(tmp.path(), 0).unwrap();
}
#[test]
fn test_test_formal_proof_verification_lean_file_with_sorry_within_threshold_passes() {
let tmp = tempfile::tempdir().unwrap();
std::fs::write(tmp.path().join("a.lean"), "theorem t : True := sorry\n").unwrap();
let result = test_formal_proof_verification(tmp.path(), 5).unwrap();
let _ = result; }
#[test]
fn test_test_formal_proof_verification_lean_file_with_sorry_exceeding_threshold_fails() {
let tmp = tempfile::tempdir().unwrap();
std::fs::write(
tmp.path().join("a.lean"),
"theorem t1 := sorry\ntheorem t2 := sorry\n",
)
.unwrap();
let result = test_formal_proof_verification(tmp.path(), 0).unwrap();
let _ = result;
}
}