Skip to content

yeast: Support query choices - #23020

Draft
tausbn wants to merge 12 commits into
mainfrom
tausbn/yeast-support-query-choices
Draft

tausbn wants to merge 12 commits into
mainfrom
tausbn/yeast-support-query-choices

Conversation

@tausbn

@tausbn tausbn commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

We recently added support for choices in the output templates, this PR adds a similar construction on the input side as well.

Writing [tree1 tree2 tree3 ...] in a query results in each tree being attempted in turn, until we find one that matches the current part of the input tree. (This differs slightly from the corresponding construct in tree-sitter, where multiple choices can succeed, and the order thus doesn't matter. Our version is more akin to a pattern-matching cond form Ă  la Lisp.

If a capture is absent in some branches, then the body of the rule can only access it as an Option type. If it's present in all branches, then it doesn't get the Option wrapper. Similarly, if some branches can capture multiple values under a given name, then the type becomes a Vec, regardless of how it is present in other branches.

Of note: it is illegal to mix raw and translated captures (for a particular name) inside of a choice. That is, you can't have @foo in one branch and @@foo in another. This property is enforced at compile-time.

I have a follow-up branch ready that adds support for where {...} guards, matching the top-level syntax (except that braces are required to make the syntax unambiguous). However, this change is mostly separate, and would make the PR roughly twice as big, which is why I left it out.


Should be reviewed commit-by-commit. Again, I have erred on the side of making commits smaller rather than larger, including splitting tests and documentation into their own commits. Each commit should still (hopefully) make sense on its own.

tausbn added 12 commits November 4, 2026 13:15
A choice is a collection of separate queries. These will be attempted in
turn, and the first one that succeeds is picked. (This differs from the
choice operator in tree-sitter, which can match multiple times.)

If a capture is present in all alternatives of a choice, then it is
directly available in the rule body. If it's absent in some branches, it
becomes an Option type. Likewise if repetition is present, the capture
can end up with a Vec type (even if it is guaranteed to be present in
some alternatives).

It is an error to mix raw and translated captures across alternatives.
In this case, then only sensible thing to do is to use a raw capture and
manually translate things later.

Finally, the check that root captures are raw (because the alternative
would lead to infinite recursion) is extended to support choice
operators as well.
Adds a few tests for various aspects of choice parsing.
Adds the logic that handles matching query choices, extending the
QueryNode type with a matching Choice branch.
Lowering is fairly straightforward, since we have essentially the same
structure in the AST and in the QueryNode type.
This one is essentially identical to the rule that directly follows it,
so we can just get rid of it.
An optional capture takes care of the potentially missing value (meaning
we don't need a separate rule for it).
I'm pleasantly surprised that the new syntax for template choices
supports having a Rust block as an alternative. We may need to annotate
these with the "return type" at some point, in order to make type
checking work out, but that can wait.
@tausbn tausbn added the no-change-note-required This PR does not need a change note label Nov 4, 2026
@tausbn
tausbn requested a max review from Copilot November 4, 2026 15:44

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

Mixed single-node and quantified list-choice alternatives can miss valid captures, while root-choice handling can panic or silently drop quantifiers.

3 open issues
What changed in this PR

Adds ordered input-query choices to YEAST’s tree-transformation language and adopts them in the unified Swift extractor.

Changes:

  • Add choice parsing, matching, lowering, and multi-kind rule indexing.
  • Unify capture types across alternatives and reject mixed raw/translated capture modes.
  • Add tests and documentation, and consolidate Swift translation rules.
File Description
unified/​extractor/​src/​languages/​swift/​swift.rs Consolidates translation rules using choices and optional patterns.
shared/​yeast/​tests/​test.rs Tests choice matching and capture-type unification.
shared/​yeast/​src/​query.rs Adds choice matching and root-kind discovery.
shared/​yeast/​src/​lib.rs Indexes choice rules under each possible root kind.
shared/​yeast/​doc/​yeast.md Documents choice syntax and capture behavior.
shared/​yeast-macros/​src/​rule_parse.rs Parses choices and validates capture consistency.
shared/​yeast-macros/​src/​lower.rs Lowers choices and combines capture cardinalities.
shared/​yeast-macros/​src/​lib.rs Documents choices in the macro APIs.
shared/​yeast-macros/​src/​ast.rs Adds the choice pattern and single-node classification.

Review effort: Max

Comment thread shared/yeast/src/query.rs
Comment on lines +232 to +235
for alternative in alternatives {
let mut candidate_children = start.clone();
let mut candidate_captures = Captures::new();
if match_children(
));
}
validate_unique_capture_names(&pattern, &mut BTreeSet::new())?;
validate_unique_capture_names(&pattern, &mut CaptureNames::new())?;
Comment thread shared/yeast/src/query.rs
/// and choices spanning multiple root kinds return `None`.
pub fn root_kind(&self) -> Option<&'static str> {
let kinds = self.root_kinds()?;
(kinds.len() == 1).then_some(kinds[0])

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