Repository navigation
Conversation
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.
Contributor
There was a problem hiding this comment.
🟡 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 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())?; |
| /// 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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


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 intree-sitter, where multiple choices can succeed, and the order thus doesn't matter. Our version is more akin to a pattern-matchingcondform à la Lisp.If a capture is absent in some branches, then the body of the rule can only access it as an
Optiontype. If it's present in all branches, then it doesn't get theOptionwrapper. Similarly, if some branches can capture multiple values under a given name, then the type becomes aVec, 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
@fooin one branch and@@fooin 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.