use std::fmt;
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RuleCode {
FST001,
FST002,
FST003,
FST004,
FST005,
FST006,
FST007,
FST008,
FST009,
FST010,
FST011,
FST012,
FST013,
FST014,
FST015,
FST016,
FST017,
}
impl RuleCode {
pub fn from_str(s: &str) -> Option<Self> {
match s.to_uppercase().as_str() {
"FST001" => Some(RuleCode::FST001),
"FST002" => Some(RuleCode::FST002),
"FST003" => Some(RuleCode::FST003),
"FST004" => Some(RuleCode::FST004),
"FST005" => Some(RuleCode::FST005),
"FST006" => Some(RuleCode::FST006),
"FST007" => Some(RuleCode::FST007),
"FST008" => Some(RuleCode::FST008),
"FST009" => Some(RuleCode::FST009),
"FST010" => Some(RuleCode::FST010),
"FST011" => Some(RuleCode::FST011),
"FST012" => Some(RuleCode::FST012),
"FST013" => Some(RuleCode::FST013),
"FST014" => Some(RuleCode::FST014),
"FST015" => Some(RuleCode::FST015),
"FST016" => Some(RuleCode::FST016),
"FST017" => Some(RuleCode::FST017),
_ => None,
}
}
pub fn all() -> &'static [RuleCode] {
&[
RuleCode::FST001,
RuleCode::FST002,
RuleCode::FST003,
RuleCode::FST004,
RuleCode::FST005,
RuleCode::FST006,
RuleCode::FST007,
RuleCode::FST008,
RuleCode::FST009,
RuleCode::FST010,
RuleCode::FST011,
RuleCode::FST012,
RuleCode::FST013,
RuleCode::FST014,
RuleCode::FST015,
RuleCode::FST016,
RuleCode::FST017,
]
}
pub fn is_fixable(&self) -> bool {
matches!(
self,
RuleCode::FST001
| RuleCode::FST002
| RuleCode::FST004
| RuleCode::FST005
| RuleCode::FST010
| RuleCode::FST012
| RuleCode::FST013
)
}
pub fn name(&self) -> &'static str {
match self {
RuleCode::FST001 => "duplicate-types",
RuleCode::FST002 => "interface-order",
RuleCode::FST003 => "comment-syntax",
RuleCode::FST004 => "unused-opens",
RuleCode::FST005 => "dead-code",
RuleCode::FST006 => "naming-conventions",
RuleCode::FST007 => "z3-complexity",
RuleCode::FST008 => "import-optimizer",
RuleCode::FST009 => "proof-hints",
RuleCode::FST010 => "spec-extractor",
RuleCode::FST011 => "effect-checker",
RuleCode::FST012 => "refinement-simplifier",
RuleCode::FST013 => "doc-checker",
RuleCode::FST014 => "test-generator",
RuleCode::FST015 => "module-deps",
RuleCode::FST016 => "perf-profiler",
RuleCode::FST017 => "security",
}
}
pub fn description(&self) -> &'static str {
match self {
RuleCode::FST001 => {
"Detects type definitions that appear in both .fst and .fsti files. \
F* only requires type definitions in interface files; duplicating them \
in implementation files can cause maintenance issues and confusion."
}
RuleCode::FST002 => {
"Detects when interface (.fsti) declarations are not in the same order \
as the implementation (.fst). This can cause F* Error 233: \
'Expected the definition of X to precede Y'."
}
RuleCode::FST003 => {
"Detects comment syntax issues including unclosed comments, \
premature comment closes, (*) which F* parses as unit value, \
and (-*) which is the magic wand operator from separation logic."
}
RuleCode::FST004 => {
"Detects unused open statements that import modules which are never \
used in the file. Removing unused opens improves compilation time \
and keeps dependencies explicit."
}
RuleCode::FST005 => {
"Detects dead code including unused private bindings, unreachable code \
after assert false/admit(), unused function parameters (with autofix), \
and unreachable match branches after wildcard patterns."
}
RuleCode::FST006 => {
"Checks naming conventions: types should be snake_case, functions should be \
snake_case, effects should be CamelCase. Allows legacy exceptions from F* stdlib."
}
RuleCode::FST007 => {
"Detects Z3 complexity patterns: quantifiers without SMTPat triggers, \
non-linear arithmetic, deep quantifier nesting, large match expressions, \
recursive functions without decreases, and high z3rlimit settings."
}
RuleCode::FST008 => {
"Detects import optimization opportunities: broad imports when selective would \
suffice, heavy modules (Tactics, Reflection) that slow verification, \
circular imports, and unnecessary transitive imports."
}
RuleCode::FST009 => {
"Suggests helpful proof hints based on code patterns. Detects list append \
length properties, modular arithmetic lemmas, pow2 normalization needs, \
sequence operation lemmas, and bitwise operation hints."
}
RuleCode::FST010 => {
"Detects .fst files without corresponding .fsti interface files. \
Interface files help with encapsulation, separate compilation, \
and documentation of public APIs."
}
RuleCode::FST011 => {
"Detects effect-related issues: admit() bypassing verification, magic() producing \
arbitrary values, unsafe_coerce bypassing type safety, overly permissive ML effect, \
and assume val axioms."
}
RuleCode::FST012 => {
"Simplifies refinement types: detects redundant refinements (nat{x >= 0}), \
promotable types (int{x >= 0} -> nat), useless refinements (T{True}), \
and unsatisfiable constraints (x > 5 /\\ x < 3)."
}
RuleCode::FST013 => {
"Checks for missing documentation on public val and type declarations. \
Skips private declarations, internal names (starting with _), type abbreviations, \
and .fst files that have a corresponding .fsti interface."
}
RuleCode::FST014 => {
"Generates test suggestions from type signatures. Analyzes function parameters \
for refinement type boundaries, collection edge cases (empty, singleton), \
integer bounds (min/max for U32, U64, etc.), and option type coverage."
}
RuleCode::FST015 => {
"Analyzes module dependencies from open/friend/include statements. \
Detects self-dependencies and warns when modules have too many dependencies (>15)."
}
RuleCode::FST016 => {
"Verification performance profiler that detects patterns causing slow verification: \
high fuel/ifuel settings, high z3rlimit values, --quake/--retry usage indicating \
instability, and high assertion density suggesting proof complexity."
}
RuleCode::FST017 => {
"Security checker for cryptographic F* code. Detects hardcoded secrets, \
RawIntTypes usage that may break secret independence, and potential \
secret-dependent branches that could enable timing side-channel attacks."
}
}
}
}
impl fmt::Display for RuleCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RuleCode::FST001 => write!(f, "FST001"),
RuleCode::FST002 => write!(f, "FST002"),
RuleCode::FST003 => write!(f, "FST003"),
RuleCode::FST004 => write!(f, "FST004"),
RuleCode::FST005 => write!(f, "FST005"),
RuleCode::FST006 => write!(f, "FST006"),
RuleCode::FST007 => write!(f, "FST007"),
RuleCode::FST008 => write!(f, "FST008"),
RuleCode::FST009 => write!(f, "FST009"),
RuleCode::FST010 => write!(f, "FST010"),
RuleCode::FST011 => write!(f, "FST011"),
RuleCode::FST012 => write!(f, "FST012"),
RuleCode::FST013 => write!(f, "FST013"),
RuleCode::FST014 => write!(f, "FST014"),
RuleCode::FST015 => write!(f, "FST015"),
RuleCode::FST016 => write!(f, "FST016"),
RuleCode::FST017 => write!(f, "FST017"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiagnosticSeverity {
Error,
Warning,
Info,
Hint,
}
impl fmt::Display for DiagnosticSeverity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DiagnosticSeverity::Error => write!(f, "error"),
DiagnosticSeverity::Warning => write!(f, "warning"),
DiagnosticSeverity::Info => write!(f, "info"),
DiagnosticSeverity::Hint => write!(f, "hint"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Range {
pub start_line: usize,
pub start_col: usize,
pub end_line: usize,
pub end_col: usize,
}
impl Range {
pub fn new(start_line: usize, start_col: usize, end_line: usize, end_col: usize) -> Self {
Self {
start_line,
start_col,
end_line,
end_col,
}
}
pub fn point(line: usize, col: usize) -> Self {
Self {
start_line: line,
start_col: col,
end_line: line,
end_col: col,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum FixConfidence {
Low = 0,
Medium = 50,
High = 100,
}
impl FixConfidence {
pub fn as_f64(&self) -> f64 {
match self {
FixConfidence::Low => 0.25,
FixConfidence::Medium => 0.5,
FixConfidence::High => 1.0,
}
}
pub fn from_f64(score: f64) -> Self {
if score >= 0.75 {
FixConfidence::High
} else if score >= 0.4 {
FixConfidence::Medium
} else {
FixConfidence::Low
}
}
}
impl Default for FixConfidence {
fn default() -> Self {
FixConfidence::Medium
}
}
impl fmt::Display for FixConfidence {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FixConfidence::Low => write!(f, "low"),
FixConfidence::Medium => write!(f, "medium"),
FixConfidence::High => write!(f, "high"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum FixSafetyLevel {
Safe,
Caution,
Unsafe,
}
impl Default for FixSafetyLevel {
fn default() -> Self {
FixSafetyLevel::Caution
}
}
impl fmt::Display for FixSafetyLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FixSafetyLevel::Safe => write!(f, "safe"),
FixSafetyLevel::Caution => write!(f, "caution"),
FixSafetyLevel::Unsafe => write!(f, "unsafe"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum DeadCodeSafetyLevel {
Safe,
Caution,
Unsafe,
}
impl Default for DeadCodeSafetyLevel {
fn default() -> Self {
DeadCodeSafetyLevel::Caution
}
}
impl fmt::Display for DeadCodeSafetyLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DeadCodeSafetyLevel::Safe => write!(f, "safe"),
DeadCodeSafetyLevel::Caution => write!(f, "caution"),
DeadCodeSafetyLevel::Unsafe => write!(f, "unsafe"),
}
}
}
#[derive(Debug, Clone)]
pub struct Fix {
pub message: String,
pub edits: Vec<Edit>,
pub confidence: FixConfidence,
pub is_safe: bool,
pub unsafe_reason: Option<String>,
pub safety_level: FixSafetyLevel,
pub reversible: bool,
pub requires_review: bool,
}
impl Fix {
pub fn new(message: impl Into<String>, edits: Vec<Edit>) -> Self {
Self {
message: message.into(),
edits,
confidence: FixConfidence::Medium,
is_safe: true,
unsafe_reason: None,
safety_level: FixSafetyLevel::Caution,
reversible: true,
requires_review: false,
}
}
pub fn unsafe_fix(
message: impl Into<String>,
edits: Vec<Edit>,
confidence: FixConfidence,
reason: impl Into<String>,
) -> Self {
Self {
message: message.into(),
edits,
confidence,
is_safe: false,
unsafe_reason: Some(reason.into()),
safety_level: FixSafetyLevel::Unsafe,
reversible: false,
requires_review: true,
}
}
pub fn safe(message: impl Into<String>, edits: Vec<Edit>) -> Self {
Self {
message: message.into(),
edits,
confidence: FixConfidence::High,
is_safe: true,
unsafe_reason: None,
safety_level: FixSafetyLevel::Safe,
reversible: true,
requires_review: false,
}
}
pub fn caution(message: impl Into<String>, edits: Vec<Edit>) -> Self {
Self {
message: message.into(),
edits,
confidence: FixConfidence::Medium,
is_safe: true,
unsafe_reason: None,
safety_level: FixSafetyLevel::Caution,
reversible: true,
requires_review: true,
}
}
pub fn with_confidence(mut self, confidence: FixConfidence) -> Self {
self.confidence = confidence;
self
}
pub fn with_safety_level(mut self, level: FixSafetyLevel) -> Self {
self.safety_level = level;
self.is_safe = level == FixSafetyLevel::Safe;
self
}
pub fn with_reversible(mut self, reversible: bool) -> Self {
self.reversible = reversible;
self
}
pub fn with_requires_review(mut self, requires_review: bool) -> Self {
self.requires_review = requires_review;
self
}
pub fn mark_unsafe(mut self, reason: impl Into<String>) -> Self {
self.is_safe = false;
self.unsafe_reason = Some(reason.into());
self.safety_level = FixSafetyLevel::Unsafe;
self.requires_review = true;
self
}
pub fn can_auto_apply(&self) -> bool {
self.is_safe && self.confidence == FixConfidence::High
}
pub fn can_apply_without_force(&self) -> bool {
self.safety_level != FixSafetyLevel::Unsafe
}
pub fn requires_force(&self) -> bool {
self.safety_level == FixSafetyLevel::Unsafe
}
}
#[derive(Debug, Clone)]
pub struct Edit {
pub file: PathBuf,
pub range: Range,
pub new_text: String,
}
#[derive(Debug, Clone)]
pub struct Diagnostic {
pub rule: RuleCode,
pub severity: DiagnosticSeverity,
pub file: PathBuf,
pub range: Range,
pub message: String,
pub fix: Option<Fix>,
}
pub trait Rule: Send + Sync {
fn code(&self) -> RuleCode;
fn check(&self, file: &PathBuf, content: &str) -> Vec<Diagnostic>;
fn check_pair(
&self,
_fst_file: &PathBuf,
_fst_content: &str,
_fsti_file: &PathBuf,
_fsti_content: &str,
) -> Vec<Diagnostic> {
vec![]
}
fn requires_pair(&self) -> bool {
false
}
}
pub fn print_rules() {
println!("Available F* lint rules:\n");
println!(
"{:<8} {:<20} {:<8} {}",
"Code", "Name", "Fixable", "Description"
);
println!("{}", "-".repeat(80));
for code in RuleCode::all() {
let fixable = if code.is_fixable() { "Yes" } else { "No" };
let desc = code.description();
let short_desc = desc.split('.').next().unwrap_or(desc);
println!(
"{:<8} {:<20} {:<8} {}",
code,
code.name(),
fixable,
short_desc
);
}
println!("\nUse --select to enable specific rules (e.g., --select FST001,FST002)");
println!("Use --ignore to disable specific rules (e.g., --ignore FST003)");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fix_safety_level_ordering() {
assert!(FixSafetyLevel::Safe < FixSafetyLevel::Caution);
assert!(FixSafetyLevel::Caution < FixSafetyLevel::Unsafe);
assert!(FixSafetyLevel::Safe < FixSafetyLevel::Unsafe);
}
#[test]
fn test_fix_safety_level_display() {
assert_eq!(format!("{}", FixSafetyLevel::Safe), "safe");
assert_eq!(format!("{}", FixSafetyLevel::Caution), "caution");
assert_eq!(format!("{}", FixSafetyLevel::Unsafe), "unsafe");
}
#[test]
fn test_fix_safety_level_default() {
assert_eq!(FixSafetyLevel::default(), FixSafetyLevel::Caution);
}
#[test]
fn test_fix_confidence_as_f64() {
assert_eq!(FixConfidence::Low.as_f64(), 0.25);
assert_eq!(FixConfidence::Medium.as_f64(), 0.5);
assert_eq!(FixConfidence::High.as_f64(), 1.0);
}
#[test]
fn test_fix_confidence_from_f64() {
assert_eq!(FixConfidence::from_f64(0.0), FixConfidence::Low);
assert_eq!(FixConfidence::from_f64(0.3), FixConfidence::Low);
assert_eq!(FixConfidence::from_f64(0.5), FixConfidence::Medium);
assert_eq!(FixConfidence::from_f64(0.7), FixConfidence::Medium);
assert_eq!(FixConfidence::from_f64(0.8), FixConfidence::High);
assert_eq!(FixConfidence::from_f64(1.0), FixConfidence::High);
}
#[test]
fn test_fix_new_defaults() {
let fix = Fix::new("test fix", vec![]);
assert_eq!(fix.confidence, FixConfidence::Medium);
assert!(fix.is_safe);
assert!(fix.unsafe_reason.is_none());
assert_eq!(fix.safety_level, FixSafetyLevel::Caution);
assert!(fix.reversible);
assert!(!fix.requires_review);
}
#[test]
fn test_fix_safe() {
let fix = Fix::safe("safe fix", vec![]);
assert_eq!(fix.confidence, FixConfidence::High);
assert!(fix.is_safe);
assert!(fix.unsafe_reason.is_none());
assert_eq!(fix.safety_level, FixSafetyLevel::Safe);
assert!(fix.reversible);
assert!(!fix.requires_review);
}
#[test]
fn test_fix_caution() {
let fix = Fix::caution("caution fix", vec![]);
assert_eq!(fix.confidence, FixConfidence::Medium);
assert!(fix.is_safe);
assert!(fix.unsafe_reason.is_none());
assert_eq!(fix.safety_level, FixSafetyLevel::Caution);
assert!(fix.reversible);
assert!(fix.requires_review);
}
#[test]
fn test_fix_unsafe_fix() {
let fix = Fix::unsafe_fix("unsafe fix", vec![], FixConfidence::Low, "reason");
assert_eq!(fix.confidence, FixConfidence::Low);
assert!(!fix.is_safe);
assert_eq!(fix.unsafe_reason, Some("reason".to_string()));
assert_eq!(fix.safety_level, FixSafetyLevel::Unsafe);
assert!(!fix.reversible);
assert!(fix.requires_review);
}
#[test]
fn test_fix_with_safety_level() {
let fix = Fix::new("test", vec![])
.with_safety_level(FixSafetyLevel::Safe);
assert_eq!(fix.safety_level, FixSafetyLevel::Safe);
assert!(fix.is_safe);
let fix = Fix::new("test", vec![])
.with_safety_level(FixSafetyLevel::Unsafe);
assert_eq!(fix.safety_level, FixSafetyLevel::Unsafe);
assert!(!fix.is_safe); }
#[test]
fn test_fix_with_reversible() {
let fix = Fix::new("test", vec![]).with_reversible(false);
assert!(!fix.reversible);
let fix = Fix::new("test", vec![]).with_reversible(true);
assert!(fix.reversible);
}
#[test]
fn test_fix_with_requires_review() {
let fix = Fix::new("test", vec![]).with_requires_review(true);
assert!(fix.requires_review);
let fix = Fix::new("test", vec![]).with_requires_review(false);
assert!(!fix.requires_review);
}
#[test]
fn test_fix_mark_unsafe() {
let fix = Fix::safe("originally safe", vec![])
.mark_unsafe("now unsafe");
assert!(!fix.is_safe);
assert_eq!(fix.unsafe_reason, Some("now unsafe".to_string()));
assert_eq!(fix.safety_level, FixSafetyLevel::Unsafe);
assert!(fix.requires_review);
}
#[test]
fn test_can_auto_apply() {
let fix = Fix::safe("safe", vec![]);
assert!(fix.can_auto_apply());
let fix = Fix::caution("caution", vec![]);
assert!(!fix.can_auto_apply());
let fix = Fix::unsafe_fix("unsafe", vec![], FixConfidence::High, "reason");
assert!(!fix.can_auto_apply());
let fix = Fix::new("medium", vec![]); assert!(!fix.can_auto_apply());
}
#[test]
fn test_can_apply_without_force() {
let fix = Fix::safe("safe", vec![]);
assert!(fix.can_apply_without_force());
let fix = Fix::caution("caution", vec![]);
assert!(fix.can_apply_without_force());
let fix = Fix::unsafe_fix("unsafe", vec![], FixConfidence::Low, "reason");
assert!(!fix.can_apply_without_force());
}
#[test]
fn test_requires_force() {
let fix = Fix::safe("safe", vec![]);
assert!(!fix.requires_force());
let fix = Fix::caution("caution", vec![]);
assert!(!fix.requires_force());
let fix = Fix::unsafe_fix("unsafe", vec![], FixConfidence::Low, "reason");
assert!(fix.requires_force());
}
#[test]
fn test_chained_builders() {
let fix = Fix::new("test", vec![])
.with_confidence(FixConfidence::High)
.with_safety_level(FixSafetyLevel::Safe)
.with_reversible(true)
.with_requires_review(false);
assert_eq!(fix.confidence, FixConfidence::High);
assert_eq!(fix.safety_level, FixSafetyLevel::Safe);
assert!(fix.reversible);
assert!(!fix.requires_review);
}
}