Skip to content

yeast: Add support for template choices - #22902

Open
tausbn wants to merge 2 commits into
mainfrom
tausbn/yeast-add-template-choices
Open

tausbn wants to merge 2 commits into
mainfrom
tausbn/yeast-add-template-choices

Conversation

@tausbn

@tausbn tausbn commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

When creating the target tree for some rewrite rule, we quite often want to make the construction conditional on the presence of certain captures, or on the current state of the context.

Previously we added the postfix ? operator for this purpose. This made it possible to write a tree template like

(foo 
  bar: (baz id: (identifier #{name}))?
)

wherein the entire baz node disappears (because of the postfix ?) if the name interpolation fails (e.g. if name is None).

There are a few drawbacks with this approach:

  • There's no way to specify a non-None alternative.
  • It only works for #{...} interpolations.
  • It works on the basis of constructing the tree eagerly and then cancelling the construction if any of the interpolations failed. This could lead to orphaned nodes in the output AST if these were constructed before the interpolation was attempted.

(This also made the implementation somewhat complicated, requiring the use of labels and breaks in the lowered Rust code.)

Instead of the above, this PR implements a generalisation of this concept. Taking inspiration from the tree-sitter query language, we use square brackets to indicate a list of alternatives that should be attempted in turn. The first alternative that succeeds is the tree that is returned from this choice operator. Moreover, individual choices can have a bool-returning Rust-block as a guard.

Thus, something like

[
  tree1 if {cond1}
  tree2
  tree3 if {cond2}
]

would have the following interpretation

  • If cond1 holds, and all #{...} interpolations in tree1 are valid (e.g. different from None), then return tree1
  • Otherwise, if all #{...} interpolations in tree2 are valid, return that
  • Otherwise, if cond3 holds and all #{...} interpolations in tree3 are valid, return that
  • Otherwise return None

This fully supersedes the existing ? syntax (which we therefore remove first): (...)? can be rewritten to just [(...)].

Before constructing any trees, the implementation first checks that the relevant condition (if present) holds, and that all #{...} interpolations have usable values. Only if both of these are true is the tree construction attempted. This prevents any orphaned nodes from being constructed.

This will be superseded by the next commit, which generalised the
mechanism and also makes the implementation simpler.
@tausbn tausbn added the no-change-note-required This PR does not need a change note label Oct 27, 2026
@tausbn
tausbn requested a max review from Copilot October 27, 2026 13:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Two critical compilation failures remain: missing OutputId support breaks translated captures, and premature candidate construction causes moved-value errors when alternatives consume the same repeated capture.

2 open issues
What changed in this PR

Adds ordered, guarded choices to Yeast tree templates, replacing postfix ?. The new syntax lets rewrite rules express fallbacks and check interpolation dependencies before constructing template nodes.

Changes:

  • Add choice parsing, validation, and lowering with guard and dependency checks.
  • Update documentation and tests for fallback selection, nested choices, and orphan-node prevention.
  • Migrate Swift translation rules to bracketed choices.
File Description
unified/​extractor/​src/​languages/​swift/​swift.rs Migrates optional nodes and conditional rewrites to choices.
shared/​yeast/​tests/​test.rs Covers guards, fallbacks, nested choices, and construction preflight.
shared/​yeast/​src/​lib.rs Updates interpolation diagnostics and optional-value implementations.
shared/​yeast/​doc/​yeast.md Documents choice syntax, behavior, and examples.
shared/​yeast-macros/​src/​template_parse.rs Parses and validates choices and guards; removes postfix ? parsing.
shared/​yeast-macros/​src/​lower.rs Generates dependency checks and alternative-selection code.
shared/​yeast-macros/​src/​lib.rs Updates public macro documentation for choices and return types.
shared/​yeast-macros/​src/​ast.rs Adds choice alternatives and removes the optional-field flag.

Review effort: Max

Comment thread shared/yeast-macros/src/lower.rs Outdated
Comment thread shared/yeast/src/lib.rs Outdated
Adds a new syntactic form
```
[tree1 if {cond1}
 tree2
 tree3 if {cond3}
]
```
to the language, the intended semantics being that we output the first
tree that is usable. Two things can prevent a tree from being used:

- the guard condition (a Rust expression evaluating to a boolean) may
  fail;
- a string interpolation (e.g. `#{name}`) may fail to have a value.

If all of the alternatives fail, then the result is `None`. This makes
the syntax a generalisation of the `?` postfix operator that was removed
in the previous commit. `(foo)?` can be replaced with a single-element
alternative `[(foo)]`.

As the changes to swift.rs show, this can clean up a bunch of
constructions where we currently rely on helper functions or somewhat
complicated Rust blocks.

In terms of the implementation, we go through the alternatives in turn,
first evaluating the guard condition. If this succeeds, we evaluate (and
temporarily store) all of the `#{...}` interpolations, and if those all
return `Some(...)` values, we instantiate the template itself. A benefit
of this implementation compared to the one for `?` (which used named
labels and `break`s) is that if either requirement fails, no AST nodes
are constructed. (The `?` implementation would eagerly construct the
tree, and then abort if any of the interpolations failed. This could
leave orphaned nodes in the AST arena.)

A choice cannot contain another choice as an immediate child (as that
choice could simply have its branches inlined in the parent), but
nesting further down is allow. In that case, the inner choice takes care
of handling its internal "failures", and these do not bubble up to the
outer choice.
@tausbn
tausbn force-pushed the tausbn/yeast-add-template-choices branch from bb7eb03 to 7027a76 Compare October 27, 2026 15:03
@tausbn
tausbn marked this pull request as ready for review October 27, 2026 15:04
@tausbn
tausbn requested a review from a team as a code owner October 27, 2026 15:04

This branch has not been deployed

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

Labels

documentation no-change-note-required This PR does not need a change note Unified

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants