1use std::sync::Arc;
15
16use serde::{Deserialize, Serialize};
17use typed_builder::TypedBuilder;
18
19use crate::ir::{Address, Span, Value};
20
21pub type AttributeMap = Vec<(Arc<str>, Expression)>;
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
28#[serde(rename_all = "kebab-case")]
29#[non_exhaustive]
30pub enum BinaryOp {
31 Add,
33 Sub,
35 Mul,
37 Div,
39 Mod,
41 Eq,
43 Ne,
45 Lt,
47 Le,
49 Gt,
51 Ge,
53 And,
55 Or,
57}
58
59#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
61#[serde(rename_all = "kebab-case")]
62#[non_exhaustive]
63pub enum UnaryOp {
64 Neg,
66 Not,
68}
69
70#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
74#[serde(rename_all = "PascalCase")]
75#[non_exhaustive]
76pub enum SymbolKind {
77 Var,
79 Local,
81 Resource,
83 Data,
85 Module,
87 Path,
89 Iteration,
91 Terraform,
93 TerragruntDependency,
95 Other,
98}
99
100#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, TypedBuilder)]
102#[non_exhaustive]
103#[serde(rename_all = "camelCase")]
104#[builder(field_defaults(setter(into)))]
105pub struct Symbolic {
106 pub kind: SymbolKind,
108
109 pub source: Arc<str>,
112
113 #[builder(default)]
116 pub address_hint: Option<Address>,
117
118 pub span: Span,
120}
121
122#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TypedBuilder)]
126#[non_exhaustive]
127#[serde(rename_all = "camelCase")]
128#[builder(field_defaults(setter(into)))]
129pub struct FuncCall {
130 pub name: Arc<str>,
132 pub args: Vec<Expression>,
134 pub span: Span,
136}
137
138#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TypedBuilder)]
140#[non_exhaustive]
141#[serde(rename_all = "camelCase")]
142pub struct Conditional {
143 pub cond: Box<Expression>,
145 pub then_branch: Box<Expression>,
147 pub else_branch: Box<Expression>,
149 pub span: Span,
151}
152
153#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TypedBuilder)]
156#[non_exhaustive]
157#[serde(rename_all = "camelCase")]
158#[builder(field_defaults(setter(into)))]
159pub struct ForExpr {
160 pub binders: Vec<Arc<str>>,
163 pub collection: Box<Expression>,
165 #[builder(default)]
167 pub key: Option<Box<Expression>>,
168 pub value: Box<Expression>,
170 #[builder(default)]
172 pub cond: Option<Box<Expression>>,
173 #[builder(default = false)]
175 pub object_form: bool,
176 pub span: Span,
178}
179
180#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
182#[non_exhaustive]
183#[serde(rename_all = "camelCase", tag = "kind", content = "node")]
184pub enum Expression {
185 Literal(Value),
187
188 Unresolved(Symbolic),
191
192 BinaryOp {
194 op: BinaryOp,
196 lhs: Box<Expression>,
198 rhs: Box<Expression>,
200 span: Span,
202 },
203
204 UnaryOp {
206 op: UnaryOp,
208 operand: Box<Expression>,
210 span: Span,
212 },
213
214 TemplateConcat(Vec<Expression>),
216
217 Array(Vec<Expression>),
230
231 Object(Vec<(Expression, Expression)>),
236
237 FuncCall(Box<FuncCall>),
239
240 Conditional(Box<Conditional>),
242
243 For(Box<ForExpr>),
245}
246
247impl Expression {
248 #[must_use]
251 pub fn as_literal(&self) -> Option<&Value> {
252 match self {
253 Self::Literal(v) => Some(v),
254 _ => None,
255 }
256 }
257
258 #[must_use]
261 pub fn is_fully_resolved(&self) -> bool {
262 match self {
263 Self::Literal(_) => true,
264 Self::Unresolved(_) => false,
265 Self::BinaryOp { lhs, rhs, .. } => lhs.is_fully_resolved() && rhs.is_fully_resolved(),
266 Self::UnaryOp { operand, .. } => operand.is_fully_resolved(),
267 Self::TemplateConcat(parts) | Self::Array(parts) => {
268 parts.iter().all(Self::is_fully_resolved)
269 }
270 Self::Object(entries) => entries
271 .iter()
272 .all(|(k, v)| k.is_fully_resolved() && v.is_fully_resolved()),
273 Self::FuncCall(call) => call.args.iter().all(Self::is_fully_resolved),
274 Self::Conditional(c) => {
275 c.cond.is_fully_resolved()
276 && c.then_branch.is_fully_resolved()
277 && c.else_branch.is_fully_resolved()
278 }
279 Self::For(f) => {
280 f.collection.is_fully_resolved()
281 && f.value.is_fully_resolved()
282 && f.key.as_ref().is_none_or(|k| k.is_fully_resolved())
283 && f.cond.as_ref().is_none_or(|c| c.is_fully_resolved())
284 }
285 }
286 }
287}
288
289#[cfg(test)]
290#[allow(
291 clippy::unwrap_used,
292 clippy::expect_used,
293 clippy::panic,
294 clippy::indexing_slicing
295)]
296mod tests {
297 use std::{path::Path, sync::Arc};
298
299 use super::*;
300
301 fn fake_span() -> Span {
302 Span::synthetic()
303 }
304
305 #[test]
306 fn test_should_classify_literal_as_resolved() {
307 let e = Expression::Literal(Value::Int(42));
308 assert!(e.is_fully_resolved());
309 assert_eq!(e.as_literal(), Some(&Value::Int(42)));
310 }
311
312 #[test]
313 fn test_should_classify_unresolved_as_not_resolved() {
314 let e = Expression::Unresolved(Symbolic {
315 kind: SymbolKind::Var,
316 source: Arc::from("var.environment"),
317 address_hint: Some(Address::new("var.environment").unwrap()),
318 span: fake_span(),
319 });
320 assert!(!e.is_fully_resolved());
321 }
322
323 #[test]
324 fn test_should_recurse_into_binary_op() {
325 let e = Expression::BinaryOp {
326 op: BinaryOp::Add,
327 lhs: Box::new(Expression::Literal(Value::Int(1))),
328 rhs: Box::new(Expression::Unresolved(Symbolic {
329 kind: SymbolKind::Local,
330 source: Arc::from("local.x"),
331 address_hint: None,
332 span: fake_span(),
333 })),
334 span: fake_span(),
335 };
336 assert!(!e.is_fully_resolved());
337 }
338
339 #[test]
340 fn test_should_serde_round_trip_expression_tree() {
341 let span = Span::new(Arc::from(Path::new("/tmp/x.tf")), 0..1, 1, 1).unwrap();
342 let e = Expression::TemplateConcat(vec![
343 Expression::Literal(Value::Str(Arc::from("prefix-"))),
344 Expression::Unresolved(Symbolic {
345 kind: SymbolKind::Var,
346 source: Arc::from("var.environment"),
347 address_hint: None,
348 span: span.clone(),
349 }),
350 ]);
351 let json = serde_json::to_string(&e).unwrap();
352 let back: Expression = serde_json::from_str(&json).unwrap();
353 assert_eq!(e, back);
354 }
355
356 #[test]
357 fn test_should_serde_round_trip_func_call() {
358 let call = FuncCall {
359 name: Arc::from("jsonencode"),
360 args: vec![Expression::Literal(Value::Str(Arc::from("hi")))],
361 span: fake_span(),
362 };
363 let e = Expression::FuncCall(Box::new(call));
364 let json = serde_json::to_string(&e).unwrap();
365 let back: Expression = serde_json::from_str(&json).unwrap();
366 assert_eq!(e, back);
367 }
368
369 #[test]
370 fn test_func_call_with_unresolved_argument_is_unresolved() {
371 let call = FuncCall {
372 name: Arc::from("jsonencode"),
373 args: vec![Expression::Unresolved(Symbolic {
374 kind: SymbolKind::Var,
375 source: Arc::from("var.x"),
376 address_hint: None,
377 span: fake_span(),
378 })],
379 span: fake_span(),
380 };
381 let e = Expression::FuncCall(Box::new(call));
382 assert!(!e.is_fully_resolved());
383 }
384
385 #[test]
386 fn test_should_serde_round_trip_conditional() {
387 let cond = Conditional {
388 cond: Box::new(Expression::Literal(Value::Bool(true))),
389 then_branch: Box::new(Expression::Literal(Value::Int(1))),
390 else_branch: Box::new(Expression::Literal(Value::Int(2))),
391 span: fake_span(),
392 };
393 let e = Expression::Conditional(Box::new(cond));
394 assert!(e.is_fully_resolved());
395 let json = serde_json::to_string(&e).unwrap();
396 let back: Expression = serde_json::from_str(&json).unwrap();
397 assert_eq!(e, back);
398 }
399
400 #[test]
401 fn test_conditional_with_unresolved_branch_is_unresolved() {
402 let cond = Conditional {
403 cond: Box::new(Expression::Literal(Value::Bool(true))),
404 then_branch: Box::new(Expression::Literal(Value::Int(1))),
405 else_branch: Box::new(Expression::Unresolved(Symbolic {
406 kind: SymbolKind::Var,
407 source: Arc::from("var.x"),
408 address_hint: None,
409 span: fake_span(),
410 })),
411 span: fake_span(),
412 };
413 let e = Expression::Conditional(Box::new(cond));
414 assert!(!e.is_fully_resolved());
415 }
416
417 #[test]
418 fn test_should_serde_round_trip_for_expr() {
419 let f = ForExpr {
420 binders: vec![Arc::from("k"), Arc::from("v")],
421 collection: Box::new(Expression::Literal(Value::List(vec![Value::Int(1)]))),
422 key: Some(Box::new(Expression::Unresolved(Symbolic {
423 kind: SymbolKind::Iteration,
424 source: Arc::from("k"),
425 address_hint: None,
426 span: fake_span(),
427 }))),
428 value: Box::new(Expression::Literal(Value::Bool(true))),
429 cond: None,
430 object_form: true,
431 span: fake_span(),
432 };
433 let e = Expression::For(Box::new(f));
434 let json = serde_json::to_string(&e).unwrap();
435 let back: Expression = serde_json::from_str(&json).unwrap();
436 assert_eq!(e, back);
437 assert!(!e.is_fully_resolved(), "Iteration ref keeps it unresolved");
438 }
439}