Files
foxhunt/CLIPPY_QUICK_FIX_GUIDE.md
jgrusewski 633435fc6f fix(ml): Fix varmap scale/zero_point preservation test
- Add .get(0)? before .to_scalar() for scale extraction (line 605)
- Add .get(0)? before .to_scalar() for zero_point extraction (line 624)
- Handles [1] shape tensors from Tensor::new(&[value], device)
- Fixes test_quantization_preserves_scale_and_zero_point
- Ensures reliable SafeTensors save/load round-trip
2025-10-23 13:36:34 +02:00

16 KiB

Clippy Quick Fix Guide

Date: 2025-10-23 Audience: Developers starting Phase 2 incremental fixes Objective: Provide copy-paste patterns for the top 10 error categories


Quick Reference Table

Category Count Priority Fix Time Pattern #
Unsafe Blocks 84 P0 2-3h #1
Indexing/Slicing 253 P1 8-12h #2-4
As Conversions 193 P2 6-8h #5-7
Float Arithmetic 461 P2 2-3h #8
Arithmetic Side Effects 84 P2 4-6h #9-11
Assert with Result 75 P2 2-3h #12-13
Println/Eprintln 166 P3 2-3h #14
Default Numeric Fallback 361 P3 4-6h #15
Documentation 77 P3 3-4h #16-18
Miscellaneous 558 P4 10-15h #19-25

Total: 2,312 errors → Phase 2 target: <460 errors (80% reduction)


1. Unsafe Blocks (84 errors, P0)

Priority: ⚠️ CRITICAL - Fix first Time: 2-3 hours Files: trading_engine/src/lockfree/*.rs, trading_engine/src/simd/*.rs, trading_engine/src/affinity.rs

Pattern #1: Add Safety Comments

Before:

unsafe {
    *ptr = value;
}

After:

// SAFETY: `ptr` is guaranteed non-null and properly aligned by the constructor.
// The caller ensures exclusive access through the borrow checker (mutable borrow).
unsafe {
    *ptr = value;
}

Template for Common Cases:

  1. Raw Pointer Dereference:
// SAFETY: `ptr` is non-null (checked by constructor), properly aligned (from Box::into_raw),
// and has exclusive access (enforced by &mut self). The pointed-to memory is valid for the
// lifetime of this struct (deallocated in Drop).
unsafe { *ptr }
  1. SIMD Intrinsics:
// SAFETY: `values` is a valid slice of f64 with length >= 4 (checked above).
// The pointer is properly aligned for AVX2 operations (guaranteed by Vec allocation).
// No other threads access this memory (single-threaded context).
unsafe { _mm256_loadu_pd(values.as_ptr()) }
  1. Lock-Free Operations:
// SAFETY: `idx` is always < RING_SIZE due to modulo operation above.
// The array is initialized in new() and never deallocated until Drop.
// Memory ordering is Acquire/Release to ensure happens-before relationships.
unsafe { &*self.buffer[idx].load(Ordering::Acquire) }
  1. Thread Affinity:
// SAFETY: `pthread_self()` always returns a valid thread ID.
// `cpu_set` is properly initialized by CPU_ZERO and modified by CPU_SET.
// `pthread_setaffinity_np` is called with valid parameters and checked for errors.
unsafe {
    pthread_setaffinity_np(pthread_self(), size_of::<cpu_set_t>(), &cpu_set)
}

Checklist for Safety Comments:

  • What invariants does the code rely on? (non-null, aligned, initialized, etc.)
  • How are these invariants established? (constructor, caller contract, checks)
  • What are the lifetime guarantees? (valid for struct lifetime, checked above, etc.)
  • Are there any data races? (single-threaded, atomic ordering, exclusive access)

2. Indexing/Slicing (253 errors, P1)

Priority: 🔴 HIGH - Fix after P0 Time: 8-12 hours Files: adaptive-strategy/src/**/*.rs, trading_engine/src/**/*.rs

Pattern #2: Array Indexing → get()

Before:

let value = arr[index];

After (Option A - With error propagation):

let value = arr.get(index)
    .ok_or_else(|| Error::IndexOutOfBounds {
        index,
        len: arr.len(),
    })?;

After (Option B - With default):

let value = arr.get(index).copied().unwrap_or(0.0);

After (Option C - With explicit check):

if index >= arr.len() {
    return Err(Error::IndexOutOfBounds { index, len: arr.len() });
}
let value = arr[index]; // Now safe

Pattern #3: Slice Range → get()

Before:

let slice = &arr[start..end];

After:

let slice = arr.get(start..end)
    .ok_or_else(|| Error::InvalidRange {
        start,
        end,
        len: arr.len(),
    })?;

Pattern #4: first()/last() → Safe Alternatives

Before:

let first = arr[0];
let last = arr[arr.len() - 1];

After:

let first = arr.first().ok_or(Error::EmptyArray)?;
let last = arr.last().ok_or(Error::EmptyArray)?;

3. As Conversions (193 errors, P2)

Priority: 🟠 MEDIUM Time: 6-8 hours Files: Multiple (see report for top 20)

Pattern #5: Infallible Conversions (usize → u64, i32 → i64)

Before:

let x: u64 = len as u64;
let y: i64 = days as i64;

After:

let x: u64 = u64::from(len);  // or u64::try_from(len)?
let y: i64 = i64::from(days);

Pattern #6: Fallible Conversions (f64 → usize, u64 → i32)

Before:

let idx = value as usize;
let count = total as i32;

After:

let idx = usize::try_from(value as u64)
    .map_err(|_| Error::Overflow { value })?;

let count = i32::try_from(total)
    .map_err(|_| Error::Overflow { value: total })?;

Pattern #7: Precision Loss (with explicit allow)

Before:

let ratio = count as f64 / total as f64;

After (when precision loss is acceptable):

#[allow(clippy::cast_precision_loss)]
let count_f64 = count as f64; // Acceptable: count bounded by business logic (max 1M)

#[allow(clippy::cast_precision_loss)]
let total_f64 = total as f64; // Acceptable: total bounded by business logic (max 1M)

let ratio = count_f64 / total_f64;

When to use #[allow]:

  • Business logic bounds value (e.g., max 1M bars, max 10K symbols)
  • Precision loss documented and acceptable (e.g., ratios, percentages)
  • Alternative (checked conversion) has performance cost >10x

4. Float Arithmetic (461 errors, P2)

Priority: 🟠 MEDIUM (but low effort) Time: 2-3 hours Strategy: Add module-level #[allow] with documentation

Pattern #8: Module-Level Allow with Documentation

Before:

// adaptive-strategy/src/ensemble/confidence_aggregator.rs
pub fn calculate_ensemble_prediction(&self, predictions: &[Prediction]) -> Result<f64> {
    let weighted_sum = predictions.iter().map(|p| p.value * p.weight).sum::<f64>();
    let total_weight = predictions.iter().map(|p| p.weight).sum::<f64>();
    Ok(weighted_sum / total_weight)
}

After:

// adaptive-strategy/src/ensemble/confidence_aggregator.rs

//! Confidence Aggregator
//!
//! # Floating-Point Arithmetic
//!
//! This module performs extensive floating-point arithmetic for ensemble prediction aggregation.
//! IEEE 754 compliance is assumed. NaN/Inf handling:
//! - **NaN propagation**: Predictions with NaN confidence are filtered out
//! - **Inf handling**: Weights are clamped to [0.0, 1.0] to prevent Inf
//! - **Zero division**: Total weight is validated > 0.0 before division
//!
//! Clippy's `float_arithmetic` lint is intentionally allowed for this module.

#![allow(clippy::float_arithmetic)]

pub fn calculate_ensemble_prediction(&self, predictions: &[Prediction]) -> Result<f64> {
    let weighted_sum = predictions.iter().map(|p| p.value * p.weight).sum::<f64>();
    let total_weight = predictions.iter().map(|p| p.weight).sum::<f64>();

    if total_weight <= 0.0 {
        return Err(Error::ZeroTotalWeight);
    }

    Ok(weighted_sum / total_weight)
}

Files to Apply This Pattern:

  1. adaptive-strategy/src/ensemble/weight_optimizer.rs (113 errors)
  2. adaptive-strategy/src/ensemble/confidence_aggregator.rs (52 errors)
  3. adaptive-strategy/src/risk/kelly_position_sizer.rs (67 errors)
  4. adaptive-strategy/src/microstructure/mod.rs (47 errors)
  5. adaptive-strategy/src/models/tlob_model.rs (41 errors)

Total Impact: ~320 errors resolved in 2-3 hours


5. Arithmetic Side Effects (84 errors, P2)

Priority: 🟠 MEDIUM Time: 4-6 hours

Pattern #9: Integer Addition/Subtraction → checked_*

Before:

let elapsed = end_time - start_time;
let total = count + 1;

After:

let elapsed = end_time.checked_sub(start_time)
    .ok_or(Error::TimeOverflow)?;
let total = count.checked_add(1)
    .ok_or(Error::CountOverflow)?;

Pattern #10: Multiplication → saturating_* (when overflow is acceptable)

Before:

let total_bytes = count * item_size;

After:

let total_bytes = count.saturating_mul(item_size); // Cap at usize::MAX

Pattern #11: Duration Arithmetic → With Allow (when safe)

Before:

let age = (current_time - record.timestamp).num_hours() as f64;

After:

#[allow(clippy::arithmetic_side_effects)]
let duration = current_time - record.timestamp; // Acceptable: chrono handles overflow internally

#[allow(clippy::cast_precision_loss)]
let age = duration.num_hours() as f64; // Acceptable: hours bounded by i64 (~292 billion years)

6. Assert with Result (75 errors, P2)

Priority: 🟠 MEDIUM Time: 2-3 hours

Pattern #12: assert!(result.is_ok()) → unwrap() or expect()

Before:

#[test]
fn test_order_validation() {
    let result = validate_order(&order);
    assert!(result.is_ok());
}

After (Option A - unwrap):

#[test]
fn test_order_validation() {
    let result = validate_order(&order);
    result.unwrap(); // Panic with error details on failure
}

After (Option B - expect with context):

#[test]
fn test_order_validation() {
    let result = validate_order(&order);
    result.expect("Order validation should succeed for valid order");
}

Pattern #13: assert!(result.is_err()) → unwrap_err()

Before:

#[test]
fn test_invalid_order() {
    let result = validate_order(&invalid_order);
    assert!(result.is_err());
}

After:

#[test]
fn test_invalid_order() {
    let result = validate_order(&invalid_order);
    result.unwrap_err(); // Panic if unexpectedly succeeds
}

7. Println/Eprintln (166 errors, P3)

Priority: 🟡 LOW (but easy) Time: 2-3 hours Strategy: Find/replace with log crate

Pattern #14: Replace with log crate

Before:

println!("Processing order: {}", order_id);
eprintln!("Error processing order: {}", error);

After:

log::info!("Processing order: {}", order_id);
log::error!("Error processing order: {}", error);

Bulk Fix Script:

# Find all println! usage
rg "println!" --type rust -l | while read file; do
  # Replace println! with log::info!
  sed -i 's/println!/log::info!/g' "$file"
done

# Find all eprintln! usage
rg "eprintln!" --type rust -l | while read file; do
  # Replace eprintln! with log::error!
  sed -i 's/eprintln!/log::error!/g' "$file"
done

# Verify no breakage
cargo test --workspace

Notes:

  • Keep println! in CLI/TLI crates (user-facing output)
  • Keep eprintln! in error handling examples
  • Replace all others with log macros

8. Default Numeric Fallback (361 errors, P3)

Priority: 🟡 LOW Time: 4-6 hours

Pattern #15: Add Type Suffixes

Before:

let threshold = 0.5;
let count = 100;
let ratio = value / 1.0;

After:

let threshold = 0.5_f64;
let count = 100_usize;
let ratio = value / 1.0_f64;

Common Suffixes:

  • _f64 for floating-point
  • _usize for array indices/lengths
  • _i64 for timestamps
  • _u64 for large counts

Bulk Fix Strategy:

  1. Search for bare literals: rg "= [0-9]+\." --type rust
  2. Add _f64 suffix to all floating-point literals
  3. Search for integer literals in indexing contexts
  4. Add _usize suffix

9. Documentation (77 errors, P3)

Priority: 🟡 LOW Time: 3-4 hours

Pattern #16: Add # Errors Section

Before:

/// Validates the order fields.
pub fn validate_order(order: &Order) -> Result<(), ValidationError> {
    // ...
}

After:

/// Validates the order fields.
///
/// # Errors
///
/// Returns [`ValidationError::InvalidQuantity`] if quantity is zero or negative.
/// Returns [`ValidationError::InvalidPrice`] if price is zero or negative.
/// Returns [`ValidationError::InvalidSymbol`] if symbol is empty.
pub fn validate_order(order: &Order) -> Result<(), ValidationError> {
    // ...
}

Pattern #17: Fix Unbalanced Backticks

Before:

/// Calculate the `average price

After:

/// Calculate the `average price`

Pattern #18: Fix List Indentation

Before:

/// Returns:
/// - `Ok(())` on success
/// - `Err(...)` on failure

After:

/// Returns:
///
/// - `Ok(())` on success
/// - `Err(...)` on failure

10. Miscellaneous (558 errors, P4)

Priority: 🟢 COSMETIC Time: 10-15 hours Strategy: Apply clippy suggestions individually

Pattern #19: Unreadable Literals

Before: let limit = 1000000; After: let limit = 1_000_000;

Pattern #20: Manual Clamp

Before: let val = x.max(0.0).min(1.0); After: let val = x.clamp(0.0, 1.0);

Pattern #21: Redundant Clone

Before: let s = string.clone(); (when string is not used after) After: let s = string;

Pattern #22: Format in Format

Before: format!("Error: {}", format!("{}", error)) After: format!("Error: {}", error)

Pattern #23: Must Use Let

Before: let _ = vec.pop(); After: vec.pop(); or #[allow(clippy::must_use_let)] let _ = vec.pop();

Pattern #24: Unnecessary Wraps

Before: fn get() -> Result<(), Error> { Ok(()) } After: fn get() {} (if never returns Err)

Pattern #25: Cast Lossless

Before: let x = val as u64; (where val: u32) After: let x = u64::from(val);


Daily Progress Tracking Template

## Day 1: Unsafe Blocks (P0)
- [ ] trading_engine/src/lockfree/mpsc_queue.rs (19 errors)
- [ ] trading_engine/src/lockfree/ring_buffer.rs (18 errors)
- [ ] trading_engine/src/lockfree/atomic_ops.rs (23 errors)
- [ ] trading_engine/src/lockfree/small_batch_ring.rs (10 errors)
- [ ] trading_engine/src/simd/mod.rs (8 errors)
- [ ] trading_engine/src/affinity.rs (6 errors)
**Total**: 84 errors → 0 errors (2-3 hours)

## Day 2: Indexing/Slicing Part 1 (P1)
- [ ] adaptive-strategy/src/ensemble/weight_optimizer.rs (30 errors)
- [ ] adaptive-strategy/src/risk/kelly_position_sizer.rs (20 errors)
- [ ] adaptive-strategy/src/ensemble/confidence_aggregator.rs (15 errors)
- [ ] adaptive-strategy/src/microstructure/mod.rs (12 errors)
**Total**: 77 errors → 0 errors (4 hours)

## Day 3: Indexing/Slicing Part 2 (P1)
- [ ] trading_engine/src/lockfree/small_batch_ring.rs (25 errors)
- [ ] trading_engine/src/types/validation.rs (10 errors)
- [ ] trading_engine/src/types/optimized_order_book.rs (8 errors)
- [ ] Remaining files (~140 errors)
**Total**: 176 errors → 0 errors (4-6 hours)

## Days 4-5: As Conversions (P2)
- [ ] Multiple files (193 errors)
**Total**: 193 errors → 0 errors (6-8 hours)

Tips for Efficient Fixing

1. Use Ripgrep for Bulk Analysis

# Find all unwrapped indexing operations
rg '\[[^\]]+\]' --type rust -g '!tests' | wc -l

# Find all as conversions
rg 'as (f64|usize|i64|u64)' --type rust | wc -l

# Find all println usage
rg 'println!' --type rust -l

2. Use Clippy's --fix Flag (with caution)

# Auto-fix safe lints (redundant_clone, needless_borrow, etc.)
cargo clippy --fix --allow-dirty --allow-staged

# Review changes before committing
git diff

3. Test After Each Batch

# Run tests after fixing each category
cargo test -p adaptive-strategy
cargo test -p trading_engine

# Run full test suite at end of day
cargo test --workspace

4. Use Git Bisect for Regressions

# If tests fail after fixes
git bisect start
git bisect bad HEAD
git bisect good <last-known-good-commit>
# Git will help you find the breaking change

Success Criteria

Phase 2 Day 1 (Unsafe Blocks)

All 84 unsafe blocks have // SAFETY: comments cargo clippy shows 0 unsafe_block warnings All tests pass: cargo test --workspace

Phase 2 Week 1 (P0 + P1)

605 errors fixed (337 P0/P1 + 268 P2) No panics in production code paths Test pass rate maintains 99.4%

Phase 2 Week 2 (P2)

1,677 total errors fixed (72.5%) <635 warnings remaining Workspace builds with <100 warnings


Quick Reference: See CLIPPY_RECONFIGURATION_REPORT.md for full roadmap and category breakdown.