Skip to content

fix: force_operation_parentheses works under a parenthesised ancestor (#342) - #348

Draft
livingstaccato wants to merge 1 commit into
amplify-education:mainfrom
livingstaccato:fix/force-parens-nested
Draft

fix: force_operation_parentheses works under a parenthesised ancestor (#342)#348
livingstaccato wants to merge 1 commit into
amplify-education:mainfrom
livingstaccato:fix/force-parens-nested

Conversation

@livingstaccato

@livingstaccato livingstaccato commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes #342.

What

force_operation_parentheses exists to make precedence explicit, and it did so for a top-level expression. Inside one the caller had already parenthesised, it added nothing:

options = SerializationOptions(force_operation_parentheses=True)

loads("a = b + c * d\n",   serialization_options=options)["a"]  # '${b + (c * d)}'
loads("a = (b + c * d)\n", serialization_options=options)["a"]  # '${(b + c * d)}'  ← unchanged

So the documents most likely to want explicit precedence — the ones already carrying parentheses — got the least of it.

Why

inside_parentheses answers "did my immediate container already wrap me". _wrap_into_parentheses reads it to avoid doubling. Two places widened it to "some ancestor is parenthesised":

  • ExprTermRule.serialize carried it down as self.parentheses or context.inside_parentheses, so one parenthesised term set it for its whole subtree;
  • the binary, unary and conditional rules passed it unchanged to their operands, which nothing directly wraps.

Each term now sets the flag from its own self.parentheses, and the operation rules clear it for their operands.

What does not change

((b + c) * d) is not doubled, (b + c) * d keeps its single pair, and the option-less path emits exactly what it did before — asserted for six shapes, since that is the half most easily broken by a change like this.

With b=2, c=3, d=4, OpenTofu v1.12.5 evaluates both (b + c * d) and the (b + (c * d)) this produces to 14.

Merging

It touches the same code as #334 (hcl2/rules/expressions.py). Whichever of those lands first, this one needs a rebase rather than a merge — the overlaps are real edits to the same methods, not adjacent lines, so resolving them by hand risks losing one of the two fixes. Say the word and I will rebase and re-run the suite.


This pull request, and the investigation behind it, were produced by an AI assistant (Claude) working on behalf of the author. Every reproduction, test run and benchmark cited was executed rather than inferred, but please review with that provenance in mind.

…amplify-education#342)

The option makes precedence explicit, and did so for a top-level
expression. Inside one the caller had already parenthesised it added
nothing: `(b + c * d)` came back unchanged, so the documents most likely
to want explicit precedence got the least of it.

`inside_parentheses` answers "did my immediate container already wrap
me", which `_wrap_into_parentheses` reads to avoid doubling them. Two
places widened it to "some ancestor is parenthesised": `ExprTermRule`
carried the flag down with `or`, and the binary, unary and conditional
rules passed it to their operands, which nothing directly wraps.

Each term now sets the flag from its own `self.parentheses`, and the
operation rules clear it for their operands. `((b + c) * d)` is still
not doubled, and the option-less path emits exactly what it did before.

With b=2, c=3, d=4, OpenTofu evaluates both `(b + c * d)` and the
`(b + (c * d))` this now produces to 14.
@livingstaccato

Copy link
Copy Markdown
Contributor Author

Please hold off on merging this one for now — I want to do another review pass over it before it goes in. Opened as a draft for that reason; I will mark it ready and say so here once I am done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

force_operation_parentheses adds no parentheses inside an already-parenthesized expression

1 participant