Skip to content

fix: heredocs_to_strings writes a value, not the heredoc's text (#337) - #351

Open
livingstaccato wants to merge 2 commits into
amplify-education:mainfrom
livingstaccato:fix/heredocs-to-strings-value
Open

fix: heredocs_to_strings writes a value, not the heredoc's text (#337)#351
livingstaccato wants to merge 2 commits into
amplify-education:mainfrom
livingstaccato:fix/heredocs-to-strings-value

Conversation

@livingstaccato

@livingstaccato livingstaccato commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes #337.

What

The option converts a heredoc into a quoted string, and was quoting the heredoc's own source — markers and all — across as many physical lines as the original occupied:

a = "<<EOT
hello
EOT"

A quoted template cannot span lines. OpenTofu rejects that with "Invalid multi-line string", and reading it back here gives '<<EOT\nhello\nEOT' — the marker text rather than the value. So the option produced neither a valid file nor the right content, and it is exposed on the shipped jsontohcl2 script, so the CLI had the same hole.

After:

a = "hello"

How

By reusing the flattening the reader already performs, rather than writing a second one: serializing the rule with preserve_heredocs=False is exactly the quoted form that option produces on the way in. The two therefore cannot drift apart the way the escape alphabets did in #329 — there is only one implementation of "what is this heredoc as a quoted string".

What that inherits

The value carried is the reader's. On main that still drops the body's trailing newline, so <<EOT\nhello\nEOT becomes "hello" where Terraform reads "hello\n". That is #326, fixed in #335, and this change deliberately follows whatever the reader does rather than second-guessing it — when #335 lands, this output gains the newline with no change here.

Eight tests, including that every converted form parses again and that no marker survives into the output.

Merging

It touches the same code as #346 (hcl2/deserializer.py), #354 (hcl2/deserializer.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. Please review with that provenance in mind.

…ify-education#337)

The option converts a heredoc into a quoted string, and was quoting the
heredoc's own source -- markers and all -- across as many physical lines
as the original occupied:

    a = "<<EOT
    hello
    EOT"

A quoted template cannot span lines. OpenTofu rejects that with "Invalid
multi-line string", and reading it back here gave the marker text rather
than the value, so the option produced neither a valid file nor the
right content. It is exposed on the shipped `jsontohcl2` script, so the
CLI had the same hole.

The flattening the reader already performs is reused rather than written
a second time: serializing the rule with `preserve_heredocs=False` is
exactly the quoted form that option produces on the way in, so the two
cannot drift apart the way the escape alphabets did.

The value that form carries is the reader's, which on this branch still
drops the body's trailing newline -- that is amplify-education#326, and this change
follows whatever the reader does rather than second-guessing it.
@livingstaccato

livingstaccato commented Sep 2, 2026

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.

@livingstaccato

livingstaccato commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Review pass done, so the hold above no longer applies — this is ready for review now.

Rebased on current main; GitHub reports it mergeable as it stands.

🤖 Drafted with Claude Code.

@livingstaccato

Copy link
Copy Markdown
Contributor Author

Brought up to date with current main (0f74596) — GitHub had this flagged as
conflicting, which was only CHANGELOG.md and the merge=union driver that the
web UI does not honour. This one conflicts with #346 and #354 (hcl2/deserializer.py) rather than
with main; the second note is about this PR's expectations too.

Merge order. Eight of the twelve open PRs apply to main in sequence with no
conflict at all — verified by merging each and running the suite, not by
inspection:

#332 → #334 → #335 → #345 → #346 → #349 → #352 → #354

(1552 → 1563 → 1591 → 1627 → 1671 → 1696 → 1703 → 1711 passing, from a 1534
baseline; each adds its own tests.) The other four — #333, #348, #350, #351
conflict with siblings rather than with main, so no ordering avoids them.

#335 changes a value two of the others assert on. It makes a heredoc body
carry the newline that ends its last content line, matching what OpenTofu
evaluates:

from hcl2.utils import SerializationOptions
opts = SerializationOptions(strip_string_quotes=True, preserve_heredocs=False)
hcl2.loads("a = <<E\nx\nE\n", serialization_options=opts)["a"]

main alone:   'x'
main + #335:  'x\n'

#350 and #351 were written before that. Once #335 is in, their expectations need
the newline — in an integration carrying all twelve, every one of the 16
resulting failures was exactly that one character and nothing else. Worth knowing
because it presents as a regression and is not one.

A resolved integration of all twelve is on the fork if it is useful:
livingstaccato/python-hcl2:int/pyvider-hcl-9 — 1784 passing, ruff and mypy
clean. Not proposed as a PR; the twelve are more reviewable apart.

Drafted with the help of an LLM, working on behalf of the author.

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.

heredocs_to_strings=True emits a multi-line quoted string, which is not valid HCL

1 participant