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.
This commit is contained in:
Tadeo Kondrak 2025-06-13 17:51:10 -06:00
parent a497f4114c
commit a7c09532a0
6 changed files with 56 additions and 8 deletions

View file

@ -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);
}
"#]],
);
}

View file

@ -13,6 +13,8 @@ macro_rules! m {
($(x),*) => (); ($(x),*) => ();
($(x)_*) => (); ($(x)_*) => ();
($(x)i*) => (); ($(x)i*) => ();
($(x)'a*) => ();
($(x)'_*) => ();
($($i:ident)*) => ($_); ($($i:ident)*) => ($_);
($($true:ident)*) => ($true); ($($true:ident)*) => ($true);
($($false:ident)*) => ($false); ($($false:ident)*) => ($false);
@ -28,6 +30,8 @@ macro_rules! m {
($(x),*) => (); ($(x),*) => ();
($(x)_*) => (); ($(x)_*) => ();
($(x)i*) => (); ($(x)i*) => ();
($(x)'a*) => ();
($(x)'_*) => ();
($($i:ident)*) => ($_); ($($i:ident)*) => ($_);
($($true:ident)*) => ($true); ($($true:ident)*) => ($true);
($($false:ident)*) => ($false); ($($false:ident)*) => ($false);

View file

@ -197,6 +197,10 @@ fn invocation_fixtures(
builder.push(tt::Leaf::Punct(*it)) builder.push(tt::Leaf::Punct(*it))
} }
} }
Separator::Lifetime(punct, ident) => {
builder.push(tt::Leaf::Punct(*punct));
builder.push(tt::Leaf::Ident(ident.clone()));
}
}; };
} }
} }

View file

@ -823,7 +823,7 @@ fn match_meta_var<'t>(
"expected token tree", "expected token tree",
) )
}), }),
MetaVarKind::Lifetime => expect_lifetime(input).map_err(|()| { MetaVarKind::Lifetime => expect_lifetime(input).map(drop).map_err(|()| {
ExpandError::binding_error( ExpandError::binding_error(
span.unwrap_or(delim_span.close), span.unwrap_or(delim_span.close),
"expected lifetime", "expected lifetime",
@ -963,6 +963,10 @@ fn expect_separator<S: Copy>(iter: &mut TtIter<'_, S>, separator: &Separator) ->
} }
Err(_) => false, Err(_) => false,
}, },
Separator::Lifetime(_punct, ident) => match expect_lifetime(&mut fork) {
Ok(lifetime) => lifetime.sym == ident.sym,
Err(_) => false,
},
}; };
if ok { if ok {
*iter = fork; *iter = fork;
@ -983,13 +987,12 @@ fn expect_tt<S: Copy>(iter: &mut TtIter<'_, S>) -> Result<(), ()> {
Ok(()) Ok(())
} }
fn expect_lifetime<S: Copy>(iter: &mut TtIter<'_, S>) -> Result<(), ()> { fn expect_lifetime<'a, S: Copy>(iter: &mut TtIter<'a, S>) -> Result<&'a tt::Ident<S>, ()> {
let punct = iter.expect_single_punct()?; let punct = iter.expect_single_punct()?;
if punct.char != '\'' { if punct.char != '\'' {
return Err(()); return Err(());
} }
iter.expect_ident_or_underscore()?; iter.expect_ident_or_underscore()
Ok(())
} }
fn eat_char<S: Copy>(iter: &mut TtIter<'_, S>, c: char) { fn eat_char<S: Copy>(iter: &mut TtIter<'_, S>, c: char) {

View file

@ -497,6 +497,10 @@ fn expand_repeat(
builder.push(tt::Leaf::from(punct)); builder.push(tt::Leaf::from(punct));
} }
} }
Separator::Lifetime(punct, ident) => {
builder.push(tt::Leaf::from(*punct));
builder.push(tt::Leaf::from(ident.clone()));
}
}; };
} }

View file

@ -155,6 +155,7 @@ pub(crate) enum Separator {
Literal(tt::Literal<Span>), Literal(tt::Literal<Span>),
Ident(tt::Ident<Span>), Ident(tt::Ident<Span>),
Puncts(ArrayVec<tt::Punct<Span>, MAX_GLUED_PUNCT_LEN>), Puncts(ArrayVec<tt::Punct<Span>, MAX_GLUED_PUNCT_LEN>),
Lifetime(tt::Punct<Span>, tt::Ident<Span>),
} }
// Note that when we compare a Separator, we just care about its textual value. // 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); let b_iter = b.iter().map(|b| b.char);
a_iter.eq(b_iter) a_iter.eq(b_iter)
} }
(Lifetime(_, a), Lifetime(_, b)) => a.sym == b.sym,
_ => false, _ => false,
} }
} }
@ -350,10 +352,19 @@ fn parse_repeat(src: &mut TtIter<'_, Span>) -> Result<(Option<Separator>, Repeat
_ => true, _ => true,
}; };
match tt { match tt {
tt::Leaf::Ident(_) | tt::Leaf::Literal(_) if has_sep => { tt::Leaf::Ident(ident) => match separator {
return Err(ParseError::InvalidRepeat); Separator::Puncts(puncts) if puncts.is_empty() => {
separator = Separator::Ident(ident.clone());
} }
tt::Leaf::Ident(ident) => 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::Literal(lit) => separator = Separator::Literal(lit.clone()),
tt::Leaf::Punct(punct) => { tt::Leaf::Punct(punct) => {
let repeat_kind = match punct.char { let repeat_kind = match punct.char {