mirsa-domains 0.2.0

Abstract interpretation domains for mirsa
use rustc_middle::ty::TyCtxt;
use rustc_span::Span;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Level {
    Safe,
    Possible,
    Definite,
}

impl Level {
    pub(crate) fn combine(self, other: Level) -> Level {
        match (self, other) {
            (Level::Definite, _) | (_, Level::Definite) => Level::Definite,
            (Level::Possible, _) | (_, Level::Possible) => Level::Possible,
            (Level::Safe, Level::Safe) => Level::Safe,
        }
    }

    pub(crate) fn is_safe(self) -> bool {
        self == Level::Safe
    }

    pub(crate) fn code(self, definite: &'static str, possible: &'static str) -> &'static str {
        match self {
            Level::Definite => definite,
            Level::Possible => possible,
            Level::Safe => unreachable!(),
        }
    }
}

pub(crate) struct Finding {
    pub(crate) span: Span,
    pub(crate) code: &'static str,
    pub(crate) message: String,
    pub(crate) notes: Vec<String>,
}

impl Finding {
    pub(crate) fn new(
        span: Span,
        code: &'static str,
        message: impl Into<String>,
        notes: Vec<String>,
    ) -> Self {
        Self {
            span,
            code,
            message: message.into(),
            notes,
        }
    }

    pub(crate) fn for_level(
        level: Level,
        span: Span,
        definite_code: &'static str,
        possible_code: &'static str,
        definite_message: impl Into<String>,
        possible_message: impl Into<String>,
        notes: Vec<String>,
    ) -> Option<Self> {
        if level.is_safe() {
            return None;
        }

        let message = match level {
            Level::Definite => definite_message.into(),
            Level::Possible => possible_message.into(),
            Level::Safe => unreachable!(),
        };
        Some(Self::new(
            span,
            level.code(definite_code, possible_code),
            message,
            notes,
        ))
    }
}

pub(crate) fn emit_finding<'tcx>(tcx: TyCtxt<'tcx>, finding: &Finding) {
    let sm = tcx.sess.source_map();
    let loc = sm.span_to_diagnostic_string(finding.span);
    let snippet = sm
        .span_to_snippet(finding.span)
        .unwrap_or_else(|_| "unsafe call".to_string())
        .replace('\n', " ");
    println!("warning[{}]: {}", finding.code, finding.message);
    println!("  --> {loc}");
    println!("   |");
    println!("   | {snippet}");
    println!("   | ^ unsafe call here");
    for note in &finding.notes {
        println!("   = {note}");
    }
}