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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
//! Implements warnings that are emitted in cases where code passed to
//! Tvix exhibits problems that the user could address.

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

use crate::SourceCode;

#[derive(Debug)]
pub enum WarningKind {
    DeprecatedLiteralURL,
    UselessInherit,
    UnusedBinding,
    ShadowedGlobal(&'static str),
    DeprecatedLegacyLet,
    InvalidNixPath(String),
    UselessBoolOperation(&'static str),
    DeadCode,
    EmptyInherit,
    EmptyLet,
    ShadowedOutput(String),
    SRIHashWrongPadding,

    /// 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, source: &SourceCode) -> String {
        let mut out = vec![];
        Emitter::vec(&mut out, Some(&*source.codemap())).emit(&[self.diagnostic(source)]);
        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, source: &SourceCode) {
        Emitter::stderr(ColorConfig::Auto, Some(&*source.codemap()))
            .emit(&[self.diagnostic(source)]);
    }

    /// 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, source: &SourceCode) -> String {
        match self.kind {
            WarningKind::DeprecatedLiteralURL => {
                "URL literal syntax is deprecated, use a quoted string instead".to_string()
            }

            WarningKind::UselessInherit => {
                "inherit does nothing (this variable already exists with the same value)"
                    .to_string()
            }

            WarningKind::UnusedBinding => {
                format!(
                    "variable '{}' is declared, but never used:",
                    source.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::InvalidNixPath(ref err) => {
                format!("invalid NIX_PATH resulted in a parse error: {}", err)
            }

            WarningKind::UselessBoolOperation(msg) => {
                format!("useless operation on boolean: {}", msg)
            }

            WarningKind::DeadCode => "this code will never be executed".to_string(),

            WarningKind::EmptyInherit => "this `inherit` statement is empty".to_string(),

            WarningKind::EmptyLet => "this `let`-expression contains no bindings".to_string(),

            WarningKind::ShadowedOutput(ref out) => format!(
                "this derivation's environment shadows the output name {}",
                out
            ),
            WarningKind::SRIHashWrongPadding => "SRI hash has wrong padding".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::InvalidNixPath(_) => "W006",
            WarningKind::UselessBoolOperation(_) => "W007",
            WarningKind::DeadCode => "W008",
            WarningKind::EmptyInherit => "W009",
            WarningKind::EmptyLet => "W010",
            WarningKind::ShadowedOutput(_) => "W011",
            WarningKind::SRIHashWrongPadding => "W012",

            WarningKind::NotImplemented(_) => "W999",
        }
    }

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

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