Repository: martinohmann/hcl-rs
Pull request #73 (2022-09-17 20:48:44)

feat!: allow any HCL expression as object key
BREAKING CHANGE: The `RawExpression` and `String` variants of the `ObjectKey`
enum were removed in favor of the newly added `Expression` variant. Furthermore
the methods `Object::raw_expression` and `ObjectKey::string` were removed. Use
`ObjectKey::from` instead.

https://github.com/hashicorp/hcl/blob/main/hclsyntax/spec.md#collection-values

Object keys can either be identifiers or any valid HCL expression.

This is the last missing piece to enable the implementation of expression
evaluation.


Patch:
diff --git a/src/format/impls.rs b/src/format/impls.rs
index 086f452..2f9da17 100644
--- a/src/format/impls.rs
+++ b/src/format/impls.rs
@@ -173,13 +173,7 @@ impl Format for ObjectKey {
     {
         match self {
             ObjectKey::Identifier(ident) => ident.format(fmt),
-            ObjectKey::String(string) => string.format(fmt),
-            ObjectKey::RawExpression(raw) => {
-                fmt.begin_interpolated_string()?;
-                raw.format(fmt)?;
-                fmt.end_interpolated_string()?;
-                Ok(())
-            }
+            ObjectKey::Expression(expr) => expr.format(fmt),
         }
     }
 }
diff --git a/src/format/mod.rs b/src/format/mod.rs
index 98c8feb..3266c50 100644
--- a/src/format/mod.rs
+++ b/src/format/mod.rs
@@ -313,16 +313,6 @@ where
         Ok(())
     }
 
-    /// Starts an interpolated string.
-    fn begin_interpolated_string(&mut self) -> io::Result<()> {
-        self.write_all(b"\"${")
-    }
-
-    /// Ends an interpolated string.
-    fn end_interpolated_string(&mut self) -> io::Result<()> {
-        self.write_all(b"}\"")
-    }
-
     /// Signals the start of an array to the formatter.
     fn begin_array(&mut self) -> io::Result<()> {
         if !self.compact_mode {
diff --git a/src/macros.rs b/src/macros.rs
index 3e0b2c5..dff4733 100644
--- a/src/macros.rs
+++ b/src/macros.rs
@@ -9,9 +9,9 @@
 ///
 /// - Attribute keys and block identifiers can be raw identifiers (`identifier`) or parenthesized
 ///   expressions (`(expr)`).
-/// - Block labels can be string literals (`"label"`), raw identifiers (`label`) or parenthesized
+/// - Block labels can be string literals (`"label"`), identifiers (`label`) or parenthesized
 ///   expressions (`(label_expr)`).
-/// - Object keys can be string literals (`"key"`), raw identifiers (`key`), parenthesized
+/// - Object keys can be string literals (`"key"`), identifiers (`key`), parenthesized
 ///   expressions (`(key_expr)`) or raw HCL expressions (`#{raw_expr}`).
 /// - Attribute expression values can be any valid primitive, collection, expression or raw HCL
 ///   expression (`#{raw_expr}`).
@@ -389,11 +389,11 @@ macro_rules! block_label {
 /// use hcl::ObjectKey;
 ///
 /// assert_eq!(hcl::object_key!(some_identifier), ObjectKey::identifier("some_identifier"));
-/// assert_eq!(hcl::object_key!("some string"), ObjectKey::string("some string"));
+/// assert_eq!(hcl::object_key!("some string"), ObjectKey::from("some string"));
 ///
 /// let key = "some expression";
 ///
-/// assert_eq!(hcl::object_key!((key)), ObjectKey::string("some expression"));
+/// assert_eq!(hcl::object_key!((key)), ObjectKey::from("some expression"));
 /// ```
 #[macro_export]
 macro_rules! object_key {
@@ -402,11 +402,11 @@ macro_rules! object_key {
     };
 
     (#{$expr:expr}) => {
-        $crate::ObjectKey::RawExpression(($expr).into())
+        $crate::ObjectKey::Expression($crate::expression!(#{$expr}))
     };
 
     (($expr:expr)) => {
-        $crate::ObjectKey::String(($expr).into())
+        $crate::ObjectKey::Expression($crate::expression!($expr))
     };
 
     ($literal:literal) => {
@@ -433,8 +433,8 @@ macro_rules! object_key {
 ///
 /// let expected = Expression::Object(Object::from([
 ///     (ObjectKey::identifier("foo"), true.into()),
-///     (ObjectKey::string("baz qux"), vec![1u64, 2].into()),
-///     (ObjectKey::string("hello"), "world".into()),
+///     (ObjectKey::from("baz qux"), vec![1u64, 2].into()),
+///     (ObjectKey::from("hello"), "world".into()),
 /// ]));
 ///
 /// assert_eq!(expression, expected);
diff --git a/src/number.rs b/src/number.rs
index 43851f3..8e2c3bd 100644
--- a/src/number.rs
+++ b/src/number.rs
@@ -13,6 +13,8 @@ pub enum Number {
     Float(f64),
 }
 
+impl Eq for Number {}
+
 impl Number {
     /// Represents the `Number` as f64 if possible. Returns None otherwise.
     pub fn as_f64(&self) -> Option<f64> {
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index b173044..54c46c9 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -319,17 +319,9 @@ fn parse_object(pair: Pair<Rule>) -> Result<Object<ObjectKey, Expression>> {
 }
 
 fn parse_object_key(pair: Pair<Rule>) -> Result<ObjectKey> {
-    let raw = pair.as_str();
-
-    // @FIXME(mohmann): according to the HCL spec, any expression is a valid object key. Fixing
-    // this requires some breaking changes to the Object and ObjectKey types though.
     match pair.as_rule() {
         Rule::Identifier => Ok(ObjectKey::identifier(parse_ident(pair))),
-        Rule::ExprTerm => match parse_expr_term(pair)? {
-            Expression::String(s) => Ok(ObjectKey::String(s)),
-            _ => Ok(ObjectKey::RawExpression(raw_expression(raw))),
-        },
-        _ => Ok(ObjectKey::RawExpression(raw_expression(raw))),
+        _ => parse_expression(pair).map(ObjectKey::Expression),
     }
 }
 
@@ -353,10 +345,6 @@ fn parse_ident(pair: Pair<Rule>) -> String {
     pair.as_str().to_owned()
 }
 
-fn raw_expression(raw: &str) -> RawExpression {
-    RawExpression::new(raw.trim_end())
-}
-
 fn parse_heredoc(pair: Pair<Rule>) -> Heredoc {
     let mut pairs = pair.into_inner();
     let intro = pairs.next().unwrap();
diff --git a/src/structure/attribute.rs b/src/structure/attribute.rs
index 28f5337..b46e62b 100644
--- a/src/structure/attribute.rs
+++ b/src/structure/attribute.rs
@@ -14,7 +14,7 @@ use std::iter;
 ///
 /// Use [`Attribute::new`] to construct an [`Attribute`] from a value that is convertible to this
 /// crate's [`Expression`] type.
-#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
+#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
 #[serde(rename = "$hcl::attribute")]
 pub struct Attribute {
     /// The HCL attribute's key.
diff --git a/src/structure/block.rs b/src/structure/block.rs
index c610dd5..62a133e 100644
--- a/src/structure/block.rs
+++ b/src/structure/block.rs
@@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize};
 ///   body
 /// }
 /// ```
-#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
+#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
 #[serde(rename = "$hcl::block")]
 pub struct Block {
     /// The block identifier.
diff --git a/src/structure/body.rs b/src/structure/body.rs
index e1d13b0..dd9c501 100644
--- a/src/structure/body.rs
+++ b/src/structure/body.rs
@@ -7,7 +7,7 @@ use std::vec::IntoIter;
 /// Represents an HCL config file body.
 ///
 /// A `Body` consists of zero or more [`Attribute`] and [`Block`] HCL structures.
-#[derive(Deserialize, Serialize, Debug, PartialEq, Default, Clone)]
+#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Default, Clone)]
 #[serde(rename = "$hcl::body")]
 pub struct Body(pub Vec<Structure>);
 
diff --git a/src/structure/conditional.rs b/src/structure/conditional.rs
index bf2647b..41c1e90 100644
--- a/src/structure/conditional.rs
+++ b/src/structure/conditional.rs
@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
 
 /// The conditional operator allows selecting from one of two expressions based on the outcome of a
 /// boolean expression.
-#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
+#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
 #[serde(rename = "$hcl::conditional")]
 pub struct Conditional {
     /// A predicate expression that evaluates to a boolean value.
diff --git a/src/structure/de.rs b/src/structure/de.rs
index ca34297..0bea73a 100644
--- a/src/structure/de.rs
+++ b/src/structure/de.rs
@@ -1045,30 +1045,90 @@ impl<'de> de::Deserializer<'de> for ObjectKey {
         string bytes byte_buf option unit unit_struct newtype_struct seq
         tuple tuple_struct map struct identifier ignored_any
     }
-    impl_deserialize_enum!();
 
     fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
     where
         V: de::Visitor<'de>,
     {
         match self {
-            ObjectKey::String(string) => visitor.visit_string(string),
             ObjectKey::Identifier(ident) => ident.into_deserializer().deserialize_any(visitor),
-            ObjectKey::RawExpression(expr) => expr.into_deserializer().deserialize_any(visitor),
+            ObjectKey::Expression(expr) => expr.into_deserializer().deserialize_any(visitor),
         }
     }
+
+    fn deserialize_enum<V>(
+        self,
+        _name: &'static str,
+        _variants: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        visitor.visit_enum(self)
+    }
 }
 
 impl VariantName for ObjectKey {
     fn variant_name(&self) -> &'static str {
         match self {
-            ObjectKey::String(_) => "String",
             ObjectKey::Identifier(_) => "Identifier",
-            ObjectKey::RawExpression(_) => "RawExpression",
+            ObjectKey::Expression(_) => "Expression",
         }
     }
 }
 
+impl<'de> de::EnumAccess<'de> for ObjectKey {
+    type Error = Error;
+    type Variant = Self;
+
+    fn variant_seed<T>(self, seed: T) -> Result<(T::Value, Self::Variant), Self::Error>
+    where
+        T: de::DeserializeSeed<'de>,
+    {
+        let variant_name = self.variant_name();
+
+        seed.deserialize(variant_name.into_deserializer())
+            .map(|variant| (variant, self))
+    }
+}
+
+impl<'de> de::VariantAccess<'de> for ObjectKey {
+    type Error = Error;
+
+    fn unit_variant(self) -> Result<(), Self::Error> {
+        de::Deserialize::deserialize(self)
+    }
+
+    fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value, Self::Error>
+    where
+        T: de::DeserializeSeed<'de>,
+    {
+        match self {
+            ObjectKey::Expression(expr) => seed.deserialize(expr.into_deserializer()),
+            value => seed.deserialize(value.into_deserializer()),
+        }
+    }
+
+    fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        self.into_deserializer().deserialize_seq(visitor)
+    }
+
+    fn struct_variant<V>(
+        self,
+        _fields: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        self.into_deserializer().deserialize_map(visitor)
+    }
+}
+
 impl<'de> IntoDeserializer<'de, Error> for RawExpression {
     type Deserializer = NewtypeStructDeserializer<String>;
 
diff --git a/src/structure/element_access.rs b/src/structure/element_access.rs
index fce30fc..d5734f8 100644
--- a/src/structure/element_access.rs
+++ b/src/structure/element_access.rs
@@ -2,7 +2,7 @@ use crate::{Expression, Identifier};
 use serde::{Deserialize, Serialize};
 
 /// Access to an element of an expression result.
-#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
+#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
 #[serde(rename = "$hcl::element_access")]
 pub struct ElementAccess {
     /// The expression that the access operator is applied to.
@@ -38,7 +38,7 @@ impl ElementAccess {
 }
 
 /// The kinds of element access that are supported by HCL.
-#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
+#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
 #[serde(rename = "$hcl::element_access_operator")]
 pub enum ElementAccessOperator {
     /// The attribute-only splat operator supports only attribute lookups into the elements from a
diff --git a/src/structure/expression.rs b/src/structure/expression.rs
index 39adc3b..99a1ecc 100644
--- a/src/structure/expression.rs
+++ b/src/structure/expression.rs
@@ -11,7 +11,7 @@ pub type Object<K, V> = vecmap::VecMap<K, V>;
 
 /// A type representing the expression sub-language is used within attribute definitions to specify
 /// values.
-#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
+#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
 #[serde(rename = "$hcl::expression")]
 #[non_exhaustive]
 pub enum Expression {
@@ -251,59 +251,32 @@ impl Display for Expression {
 }
 
 /// Represents an object key.
-#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash)]
+#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
 #[serde(rename = "$hcl::object_key")]
 #[non_exhaustive]
 pub enum ObjectKey {
-    /// Represents a bare unquoted identifer used as object key.
+    /// Represents an unquoted identifier used as object key.
     Identifier(Identifier),
-    /// Represents a quoted string used as object key.
-    String(String),
-    /// Represents a raw HCL expression. See [`RawExpression`] for more details.
-    RawExpression(RawExpression),
+    /// Any valid HCL expression can be an object key.
+    Expression(Expression),
 }
 
 impl ObjectKey {
-    /// Creates a new bare `ObjectKey` identifier.
+    /// Creates an unquoted string identifier `ObjectKey`.
     pub fn identifier<I>(identifier: I) -> Self
     where
         I: Into<Identifier>,
     {
         ObjectKey::Identifier(identifier.into())
     }
-
-    /// Creates a new quoted string `ObjectKey`.
-    pub fn string<S>(string: S) -> Self
-    where
-        S: Into<String>,
-    {
-        ObjectKey::String(string.into())
-    }
-
-    /// Creates a new raw expression `ObjectKey`.
-    pub fn raw_expression<E>(expr: E) -> Self
-    where
-        E: Into<RawExpression>,
-    {
-        ObjectKey::RawExpression(expr.into())
-    }
-}
-
-impl From<&str> for ObjectKey {
-    fn from(key: &str) -> Self {
-        ObjectKey::String(key.into())
-    }
-}
-
-impl From<String> for ObjectKey {
-    fn from(key: String) -> Self {
-        ObjectKey::String(key)
-    }
 }
 
-impl<'a> From<Cow<'a, str>> for ObjectKey {
-    fn from(key: Cow<'a, str>) -> Self {
-        ObjectKey::String(key.into())
+impl<T> From<T> for ObjectKey
+where
+    T: Into<Expression>,
+{
+    fn from(value: T) -> Self {
+        ObjectKey::Expression(value.into())
     }
 }
 
@@ -316,9 +289,11 @@ impl From<ObjectKey> for String {
 impl Display for ObjectKey {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
-            ObjectKey::Identifier(k) => Display::fmt(k.as_str(), f),
-            ObjectKey::String(k) => Display::fmt(k, f),
-            ObjectKey::RawExpression(raw) => Display::fmt(raw, f),
+            ObjectKey::Identifier(ident) => Display::fmt(ident.as_str(), f),
+            ObjectKey::Expression(expr) => match expr {
+                Expression::String(string) => Display::fmt(string, f),
+                expr => Display::fmt(expr, f),
+            },
         }
     }
 }
diff --git a/src/structure/for_expr.rs b/src/structure/for_expr.rs
index 89c9aa9..188e2bf 100644
--- a/src/structure/for_expr.rs
+++ b/src/structure/for_expr.rs
@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
 
 /// A for expression is a construct for constructing a collection by projecting the items from
 /// another collection.
-#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
+#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
 #[serde(rename = "$hcl::for_expr")]
 pub enum ForExpr {
     /// Represents a `for` expression that produces a list.
@@ -25,7 +25,7 @@ impl From<ForObjectExpr> for ForExpr {
 }
 
 /// A `for` expression that produces a list.
-#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
+#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
 #[serde(rename = "$hcl::for_list_expr")]
 pub struct ForListExpr {
     /// The "introduction" of the `for` expression.
@@ -63,7 +63,7 @@ impl ForListExpr {
 }
 
 /// A `for` expression that produces an object.
-#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
+#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
 #[serde(rename = "$hcl::for_object_expr")]
 pub struct ForObjectExpr {
     /// The "introduction" of the `for` expression.
@@ -117,7 +117,7 @@ impl ForObjectExpr {
 
 /// The `for` keyword followed by either one or two identifiers, the `in` keyword and an
 /// expression that must evaluate to a value that can be iterated.
-#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
+#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
 #[serde(rename = "$hcl::for_intro")]
 pub struct ForIntro {
     /// Optional name of the variable that will be temporarily assigned the key of each element
diff --git a/src/structure/func.rs b/src/structure/func.rs
index b0e55a4..8179ef5 100644
--- a/src/structure/func.rs
+++ b/src/structure/func.rs
@@ -3,7 +3,7 @@ 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)]
+#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
 #[serde(rename = "$hcl::func_call")]
 pub struct FuncCall {
     /// The name of the function.
diff --git a/src/structure/mod.rs b/src/structure/mod.rs
index 08bc5dc..ace1bee 100644
--- a/src/structure/mod.rs
+++ b/src/structure/mod.rs
@@ -129,7 +129,7 @@ impl From<Identifier> for String {
 /// Represents an HCL structure.
 ///
 /// There are two possible structures that can occur in an HCL [`Body`]: [`Attribute`]s and [`Block`]s.
-#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
+#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
 #[serde(rename = "$hcl::structure")]
 pub enum Structure {
     /// Represents an HCL attribute.
diff --git a/src/structure/operation.rs b/src/structure/operation.rs
index 4b1a779..93d8aa2 100644
--- a/src/structure/operation.rs
+++ b/src/structure/operation.rs
@@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
 use std::str::FromStr;
 
 /// Operations apply a particular operator to either one or two expression terms.
-#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
+#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
 #[serde(rename = "$hcl::operation")]
 pub enum Operation {
     /// Represents an operation that applies an operator to a single expression.
@@ -26,7 +26,7 @@ impl From<BinaryOp> for Operation {
 }
 
 /// An operation that applies an operator to one expression.
-#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
+#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
 #[serde(rename = "$hcl::unary_op")]
 pub struct UnaryOp {
     /// The unary operator to use on the expression.
@@ -98,7 +98,7 @@ impl<'de> Deserialize<'de> for UnaryOperator {
 }
 
 /// An operation that applies an operator to two expressions.
-#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
+#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
 #[serde(rename = "$hcl::binary_op")]
 pub struct BinaryOp {
     /// The expression on the left-hand-side of the operation.
diff --git a/src/structure/ser/expression.rs b/src/structure/ser/expression.rs
index 7f4ef80..037b4be 100644
--- a/src/structure/ser/expression.rs
+++ b/src/structure/ser/expression.rs
@@ -443,43 +443,43 @@ impl ser::Serializer for ObjectKeySerializer {
     serialize_self! { some newtype_struct }
 
     fn serialize_i8(self, value: i8) -> Result<Self::Ok> {
-        Ok(ObjectKey::String(value.to_string()))
+        Ok(ObjectKey::from(value))
     }
 
     fn serialize_i16(self, value: i16) -> Result<Self::Ok> {
-        Ok(ObjectKey::String(value.to_string()))
+        Ok(ObjectKey::from(value))
     }
 
     fn serialize_i32(self, value: i32) -> Result<Self::Ok> {
-        Ok(ObjectKey::String(value.to_string()))
+        Ok(ObjectKey::from(value))
     }
 
     fn serialize_i64(self, value: i64) -> Result<Self::Ok> {
-        Ok(ObjectKey::String(value.to_string()))
+        Ok(ObjectKey::from(value))
     }
 
     fn serialize_u8(self, value: u8) -> Result<Self::Ok> {
-        Ok(ObjectKey::String(value.to_string()))
+        Ok(ObjectKey::from(value))
     }
 
     fn serialize_u16(self, value: u16) -> Result<Self::Ok> {
-        Ok(ObjectKey::String(value.to_string()))
+        Ok(ObjectKey::from(value))
     }
 
     fn serialize_u32(self, value: u32) -> Result<Self::Ok> {
-        Ok(ObjectKey::String(value.to_string()))
+        Ok(ObjectKey::from(value))
     }
 
     fn serialize_u64(self, value: u64) -> Result<Self::Ok> {
-        Ok(ObjectKey::String(value.to_string()))
+        Ok(ObjectKey::from(value))
     }
 
     fn serialize_char(self, value: char) -> Result<Self::Ok> {
-        Ok(ObjectKey::String(value.to_string()))
+        Ok(ObjectKey::from(value.to_string()))
     }
 
     fn serialize_str(self, value: &str) -> Result<Self::Ok> {
-        Ok(ObjectKey::string(value))
+        Ok(ObjectKey::from(value))
     }
 
     fn serialize_unit_variant(
@@ -506,19 +506,12 @@ impl ser::Serializer for ObjectKeySerializer {
             ("$hcl::object_key", "Identifier") => {
                 Ok(ObjectKey::identifier(value.serialize(StringSerializer)?))
             }
-            ("$hcl::object_key", "RawExpression") => Ok(ObjectKey::raw_expression(
-                value.serialize(StringSerializer)?,
+            ("$hcl::object_key", "Expression") => Ok(ObjectKey::Expression(
+                value.serialize(ExpressionSerializer)?,
             )),
             (_, _) => value.serialize(self),
         }
     }
-
-    fn collect_str<T>(self, value: &T) -> Result<Self::Ok>
-    where
-        T: ?Sized + Display,
-    {
-        Ok(ObjectKey::String(value.to_string()))
-    }
 }
 
 /// Convert a `T` into `hcl::Expression` which is an enum that can represent any valid HCL
diff --git a/src/template.rs b/src/template.rs
index 6d636d1..05727a0 100644
--- a/src/template.rs
+++ b/src/template.rs
@@ -88,7 +88,7 @@ use std::str::FromStr;
 /// elements of the template are evaluated and combined into a single string to return.
 ///
 /// See the [`module level`][`crate::template`] documentation for usage examples.
-#[derive(Debug, Clone, PartialEq, Default)]
+#[derive(Debug, Clone, PartialEq, Eq, Default)]
 pub struct Template {
     elements: Vec<Element>,
 }
@@ -181,7 +181,7 @@ where
 }
 
 /// An element of an HCL template.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub enum Element {
     /// A literal sequence of characters to include in the resulting string.
     Literal(String),
@@ -218,7 +218,7 @@ impl From<Directive> for Element {
 
 /// An interpolation sequence evaluates an expression (written in the expression sub-language),
 /// converts the result to a string value, and replaces itself with the resulting string.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub struct Interpolation {
     /// The interpolated expression.
     pub expr: Expression,
@@ -257,7 +257,7 @@ impl From<Expression> for Interpolation {
 }
 
 /// A template directive that allows for conditional template evaluation.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub enum Directive {
     /// Represents a template `if` directive.
     If(IfDirective),
@@ -279,7 +279,7 @@ impl From<ForDirective> for Directive {
 
 /// The template `if` directive is the template equivalent of the conditional expression, allowing
 /// selection of one of two sub-templates based on the value of a predicate expression.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub struct IfDirective {
     /// The `if` branch expression.
     pub if_expr: IfExpr,
@@ -327,7 +327,7 @@ impl From<IfExpr> for IfDirective {
 }
 
 /// The `if` branch of an `if` directive.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub struct IfExpr {
     /// The conditional expression.
     pub expr: Expression,
@@ -362,7 +362,7 @@ impl IfExpr {
 }
 
 /// The `else` branch expression of an `if` directive.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub struct ElseExpr {
     /// The template that is included in the result string if the `if` branch's conditional
     /// expression evaluates to `false`.
@@ -392,7 +392,7 @@ impl ElseExpr {
 
 /// The template `for` directive is the template equivalent of the for expression, producing zero
 /// or more copies of its sub-template based on the elements of a collection.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub struct ForDirective {
     /// The loop expression.
     pub for_expr: ForExpr,
@@ -428,7 +428,7 @@ impl From<ForExpr> for ForDirective {
 }
 
 /// The `for` expression header of a `for` directive.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub struct ForExpr {
     /// Optional iterator key variable identifier.
     pub key: Option<Identifier>,


Test patch:
diff --git a/src/de/tests.rs b/src/de/tests.rs
index 2c0bebd..c153218 100644
--- a/src/de/tests.rs
+++ b/src/de/tests.rs
@@ -341,11 +341,11 @@ fn deserialize_terraform() {
                     "tags",
                     Expression::from_iter([
                         (
-                            ObjectKey::RawExpression("var.dynamic".into()),
+                            ObjectKey::from(ElementAccess::new(Identifier::new("var"), "dynamic")),
                             Expression::Null,
                         ),
                         (
-                            ObjectKey::String("application".into()),
+                            ObjectKey::from("application"),
                             Expression::String("myapp".into()),
                         ),
                         (
diff --git a/src/ser/tests.rs b/src/ser/tests.rs
index 1d80460..9a77a92 100644
--- a/src/ser/tests.rs
+++ b/src/ser/tests.rs
@@ -92,12 +92,15 @@ fn serialize_body() {
                 )
                 .add_attribute(("an_object", {
                     Object::from([
-                        (ObjectKey::identifier("foo"), "bar".into()),
+                        (ObjectKey::identifier("foo"), Expression::from("bar")),
                         (
-                            ObjectKey::string("enabled"),
-                            RawExpression::new("var.enabled").into(),
+                            ObjectKey::from("enabled"),
+                            Expression::from(RawExpression::new("var.enabled")),
+                        ),
+                        (
+                            ObjectKey::Expression(RawExpression::from("var.name").into()),
+                            Expression::from("the value"),
                         ),
-                        (ObjectKey::raw_expression("var.name"), "the value".into()),
                     ])
                 }))
                 .build(),
@@ -121,7 +124,7 @@ qux {
   an_object = {
     foo = "bar"
     "enabled" = var.enabled
-    "${var.name}" = "the value"
+    var.name = "the value"
   }
 }
 "#;
@@ -477,7 +480,7 @@ fn roundtrip() {
                     "tags",
                     Expression::from_iter([
                         (
-                            ObjectKey::String("application".into()),
+                            ObjectKey::from("application"),
                             Expression::String("myapp".into()),
                         ),
                         (
diff --git a/src/tests.rs b/src/tests.rs
index 587e6de..d34750b 100644
--- a/src/tests.rs
+++ b/src/tests.rs
@@ -25,24 +25,29 @@ fn expression_macro_arrays() {
 #[test]
 fn expression_macro_objects() {
     let expected = Expression::Object(Object::from([
-        ("foo".into(), "bar".into()),
-        ("baz".into(), true.into()),
-        ("qux".into(), vec![1, 2, 3].into()),
+        (ObjectKey::from("foo"), "bar".into()),
+        (ObjectKey::from("baz"), true.into()),
+        (ObjectKey::from("qux"), vec![1, 2, 3].into()),
+        (ObjectKey::from(1), 2.into()),
     ]));
 
     assert_eq!(
         expression!({
             "foo" = "bar",
             "baz" = true,
-            "qux" = [1, 2, 3]
+            "qux" = [1, 2, 3],
+            1 = 2
         }),
         expected
     );
 
     let expected = Expression::Object(Object::from([
         (ObjectKey::identifier("foo"), "bar".into()),
-        ("bar".into(), true.into()),
-        (ObjectKey::raw_expression("qux"), vec![1, 2, 3].into()),
+        (ObjectKey::from("bar"), true.into()),
+        (
+            ObjectKey::Expression(RawExpression::from("qux").into()),
+            vec![1, 2, 3].into(),
+        ),
     ]));
 
     let baz = "bar";