From aaef879330fa9f6af88fda4d300fa5235fa25294 Mon Sep 17 00:00:00 2001 From: Thayne McCombs Date: Thu, 12 Feb 2026 09:58:40 -0700 Subject: [PATCH] fix: Use valid syntax for conditionals (#254) * fix: Use valid syntax for conditionals Fix an issue where object literals inside conditional expressions were not rendered correctly, resulting in invalid expresions. --- hcl2/transformer.py | 34 ++++++++++++++++++++++------------ test/unit/test_hcl2_syntax.py | 12 +++++++++--- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/hcl2/transformer.py b/hcl2/transformer.py index 382092d6..6e77d4da 100644 --- a/hcl2/transformer.py +++ b/hcl2/transformer.py @@ -178,7 +178,7 @@ def attribute(self, args: List) -> Attribute: def conditional(self, args: List) -> str: args = self.strip_new_line_tokens(args) - args = self.process_nulls(args) + args = self.all_to_tf_inline(args) return f"{args[0]} ? {args[1]} : {args[2]}" def binary_op(self, args: List) -> str: @@ -187,13 +187,13 @@ def binary_op(self, args: List) -> str: ) def unary_op(self, args: List) -> str: - args = self.process_nulls(args) - return "".join([self.to_tf_inline(arg) for arg in args]) + args = self.all_to_tf_inline(args) + return "".join(args) def binary_term(self, args: List) -> str: args = self.strip_new_line_tokens(args) - args = self.process_nulls(args) - return " ".join([self.to_tf_inline(arg) for arg in args]) + args = self.all_to_tf_inline(args) + return " ".join(args) def body(self, args: List) -> Dict[str, List]: # See https://github.com/hashicorp/hcl/blob/main/hclsyntax/spec.md#bodies @@ -279,20 +279,24 @@ def new_line_or_comment(self, args: List) -> _DiscardType: def for_tuple_expr(self, args: List) -> str: args = self.strip_new_line_tokens(args) - for_expr = " ".join([self.to_tf_inline(arg) for arg in args[1:-1]]) + args = self.all_to_tf_inline(args[1:-1]) + for_expr = " ".join(args) return f"[{for_expr}]" def for_intro(self, args: List) -> str: args = self.strip_new_line_tokens(args) - return " ".join([self.to_tf_inline(arg) for arg in args]) + args = self.all_to_tf_inline(args) + return " ".join(args) def for_cond(self, args: List) -> str: args = self.strip_new_line_tokens(args) - return " ".join([self.to_tf_inline(arg) for arg in args]) + args = self.all_to_tf_inline(args) + return " ".join(args) def for_object_expr(self, args: List) -> str: args = self.strip_new_line_tokens(args) - for_expr = " ".join([self.to_tf_inline(arg) for arg in args[1:-1]]) + args = self.all_to_tf_inline(args[1:-1]) + for_expr = " ".join(args) # doubled curly braces stands for inlining the braces # and the third pair of braces is for the interpolation # e.g. f"{2 + 2} {{2 + 2}}" == "4 {2 + 2}" @@ -308,7 +312,7 @@ def string_part(self, args: List) -> str: return value def interpolation(self, args: List) -> str: - return '"${' + str(args[0]) + '}"' + return '"${' + self.to_tf_inline(args[0]) + '}"' def strip_new_line_tokens(self, args: List) -> List: """ @@ -369,6 +373,12 @@ def process_escape_sequences(self, value: str) -> str: def process_nulls(self, args: List) -> List: return ["null" if arg is None else arg for arg in args] + def all_to_tf_inline(self, args: List) -> List: + """ + Convert all items in a list to "inline" HCL syntax + """ + return [self.to_tf_inline(arg) for arg in args] + def to_tf_inline(self, value: Any) -> str: """ Converts complex objects (e.g.) dicts to an "inline" HCL syntax @@ -383,11 +393,11 @@ def to_tf_inline(self, value: Any) -> str: if isinstance(value, bool): return "true" if value else "false" if isinstance(value, str): - return value + return self.unwrap_string_dollar(value) if isinstance(value, (int, float)): return str(value) if value is None: - return "None" + return "null" raise RuntimeError(f"Invalid type to convert to inline HCL: {type(value)}") diff --git a/test/unit/test_hcl2_syntax.py b/test/unit/test_hcl2_syntax.py index 96113df3..d3960f00 100644 --- a/test/unit/test_hcl2_syntax.py +++ b/test/unit/test_hcl2_syntax.py @@ -132,9 +132,7 @@ def test_function_call_and_arguments(self): arg1, arg2, arg3, ) - """: { - "r": "${function(arg1, arg2, arg3)}" - }, + """: {"r": "${function(arg1, arg2, arg3)}"}, } for call, expected in calls.items(): @@ -191,3 +189,11 @@ def test_expr_term_parenthesis(self): for actual, expected in literals.items(): result = self.load_to_dict(actual) self.assertDictEqual(result, expected) + + def test_dict_in_conditional(self): + cond = "x = a ? { id = 1 } : null" + + expected = {"x": '${a ? {"id": 1} : null}'} + + result = self.load_to_dict(cond) + self.assertDictEqual(result, expected)