Merge #8122
8122: Make bare underscore token an Ident rather than Punct in proc-macro r=edwin0cheng a=kevinmehall In rustc and proc-macro2, a bare `_` token is parsed for procedural macro purposes as `Ident` rather than `Punct` (see https://github.com/rust-lang/rust/pull/48842). This changes rust-analyzer to match rustc's behavior and implementation by handling `_` as an Ident in token trees, but explicitly preventing `$x:ident` from matching it in MBE. proc macro crate: ```rust #[proc_macro] pub fn input(input: proc_macro::TokenStream) -> proc_macro::TokenStream { dbg!(input) } ``` test crate: ```rust test_proc_macro::input!(_); ``` output (rustc): ```rust [test-proc-macro/src/lib.rs:10] input = TokenStream [ Ident { ident: "_", span: #0 bytes(173..174), }, ] ``` output (rust-analyzer before this change): ```rust [test-proc-macro/src/lib.rs:10] input = TokenStream [ Punct { ch: '_', spacing: Joint, span: 4294967295, }, ] ``` output (rust-analyzer after this change): ```rust [test-proc-macro/src/lib.rs:10] input = TokenStream [ Ident { ident: "_", span: 4294967295, }, ] ``` Co-authored-by: Kevin Mehall <km@kevinmehall.net>
This commit is contained in:
commit
787bd3c551
@ -762,7 +762,7 @@ impl<'a> TtIter<'a> {
|
|||||||
fn expect_separator(&mut self, separator: &Separator, idx: usize) -> bool {
|
fn expect_separator(&mut self, separator: &Separator, idx: usize) -> bool {
|
||||||
let mut fork = self.clone();
|
let mut fork = self.clone();
|
||||||
let ok = match separator {
|
let ok = match separator {
|
||||||
Separator::Ident(lhs) if idx == 0 => match fork.expect_ident() {
|
Separator::Ident(lhs) if idx == 0 => match fork.expect_ident_or_underscore() {
|
||||||
Ok(rhs) => rhs.text == lhs.text,
|
Ok(rhs) => rhs.text == lhs.text,
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
@ -852,7 +852,7 @@ impl<'a> TtIter<'a> {
|
|||||||
if punct.char != '\'' {
|
if punct.char != '\'' {
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
let ident = self.expect_ident()?;
|
let ident = self.expect_ident_or_underscore()?;
|
||||||
|
|
||||||
Ok(tt::Subtree {
|
Ok(tt::Subtree {
|
||||||
delimiter: None,
|
delimiter: None,
|
||||||
|
@ -177,16 +177,8 @@ fn next_op<'a>(first: &tt::TokenTree, src: &mut TtIter<'a>, mode: Mode) -> Resul
|
|||||||
Op::Repeat { tokens: MetaTemplate(tokens), separator, kind }
|
Op::Repeat { tokens: MetaTemplate(tokens), separator, kind }
|
||||||
}
|
}
|
||||||
tt::TokenTree::Leaf(leaf) => match leaf {
|
tt::TokenTree::Leaf(leaf) => match leaf {
|
||||||
tt::Leaf::Punct(punct) => {
|
tt::Leaf::Punct(_) => {
|
||||||
static UNDERSCORE: SmolStr = SmolStr::new_inline("_");
|
return Err(ParseError::Expected("ident".to_string()));
|
||||||
|
|
||||||
if punct.char != '_' {
|
|
||||||
return Err(ParseError::Expected("_".to_string()));
|
|
||||||
}
|
|
||||||
let name = UNDERSCORE.clone();
|
|
||||||
let kind = eat_fragment_kind(src, mode)?;
|
|
||||||
let id = punct.id;
|
|
||||||
Op::Var { name, kind, id }
|
|
||||||
}
|
}
|
||||||
tt::Leaf::Ident(ident) if ident.text == "crate" => {
|
tt::Leaf::Ident(ident) if ident.text == "crate" => {
|
||||||
// We simply produce identifier `$crate` here. And it will be resolved when lowering ast to Path.
|
// We simply produce identifier `$crate` here. And it will be resolved when lowering ast to Path.
|
||||||
|
@ -150,6 +150,7 @@ fn convert_ident(ident: &tt::Ident) -> TtToken {
|
|||||||
let kind = match ident.text.as_ref() {
|
let kind = match ident.text.as_ref() {
|
||||||
"true" => T![true],
|
"true" => T![true],
|
||||||
"false" => T![false],
|
"false" => T![false],
|
||||||
|
"_" => UNDERSCORE,
|
||||||
i if i.starts_with('\'') => LIFETIME_IDENT,
|
i if i.starts_with('\'') => LIFETIME_IDENT,
|
||||||
_ => SyntaxKind::from_keyword(ident.text.as_str()).unwrap_or(IDENT),
|
_ => SyntaxKind::from_keyword(ident.text.as_str()).unwrap_or(IDENT),
|
||||||
};
|
};
|
||||||
|
@ -350,7 +350,7 @@ trait TokenConvertor {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push(if k.is_punct() {
|
result.push(if k.is_punct() && k != UNDERSCORE {
|
||||||
assert_eq!(range.len(), TextSize::of('.'));
|
assert_eq!(range.len(), TextSize::of('.'));
|
||||||
let delim = match k {
|
let delim = match k {
|
||||||
T!['('] => Some((tt::DelimiterKind::Parenthesis, T![')'])),
|
T!['('] => Some((tt::DelimiterKind::Parenthesis, T![')'])),
|
||||||
@ -395,7 +395,9 @@ trait TokenConvertor {
|
|||||||
{
|
{
|
||||||
tt::Spacing::Alone
|
tt::Spacing::Alone
|
||||||
}
|
}
|
||||||
Some(next) if next.kind().is_punct() => tt::Spacing::Joint,
|
Some(next) if next.kind().is_punct() && next.kind() != UNDERSCORE => {
|
||||||
|
tt::Spacing::Joint
|
||||||
|
}
|
||||||
_ => tt::Spacing::Alone,
|
_ => tt::Spacing::Alone,
|
||||||
};
|
};
|
||||||
let char = match token.to_char() {
|
let char = match token.to_char() {
|
||||||
@ -415,6 +417,7 @@ trait TokenConvertor {
|
|||||||
let leaf: tt::Leaf = match k {
|
let leaf: tt::Leaf = match k {
|
||||||
T![true] | T![false] => make_leaf!(Ident),
|
T![true] | T![false] => make_leaf!(Ident),
|
||||||
IDENT => make_leaf!(Ident),
|
IDENT => make_leaf!(Ident),
|
||||||
|
UNDERSCORE => make_leaf!(Ident),
|
||||||
k if k.is_keyword() => make_leaf!(Ident),
|
k if k.is_keyword() => make_leaf!(Ident),
|
||||||
k if k.is_literal() => make_leaf!(Literal),
|
k if k.is_literal() => make_leaf!(Literal),
|
||||||
LIFETIME_IDENT => {
|
LIFETIME_IDENT => {
|
||||||
|
@ -1079,6 +1079,12 @@ macro_rules! q {
|
|||||||
.assert_expand_items(r#"q![_]"#, r#"0"#);
|
.assert_expand_items(r#"q![_]"#, r#"0"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_underscore_lifetime() {
|
||||||
|
parse_macro(r#"macro_rules! q { ($a:lifetime) => {0}; }"#)
|
||||||
|
.assert_expand_items(r#"q!['_]"#, r#"0"#);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_vertical_bar_with_pat() {
|
fn test_vertical_bar_with_pat() {
|
||||||
parse_macro(
|
parse_macro(
|
||||||
|
@ -12,6 +12,9 @@ fn test_valid_arms() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
check("($i:ident) => ()");
|
check("($i:ident) => ()");
|
||||||
|
check("($(x),*) => ()");
|
||||||
|
check("($(x)_*) => ()");
|
||||||
|
check("($(x)i*) => ()");
|
||||||
check("($($i:ident)*) => ($_)");
|
check("($($i:ident)*) => ($_)");
|
||||||
check("($($true:ident)*) => ($true)");
|
check("($($true:ident)*) => ($true)");
|
||||||
check("($($false:ident)*) => ($false)");
|
check("($($false:ident)*) => ($false)");
|
||||||
@ -32,6 +35,7 @@ fn test_invalid_arms() {
|
|||||||
|
|
||||||
check("($i) => ($i)", ParseError::UnexpectedToken("bad fragment specifier 1".into()));
|
check("($i) => ($i)", ParseError::UnexpectedToken("bad fragment specifier 1".into()));
|
||||||
check("($i:) => ($i)", ParseError::UnexpectedToken("bad fragment specifier 1".into()));
|
check("($i:) => ($i)", ParseError::UnexpectedToken("bad fragment specifier 1".into()));
|
||||||
|
check("($i:_) => ()", ParseError::UnexpectedToken("bad fragment specifier 1".into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_macro_arm(arm_definition: &str) -> Result<crate::MacroRules, ParseError> {
|
fn parse_macro_arm(arm_definition: &str) -> Result<crate::MacroRules, ParseError> {
|
||||||
|
@ -49,6 +49,13 @@ impl<'a> TtIter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn expect_ident(&mut self) -> Result<&'a tt::Ident, ()> {
|
pub(crate) fn expect_ident(&mut self) -> Result<&'a tt::Ident, ()> {
|
||||||
|
match self.expect_leaf()? {
|
||||||
|
tt::Leaf::Ident(it) if it.text != "_" => Ok(it),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn expect_ident_or_underscore(&mut self) -> Result<&'a tt::Ident, ()> {
|
||||||
match self.expect_leaf()? {
|
match self.expect_leaf()? {
|
||||||
tt::Leaf::Ident(it) => Ok(it),
|
tt::Leaf::Ident(it) => Ok(it),
|
||||||
_ => Err(()),
|
_ => Err(()),
|
||||||
|
@ -805,5 +805,14 @@ mod tests {
|
|||||||
let t2 = TokenStream::from_str("(a);").unwrap();
|
let t2 = TokenStream::from_str("(a);").unwrap();
|
||||||
assert_eq!(t2.token_trees.len(), 2);
|
assert_eq!(t2.token_trees.len(), 2);
|
||||||
assert_eq!(t2.token_trees[0], subtree_paren_a);
|
assert_eq!(t2.token_trees[0], subtree_paren_a);
|
||||||
|
|
||||||
|
let underscore = TokenStream::from_str("_").unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
underscore.token_trees[0],
|
||||||
|
tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident {
|
||||||
|
text: "_".into(),
|
||||||
|
id: tt::TokenId::unspecified(),
|
||||||
|
}))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user