Skip to content

Proposal: Generalize the idea of using [*] in traversals to describe "traversal patterns"#805

Open
apparentlymart wants to merge 6 commits into
hashicorp:mainfrom
opentofu:f-splat-as-traversal-2
Open

Proposal: Generalize the idea of using [*] in traversals to describe "traversal patterns"#805
apparentlymart wants to merge 6 commits into
hashicorp:mainfrom
opentofu:f-splat-as-traversal-2

Conversation

@apparentlymart

Copy link
Copy Markdown
Contributor

The earlier PR #673 introduced hclsyntax.ParseTraversalPartial that is a variant of hclsyntax.ParseTraversalAbs which additionally accepts [*] steps representing placeholders for arbitrary indices. It introduced a new use of the previously-vestigial hcl.TraverseSplat type1 as the representation of those placeholders.

However, that idea wasn't generalized to the other main way callers can produce hcl.Traversal values: asking HCL to perform static analysis of an already-parsed expression, like Terraform does when it's analyzing references in parts of its language like depends_on and the from/to expressions in moved, removed, and import blocks.

This PR is proposing to generalize this idea of "traversals with wildcards" into a new first-class concept that I've called "traversal patterns". This new idea extends the idea of "traversal" to include the possibility of hcl.TraverseSplat steps, and makes it possible to turn both source code and certain already-parsed expressions into traversal patterns.

Of course I have my own reasons to propose this, but as a "closer-to-home" motivation consider that Terraform could hypothetically use this to support wildcard patterns in removed blocks:

removed {
  # Forget rather than destroy the database object in _any_ instance of
  # this module call, rather than having to name each one individually.
  from = module.example[*].aws_db_instance.example

  lifecycle {
    destroy = false
  }
}

...or for wildcard index steps in ignore_changes for list-based blocks, without having to write out every possible index explicitly (hashicorp/terraform#5666):

  lifecycle {
    ignore_changes = [
      # Ignore changes to the last_updated attribute in all blocks
      # of type "listy_thing", regardless of how many there are.
      listy_thing[*].last_updated,
    ]
  }

I understand that there'd be a lot more work to do in Terraform to do these things than just merging this PR, but this changeset would create a new building block in the HCL grammar for languages like Terraform's to use.

The ParseTraversalPartial function was already producing traversals that can't support Traversal.Value, and that remains true for the concept of "traversal pattern" introduced here. However, calls to Value now return an error diagnostic instead of panicking as before.

Turning an AST that includes a mixture of absolute traversals, relative traversals, and splat expressions into a flat hcl.Traversal is kinda annoying to achieve and so the code I wrote for it here is honestly quite dense and tricky to follow, but I tried to write a good suite of unit tests covering it, both at the parser level (to make sure the parser generates the AST that the flattening code expects) and at the "as traversal pattern" level.

Footnotes

  1. That TraverseSplat type originated from a much earlier implementation of splat expressions in the ZCL library that HCL 2 was forked from, where they were treated as a special kind of traversal step instead of as the separate expression type hclsyntax.SplatExpr.

    I forgot to actually remove the TraverseSplat type after rewriting the implementation of splat expressions and so it ended up surviving into the first stable release of HCL 2 but without any uses until hclsyntax.ParseTraversalPartial came along and gave it a new purpose.

Our current hcl.AbsTraversalForExpr function and our planned future
hcl.AbsTraversalPatternForExpr depend on the overall shape of AST that the
parser produces for certain combinations of traversal and splat
expressions, so this test is intended to ensure we preserve those AST
shapes under future changes to the parser, and doubles as a set of
examples for which AST shapes we're intending to support in this way.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
@apparentlymart apparentlymart requested a review from a team as a code owner June 10, 2026 21:52
@apparentlymart

Copy link
Copy Markdown
Contributor Author

I see that the "copyright headers" check is failing but I'm not sure what exactly it's wanting me to do, since the files it mentioned do seem to have the "Copyright IBM Corp" headers in them. 🤔

@mildwonkey

Copy link
Copy Markdown
Contributor

It was complaining because the modified files should get updated with 2026 - I don't know if you want me pushing a commit to your branch but I can do it, or you can. (Hi Martin!!! 👋🏻 ❤️ )

@apparentlymart apparentlymart force-pushed the f-splat-as-traversal-2 branch from 30483e6 to e7fdd16 Compare June 11, 2026 15:21
@apparentlymart

Copy link
Copy Markdown
Contributor Author

Hello! 👋😀

Thanks for explaining what that check is looking for. I've pushed an additional commit to meet its expectations. ✔️

The hclsyntax.ParseTraversalPartial function previously introduced the idea
of using the splat expression syntax to represent a wildcard index,
exploiting the accidentally-left-over hcl.TraverseSplat traverser type
to represent the wildcard steps. But that idea wasn't previously
generalized to work with expressions that appear as part of a configuration
file or expression.

This retroactively adopts the term "traversal pattern" to describe this
special kind of hcl.Traversal that has at least one TraverseSplat step,
renames the existing function to hclsyntax.ParseTraversalAbsPattern to
reflect that new nomenclature, and then introduces
hcl.AbsTraversalPatternForExpr as the corresponding companion to
hcl.AbsTraversalForExpr, so that expression types can have their own rules
for whether and how they can be interpreted as traversal patterns.

This commit does not yet include any implementations of AsTraversalPattern.
Those will follow in subsequent commits.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
Previously there was no explicit representation of what kind of splat we
had parsed and instead it was just implied by the shape of the AST, which
varies due to attribute-only splat having different precedence relative
to the index operator.

Now we'll make that explicit as a new field. We're not using that field
yet here, but a future commit will use it to support treating a splat
expression as a "traversal pattern", but only when using the modern splat
syntax because attribute-only splat's precedence isn't appropriate for
being treated as a step in a traversal.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
Implementing the AsTraversalPattern method makes SplatExpr eligible to
be interpreted as a "traversal pattern" as long as everything nested inside
it can be suitably flattened into a series of traversal steps.

This must be AsTraversalPattern rather than just AsTraversal because this
particular expression type causes hcl.TraverseSplat steps to be included,
which makes the result a traversal pattern rather than a concrete
traversal.

The test added here is more general, also covering various expressions that
don't include SplatExpr but are nonetheless eligible to be treated as
traversal patterns. The ones without SplatExpr actually return concrete
traversals rather than traversal patterns, but all concrete traversals can
be used as patterns that match exactly one traversal.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
This makes expressions from JSON source files be compatible with the
recently-added hcl.AbsTraversalPatternForExpr, following the same principle
as how we've previously been handling hcl.AbsTraversalForExpr: the JSON
representation is a JSON string containing a traversal pattern in HCL
native syntax.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
This repository's checking tool is written under the assumption that only
IBM contributes to this repository and so expects the copyright year
associated with that company to be updated on every change.

The changes that prompted this update (the previous five commits) are
actually Copyright 2026 Spacelift, Inc, but contributed to HCL under the
terms of the Mozilla public license.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
@apparentlymart apparentlymart force-pushed the f-splat-as-traversal-2 branch from e7fdd16 to b5698d9 Compare June 24, 2026 22:20
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.

2 participants