use crate::span::Span;
use std::cell::Cell;

thread_local! {
    static SUPPRESS_WARNINGS: Cell<bool> = const { Cell::new(false) };
}

/// Suppress warning diagnostics on the current thread.
/// Returns a guard that restores the previous state on drop.
pub fn suppress_warnings() -> SuppressGuard {
    let prev = SUPPRESS_WARNINGS.with(|s| s.replace(true));
    SuppressGuard(prev)
}

pub struct SuppressGuard(bool);

impl Drop for SuppressGuard {
    fn drop(&mut self) {
        SUPPRESS_WARNINGS.with(|s| s.set(self.0));
    }
}

fn warnings_suppressed() -> bool {
    SUPPRESS_WARNINGS.with(|s| s.get())
}

/// A compiler diagnostic (error, warning, or hint).
#[derive(Clone, Debug)]
pub struct Diagnostic {
    pub severity: Severity,
    pub message: String,
    pub span: Span,
    pub notes: Vec<String>,
    pub help: Option<String>,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Severity {
    Error,
    Warning,
}

impl Diagnostic {
    pub fn error(message: String, span: Span) -> Self {
        Self {
            severity: Severity::Error,
            message,
            span,
            notes: Vec::new(),
            help: None,
        }
    }

    pub fn warning(message: String, span: Span) -> Self {
        Self {
            severity: Severity::Warning,
            message,
            span,
            notes: Vec::new(),
            help: None,
        }
    }

    pub fn with_note(mut self, note: String) -> Self {
        self.notes.push(note);
        self
    }

    pub fn with_help(mut self, help: String) -> Self {
        self.help = Some(help);
        self
    }

    /// Render the diagnostic to stderr using ariadne.
    pub fn render(&self, filename: &str, source: &str) {
        if self.severity == Severity::Warning && warnings_suppressed() {
            return;
        }
        use ariadne::{Color, Label, Report, ReportKind, Source};

        let kind = match self.severity {
            Severity::Error => ReportKind::Error,
            Severity::Warning => ReportKind::Warning,
        };

        let color = match self.severity {
            Severity::Error => Color::Red,
            Severity::Warning => Color::Yellow,
        };

        let mut report = Report::build(kind, filename, self.span.start as usize)
            .with_message(&self.message)
            .with_label(
                Label::new((filename, self.span.start as usize..self.span.end as usize))
                    .with_message(&self.message)
                    .with_color(color),
            );

        for note in &self.notes {
            report = report.with_note(note);
        }

        if let Some(help) = &self.help {
            report = report.with_help(help);
        }

        let _ = report.finish().eprint((filename, Source::from(source)));
    }
}

/// Render a list of diagnostics.
pub fn render_diagnostics(diagnostics: &[Diagnostic], filename: &str, source: &str) {
    for diag in diagnostics {
        diag.render(filename, source);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_error_construction() {
        let span = Span::new(0, 10, 15);
        let d = Diagnostic::error("type mismatch".to_string(), span);
        assert_eq!(d.severity, Severity::Error);
        assert_eq!(d.message, "type mismatch");
        assert_eq!(d.span.start, 10);
        assert_eq!(d.span.end, 15);
        assert!(d.notes.is_empty());
        assert!(d.help.is_none());
    }

    #[test]
    fn test_warning_construction() {
        let span = Span::dummy();
        let d = Diagnostic::warning("unused variable".to_string(), span);
        assert_eq!(d.severity, Severity::Warning);
        assert_eq!(d.message, "unused variable");
    }

    #[test]
    fn test_with_note() {
        let d = Diagnostic::error("error".to_string(), Span::dummy())
            .with_note("expected Field".to_string())
            .with_note("found U32".to_string());
        assert_eq!(d.notes.len(), 2);
        assert_eq!(d.notes[0], "expected Field");
        assert_eq!(d.notes[1], "found U32");
    }

    #[test]
    fn test_with_help() {
        let d = Diagnostic::error("error".to_string(), Span::dummy())
            .with_help("try as_field()".to_string());
        assert_eq!(d.help.as_deref(), Some("try as_field()"));
    }

    #[test]
    fn test_chained_builders() {
        let d = Diagnostic::warning("hint".to_string(), Span::new(0, 0, 5))
            .with_note("note 1".to_string())
            .with_help("help text".to_string())
            .with_note("note 2".to_string());
        assert_eq!(d.severity, Severity::Warning);
        assert_eq!(d.notes.len(), 2);
        assert!(d.help.is_some());
    }

    #[test]
    fn test_render_does_not_panic() {
        let source = "let x: Field = 1\nlet y: U32 = x\n";
        let d = Diagnostic::error("type mismatch".to_string(), Span::new(0, 18, 32))
            .with_note("expected U32, found Field".to_string());
        // Render to stderr โ€” just verify it doesn't panic
        d.render("test.tri", source);
    }

    #[test]
    fn test_render_diagnostics_multiple() {
        let source = "let x = 1\nlet y = 2\n";
        let diagnostics = vec![
            Diagnostic::warning("unused x".to_string(), Span::new(0, 4, 5)),
            Diagnostic::warning("unused y".to_string(), Span::new(0, 14, 15)),
        ];
        // Just verify it doesn't panic
        render_diagnostics(&diagnostics, "test.tri", source);
    }

    #[test]
    fn test_render_warning_does_not_panic() {
        let source = "fn main() {\n    as_u32(x)\n}\n";
        let d = Diagnostic::warning("redundant range check".to_string(), Span::new(0, 16, 25))
            .with_help("x is already proven U32".to_string());
        d.render("test.tri", source);
    }
}

Dimensions

trident/src/ir/mod.rs
trident/src/deploy/mod.rs
trident/src/syntax/mod.rs
trident/src/api/mod.rs
nebu/rs/extension/mod.rs
optica/src/render/mod.rs
trident/src/config/mod.rs
trident/src/field/mod.rs
trident/src/cli/mod.rs
optica/src/parser/mod.rs
trident/src/neural/mod.rs
trident/src/cost/mod.rs
trident/src/typecheck/mod.rs
optica/src/server/mod.rs
trident/src/package/mod.rs
optica/src/scanner/mod.rs
optica/src/output/mod.rs
trident/src/verify/mod.rs
optica/src/graph/mod.rs
trident/src/ast/mod.rs
trident/src/lsp/mod.rs
trident/src/runtime/mod.rs
trident/src/gpu/mod.rs
optica/src/query/mod.rs
trident/src/lsp/semantic/mod.rs
trident/src/verify/equiv/mod.rs
trident/src/package/hash/mod.rs
trident/src/neural/training/mod.rs
trident/src/verify/synthesize/mod.rs
trident/src/ir/tir/mod.rs
rs/macros/src/addressed/mod.rs
trident/src/package/registry/mod.rs
rs/rsc/src/lints/mod.rs
trident/src/verify/report/mod.rs
trident/src/config/resolve/mod.rs
trident/src/verify/solve/mod.rs
rs/macros/src/registers/mod.rs
trident/src/verify/smt/mod.rs
rs/macros/src/cell/mod.rs
rs/core/src/fixed_point/mod.rs
trident/src/neural/data/mod.rs
rs/core/src/bounded/mod.rs
trident/src/lsp/util/mod.rs
trident/src/typecheck/tests/mod.rs
trident/src/neural/model/mod.rs
trident/src/cost/stack_verifier/mod.rs
trident/src/syntax/grammar/mod.rs
trident/src/package/manifest/mod.rs
trident/src/syntax/parser/mod.rs
trident/src/ir/kir/mod.rs
trident/src/neural/inference/mod.rs
trident/src/syntax/lexer/mod.rs
trident/src/cost/model/mod.rs
trident/src/ir/lir/mod.rs
trident/src/syntax/format/mod.rs
trident/src/config/scaffold/mod.rs
trident/src/verify/sym/mod.rs
trident/src/api/tests/mod.rs
trident/src/package/store/mod.rs
trident/src/ir/tree/mod.rs
trident/src/ir/kir/lower/mod.rs
trident/src/ir/lir/lower/mod.rs
trident/src/ir/tir/lower/mod.rs
trident/src/ir/tir/builder/mod.rs
trident/src/ir/tir/neural/mod.rs
trident/src/neural/data/tir_graph/mod.rs
trident/src/syntax/parser/tests/mod.rs
cw-cyber/packages/cyber-std/src/tokenfactory/mod.rs
trident/src/ir/tree/lower/mod.rs
trident/src/ir/tir/stack/mod.rs
cw-cyber/contracts/cybernet/src/tests/mod.rs
trident/src/ir/tir/optimize/mod.rs

Local Graph