Repository: martinohmann/hcl-rs
Pull request #64 (2022-09-07 19:58:12)

feat: add `Expression::FuncCall`
This adds supports for parsing and serializing function call expressions of the form `func_name(arg1, arg2...)`.


Patch:
diff --git a/specsuite/hcl/terraform.hcl.json b/specsuite/hcl/terraform.hcl.json
index 52facb3..ff6fc18 100644
--- a/specsuite/hcl/terraform.hcl.json
+++ b/specsuite/hcl/terraform.hcl.json
@@ -27,7 +27,7 @@
               }
             }
           },
-          "tags": "${merge(\n    var.tags,\n    var.cluster_tags,\n  )}",
+          "tags": "${merge(var.tags, var.cluster_tags)}",
           "depends_on": [
             "${aws_cloudwatch_log_group.this}"
           ]
diff --git a/src/format/impls.rs b/src/format/impls.rs
index a7d9853..641dd82 100644
--- a/src/format/impls.rs
+++ b/src/format/impls.rs
@@ -1,8 +1,8 @@
 use super::{private, Format, Formatter};
 use crate::{
     structure::{
-        Attribute, Block, BlockLabel, Body, Expression, Heredoc, HeredocStripMode, Identifier,
-        ObjectKey, RawExpression, Structure, TemplateExpr,
+        Attribute, Block, BlockLabel, Body, Expression, FuncCall, Heredoc, HeredocStripMode,
+        Identifier, ObjectKey, RawExpression, Structure, TemplateExpr,
     },
     ElementAccess, ElementAccessOperator, Map, Number, Result, Value,
 };
@@ -107,6 +107,7 @@ impl Format for Expression {
             Expression::TemplateExpr(expr) => expr.format(fmt),
             Expression::VariableExpr(ident) => ident.format(fmt),
             Expression::ElementAccess(access) => access.format(fmt),
+            Expression::FuncCall(func_call) => func_call.format(fmt),
         }
     }
 }
@@ -277,6 +278,38 @@ impl Format for ElementAccessOperator {
     }
 }
 
+impl private::Sealed for FuncCall {}
+
+impl Format for FuncCall {
+    fn format<W>(&self, fmt: &mut Formatter<W>) -> Result<()>
+    where
+        W: io::Write,
+    {
+        self.name.format(fmt)?;
+        fmt.write_all(b"(")?;
+
+        fmt.compact_mode(true);
+
+        for (i, arg) in self.args.iter().enumerate() {
+            if i > 0 {
+                fmt.write_all(b", ")?;
+            }
+
+            arg.format(fmt)?;
+        }
+
+        fmt.compact_mode(false);
+
+        if self.variadic {
+            fmt.write_all(b"...)")?;
+        } else {
+            fmt.write_all(b")")?;
+        }
+
+        Ok(())
+    }
+}
+
 impl private::Sealed for String {}
 
 impl Format for String {
diff --git a/src/format/mod.rs b/src/format/mod.rs
index 68ef46c..98c8feb 100644
--- a/src/format/mod.rs
+++ b/src/format/mod.rs
@@ -145,6 +145,7 @@ pub struct Formatter<'a, W> {
     first_element: bool,
     current_indent: usize,
     has_value: bool,
+    compact_mode: bool,
 }
 
 /// A builder to create a `Formatter`.
@@ -179,6 +180,7 @@ impl<'a, W> FormatterBuilder<'a, W> {
             first_element: false,
             current_indent: 0,
             has_value: false,
+            compact_mode: false,
         }
     }
 }
@@ -323,7 +325,9 @@ where
 
     /// Signals the start of an array to the formatter.
     fn begin_array(&mut self) -> io::Result<()> {
-        self.current_indent += 1;
+        if !self.compact_mode {
+            self.current_indent += 1;
+        }
         self.has_value = false;
         self.first_element = true;
         self.write_all(b"[")
@@ -333,7 +337,11 @@ where
     fn begin_array_value(&mut self) -> io::Result<()> {
         if self.first_element {
             self.first_element = false;
-            self.write_all(b"\n")?;
+            if !self.compact_mode {
+                self.write_all(b"\n")?;
+            }
+        } else if self.compact_mode {
+            self.write_all(b", ")?;
         } else {
             self.write_all(b",\n")?;
         }
@@ -349,11 +357,13 @@ where
 
     /// Signals the end of an array to the formatter.
     fn end_array(&mut self) -> io::Result<()> {
-        self.current_indent -= 1;
+        if !self.compact_mode {
+            self.current_indent -= 1;
 
-        if self.has_value {
-            self.write_all(b"\n")?;
-            self.write_indent(self.current_indent)?;
+            if self.has_value {
+                self.write_all(b"\n")?;
+                self.write_indent(self.current_indent)?;
+            }
         }
 
         self.write_all(b"]")
@@ -361,15 +371,21 @@ where
 
     /// Signals the start of an object to the formatter.
     fn begin_object(&mut self) -> io::Result<()> {
-        self.current_indent += 1;
+        if !self.compact_mode {
+            self.current_indent += 1;
+        }
         self.has_value = false;
         self.write_all(b"{")
     }
 
     /// Signals the start of an object key to the formatter.
     fn begin_object_key(&mut self) -> io::Result<()> {
-        self.write_all(b"\n")?;
-        self.write_indent(self.current_indent)
+        if !self.compact_mode {
+            self.write_all(b"\n")?;
+            self.write_indent(self.current_indent)?;
+        }
+
+        Ok(())
     }
 
     /// Signals the start of an object value to the formatter.
@@ -384,11 +400,13 @@ where
 
     /// Signals the end of an object to the formatter.
     fn end_object(&mut self) -> io::Result<()> {
-        self.current_indent -= 1;
+        if !self.compact_mode {
+            self.current_indent -= 1;
 
-        if self.has_value {
-            self.write_all(b"\n")?;
-            self.write_indent(self.current_indent)?;
+            if self.has_value {
+                self.write_all(b"\n")?;
+                self.write_indent(self.current_indent)?;
+            }
         }
 
         self.write_all(b"}")
@@ -480,6 +498,12 @@ where
 
         Ok(())
     }
+
+    /// Enables compact mode for the formatter. This is mostly used while serializing array and
+    /// object function arguments.
+    fn compact_mode(&mut self, yes: bool) {
+        self.compact_mode = yes;
+    }
 }
 
 impl<'a, W> io::Write for Formatter<'a, W>
diff --git a/src/lib.rs b/src/lib.rs
index 862cd80..4b9790a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -28,8 +28,8 @@ pub use ser::{to_string, to_vec, to_writer};
 #[doc(inline)]
 pub use structure::{
     ser::to_expression, Attribute, Block, BlockBuilder, BlockLabel, Body, BodyBuilder,
-    ElementAccess, ElementAccessOperator, Expression, Heredoc, HeredocStripMode, Identifier,
-    Object, ObjectKey, RawExpression, Structure, TemplateExpr,
+    ElementAccess, ElementAccessOperator, Expression, FuncCall, FuncCallBuilder, Heredoc,
+    HeredocStripMode, Identifier, Object, ObjectKey, RawExpression, Structure, TemplateExpr,
 };
 #[doc(inline)]
 pub use value::{Map, Value};
diff --git a/src/parser/grammar/hcl.pest b/src/parser/grammar/hcl.pest
index 3f8ff43..37aed81 100644
--- a/src/parser/grammar/hcl.pest
+++ b/src/parser/grammar/hcl.pest
@@ -83,7 +83,7 @@ StringPart = {
 }
 
 // Functions and function calls
-FunctionCall = @{
+FunctionCall = ${
     Identifier ~ "(" ~
     WHITESPACE* ~ (COMMENT ~ WHITESPACE*)* ~
     Arguments ~
@@ -91,7 +91,8 @@ FunctionCall = @{
     ")"
 }
 
-Arguments = !{ (Expression ~ ("," ~ Expression)* ~ ("," | "...")?)? }
+Arguments = !{ (Expression ~ ("," ~ Expression)* ~ ("," | Variadic)?)? }
+Variadic  =  { "..." }
 
 // For expressions
 ForExpr       = { ForTupleExpr | ForObjectExpr }
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 605a8bf..4da7702 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -4,8 +4,8 @@ mod tests;
 
 use crate::{
     structure::Identifier, util::unescape, Attribute, Block, BlockLabel, Body,
-    ElementAccessOperator, Expression, Heredoc, HeredocStripMode, Number, Object, ObjectKey,
-    RawExpression, Result, Structure, TemplateExpr,
+    ElementAccessOperator, Expression, FuncCall, FuncCallBuilder, Heredoc, HeredocStripMode,
+    Number, Object, ObjectKey, RawExpression, Result, Structure, TemplateExpr,
 };
 use pest::{
     iterators::{Pair, Pairs},
@@ -172,6 +172,7 @@ fn parse_expr_term(pair: Pair<Rule>) -> Result<Expression> {
         Rule::Tuple => parse_expressions(pair).map(Expression::Array)?,
         Rule::Object => parse_object(pair).map(Expression::Object)?,
         Rule::VariableExpr => Expression::VariableExpr(parse_ident(pair).into()),
+        Rule::FunctionCall => Expression::FuncCall(Box::new(parse_func_call(pair)?)),
         // @TODO(mohmann): Process ForExpr etc.
         _ => Expression::Raw(raw_expression(pair.as_str())),
     };
@@ -181,6 +182,19 @@ fn parse_expr_term(pair: Pair<Rule>) -> Result<Expression> {
     })
 }
 
+fn parse_func_call(pair: Pair<Rule>) -> Result<FuncCall> {
+    let mut pairs = pair.into_inner();
+    let name = pairs.next().unwrap();
+    let mut args = pairs.next().unwrap().into_inner();
+    let builder = FuncCall::builder(name.as_str());
+
+    args.try_fold(builder, |builder, pair| match pair.as_rule() {
+        Rule::Variadic => Ok(builder.variadic(true)),
+        _ => Ok(builder.arg(parse_expression(pair)?)),
+    })
+    .map(FuncCallBuilder::build)
+}
+
 fn parse_element_access_operator(pair: Pair<Rule>) -> Result<ElementAccessOperator> {
     let operator = match pair.as_rule() {
         Rule::AttrSplat => ElementAccessOperator::AttrSplat,
diff --git a/src/structure/de.rs b/src/structure/de.rs
index 1e6853e..4b86e0c 100644
--- a/src/structure/de.rs
+++ b/src/structure/de.rs
@@ -1,8 +1,8 @@
 //! Deserialize impls for HCL structure types.
 
 use super::{
-    Attribute, Block, BlockLabel, Body, ElementAccess, ElementAccessOperator, Expression, Heredoc,
-    HeredocStripMode, Identifier, ObjectKey, RawExpression, Structure, TemplateExpr,
+    Attribute, Block, BlockLabel, Body, ElementAccess, ElementAccessOperator, Expression, FuncCall,
+    Heredoc, HeredocStripMode, Identifier, ObjectKey, RawExpression, Structure, TemplateExpr,
 };
 use crate::{Error, Number, Result};
 use serde::de::{self, value::MapAccessDeserializer, IntoDeserializer};
@@ -264,6 +264,9 @@ impl<'de> de::Deserializer<'de> for Expression {
             Expression::ElementAccess(access) => {
                 access.into_deserializer().deserialize_any(visitor)
             }
+            Expression::FuncCall(func_call) => {
+                func_call.into_deserializer().deserialize_any(visitor)
+            }
         }
     }
 
@@ -293,6 +296,7 @@ impl VariantName for Expression {
             Expression::TemplateExpr(_) => "TemplateExpr",
             Expression::VariableExpr(_) => "VariableExpr",
             Expression::ElementAccess(_) => "ElementAccess",
+            Expression::FuncCall(_) => "FuncCall",
         }
     }
 }
@@ -446,6 +450,64 @@ impl VariantName for ElementAccessOperator {
     }
 }
 
+impl<'de> IntoDeserializer<'de, Error> for FuncCall {
+    type Deserializer = MapAccessDeserializer<FuncCallAccess>;
+
+    fn into_deserializer(self) -> Self::Deserializer {
+        MapAccessDeserializer::new(FuncCallAccess::new(self))
+    }
+}
+
+pub struct FuncCallAccess {
+    name: Option<Identifier>,
+    args: Option<Vec<Expression>>,
+    variadic: Option<bool>,
+}
+
+impl FuncCallAccess {
+    fn new(func_call: FuncCall) -> Self {
+        FuncCallAccess {
+            name: Some(func_call.name),
+            args: Some(func_call.args),
+            variadic: Some(func_call.variadic),
+        }
+    }
+}
+
+impl<'de> de::MapAccess<'de> for FuncCallAccess {
+    type Error = Error;
+
+    fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Error>
+    where
+        K: de::DeserializeSeed<'de>,
+    {
+        if self.name.is_some() {
+            seed.deserialize("name".into_deserializer()).map(Some)
+        } else if self.args.is_some() {
+            seed.deserialize("args".into_deserializer()).map(Some)
+        } else if self.variadic.is_some() {
+            seed.deserialize("variadic".into_deserializer()).map(Some)
+        } else {
+            Ok(None)
+        }
+    }
+
+    fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Error>
+    where
+        V: de::DeserializeSeed<'de>,
+    {
+        if let Some(name) = self.name.take() {
+            seed.deserialize(name.into_deserializer())
+        } else if let Some(args) = self.args.take() {
+            seed.deserialize(args.into_deserializer())
+        } else if let Some(variadic) = self.variadic.take() {
+            seed.deserialize(variadic.into_deserializer())
+        } else {
+            Err(de::Error::custom("invalid HCL function call"))
+        }
+    }
+}
+
 impl<'de> IntoDeserializer<'de, Error> for ObjectKey {
     type Deserializer = Self;
 
diff --git a/src/structure/expression.rs b/src/structure/expression.rs
index 434c1fb..2901ab0 100644
--- a/src/structure/expression.rs
+++ b/src/structure/expression.rs
@@ -1,6 +1,6 @@
 //! Types to represent HCL attribute value expressions.
 
-use super::{ElementAccess, ElementAccessOperator, Identifier, TemplateExpr};
+use super::{ElementAccess, ElementAccessOperator, FuncCall, Identifier, TemplateExpr};
 use crate::{Number, Value};
 use serde::{Deserialize, Serialize};
 use std::borrow::Cow;
@@ -34,6 +34,8 @@ pub enum Expression {
     VariableExpr(Identifier),
     /// Represents an attribute or element access.
     ElementAccess(Box<ElementAccess>),
+    /// Represents a function call.
+    FuncCall(Box<FuncCall>),
     /// Represents a raw HCL expression. This includes any expression kind that does match any of
     /// the enum variants above. See [`RawExpression`] for more details.
     Raw(RawExpression),
@@ -187,6 +189,12 @@ impl From<ElementAccess> for Expression {
     }
 }
 
+impl From<FuncCall> for Expression {
+    fn from(func_call: FuncCall) -> Self {
+        Expression::FuncCall(Box::new(func_call))
+    }
+}
+
 impl From<TemplateExpr> for Expression {
     fn from(expr: TemplateExpr) -> Self {
         Expression::TemplateExpr(Box::new(expr))
diff --git a/src/structure/func.rs b/src/structure/func.rs
new file mode 100644
index 0000000..b0e55a4
--- /dev/null
+++ b/src/structure/func.rs
@@ -0,0 +1,67 @@
+use crate::{Expression, Identifier};
+use serde::{Deserialize, Serialize};
+
+/// Represents a function call expression with zero or more arguments. Function calls can be
+/// variadic.
+#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
+#[serde(rename = "$hcl::func_call")]
+pub struct FuncCall {
+    /// The name of the function.
+    pub name: Identifier,
+    /// The function arguments.
+    pub args: Vec<Expression>,
+    /// Specifies whether this is a variadic function call or not.
+    pub variadic: bool,
+}
+
+impl FuncCall {
+    /// Creates a new `FuncCall` for the function with given name.
+    pub fn new<T>(name: T) -> FuncCall
+    where
+        T: Into<Identifier>,
+    {
+        FuncCall {
+            name: name.into(),
+            args: Vec::new(),
+            variadic: false,
+        }
+    }
+
+    /// Creates a new `FuncCallBuilder` for the function with given name.
+    pub fn builder<T>(name: T) -> FuncCallBuilder
+    where
+        T: Into<Identifier>,
+    {
+        FuncCallBuilder {
+            f: FuncCall::new(name),
+        }
+    }
+}
+
+/// A builder for function calls.
+#[derive(Debug)]
+pub struct FuncCallBuilder {
+    f: FuncCall,
+}
+
+impl FuncCallBuilder {
+    /// Adds an argument to the function call.
+    pub fn arg<T>(mut self, arg: T) -> FuncCallBuilder
+    where
+        T: Into<Expression>,
+    {
+        self.f.args.push(arg.into());
+        self
+    }
+
+    /// Marks the function as variadic or not.
+    pub fn variadic(mut self, yes: bool) -> FuncCallBuilder {
+        self.f.variadic = yes;
+        self
+    }
+
+    /// Consumes the `FuncCallBuilder` and returns the `FuncCall`.
+    pub fn build(self) -> FuncCall {
+        self.f
+    }
+}
diff --git a/src/structure/mod.rs b/src/structure/mod.rs
index c83e705..9e28765 100644
--- a/src/structure/mod.rs
+++ b/src/structure/mod.rs
@@ -51,6 +51,7 @@ mod body;
 pub(crate) mod de;
 mod element_access;
 mod expression;
+mod func;
 pub(crate) mod ser;
 mod template;
 #[cfg(test)]
@@ -62,6 +63,7 @@ pub use self::{
     body::{Body, BodyBuilder},
     element_access::{ElementAccess, ElementAccessOperator},
     expression::{Expression, Object, ObjectKey, RawExpression},
+    func::{FuncCall, FuncCallBuilder},
     template::{Heredoc, HeredocStripMode, TemplateExpr},
 };
 use crate::{Map, Value};
diff --git a/src/structure/ser/expression.rs b/src/structure/ser/expression.rs
index 72a570b..2f29897 100644
--- a/src/structure/ser/expression.rs
+++ b/src/structure/ser/expression.rs
@@ -1,6 +1,6 @@
 use super::{
-    element_access::SerializeElementAccessStruct, template::TemplateExprSerializer,
-    StringSerializer,
+    element_access::SerializeElementAccessStruct, func::SerializeFuncCallStruct,
+    template::TemplateExprSerializer, StringSerializer,
 };
 use crate::{
     serialize_unsupported, Error, Expression, Identifier, Object, ObjectKey, RawExpression, Result,
@@ -306,6 +306,7 @@ impl ser::SerializeMap for SerializeExpressionMap {
 
 pub enum SerializeExpressionStruct {
     ElementAccess(SerializeElementAccessStruct),
+    FuncCall(SerializeFuncCallStruct),
     Other(SerializeExpressionMap),
 }
 
@@ -316,6 +317,9 @@ impl SerializeExpressionStruct {
             "$hcl::element_access" => {
                 SerializeExpressionStruct::ElementAccess(SerializeElementAccessStruct::new())
             }
+            "$hcl::func_call" => {
+                SerializeExpressionStruct::FuncCall(SerializeFuncCallStruct::new())
+            }
             _ => SerializeExpressionStruct::Other(SerializeExpressionMap::new(Some(len))),
         }
     }
@@ -331,6 +335,7 @@ impl ser::SerializeStruct for SerializeExpressionStruct {
     {
         match self {
             SerializeExpressionStruct::ElementAccess(ser) => ser.serialize_field(key, value),
+            SerializeExpressionStruct::FuncCall(ser) => ser.serialize_field(key, value),
             SerializeExpressionStruct::Other(ser) => ser.serialize_entry(key, value),
         }
     }
@@ -338,6 +343,7 @@ impl ser::SerializeStruct for SerializeExpressionStruct {
     fn end(self) -> Result<Self::Ok> {
         match self {
             SerializeExpressionStruct::ElementAccess(ser) => ser.end().map(Into::into),
+            SerializeExpressionStruct::FuncCall(ser) => ser.end().map(Into::into),
             SerializeExpressionStruct::Other(ser) => ser.end(),
         }
     }
diff --git a/src/structure/ser/func.rs b/src/structure/ser/func.rs
new file mode 100644
index 0000000..f8a450f
--- /dev/null
+++ b/src/structure/ser/func.rs
@@ -0,0 +1,81 @@
+use super::{expression::ExpressionSerializer, BoolSerializer, SeqSerializer, StringSerializer};
+use crate::{serialize_unsupported, Error, Expression, FuncCall, Identifier, Result};
+use serde::ser::{self, Impossible};
+
+pub struct FuncCallSerializer;
+
+impl ser::Serializer for FuncCallSerializer {
+    type Ok = FuncCall;
+    type Error = Error;
+
+    type SerializeSeq = Impossible<FuncCall, Error>;
+    type SerializeTuple = Impossible<FuncCall, Error>;
+    type SerializeTupleStruct = Impossible<FuncCall, Error>;
+    type SerializeTupleVariant = Impossible<FuncCall, Error>;
+    type SerializeMap = Impossible<FuncCall, Error>;
+    type SerializeStruct = SerializeFuncCallStruct;
+    type SerializeStructVariant = Impossible<FuncCall, Error>;
+
+    serialize_unsupported! {
+        bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64
+        char str bytes none unit unit_struct unit_variant
+        newtype_variant seq tuple tuple_struct tuple_variant map struct_variant
+    }
+    serialize_self! { some newtype_struct }
+
+    fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeStruct> {
+        Ok(SerializeFuncCallStruct::new())
+    }
+}
+
+pub struct SerializeFuncCallStruct {
+    name: Option<Identifier>,
+    args: Option<Vec<Expression>>,
+    variadic: Option<bool>,
+}
+
+impl SerializeFuncCallStruct {
+    pub fn new() -> Self {
+        SerializeFuncCallStruct {
+            name: None,
+            args: None,
+            variadic: None,
+        }
+    }
+}
+
+impl ser::SerializeStruct for SerializeFuncCallStruct {
+    type Ok = FuncCall;
+    type Error = Error;
+
+    fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
+    where
+        T: ?Sized + ser::Serialize,
+    {
+        match key {
+            "name" => self.name = Some(Identifier::from(value.serialize(StringSerializer)?)),
+            "args" => self.args = Some(value.serialize(SeqSerializer::new(ExpressionSerializer))?),
+            "variadic" => self.variadic = Some(value.serialize(BoolSerializer)?),
+            _ => {
+                return Err(ser::Error::custom(
+                    "expected struct with fields `name`, `args` and optional `variadic`",
+                ))
+            }
+        };
+
+        Ok(())
+    }
+
+    fn end(self) -> Result<Self::Ok> {
+        match (self.name, self.args) {
+            (Some(name), Some(args)) => Ok(FuncCall {
+                name,
+                args,
+                variadic: self.variadic.unwrap_or_default(),
+            }),
+            (_, _) => Err(ser::Error::custom(
+                "expected struct with fields `name`, `args` and optional `variadic`",
+            )),
+        }
+    }
+}
diff --git a/src/structure/ser/mod.rs b/src/structure/ser/mod.rs
index de6dd2a..168109b 100644
--- a/src/structure/ser/mod.rs
+++ b/src/structure/ser/mod.rs
@@ -5,6 +5,7 @@ mod block;
 pub(crate) mod body;
 mod element_access;
 mod expression;
+mod func;
 mod structure;
 mod template;
 #[cfg(test)]
@@ -61,6 +62,32 @@ impl ser::Serializer for StringSerializer {
     }
 }
 
+pub struct BoolSerializer;
+
+impl ser::Serializer for BoolSerializer {
+    type Ok = bool;
+    type Error = Error;
+
+    type SerializeSeq = Impossible<bool, Error>;
+    type SerializeTuple = Impossible<bool, Error>;
+    type SerializeTupleStruct = Impossible<bool, Error>;
+    type SerializeTupleVariant = Impossible<bool, Error>;
+    type SerializeMap = Impossible<bool, Error>;
+    type SerializeStruct = Impossible<bool, Error>;
+    type SerializeStructVariant = Impossible<bool, Error>;
+
+    serialize_unsupported! {
+        i8 i16 i32 i64 u8 u16 u32 u64
+        f32 f64 char str bytes unit unit_struct unit_variant newtype_variant none
+        seq tuple tuple_struct tuple_variant map struct struct_variant
+    }
+    serialize_self! { some newtype_struct }
+
+    fn serialize_bool(self, value: bool) -> Result<Self::Ok> {
+        Ok(value)
+    }
+}
+
 pub struct SeqSerializer<S> {
     inner: S,
 }


Test patch:
diff --git a/src/de/tests.rs b/src/de/tests.rs
index e4465d2..8c99311 100644
--- a/src/de/tests.rs
+++ b/src/de/tests.rs
@@ -1,5 +1,5 @@
 use super::*;
-use crate::{Block, Body, ElementAccess, Expression, Identifier, ObjectKey};
+use crate::{Block, Body, ElementAccess, Expression, FuncCall, Identifier, ObjectKey};
 use pretty_assertions::assert_eq;
 use serde::Deserialize;
 use serde_json::{json, Value};
@@ -191,6 +191,23 @@ fn deserialize_enum() {
     assert_eq!(expected, from_str::<Test>(input).unwrap());
 }
 
+#[test]
+fn deserialize_func_call() {
+    let input = r#"attr = foo(1, "bar", ["baz", "qux"]...)"#;
+    let expected = Body::builder()
+        .add_attribute((
+            "attr",
+            FuncCall::builder("foo")
+                .arg(1)
+                .arg("bar")
+                .arg(vec!["baz", "qux"])
+                .variadic(true)
+                .build(),
+        ))
+        .build();
+    assert_eq!(expected, from_str::<Body>(input).unwrap());
+}
+
 #[test]
 fn deserialize_invalid_hcl() {
     let h = r#"invalid["#;
diff --git a/src/parser/tests.rs b/src/parser/tests.rs
index 65240d9..14f974b 100644
--- a/src/parser/tests.rs
+++ b/src/parser/tests.rs
@@ -421,9 +421,11 @@ fn parse_nested_function_call_with_splat() {
         rule: Rule::FunctionCall,
         tokens: [
             FunctionCall(0, 72, [
+                Identifier(0, 7),
                 Arguments(8, 71, [
                     ExprTerm(8, 68, [
                         FunctionCall(8, 68, [
+                            Identifier(8, 14),
                             Arguments(15, 67, [
                                 ExprTerm(15, 40, [
                                     VariableExpr(15, 26),
diff --git a/src/ser/tests.rs b/src/ser/tests.rs
index 5bcda11..8e6aaa9 100644
--- a/src/ser/tests.rs
+++ b/src/ser/tests.rs
@@ -1,6 +1,6 @@
 use super::*;
 use crate::{
-    Block, BlockLabel, Body, ElementAccess, ElementAccessOperator, Expression, Heredoc,
+    Block, BlockLabel, Body, ElementAccess, ElementAccessOperator, Expression, FuncCall, Heredoc,
     HeredocStripMode, Identifier, Object, ObjectKey, RawExpression, TemplateExpr,
 };
 use pretty_assertions::assert_eq;
@@ -216,6 +216,37 @@ fn serialize_element_access() {
     );
 }
 
+#[test]
+fn serialize_func_call() {
+    let body = Body::builder()
+        .add_attribute(("attr", FuncCall::new("foo")))
+        .build();
+
+    assert_eq!(to_string(&body).unwrap(), "attr = foo()\n");
+
+    let body = Body::builder()
+        .add_attribute(("attr", FuncCall::builder("foo").arg(1).arg("two").build()))
+        .build();
+
+    assert_eq!(to_string(&body).unwrap(), "attr = foo(1, \"two\")\n");
+
+    let body = Body::builder()
+        .add_attribute((
+            "attr",
+            FuncCall::builder("foo")
+                .arg(1)
+                .arg(vec!["two", "three"])
+                .variadic(true)
+                .build(),
+        ))
+        .build();
+
+    assert_eq!(
+        to_string(&body).unwrap(),
+        "attr = foo(1, [\"two\", \"three\"]...)\n"
+    );
+}
+
 #[test]
 fn serialize_heredoc() {
     let body = Body::builder()