From 3f4a23e5a8994b49ab67321177c6d5683f8d6494 Mon Sep 17 00:00:00 2001 From: Tadeo Kondrak Date: Fri, 13 Jun 2025 17:51:10 -0600 Subject: Allow lifetime repeats in macros: $($x)'a* This works in rustc. This change isn't motivated by any real code. I just learned about it and decided to see why it doesn't work with rust-analyzer. --- .../hir-def/src/macro_expansion_tests/mbe.rs | 22 ++++++++++++++++++++++ .../src/macro_expansion_tests/mbe/meta_syntax.rs | 4 ++++ .../rust-analyzer/crates/mbe/src/benchmark.rs | 4 ++++ .../crates/mbe/src/expander/matcher.rs | 11 +++++++---- .../crates/mbe/src/expander/transcriber.rs | 4 ++++ src/tools/rust-analyzer/crates/mbe/src/parser.rs | 19 +++++++++++++++---- 6 files changed, 56 insertions(+), 8 deletions(-) (limited to 'src/tools') diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs index eea50d16f5a..ef93a2a790d 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs @@ -2029,3 +2029,25 @@ fn f() { "#]], ); } + +#[test] +fn lifetime_repeat() { + check( + r#" +macro_rules! m { + ($($x:expr)'a*) => (stringify!($($x)'b*)); +} +fn f() { + let _ = m!(0 'a 1 'a 2); +} + "#, + expect![[r#" +macro_rules! m { + ($($x:expr)'a*) => (stringify!($($x)'b*)); +} +fn f() { + let _ = stringify!(0 'b1 'b2); +} + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs index 2d289b76833..2c94f0e8d09 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs @@ -13,6 +13,8 @@ macro_rules! m { ($(x),*) => (); ($(x)_*) => (); ($(x)i*) => (); + ($(x)'a*) => (); + ($(x)'_*) => (); ($($i:ident)*) => ($_); ($($true:ident)*) => ($true); ($($false:ident)*) => ($false); @@ -28,6 +30,8 @@ macro_rules! m { ($(x),*) => (); ($(x)_*) => (); ($(x)i*) => (); + ($(x)'a*) => (); + ($(x)'_*) => (); ($($i:ident)*) => ($_); ($($true:ident)*) => ($true); ($($false:ident)*) => ($false); diff --git a/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs b/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs index db75dceae1c..04ac85ad43d 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs @@ -197,6 +197,10 @@ fn invocation_fixtures( builder.push(tt::Leaf::Punct(*it)) } } + Separator::Lifetime(punct, ident) => { + builder.push(tt::Leaf::Punct(*punct)); + builder.push(tt::Leaf::Ident(ident.clone())); + } }; } } diff --git a/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs b/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs index 940aaacb02e..a8d5965d480 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs @@ -823,7 +823,7 @@ fn match_meta_var<'t>( "expected token tree", ) }), - MetaVarKind::Lifetime => expect_lifetime(input).map_err(|()| { + MetaVarKind::Lifetime => expect_lifetime(input).map(drop).map_err(|()| { ExpandError::binding_error( span.unwrap_or(delim_span.close), "expected lifetime", @@ -963,6 +963,10 @@ fn expect_separator(iter: &mut TtIter<'_, S>, separator: &Separator) -> } Err(_) => false, }, + Separator::Lifetime(_punct, ident) => match expect_lifetime(&mut fork) { + Ok(lifetime) => lifetime.sym == ident.sym, + Err(_) => false, + }, }; if ok { *iter = fork; @@ -983,13 +987,12 @@ fn expect_tt(iter: &mut TtIter<'_, S>) -> Result<(), ()> { Ok(()) } -fn expect_lifetime(iter: &mut TtIter<'_, S>) -> Result<(), ()> { +fn expect_lifetime<'a, S: Copy>(iter: &mut TtIter<'a, S>) -> Result<&'a tt::Ident, ()> { let punct = iter.expect_single_punct()?; if punct.char != '\'' { return Err(()); } - iter.expect_ident_or_underscore()?; - Ok(()) + iter.expect_ident_or_underscore() } fn eat_char(iter: &mut TtIter<'_, S>, c: char) { diff --git a/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs b/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs index ec277ba72e9..2c046df10f5 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs @@ -497,6 +497,10 @@ fn expand_repeat( builder.push(tt::Leaf::from(punct)); } } + Separator::Lifetime(punct, ident) => { + builder.push(tt::Leaf::from(*punct)); + builder.push(tt::Leaf::from(ident.clone())); + } }; } diff --git a/src/tools/rust-analyzer/crates/mbe/src/parser.rs b/src/tools/rust-analyzer/crates/mbe/src/parser.rs index fbc353d6103..711101260a0 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/parser.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/parser.rs @@ -155,6 +155,7 @@ pub(crate) enum Separator { Literal(tt::Literal), Ident(tt::Ident), Puncts(ArrayVec, MAX_GLUED_PUNCT_LEN>), + Lifetime(tt::Punct, tt::Ident), } // Note that when we compare a Separator, we just care about its textual value. @@ -170,6 +171,7 @@ impl PartialEq for Separator { let b_iter = b.iter().map(|b| b.char); a_iter.eq(b_iter) } + (Lifetime(_, a), Lifetime(_, b)) => a.sym == b.sym, _ => false, } } @@ -350,10 +352,19 @@ fn parse_repeat(src: &mut TtIter<'_, Span>) -> Result<(Option, Repeat _ => true, }; match tt { - tt::Leaf::Ident(_) | tt::Leaf::Literal(_) if has_sep => { - return Err(ParseError::InvalidRepeat); - } - tt::Leaf::Ident(ident) => separator = Separator::Ident(ident.clone()), + tt::Leaf::Ident(ident) => match separator { + Separator::Puncts(puncts) if puncts.is_empty() => { + separator = Separator::Ident(ident.clone()); + } + Separator::Puncts(puncts) => match puncts.as_slice() { + [tt::Punct { char: '\'', .. }] => { + separator = Separator::Lifetime(puncts[0], ident.clone()); + } + _ => return Err(ParseError::InvalidRepeat), + }, + _ => return Err(ParseError::InvalidRepeat), + }, + tt::Leaf::Literal(_) if has_sep => return Err(ParseError::InvalidRepeat), tt::Leaf::Literal(lit) => separator = Separator::Literal(lit.clone()), tt::Leaf::Punct(punct) => { let repeat_kind = match punct.char { -- cgit 1.4.1-3-g733a5