From c0a00a9bd3dfe44f0f4fb1527bcc4b107149e2b7 Mon Sep 17 00:00:00 2001 From: Taus Date: Thu, 15 Oct 2026 12:04:04 +0000 Subject: [PATCH 1/2] yeast: Get rid of postfix `?` operator This will be superseded by the next commit, which generalised the mechanism and also makes the implementation simpler. --- shared/yeast-macros/src/ast.rs | 4 +- shared/yeast-macros/src/lib.rs | 27 ------ shared/yeast-macros/src/lower.rs | 101 ++++----------------- shared/yeast-macros/src/template_parse.rs | 60 +----------- shared/yeast/doc/yeast.md | 42 --------- shared/yeast/src/lib.rs | 79 +--------------- shared/yeast/tests/test.rs | 106 ---------------------- 7 files changed, 24 insertions(+), 395 deletions(-) diff --git a/shared/yeast-macros/src/ast.rs b/shared/yeast-macros/src/ast.rs index f400257fd1d4..09746b550235 100644 --- a/shared/yeast-macros/src/ast.rs +++ b/shared/yeast-macros/src/ast.rs @@ -121,15 +121,13 @@ pub(crate) enum Content { pub(crate) enum Field { /// A named field such as `argument: {args}` or - /// `label: (identifier #{name})?`. + /// `label: (identifier #{name})`. Named { /// The field name before `:`, such as `argument`. name: Ident, /// The node or Rust splice after `:`, such as `(identifier "x")` or /// `{args}`. value: Template, - /// Whether the field value has a trailing `?`. - optional: bool, }, /// Source-location metadata such as `location: {source}`. Location { diff --git a/shared/yeast-macros/src/lib.rs b/shared/yeast-macros/src/lib.rs index 75d737372965..aeb7b0a5df56 100644 --- a/shared/yeast-macros/src/lib.rs +++ b/shared/yeast-macros/src/lib.rs @@ -63,37 +63,10 @@ pub fn query(input: TokenStream) -> TokenStream { /// `Option`, iterator chains) splice /// their elements /// field: {expr} - extend a named field with `{expr}`'s ids -/// field: (kind ...)? - set the field only if every `#{expr}` -/// beneath it has a value (see below) /// location: {expr} - override this node's source range with the /// span of one or more nodes; may be repeated /// ``` /// -/// # Optional fields -/// -/// A `?` on a field's value makes that field fallible: if a `#{expr}` -/// anywhere beneath it interpolates an absent value — an `Option` that is -/// `None` — the subtree is abandoned and the field is left unset. This -/// replaces the surrounding `Option::map` that would otherwise be needed: -/// -/// ```text -/// (break_expr label: {lbl.map(|l| tree!((identifier #{l})))}) // before -/// (break_expr label: (identifier #{lbl})?) // after -/// ``` -/// -/// The value may be nested arbitrarily deeply, and a nested `?` catches -/// first, so an inner absent value need not discard the outer node: -/// -/// ```text -/// (parameter pattern: (name_pattern identifier: (identifier #{name}))? type: {ty}) -/// ``` -/// -/// Only `#{expr}` propagates absence. A `{expr}` splice is unaffected, since -/// yielding no ids already leaves a field unset, and `?` is rejected on one. -/// Outside a `?`, interpolating an `Option` with `#{expr}` remains a compile -/// error, so the choice between "unset the field" and "unwrap it" stays -/// explicit. -/// /// Uses the `BuildCtx` binding named `ctx` from the surrounding scope: /// /// ```text diff --git a/shared/yeast-macros/src/lower.rs b/shared/yeast-macros/src/lower.rs index 6bf22c7e8b9e..89bfdf0a6e76 100644 --- a/shared/yeast-macros/src/lower.rs +++ b/shared/yeast-macros/src/lower.rs @@ -1,21 +1,8 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -use std::sync::atomic::{AtomicUsize, Ordering}; -use syn::Lifetime; use crate::ast::{Cardinality, Content, Field, Node, Pattern, Replacement, Rule, Template}; -struct LoweringContext<'a> { - /// Enclosing optional-field label to exit when a computed value is absent. - fallible_scope: Option<&'a Lifetime>, -} - -fn fresh_fallible_label() -> Lifetime { - static COUNTER: AtomicUsize = AtomicUsize::new(0); - let n = COUNTER.fetch_add(1, Ordering::Relaxed); - Lifetime::new(&format!("'__yeast_field_{n}"), Span::call_site()) -} - #[derive(Clone)] struct CaptureBinding { name: Ident, @@ -286,20 +273,15 @@ fn capture_bindings<'a>( impl Template { pub(crate) fn lower_root(&self) -> TokenStream { - self.lower(&LoweringContext { - fallible_scope: None, - }) + self.lower() } pub(crate) fn lower_list(templates: &[Self]) -> TokenStream { - let context = LoweringContext { - fallible_scope: None, - }; let items = templates .iter() .map(|template| match template { Template::Node(node) => { - let node = node.lower(&context); + let node = node.lower(); quote! { __nodes.push(#node); } } Template::Splice(block) => { @@ -322,9 +304,9 @@ impl Template { } } - fn lower(&self, context: &LoweringContext<'_>) -> TokenStream { + fn lower(&self) -> TokenStream { match self { - Template::Node(node) => node.lower(context), + Template::Node(node) => node.lower(), Template::Splice(block) => { let block = rust_block(block); quote! { ::std::convert::Into::::into(#block) } @@ -334,7 +316,7 @@ impl Template { } impl Node { - fn lower(&self, context: &LoweringContext<'_>) -> TokenStream { + fn lower(&self) -> TokenStream { let ctx = Ident::new("ctx", Span::call_site()); let kind_str = self.kind.to_string(); @@ -343,40 +325,18 @@ impl Node { Content::Static(lit) => quote! { #ctx.literal(#kind_str, #lit) }, Content::Computed(block) => { let block = rust_block(block); - if let Some(label) = context.fallible_scope { - quote! { - { - let __expr = #block; - let ::std::option::Option::Some(__value_ref) = - yeast::MaybeYeastValue::maybe_yeast_value(&__expr) - else { - break #label ::std::option::Option::None; - }; - let __value = - yeast::YeastDisplay::yeast_to_string(__value_ref, &#ctx); - let __source_range = - yeast::YeastSourceRange::yeast_source_range(__value_ref, &#ctx); - #ctx.literal_with_source_range( - #kind_str, - &__value, - __source_range, - ) - } - } - } else { - quote! { - { - let __expr = #block; - let __value = - yeast::YeastDisplay::yeast_to_string(&__expr, &#ctx); - let __source_range = - yeast::YeastSourceRange::yeast_source_range(&__expr, &#ctx); - #ctx.literal_with_source_range( - #kind_str, - &__value, - __source_range, - ) - } + quote! { + { + let __expr = #block; + let __value = + yeast::YeastDisplay::yeast_to_string(&__expr, &#ctx); + let __source_range = + yeast::YeastSourceRange::yeast_source_range(&__expr, &#ctx); + #ctx.literal_with_source_range( + #kind_str, + &__value, + __source_range, + ) } } } @@ -399,11 +359,7 @@ impl Node { #ctx.extend_location_ranges(__value, &mut __location_ranges); }); } - Field::Named { - name, - value, - optional, - } => { + Field::Named { name, value } => { let field_name = name.to_string(); let field_str = field_name .strip_prefix("r#") @@ -413,10 +369,8 @@ impl Node { &format!("__field_{field_str}_{field_counter}"), Span::call_site(), ); - match value { Template::Splice(block) => { - debug_assert!(!optional); let block = rust_block(block); stmts.push(quote! { let mut #temp: Vec = Vec::new(); @@ -430,25 +384,8 @@ impl Node { } }); } - Template::Node(child) if *optional => { - let label = fresh_fallible_label(); - let value = child.lower(&LoweringContext { - fallible_scope: Some(&label), - }); - stmts.push(quote! { - #[allow(unused_labels)] - let #temp: ::std::option::Option = #label: { - ::std::option::Option::Some(#value) - }; - }); - field_args.push(quote! { - if let ::std::option::Option::Some(__id) = #temp { - __fields.push((#field_str, vec![__id])); - } - }); - } Template::Node(child) => { - let value = child.lower(context); + let value = child.lower(); stmts.push(quote! { let #temp: yeast::OutputId = #value; }); field_args.push(quote! { __fields.push((#field_str, vec![#temp])); diff --git a/shared/yeast-macros/src/template_parse.rs b/shared/yeast-macros/src/template_parse.rs index 782407466a46..8a7d176809ef 100644 --- a/shared/yeast-macros/src/template_parse.rs +++ b/shared/yeast-macros/src/template_parse.rs @@ -9,7 +9,6 @@ type Result = std::result::Result; pub(crate) fn parse_template(input: TokenStream) -> Result