Repository: lalrpop/lalrpop (2025-07-09T07:34:32Z)

Issue:
Clippy warnings in 1.88
We produce some clippy warnings in 1.88 which was released last Friday.  This is the last set of tricky to resolve warnings on the uninlined-format-args lint.

@Pat-Lafon You had proposed a possible solution [here](https://github.com/lalrpop/lalrpop/pull/1058#issuecomment-2852471662).  Is that something you want to work on and submit a PR for?  It sounded like you had a branch that was a substantial chunk of the way there already.


Discussion:

Patch:
diff --git a/doc/src/tutorial/003_type_inference.md b/doc/src/tutorial/003_type_inference.md
index 6ca2e8c26..82dcc7865 100644
--- a/doc/src/tutorial/003_type_inference.md
+++ b/doc/src/tutorial/003_type_inference.md
@@ -81,6 +81,8 @@ give you the idea:
 | `<p:A> <q:B> => bar(<>)` | `<p:A> <q:B> => bar(p, q)` |
 | `<p:A> B => Foo {<>}` | `<p:A> B => Foo {p:p}` |
 | `<p:A> <q:B> => Foo {<>}` | `<p:A> <q:B> => Foo {p:p, q:q}` |
+| `<A> <B> => format!("<> <>")` | `<a:A> <b:B> => format!("<a> <b>")` |
+| `<p:A> <q:B> => format!("<> <>")` | `<p:A> <q:B> => format!("<p> <q>")` |
 
 The `<>` expressions also works with struct constructors (like `Foo
 {...}` in examples above). This works out well if the names of your
diff --git a/lalrpop/src/lr1/codegen/parse_table.rs b/lalrpop/src/lr1/codegen/parse_table.rs
index 655143570..df4b9fa39 100644
--- a/lalrpop/src/lr1/codegen/parse_table.rs
+++ b/lalrpop/src/lr1/codegen/parse_table.rs
@@ -1329,7 +1329,7 @@ impl<'ascent, 'grammar, W: Write> CodeGenerator<'ascent, 'grammar, W, TableDrive
         }
         rust!(
             self.out,
-            "_ => panic!(\"invalid reduction index {{{}reduce_index}}\",)",
+            "_ => panic!(\"invalid reduction index {{{}reduce_index}}\")",
             self.prefix,
         );
         rust!(self.out, "}}"); // end match
diff --git a/lalrpop/src/normalize/lower/mod.rs b/lalrpop/src/normalize/lower/mod.rs
index 586aeeaf0..efb2c2b89 100644
--- a/lalrpop/src/normalize/lower/mod.rs
+++ b/lalrpop/src/normalize/lower/mod.rs
@@ -8,9 +8,9 @@ use crate::grammar::parse_tree::{
     Path, TerminalString,
 };
 use crate::grammar::pattern::{Pattern, PatternKind};
-use crate::grammar::repr as r;
+use crate::grammar::repr::{self as r, Span};
 use crate::normalize::norm_util::{self, Symbols};
-use crate::normalize::NormResult;
+use crate::normalize::{NormError, NormResult};
 use crate::session::Session;
 use string_cache::DefaultAtom as Atom;
 
@@ -47,7 +47,7 @@ impl<'s> LowerState<'s> {
     }
 
     fn lower(mut self, session: &Session, grammar: pt::Grammar) -> NormResult<r::Grammar> {
-        let start_symbols = self.synthesize_start_symbols(&grammar);
+        let start_symbols = self.synthesize_start_symbols(&grammar)?;
 
         let mut uses = vec![];
         let internal_token_path = Path {
@@ -131,15 +131,16 @@ impl<'s> LowerState<'s> {
                         .map(|alt| {
                             let nt_type = self.types.nonterminal_type(nt_name).clone();
                             let symbols = self.symbols(&alt.expr.symbols);
-                            let action = self.action_kind(nt_type, &alt.expr, &symbols, alt.action);
-                            r::Production {
-                                nonterminal: nt_name.clone(),
-                                span: alt.span,
-                                symbols,
-                                action,
-                            }
+
+                            self.action_kind(nt_type, &alt.expr, &symbols, alt.action)
+                                .map(|action| r::Production {
+                                    nonterminal: nt_name.clone(),
+                                    span: alt.span,
+                                    symbols,
+                                    action,
+                                })
                         })
-                        .collect();
+                        .collect::<Result<Vec<_>, _>>()?;
                     self.nonterminals.insert(
                         nt_name.clone(),
                         r::NonterminalData {
@@ -216,7 +217,7 @@ impl<'s> LowerState<'s> {
     fn synthesize_start_symbols(
         &mut self,
         grammar: &pt::Grammar,
-    ) -> Map<NonterminalString, NonterminalString> {
+    ) -> NormResult<Map<NonterminalString, NonterminalString>> {
         grammar
             .items
             .iter()
@@ -238,23 +239,25 @@ impl<'s> LowerState<'s> {
                     )],
                 };
                 let symbols = vec![r::Symbol::Nonterminal(nt.name.clone())];
-                let action_fn = self.action_fn(nt_type, false, &expr, &symbols, None);
-                let production = r::Production {
-                    nonterminal: fake_name.clone(),
-                    symbols,
-                    action: action_fn,
-                    span: nt.span,
-                };
-                self.nonterminals.insert(
-                    fake_name.clone(),
-                    r::NonterminalData {
-                        visibility: nt.visibility.clone(),
-                        attributes: vec![],
-                        span: nt.span,
-                        productions: vec![production],
-                    },
-                );
-                (nt.name.clone(), fake_name)
+                self.action_fn(nt_type, false, &expr, &symbols, None)
+                    .map(|action_fn| {
+                        let production = r::Production {
+                            nonterminal: fake_name.clone(),
+                            symbols,
+                            action: action_fn,
+                            span: nt.span,
+                        };
+                        self.nonterminals.insert(
+                            fake_name.clone(),
+                            r::NonterminalData {
+                                visibility: nt.visibility.clone(),
+                                attributes: vec![],
+                                span: nt.span,
+                                productions: vec![production],
+                            },
+                        );
+                        (nt.name.clone(), fake_name)
+                    })
             })
             .collect()
     }
@@ -298,10 +301,10 @@ impl<'s> LowerState<'s> {
         expr: &pt::ExprSymbol,
         symbols: &[r::Symbol],
         action: Option<pt::ActionKind>,
-    ) -> r::ActionFn {
+    ) -> NormResult<r::ActionFn> {
         match action {
-            Some(pt::ActionKind::Lookahead) => self.lookahead_action_fn(),
-            Some(pt::ActionKind::Lookbehind) => self.lookbehind_action_fn(),
+            Some(pt::ActionKind::Lookahead) => Ok(self.lookahead_action_fn()),
+            Some(pt::ActionKind::Lookbehind) => Ok(self.lookbehind_action_fn()),
             Some(pt::ActionKind::User(string)) => {
                 self.action_fn(nt_type, false, expr, symbols, Some(string))
             }
@@ -339,7 +342,7 @@ impl<'s> LowerState<'s> {
         expr: &pt::ExprSymbol,
         symbols: &[r::Symbol],
         action: Option<String>,
-    ) -> r::ActionFn {
+    ) -> NormResult<r::ActionFn> {
         let normalized_symbols = norm_util::analyze_expr(expr);
 
         let action = match action {
@@ -427,7 +430,35 @@ impl<'s> LowerState<'s> {
                     let name_strs: Vec<_> = names.iter().map(AsRef::as_ref).collect();
                     name_strs.join(", ")
                 };
-                let action = action.replace("<>", &name_str);
+
+                let action = if action.matches("<>").count() > 1 {
+                    if action.matches("<>").count() != names.len() {
+                        // Here the error span will be based on the indices
+                        // since that is what I have the span information for.
+
+                        // Alternatively, one could pass in the action span
+                        // information instead of just the action string.
+                        let span_start = indices.first().unwrap().1.span;
+
+                        let span_end = indices.last().unwrap().1.span;
+
+                        let indicies_span = Span(span_start.0, span_end.1);
+
+                        return_err!(
+                            indicies_span,
+                            "When there are multiple `<>` in the action, \
+                             there must be the same number of sources for the `<>`s. \
+                             Found {} `<`>`s and {} anonymous sources.",
+                            action.matches("<>").count(),
+                            names.len()
+                        );
+                    }
+                    names
+                        .iter()
+                        .fold(action, |acc, name| acc.replacen("<>", name.as_ref(), 1))
+                } else {
+                    action.replace("<>", &name_str)
+                };
                 r::ActionFnDefn {
                     fallible,
                     ret_type: nt_type,
@@ -440,7 +471,7 @@ impl<'s> LowerState<'s> {
             }
         };
 
-        self.add_action_fn(action_fn_defn)
+        Ok(self.add_action_fn(action_fn_defn))
     }
 
     fn add_action_fn(&mut self, action_fn_defn: r::ActionFnDefn) -> r::ActionFn {
diff --git a/lalrpop/src/parser/lrgrammar.rs b/lalrpop/src/parser/lrgrammar.rs
index 5bc061fb8..8312216a1 100644
--- a/lalrpop/src/parser/lrgrammar.rs
+++ b/lalrpop/src/parser/lrgrammar.rs
@@ -6458,7 +6458,7 @@ nonterminal_produced: 171,
 }
 }
 524 => ___state_machine::SimulatedReduce::Accept,
-_ => panic!("invalid reduction index {___reduce_index}",)
+_ => panic!("invalid reduction index {___reduce_index}")
 }
 }
 pub struct TopParser {


Test patch:
diff --git a/lalrpop-test/src/error_recovery_span.lalrpop b/lalrpop-test/src/error_recovery_span.lalrpop
index 15d8f78d2..7a6c193d2 100644
--- a/lalrpop-test/src/error_recovery_span.lalrpop
+++ b/lalrpop-test/src/error_recovery_span.lalrpop
@@ -16,8 +16,8 @@ extern {
 
 pub Expr: String = {
     Expr1,
-    <Expr> "+" <Expr1> => format!("{} + {}", <>),
-    <Expr> "-" <Expr1> => format!("{} - {}", <>),
+    <Expr> "+" <Expr1> => format!("{<>} + {<>}"),
+    <Expr> "-" <Expr1> => format!("{<>} - {<>}"),
 
     // NB we intentionally cannot recover from a `"+"` here,
     // just a `"-"`. This allows us to test when we must drop
diff --git a/lalrpop-test/src/inline.lalrpop b/lalrpop-test/src/inline.lalrpop
index fa7e04330..5cfd9e312 100644
--- a/lalrpop-test/src/inline.lalrpop
+++ b/lalrpop-test/src/inline.lalrpop
@@ -3,7 +3,7 @@ grammar;
 
 pub E: String = {
     "L" => "L".to_string(),
-    "&" <OPT_L> <E> => format!("& {} {}", <>),
+    "&" <OPT_L> <E> => format!("& {<>} {<>}"),
 };
 
 #[inline] // without this, it'd be a SR conflict
diff --git a/lalrpop-test/src/match_alternatives.lalrpop b/lalrpop-test/src/match_alternatives.lalrpop
index 46112c911..14df9d656 100644
--- a/lalrpop-test/src/match_alternatives.lalrpop
+++ b/lalrpop-test/src/match_alternatives.lalrpop
@@ -1,6 +1,6 @@
 grammar;
 
-pub File: String = bare_key bool => format!("{} {}", <>);
+pub File: String = bare_key bool => format!("{<>} {<>}");
 
 match {
     r"false|true" => bool,
diff --git a/lalrpop-test/src/match_section.lalrpop b/lalrpop-test/src/match_section.lalrpop
index aa55c96ce..c83e161d9 100644
--- a/lalrpop-test/src/match_section.lalrpop
+++ b/lalrpop-test/src/match_section.lalrpop
@@ -21,5 +21,5 @@ Table: String = {
 };
 
 pub Query: String = {
-    <Keyword> <Table> => format!("{} {}", <>)
+    <Keyword> <Table> => format!("{<>} {<>}")
 };
diff --git a/lalrpop-test/src/match_section_byte.lalrpop b/lalrpop-test/src/match_section_byte.lalrpop
index 258d11d7e..b1c390ab8 100644
--- a/lalrpop-test/src/match_section_byte.lalrpop
+++ b/lalrpop-test/src/match_section_byte.lalrpop
@@ -21,5 +21,5 @@ Table: String = {
 };
 
 pub Query: String = {
-    <Keyword> <Table> => format!("{} {}", <>)
+    <Keyword> <Table> => format!("{<>} {<>}")
 };