Skip to content

fix(parser): process pending heredocs before use/no BEGIN-time evaluation#554

Merged
fglock merged 1 commit intomasterfrom
fix/use-heredoc-args
Apr 24, 2026
Merged

fix(parser): process pending heredocs before use/no BEGIN-time evaluation#554
fglock merged 1 commit intomasterfrom
fix/use-heredoc-args

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Apr 24, 2026

Summary

Fixes a parser bug where heredocs used as arguments to use/no declarations (e.g. use constant FOO => <<'EOT';) were not resolved before the import list was compiled and executed at BEGIN time, causing:

HEREDOC marker EOT not found
BEGIN failed--compilation aborted

Root cause

StatementParser.parseUseDeclaration evaluates the import list immediately via runSpecialBlock(parser, "BEGIN", list, ...). Heredoc bodies are normally filled in by the whitespace handler when it consumes the NEWLINE after the statement, but that hasn't happened yet at the point runSpecialBlock runs — so the HEREDOC placeholder OperatorNode is still unresolved and EmitOperatorNode.handleMissingHeredoc throws.

SpecialBlockParser already had the same fix for explicit BEGIN { ... } blocks; the use/no path just wasn't mirroring it.

Fix

Before runSpecialBlock, if heredocs are pending, seek forward to the next NEWLINE, call ParseHeredoc.parseHeredocAfterNewline to fill the bodies, record heredocSkipToIndex / heredocNewlineIndex so the outer whitespace handler does not re-process them, then restore parser.tokenIndex.

Discovered via

jcpan -t Text::Tablet/10_Table.t previously failed all 166 subtests at:

use constant T_SINGLE => <<'EOT2';
single line title
EOT2

Test plan

  • Minimal repros for use constant NAME => <<'EOT', use constant { NAME => <<'EOT' }, and use constant A => "x", B => <<'EOT' all compile and run correctly.
  • jcpan -t Text::TableResult: PASS, 175/175 tests across 7 files (was 0/166 in t/10_Table.t).
  • make (full unit test suite) — BUILD SUCCESSFUL, no regressions.

Generated with Devin

…aluation

`use Module LIST;` compiles and executes LIST immediately via
runSpecialBlock("BEGIN", ...) inside parseUseDeclaration. If LIST
contained a heredoc operator (e.g. `use constant FOO => <<'EOT';`),
the heredoc body was still un-resolved at emit time because heredoc
bodies are normally filled in only when whitespace processing consumes
the following NEWLINE — which had not happened yet. The emitter then
threw "HEREDOC marker <id> not found".

Mirror the existing fix for explicit BEGIN { ... } blocks in
SpecialBlockParser: before runSpecialBlock, if heredocs are pending,
seek to the next NEWLINE, call ParseHeredoc.parseHeredocAfterNewline
to fill bodies, record heredocSkipToIndex/heredocNewlineIndex so the
outer whitespace handler does not re-process them, then restore
tokenIndex.

Observed via `jcpan -t Text::Table` where t/10_Table.t failed all
166 subtests at a `use constant T_SINGLE => <<'EOT2';` declaration.
After the fix all 175 Text::Table tests pass across 7 files.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@fglock fglock merged commit 21a48f5 into master Apr 24, 2026
2 checks passed
@fglock fglock deleted the fix/use-heredoc-args branch April 24, 2026 12:04
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.

1 participant