about summary refs log tree commit diff
path: root/tvix/eval/src/warnings.rs
blob: ddb7510c8ab7ea58988eae3e3550273a63dabe5c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//! Implements warnings that are emitted in cases where code passed to
//! Tvix exhibits problems that the user could address.

use codemap::CodeMap;
use codemap_diagnostic::{ColorConfig, Diagnostic, Emitter, Level, SpanLabel, SpanStyle};

#[derive(Debug)]
pub enum WarningKind {
    DeprecatedLiteralURL,
    UselessInherit,
    UnusedBinding,
    ShadowedGlobal(&'static str),
    DeprecatedLegacyLet,

    /// Tvix internal warning for features triggered by users that are
    /// not actually implemented yet, but do not cause runtime failures.
    NotImplemented(&'static str),
}

#[derive(Debug)]
pub struct EvalWarning {
    pub kind: WarningKind,
    pub span: codemap::Span,
}

impl EvalWarning {
    /// Render a fancy, human-readable output of this warning and
    /// return it as a String. Note that this version of the output
    /// does not include any colours or font styles.
    pub fn fancy_format_str(&self, codemap: &CodeMap) -> String {
        let mut out = vec![];
        Emitter::vec(&mut out, Some(codemap)).emit(&[self.diagnostic(codemap)]);
        String::from_utf8_lossy(&out).to_string()
    }

    /// Render a fancy, human-readable output of this warning and
    /// print it to stderr. If rendered in a terminal that supports
    /// colours and font styles, the output will include those.
    pub fn fancy_format_stderr(&self, codemap: &CodeMap) {
        Emitter::stderr(ColorConfig::Auto, Some(codemap)).emit(&[self.diagnostic(codemap)]);
    }

    /// Create the optional span label displayed as an annotation on
    /// the underlined span of the warning.
    fn span_label(&self) -> Option<String> {
        match self.kind {
            WarningKind::UnusedBinding | WarningKind::ShadowedGlobal(_) => {
                Some("variable declared here".into())
            }
            _ => None,
        }
    }

    /// Create the primary warning message displayed to users for a
    /// warning.
    fn message(&self, codemap: &CodeMap) -> String {
        match self.kind {
            WarningKind::DeprecatedLiteralURL => {
                "URL literal syntax is deprecated, use a quoted string instead".to_string()
            }

            WarningKind::UselessInherit => {
                "inherited variable already exists with the same value".to_string()
            }

            WarningKind::UnusedBinding => {
                let file = codemap.find_file(self.span.low());

                format!(
                    "variable '{}' is declared, but never used:",
                    file.source_slice(self.span)
                )
            }

            WarningKind::ShadowedGlobal(name) => {
                format!("declared variable '{}' shadows a built-in global!", name)
            }

            WarningKind::DeprecatedLegacyLet => {
                "legacy `let` syntax used, please rewrite this as `let .. in ...`".to_string()
            }

            WarningKind::NotImplemented(what) => {
                format!("feature not yet implemented in tvix: {}", what)
            }
        }
    }

    /// Return the unique warning code for this variant which can be
    /// used to refer users to documentation.
    fn code(&self) -> &'static str {
        match self.kind {
            WarningKind::DeprecatedLiteralURL => "W001",
            WarningKind::UselessInherit => "W002",
            WarningKind::UnusedBinding => "W003",
            WarningKind::ShadowedGlobal(_) => "W004",
            WarningKind::DeprecatedLegacyLet => "W005",
            WarningKind::NotImplemented(_) => "W999",
        }
    }

    fn diagnostic(&self, codemap: &CodeMap) -> Diagnostic {
        let span_label = SpanLabel {
            label: self.span_label(),
            span: self.span,
            style: SpanStyle::Primary,
        };

        Diagnostic {
            level: Level::Warning,
            message: self.message(codemap),
            spans: vec![span_label],
            code: Some(self.code().into()),
        }
    }
}