tvix_eval/
warnings.rs

1//! Implements warnings that are emitted in cases where code passed to
2//! Tvix exhibits problems that the user could address.
3
4use codemap_diagnostic::{ColorConfig, Diagnostic, Emitter, Level, SpanLabel, SpanStyle};
5
6use crate::SourceCode;
7
8#[derive(Debug)]
9pub enum WarningKind {
10    DeprecatedLiteralURL,
11    UselessInherit,
12    UnusedBinding,
13    ShadowedGlobal(&'static str),
14    DeprecatedLegacyLet,
15    InvalidNixPath(String),
16    UselessBoolOperation(&'static str),
17    DeadCode,
18    EmptyInherit,
19    EmptyLet,
20    ShadowedOutput(String),
21    SRIHashWrongPadding,
22
23    /// Tvix internal warning for features triggered by users that are
24    /// not actually implemented yet, but do not cause runtime failures.
25    NotImplemented(&'static str),
26}
27
28#[derive(Debug)]
29pub struct EvalWarning {
30    pub kind: WarningKind,
31    pub span: codemap::Span,
32}
33
34impl EvalWarning {
35    /// Render a fancy, human-readable output of this warning and
36    /// return it as a String. Note that this version of the output
37    /// does not include any colours or font styles.
38    pub fn fancy_format_str(&self, source: &SourceCode) -> String {
39        let mut out = vec![];
40        Emitter::vec(&mut out, Some(&*source.codemap())).emit(&[self.diagnostic(source)]);
41        String::from_utf8_lossy(&out).to_string()
42    }
43
44    /// Render a fancy, human-readable output of this warning and
45    /// print it to stderr. If rendered in a terminal that supports
46    /// colours and font styles, the output will include those.
47    pub fn fancy_format_stderr(&self, source: &SourceCode) {
48        Emitter::stderr(ColorConfig::Auto, Some(&*source.codemap()))
49            .emit(&[self.diagnostic(source)]);
50    }
51
52    /// Create the optional span label displayed as an annotation on
53    /// the underlined span of the warning.
54    fn span_label(&self) -> Option<String> {
55        match self.kind {
56            WarningKind::UnusedBinding | WarningKind::ShadowedGlobal(_) => {
57                Some("variable declared here".into())
58            }
59            _ => None,
60        }
61    }
62
63    /// Create the primary warning message displayed to users for a
64    /// warning.
65    fn message(&self, source: &SourceCode) -> String {
66        match self.kind {
67            WarningKind::DeprecatedLiteralURL => {
68                "URL literal syntax is deprecated, use a quoted string instead".to_string()
69            }
70
71            WarningKind::UselessInherit => {
72                "inherit does nothing (this variable already exists with the same value)"
73                    .to_string()
74            }
75
76            WarningKind::UnusedBinding => {
77                format!(
78                    "variable '{}' is declared, but never used:",
79                    source.source_slice(self.span)
80                )
81            }
82
83            WarningKind::ShadowedGlobal(name) => {
84                format!("declared variable '{name}' shadows a built-in global!")
85            }
86
87            WarningKind::DeprecatedLegacyLet => {
88                "legacy `let` syntax used, please rewrite this as `let .. in ...`".to_string()
89            }
90
91            WarningKind::InvalidNixPath(ref err) => {
92                format!("invalid NIX_PATH resulted in a parse error: {err}")
93            }
94
95            WarningKind::UselessBoolOperation(msg) => {
96                format!("useless operation on boolean: {msg}")
97            }
98
99            WarningKind::DeadCode => "this code will never be executed".to_string(),
100
101            WarningKind::EmptyInherit => "this `inherit` statement is empty".to_string(),
102
103            WarningKind::EmptyLet => "this `let`-expression contains no bindings".to_string(),
104
105            WarningKind::ShadowedOutput(ref out) => {
106                format!("this derivation's environment shadows the output name {out}")
107            }
108            WarningKind::SRIHashWrongPadding => "SRI hash has wrong padding".to_string(),
109
110            WarningKind::NotImplemented(what) => {
111                format!("feature not yet implemented in tvix: {what}")
112            }
113        }
114    }
115
116    /// Return the unique warning code for this variant which can be
117    /// used to refer users to documentation.
118    fn code(&self) -> &'static str {
119        match self.kind {
120            WarningKind::DeprecatedLiteralURL => "W001",
121            WarningKind::UselessInherit => "W002",
122            WarningKind::UnusedBinding => "W003",
123            WarningKind::ShadowedGlobal(_) => "W004",
124            WarningKind::DeprecatedLegacyLet => "W005",
125            WarningKind::InvalidNixPath(_) => "W006",
126            WarningKind::UselessBoolOperation(_) => "W007",
127            WarningKind::DeadCode => "W008",
128            WarningKind::EmptyInherit => "W009",
129            WarningKind::EmptyLet => "W010",
130            WarningKind::ShadowedOutput(_) => "W011",
131            WarningKind::SRIHashWrongPadding => "W012",
132
133            WarningKind::NotImplemented(_) => "W999",
134        }
135    }
136
137    fn diagnostic(&self, source: &SourceCode) -> Diagnostic {
138        let span_label = SpanLabel {
139            label: self.span_label(),
140            span: self.span,
141            style: SpanStyle::Primary,
142        };
143
144        Diagnostic {
145            level: Level::Warning,
146            message: self.message(source),
147            spans: vec![span_label],
148            code: Some(self.code().into()),
149        }
150    }
151}