Fix wrong macro argument parsing

This commit is contained in:
Guillaume Gomez 2025-08-13 23:11:01 +02:00 committed by René Kijewski
parent e8d2391d48
commit 1f31021632
5 changed files with 112 additions and 2 deletions

View File

@ -927,6 +927,35 @@ impl<'a> Suffix<'a> {
}
}
fn lifetime<'a>(i: &mut InputStream<'a>) -> ParseResult<'a, ()> {
// Before the 2021 edition, we can have whitespace characters between "r#" and the
// identifier so we allow it here.
let start = *i;
'\''.parse_next(i)?;
let Some((is_raw, identifier)) = opt(alt((
('r', '#', identifier).map(|(_, _, ident)| (true, ident)),
(identifier, not(peek('#'))).map(|(ident, _)| (false, ident)),
)))
.parse_next(i)?
else {
return cut_error!("wrong lifetime format", **start);
};
if !is_raw {
if crate::is_rust_keyword(identifier) {
return cut_error!(
"a non-raw lifetime cannot be named like an existing keyword",
**start,
);
}
} else if ["Self", "self", "crate", "super", "_"].contains(&identifier) {
return cut_error!(format!("`{identifier}` cannot be a raw lifetime"), **start,);
}
if opt(peek('\'')).parse_next(i)?.is_some() {
return cut_error!("unexpected `'` after lifetime", **start);
}
Ok(())
}
fn token<'a>(i: &mut InputStream<'a>) -> ParseResult<'a, Token> {
// <https://doc.rust-lang.org/reference/tokens.html>
let some_other = alt((
@ -936,8 +965,7 @@ impl<'a> Suffix<'a> {
num_lit.value(Token::SomeOther),
// keywords + (raw) identifiers + raw strings
identifier_or_prefixed_string.value(Token::SomeOther),
// lifetimes
('\'', identifier, not(peek('\''))).value(Token::SomeOther),
lifetime.value(Token::SomeOther),
// comments
line_comment.value(Token::SomeOther),
block_comment.value(Token::SomeOther),

View File

@ -0,0 +1 @@
˙˙˙{{z!{'r#}}}˙ ˙s˙

View File

@ -630,3 +630,19 @@ fn test_macro_caller_is_defined_check() {
"no caller defined|this time with caller"
);
}
// This test ensures that raw lifetimes are correctly handled.
#[test]
fn test_macro_raw_lifetime() {
macro_rules! test {
('r#ignore_me) => {
"ok"
};
}
#[derive(Template)]
#[template(source = r##"{{ test!('r#ignore_me) }}"##, ext = "txt")]
struct Foo;
assert_eq!(Foo.render().unwrap(), "ok");
}

View File

@ -0,0 +1,34 @@
// This test ensures that we have the right error if a `#` character isn't prepended by
// a whitespace character and also that lifetimes are correctly handled.
use askama::Template;
#[derive(Template)]
#[template(
source = r###"{{z!{'r#}}}"###,
ext = "html"
)]
struct Example;
#[derive(Template)]
#[template(
source = r###"{{z!{'r# y}}}"###,
ext = "html"
)]
struct Example2;
#[derive(Template)]
#[template(
source = r###"{{z!{'break}}}"###,
ext = "html"
)]
struct Example3;
#[derive(Template)]
#[template(
source = r###"{{z!{'r#self}}}"###,
ext = "html"
)]
struct Example4;
fn main() {}

View File

@ -0,0 +1,31 @@
error: wrong lifetime format
--> <source attribute>:1:5
"'r#}}}"
--> tests/ui/macro-args-hashtag.rs:8:14
|
8 | source = r###"{{z!{'r#}}}"###,
| ^^^^^^^^^^^^^^^^^^^^
error: wrong lifetime format
--> <source attribute>:1:5
"'r# y}}}"
--> tests/ui/macro-args-hashtag.rs:15:14
|
15 | source = r###"{{z!{'r# y}}}"###,
| ^^^^^^^^^^^^^^^^^^^^^^
error: a non-raw lifetime cannot be named like an existing keyword
--> <source attribute>:1:5
"'break}}}"
--> tests/ui/macro-args-hashtag.rs:22:14
|
22 | source = r###"{{z!{'break}}}"###,
| ^^^^^^^^^^^^^^^^^^^^^^^
error: `self` cannot be a raw lifetime
--> <source attribute>:1:5
"'r#self}}}"
--> tests/ui/macro-args-hashtag.rs:29:14
|
29 | source = r###"{{z!{'r#self}}}"###,
| ^^^^^^^^^^^^^^^^^^^^^^^^