diff --git a/crates/mbe/src/parser.rs b/crates/mbe/src/parser.rs index c3fdd40404..d681905f57 100644 --- a/crates/mbe/src/parser.rs +++ b/crates/mbe/src/parser.rs @@ -101,8 +101,15 @@ fn next_op<'a>( Op::Repeat { subtree, separator, kind } } tt::TokenTree::Leaf(leaf) => match leaf { - tt::Leaf::Punct(_) => { - return Err(ExpandError::UnexpectedToken); + tt::Leaf::Punct(punct) => { + static UNDERSCORE: SmolStr = SmolStr::new_inline("_"); + + if punct.char != '_' { + return Err(ExpandError::UnexpectedToken); + } + let name = &UNDERSCORE; + let kind = eat_fragment_kind(src, mode)?; + Op::Var { name, kind } } tt::Leaf::Ident(ident) => { let name = &ident.text; diff --git a/crates/mbe/src/syntax_bridge.rs b/crates/mbe/src/syntax_bridge.rs index 2bec7fd495..265c0d63d8 100644 --- a/crates/mbe/src/syntax_bridge.rs +++ b/crates/mbe/src/syntax_bridge.rs @@ -313,7 +313,7 @@ trait TokenConvertor { return; } - result.push(if k.is_punct() && k != UNDERSCORE { + result.push(if k.is_punct() { assert_eq!(range.len(), TextSize::of('.')); let delim = match k { T!['('] => Some((tt::DelimiterKind::Parenthesis, T![')'])), @@ -378,7 +378,6 @@ trait TokenConvertor { let leaf: tt::Leaf = match k { T![true] | T![false] => make_leaf!(Ident), IDENT => make_leaf!(Ident), - UNDERSCORE => make_leaf!(Ident), k if k.is_keyword() => make_leaf!(Ident), k if k.is_literal() => make_leaf!(Literal), LIFETIME_IDENT => { diff --git a/crates/mbe/src/tests.rs b/crates/mbe/src/tests.rs index 6cd0ed2053..9958a33a08 100644 --- a/crates/mbe/src/tests.rs +++ b/crates/mbe/src/tests.rs @@ -1019,6 +1019,42 @@ fn test_underscore() { .assert_expand_items(r#"foo! { => }"#, r#"0"#); } +#[test] +fn test_underscore_not_greedily() { + parse_macro( + r#" +macro_rules! q { + ($($a:ident)* _) => {0}; +} +"#, + ) + // `_` overlaps with `$a:ident` but rustc matches it under the `_` token + .assert_expand_items(r#"q![a b c d _]"#, r#"0"#); + + parse_macro( + r#" +macro_rules! q { + ($($a:expr => $b:ident)* _ => $c:expr) => {0}; +} +"#, + ) + // `_ => ou` overlaps with `$a:expr => $b:ident` but rustc matches it under `_ => $c:expr` + .assert_expand_items(r#"q![a => b c => d _ => ou]"#, r#"0"#); +} + +#[test] +fn test_underscore_as_type() { + parse_macro( + r#" +macro_rules! q { + ($a:ty) => {0}; +} +"#, + ) + // Underscore is a type + .assert_expand_items(r#"q![_]"#, r#"0"#); +} + #[test] fn test_vertical_bar_with_pat() { parse_macro(