vrl/compiler/expression/
variable.rs

1use crate::diagnostic::{DiagnosticMessage, Label};
2use crate::value::Value;
3use std::fmt;
4
5use crate::compiler::state::{TypeInfo, TypeState};
6use crate::compiler::{
7    Context, Expression, Span, TypeDef,
8    expression::{Resolved, levenstein},
9    parser::ast::Ident,
10    state::LocalEnv,
11};
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct Variable {
15    ident: Ident,
16}
17
18impl Variable {
19    pub(crate) fn new(span: Span, ident: Ident, local: &LocalEnv) -> Result<Self, Error> {
20        if local.variable(&ident).is_none() {
21            let idents = local
22                .variable_idents()
23                .map(std::clone::Clone::clone)
24                .collect::<Vec<_>>();
25
26            return Err(Error::undefined(ident, span, idents));
27        }
28
29        Ok(Self { ident })
30    }
31
32    #[must_use]
33    pub fn ident(&self) -> &Ident {
34        &self.ident
35    }
36}
37
38impl Expression for Variable {
39    fn resolve(&self, ctx: &mut Context) -> Resolved {
40        Ok(ctx
41            .state()
42            .variable(&self.ident)
43            .cloned()
44            .unwrap_or(Value::Null))
45    }
46
47    fn resolve_constant(&self, state: &TypeState) -> Option<Value> {
48        state
49            .local
50            .variable(self.ident())
51            .and_then(|details| details.value.clone())
52    }
53
54    fn type_info(&self, state: &TypeState) -> TypeInfo {
55        let result = state
56            .local
57            .variable(&self.ident)
58            .map_or_else(|| TypeDef::undefined().infallible(), |d| d.type_def.clone());
59
60        TypeInfo::new(state, result)
61    }
62}
63
64impl fmt::Display for Variable {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        self.ident.fmt(f)
67    }
68}
69
70#[derive(Debug)]
71pub(crate) struct Error {
72    variant: ErrorVariant,
73    ident: Ident,
74    span: Span,
75}
76
77impl Error {
78    fn undefined(ident: Ident, span: Span, idents: Vec<Ident>) -> Self {
79        Error {
80            variant: ErrorVariant::Undefined { idents },
81            ident,
82            span,
83        }
84    }
85}
86
87#[derive(thiserror::Error, Debug)]
88pub(crate) enum ErrorVariant {
89    #[error("call to undefined variable")]
90    Undefined { idents: Vec<Ident> },
91}
92
93impl fmt::Display for Error {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        write!(f, "{:#}", self.variant)
96    }
97}
98
99impl std::error::Error for Error {
100    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
101        Some(&self.variant)
102    }
103}
104
105impl DiagnosticMessage for Error {
106    fn code(&self) -> usize {
107        use ErrorVariant::Undefined;
108
109        match &self.variant {
110            Undefined { .. } => 701,
111        }
112    }
113
114    fn labels(&self) -> Vec<Label> {
115        use ErrorVariant::Undefined;
116
117        match &self.variant {
118            Undefined { idents } => {
119                let mut vec = vec![Label::primary("undefined variable", self.span)];
120                let ident_chars = self.ident.as_ref().chars().collect::<Vec<_>>();
121
122                let mut builtin = vec![Ident::new("null"), Ident::new("true"), Ident::new("false")];
123                let mut idents = idents.clone();
124
125                idents.append(&mut builtin);
126
127                if let Some((idx, _)) = idents
128                    .iter()
129                    .map(|possible| {
130                        let possible_chars = possible.chars().collect::<Vec<_>>();
131                        levenstein::distance(&ident_chars, &possible_chars)
132                    })
133                    .enumerate()
134                    .min_by_key(|(_, score)| *score)
135                {
136                    {
137                        let guessed = &idents[idx];
138                        vec.push(Label::context(
139                            format!(r#"did you mean "{guessed}"?"#),
140                            self.span,
141                        ));
142                    }
143                }
144
145                vec
146            }
147        }
148    }
149}
150
151#[cfg(test)]
152mod test {
153    use crate::compiler::type_def::Details;
154
155    use super::*;
156
157    #[test]
158    fn test_resolve_const() {
159        let mut state = TypeState::default();
160        state.local.insert_variable(
161            Ident::new("foo"),
162            Details {
163                type_def: TypeDef::integer(),
164                value: Some(Value::Integer(42)),
165            },
166        );
167
168        let var = Variable::new((0, 0).into(), Ident::new("foo"), &state.local).unwrap();
169        assert_eq!(var.resolve_constant(&state), Some(Value::Integer(42)));
170    }
171}