//! The libm rule, enforced rather than asserted in prose. //! //! CLAUDE.md requires transcendentals to go through `libm`, not `std`: //! //! > IEEE 754 pins the basic operations and `sqrt` but says nothing about //! > `exp`/`log`/`erf`, and `std` delegates to the *system* math library — //! > measured, `f64::exp` and `libm::exp` disagree on 9.7% of inputs by one //! > ULP. Since inference is an iterative fixed point, one ULP can change an //! > iteration count. //! //! The rule was stated clearly and still violated in three production sites, //! one of them `hypot` on the path of every scored event — whose measured //! divergence, 12.1%, is *higher* than the `exp` figure the rule cites as its //! own justification. Prose is evidently not enough, so this is a test. //! //! Tests may use either, which the crate documents, so `#[cfg(test)]` blocks //! are excluded. use std::{fs, path::Path}; /// Method-call spellings that reach the system math library. /// /// `sqrt` is deliberately absent: IEEE 754 specifies it exactly, so `std` and /// `libm` cannot disagree. `abs`, `recip`, `powi` and `mul_add` are likewise /// exact or specified. const FORBIDDEN: &[&str] = &[ "exp", "exp2", "exp_m1", "ln", "ln_1p", "log", "log2", "log10", "powf", "sin", "cos", "tan", "asin", "acos", "atan", "atan2", "sinh", "cosh", "tanh", "hypot", "cbrt", "erf", "erfc", ]; /// Strip `#[cfg(test)]` items by brace matching, plus comments and string /// literals, so a mention in prose is not mistaken for a call. fn production_code(source: &str) -> String { let mut out = String::with_capacity(source.len()); let bytes: Vec = source.chars().collect(); let mut i = 0; while i < bytes.len() { let rest: String = bytes[i..].iter().take(16).collect(); if rest.starts_with("#[cfg(test)]") { // Skip to the opening brace of the guarded item, then past its // matching close. let mut j = i; while j < bytes.len() && bytes[j] != '{' { j += 1; } let mut depth = 0usize; while j < bytes.len() { match bytes[j] { '{' => depth += 1, '}' => { depth -= 1; if depth == 0 { j += 1; break; } } _ => {} } j += 1; } i = j; continue; } if rest.starts_with("//") { while i < bytes.len() && bytes[i] != '\n' { i += 1; } continue; } if rest.starts_with("/*") { i += 2; while i + 1 < bytes.len() && !(bytes[i] == '*' && bytes[i + 1] == '/') { i += 1; } i += 2; continue; } if bytes[i] == '"' { i += 1; while i < bytes.len() && bytes[i] != '"' { if bytes[i] == '\\' { i += 1; } i += 1; } i += 1; continue; } out.push(bytes[i]); i += 1; } out } fn rust_files(dir: &Path, out: &mut Vec) { for entry in fs::read_dir(dir).expect("read src") { let path = entry.expect("dir entry").path(); if path.is_dir() { rust_files(&path, out); } else if path.extension().is_some_and(|e| e == "rs") { out.push(path); } } } #[test] fn production_code_never_calls_a_std_transcendental() { let mut files = Vec::new(); rust_files(Path::new("src"), &mut files); assert!(files.len() > 10, "expected to find the crate's sources"); let mut offences = Vec::new(); for path in &files { let source = fs::read_to_string(path).expect("read source"); let code = production_code(&source); for (n, line) in code.lines().enumerate() { for name in FORBIDDEN { let needle = format!(".{name}("); if line.contains(&needle) { offences.push(format!("{}:{}: {}", path.display(), n + 1, line.trim())); } } } } assert!( offences.is_empty(), "production code must call libm, not std, for transcendentals \ (`sqrt` is exempt — IEEE 754 specifies it):\n{}", offences.join("\n") ); } /// The stripper has to actually strip, or the test above passes vacuously. #[test] fn the_test_module_stripper_works() { let source = r#" fn production() { let _ = libm::exp(1.0); } #[cfg(test)] mod tests { fn allowed() { let x = 1.0f64.exp(); } } fn also_production() {} "#; let code = production_code(source); assert!( code.contains("also_production"), "stripped too much: {code}" ); assert!( !code.contains(".exp()"), "failed to strip cfg(test): {code}" ); } /// And it must not strip a doc comment's worth of prose into oblivion, nor /// mistake prose for a call. #[test] fn prose_is_not_mistaken_for_a_call() { let source = "/// Uses `x.exp()` in the docs.\nfn f() { let _ = libm::exp(1.0); }\n"; let code = production_code(source); assert!(!code.contains(".exp()"), "doc comment leaked: {code}"); assert!(code.contains("libm::exp"), "stripped real code: {code}"); }