Skip to content

Commit 39547c7

Browse files
authored
Merge pull request #22626 from github/tausbn/yeast-remove-rule-shorthand
yeast: Get rid of shorthand form for rules
2 parents 09d8d9a + d28b7ae commit 39547c7

4 files changed

Lines changed: 13 additions & 165 deletions

File tree

‎shared/yeast-macros/src/lib.rs‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,6 @@ pub fn trees(input: TokenStream) -> TokenStream {
131131
/// (output_template)
132132
/// )
133133
///
134-
/// // Shorthand: captures become fields on the output node
135-
/// rule!((query ...) => output_kind)
136134
/// ```
137135
///
138136
/// Captures become Rust variables automatically:

‎shared/yeast-macros/src/parse.rs‎

Lines changed: 3 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -768,8 +768,7 @@ fn extract_captures_inner(
768768
/// ```
769769
///
770770
/// Template bodies (`=> (kind …)`) never carry an annotation — the
771-
/// output kind is the template root. The shorthand `=> kind` (no
772-
/// body) also carries no annotation. See `parse_rule_top` for dispatch.
771+
/// output kind is the template root.
773772
#[derive(Clone, Debug)]
774773
struct ReturnAnnotation {
775774
kind: Ident,
@@ -793,7 +792,6 @@ enum AnnotationMultiplicity {
793792
/// `kind {` → annotation (single)
794793
/// `kind? {` → annotation (optional)
795794
/// `kind* {` → annotation (repeated)
796-
/// `kind` → shorthand form (no `{` follows) — NOT an annotation
797795
/// anything else → template or bare block — NOT an annotation
798796
fn try_consume_return_annotation(tokens: &mut Tokens) -> Result<Option<ReturnAnnotation>> {
799797
// Must start with an identifier (the kind name).
@@ -881,15 +879,13 @@ pub fn parse_rule_top(input: TokenStream) -> Result<TokenStream> {
881879
let raw_bindings = capture_bindings(raw_captures.into_iter());
882880
let translated_bindings = capture_bindings(translated_captures.into_iter());
883881

884-
// Parse transform: the token(s) after `=>` fall into one of three
882+
// Parse transform: the token(s) after `=>` fall into one of two
885883
// shapes, dispatched in order:
886884
//
887885
// 1. `kind [? | *] { rust_body }` — annotated Rust body (NEW).
888886
// Static-analysis-ready: the annotation declares the output
889887
// kind and multiplicity in the schema's own vocabulary.
890-
// 2. `kind` alone — shorthand: emit `(kind field: {@cap})…` from
891-
// the query's captures.
892-
// 3. anything else — full template form (`(kind …)` or bare
888+
// 2. anything else — full template form (`(kind …)` or bare
893889
// `{ … }` splice via `parse_direct_list`).
894890
let annotation = try_consume_return_annotation(&mut tokens)?;
895891

@@ -926,65 +922,6 @@ pub fn parse_rule_top(input: TokenStream) -> Result<TokenStream> {
926922
yeast::IntoFieldIds::extend_into(__value, &mut __ids);
927923
__ids
928924
}
929-
} else if peek_is_field(&mut tokens) && {
930-
// Shorthand form: bare identifier = output node kind.
931-
// Auto-generate template from captures.
932-
let mut lookahead = tokens.clone();
933-
lookahead.next(); // skip ident
934-
lookahead.peek().is_none() // nothing after = shorthand
935-
} {
936-
let output_kind = expect_ident(&mut tokens, "expected output node kind")?;
937-
let output_kind_str = output_kind.to_string();
938-
939-
// Generate field assignments from captures
940-
let field_stmts: Vec<TokenStream> = captures
941-
.iter()
942-
.map(|cap| {
943-
let name = Ident::new(&cap.name, Span::call_site());
944-
let name_str = &cap.name;
945-
match cap.multiplicity {
946-
CaptureMultiplicity::Repeated => quote! {
947-
let __field_id = #ctx_ident.ast.field_id_for_name(#name_str)
948-
.unwrap_or_else(|| panic!("field '{}' not found", #name_str));
949-
__fields.insert(
950-
__field_id,
951-
#name.into_iter()
952-
.map(::std::convert::Into::<yeast::Id>::into)
953-
.collect(),
954-
);
955-
},
956-
CaptureMultiplicity::Optional => quote! {
957-
let __field_id = #ctx_ident.ast.field_id_for_name(#name_str)
958-
.unwrap_or_else(|| panic!("field '{}' not found", #name_str));
959-
if let Some(__id) = #name {
960-
__fields.entry(__field_id).or_insert_with(Vec::new)
961-
.push(::std::convert::Into::<yeast::Id>::into(__id));
962-
}
963-
},
964-
CaptureMultiplicity::Single => quote! {
965-
let __field_id = #ctx_ident.ast.field_id_for_name(#name_str)
966-
.unwrap_or_else(|| panic!("field '{}' not found", #name_str));
967-
__fields.entry(__field_id).or_insert_with(Vec::new)
968-
.push(::std::convert::Into::<yeast::Id>::into(#name));
969-
},
970-
}
971-
})
972-
.collect();
973-
974-
quote! {
975-
let __kind = #ctx_ident.ast.id_for_node_kind(#output_kind_str)
976-
.unwrap_or_else(|| panic!("node kind '{}' not found", #output_kind_str));
977-
let mut __fields = std::collections::BTreeMap::new();
978-
#(#field_stmts)*
979-
let __id = #ctx_ident.ast.create_node_with_range(
980-
__kind,
981-
yeast::NodeContent::DynamicString(String::new()),
982-
__fields,
983-
true,
984-
__source_range,
985-
);
986-
vec![__id]
987-
}
988925
} else {
989926
// Reject bare `{ ... }` transforms — they used to be accepted
990927
// as either a Rust body producing a `Vec<Id>` or a template
@@ -1482,8 +1419,5 @@ mod rules_tests {
14821419
// Match expressions inside a block: `=>` is inside braces.
14831420
let toks = quote! { { match x { 1 => 2, _ => 3 } } };
14841421
assert!(!has_top_level_arrow(&toks));
1485-
// Bare shorthand form: top-level `=>` followed by a bare ident.
1486-
let toks = quote! { (a) => kind };
1487-
assert!(has_top_level_arrow(&toks));
14881422
}
14891423
}

‎shared/yeast/doc/yeast.md‎

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ automatically: single captures bind as `Id`, repeated captures (after
433433
## The `rule!` macro
434434

435435
`rule!` combines a query and a transform into a single declaration.
436-
There are three transform forms, each suited to a different level of
436+
There are two transform forms, each suited to a different level of
437437
rule complexity:
438438

439439
```rust
@@ -444,13 +444,7 @@ yeast::rule!(
444444
(output_template field: {capture})
445445
)
446446

447-
// 2. Shorthand form — captures become fields on a bare output kind.
448-
yeast::rule!(
449-
(query_pattern field: (_) @capture)
450-
=> output_kind
451-
)
452-
453-
// 3. Annotation form — a Rust block body preceded by the output kind.
447+
// 2. Annotation form — a Rust block body preceded by the output kind.
454448
yeast::rule!(
455449
(query_pattern child: (_)+ @@children)
456450
=>
@@ -466,9 +460,6 @@ yeast::rule!(
466460
)
467461
```
468462

469-
The shorthand `=> kind` form auto-generates the template, mapping each
470-
capture name to a field of the same name on the output node.
471-
472463
### Guards
473464

474465
A rule may include a Rust guard between its query and `=>`. The guard runs
@@ -548,8 +539,6 @@ having to inspect the block's expression.
548539
Prefer the simplest form that fits:
549540

550541
- If the whole transform is a tree literal, use the **template form**.
551-
- If the transform is a template whose root matches a query capture
552-
1:1, use the **shorthand form**.
553542
- If the transform needs Rust logic (loops, `let` bindings, calls to
554543
`ctx.translate`, etc.), use the **annotation form**.
555544

‎shared/yeast/tests/test.rs‎

Lines changed: 8 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -896,28 +896,6 @@ fn test_desugar_for_loop() {
896896
);
897897
}
898898

899-
#[test]
900-
fn test_shorthand_rule() {
901-
let rule: Rule = yeast::rule!(
902-
(assignment
903-
left: (_) @method
904-
right: (_) @receiver
905-
)
906-
=> call
907-
);
908-
909-
let dump = run_and_dump("x = 1", vec![rule]);
910-
assert_dump_eq(
911-
&dump,
912-
r#"
913-
program
914-
call
915-
method: identifier "x"
916-
receiver: integer "1"
917-
"#,
918-
);
919-
}
920-
921899
#[derive(Clone, Default)]
922900
struct GuardTestContext {
923901
enabled: bool,
@@ -1024,19 +1002,19 @@ fn test_chained_rules_output_only_kind() {
10241002
// first_node → second_node (output-only → output-only)
10251003
// The matcher must look up `first_node` against the schema, which only
10261004
// knows about it via the YAML node-types file.
1027-
let assignment_to_first = yeast::rule!(
1005+
let assignment_to_first: Rule = yeast::rule!(
10281006
(assignment
10291007
left: (_) @left
10301008
right: (_) @right
10311009
)
1032-
=> first_node
1010+
=> (first_node left: {left} right: {right})
10331011
);
1034-
let first_to_second = yeast::rule!(
1012+
let first_to_second: Rule = yeast::rule!(
10351013
(first_node
10361014
left: (_) @left
10371015
right: (_) @right
10381016
)
1039-
=> second_node
1017+
=> (second_node left: {left} right: {right})
10401018
);
10411019

10421020
let dump = run_and_dump("x = 1", vec![assignment_to_first, first_to_second]);
@@ -1103,19 +1081,19 @@ fn test_phased_desugaring() {
11031081
// Two phases that could equally have been a single one with chained
11041082
// rules. Splitting them makes the intent (cleanup, then desugar)
11051083
// explicit and provides per-phase error messages.
1106-
let cleanup = vec![yeast::rule!(
1084+
let cleanup: Vec<Rule> = vec![yeast::rule!(
11071085
(assignment
11081086
left: (_) @left
11091087
right: (_) @right
11101088
)
1111-
=> first_node
1089+
=> (first_node left: {left} right: {right})
11121090
)];
1113-
let desugar = vec![yeast::rule!(
1091+
let desugar: Vec<Rule> = vec![yeast::rule!(
11141092
(first_node
11151093
left: (_) @left
11161094
right: (_) @right
11171095
)
1118-
=> second_node
1096+
=> (second_node left: {left} right: {right})
11191097
)];
11201098

11211099
let dump = run_phased_and_dump(
@@ -1731,33 +1709,6 @@ fn test_rules_macro_accepts_bare_rule_body() {
17311709
);
17321710
}
17331711

1734-
/// The bare-rule-body shorthand `=> output_kind` should also be accepted.
1735-
#[test]
1736-
fn test_rules_macro_accepts_bare_shorthand_form() {
1737-
let rules: Vec<Rule> = yeast::rules! {
1738-
input: "tests/input-types.yml",
1739-
output: "tests/node-types.yml",
1740-
[
1741-
(assignment
1742-
left: (_) @method
1743-
right: (_) @receiver
1744-
)
1745-
=> call,
1746-
]
1747-
};
1748-
1749-
let dump = run_and_dump("x = 1", rules);
1750-
assert_dump_eq(
1751-
&dump,
1752-
r#"
1753-
program
1754-
call
1755-
method: identifier "x"
1756-
receiver: integer "1"
1757-
"#,
1758-
);
1759-
}
1760-
17611712
#[test]
17621713
fn test_rules_macro_accepts_bare_guarded_rule() {
17631714
let rules: Vec<Rule> = yeast::rules! {
@@ -1926,27 +1877,3 @@ fn test_rule_annotation_single() {
19261877
}
19271878
assert!(has_assignment, "expected an assignment node");
19281879
}
1929-
1930-
/// The shorthand `=> kind` form (no body, no annotation) must still be
1931-
/// distinguished from the annotation form and continue to work.
1932-
#[test]
1933-
fn test_shorthand_still_works_alongside_annotation_syntax() {
1934-
let r: Rule = rule!(
1935-
(assignment left: (_) @method right: (_) @receiver)
1936-
=>
1937-
call
1938-
);
1939-
let ast = run_and_ast("x = 1", vec![r]);
1940-
let mut has_call = false;
1941-
for id in ast.reachable_node_ids() {
1942-
if let Some(n) = ast.get_node(id) {
1943-
if n.kind_name() == "call" {
1944-
has_call = true;
1945-
}
1946-
}
1947-
}
1948-
assert!(
1949-
has_call,
1950-
"shorthand form should still produce a `call` node"
1951-
);
1952-
}

0 commit comments

Comments
 (0)