vrl/compiler/expression/
variable.rs1use 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}