Rollup merge of #53183 - estebank:println-comma, r=oli-obk

Suggest comma when missing in macro call

When missing a comma in a macro call, suggest it, regardless of
position. When a macro call doesn't match any of the patterns, check
if the call's token stream could be missing a comma between two idents,
and if so, create a new token stream containing the comma and try to
match against the macro patterns. If successful, emit the suggestion.

This works on arbitrary macros, with no need of special support from
the macro writers.

```
error: no rules expected the token `d`
  --> $DIR/missing-comma.rs:26:18
   |
LL |     foo!(a, b, c d, e);
   |                 -^
   |                 |
   |                 help: missing comma here
```
Follow up to #52397.
This commit is contained in:
kennytm 2018-08-09 16:58:49 +08:00
commit 8188f12aaa
No known key found for this signature in database
GPG Key ID: FEF6C8051D0E013C
4 changed files with 73 additions and 19 deletions

View File

@ -181,7 +181,7 @@ fn generic_extension<'cx>(cx: &'cx mut ExtCtxt,
for lhs in lhses { // try each arm's matchers for lhs in lhses { // try each arm's matchers
let lhs_tt = match *lhs { let lhs_tt = match *lhs {
quoted::TokenTree::Delimited(_, ref delim) => &delim.tts[..], quoted::TokenTree::Delimited(_, ref delim) => &delim.tts[..],
_ => cx.span_bug(sp, "malformed macro lhs") _ => continue,
}; };
match TokenTree::parse(cx, lhs_tt, arg.clone()) { match TokenTree::parse(cx, lhs_tt, arg.clone()) {
Success(_) => { Success(_) => {
@ -191,7 +191,7 @@ fn generic_extension<'cx>(cx: &'cx mut ExtCtxt,
err.span_suggestion_short( err.span_suggestion_short(
comma_span, comma_span,
"missing comma here", "missing comma here",
",".to_string(), ", ".to_string(),
); );
} }
} }

View File

@ -186,21 +186,43 @@ impl TokenStream {
/// Given a `TokenStream` with a `Stream` of only two arguments, return a new `TokenStream` /// Given a `TokenStream` with a `Stream` of only two arguments, return a new `TokenStream`
/// separating the two arguments with a comma for diagnostic suggestions. /// separating the two arguments with a comma for diagnostic suggestions.
pub(crate) fn add_comma(&self) -> Option<(TokenStream, Span)> { pub(crate) fn add_comma(&self) -> Option<(TokenStream, Span)> {
// Used to suggest if a user writes `println!("{}" a);` // Used to suggest if a user writes `foo!(a b);`
if let TokenStreamKind::Stream(ref slice) = self.kind { if let TokenStreamKind::Stream(ref slice) = self.kind {
if slice.len() == 2 { let mut suggestion = None;
let comma_span = match slice[0] { let mut iter = slice.iter().enumerate().peekable();
TokenStream { kind: TokenStreamKind::Tree(TokenTree::Token(sp, _)) } | while let Some((pos, ts)) = iter.next() {
TokenStream { kind: TokenStreamKind::Tree(TokenTree::Delimited(sp, _)) } => { if let Some((_, next)) = iter.peek() {
sp.shrink_to_hi() match (ts, next) {
(TokenStream {
kind: TokenStreamKind::Tree(TokenTree::Token(_, token::Token::Comma))
}, _) |
(_, TokenStream {
kind: TokenStreamKind::Tree(TokenTree::Token(_, token::Token::Comma))
}) => {}
(TokenStream {
kind: TokenStreamKind::Tree(TokenTree::Token(sp, _))
}, _) |
(TokenStream {
kind: TokenStreamKind::Tree(TokenTree::Delimited(sp, _))
}, _) => {
let sp = sp.shrink_to_hi();
let comma = TokenStream {
kind: TokenStreamKind::Tree(TokenTree::Token(sp, token::Comma)),
};
suggestion = Some((pos, comma, sp));
}
_ => {}
} }
_ => DUMMY_SP, }
}; }
let comma = TokenStream { if let Some((pos, comma, sp)) = suggestion {
kind: TokenStreamKind::Tree(TokenTree::Token(comma_span, token::Comma)), let mut new_slice = vec![];
}; let parts = slice.split_at(pos + 1);
let slice = RcSlice::new(vec![slice[0].clone(), comma, slice[1].clone()]); new_slice.extend_from_slice(parts.0);
return Some((TokenStream { kind: TokenStreamKind::Stream(slice) }, comma_span)); new_slice.push(comma);
new_slice.extend_from_slice(parts.1);
let slice = RcSlice::new(new_slice);
return Some((TokenStream { kind: TokenStreamKind::Stream(slice) }, sp));
} }
} }
None None

View File

@ -9,7 +9,11 @@
// except according to those terms. // except according to those terms.
macro_rules! foo { macro_rules! foo {
($a:ident, $b:ident) => () ($a:ident) => ();
($a:ident, $b:ident) => ();
($a:ident, $b:ident, $c:ident) => ();
($a:ident, $b:ident, $c:ident, $d:ident) => ();
($a:ident, $b:ident, $c:ident, $d:ident, $e:ident) => ();
} }
fn main() { fn main() {
@ -17,4 +21,10 @@ fn main() {
//~^ ERROR expected token: `,` //~^ ERROR expected token: `,`
foo!(a b); foo!(a b);
//~^ ERROR no rules expected the token `b` //~^ ERROR no rules expected the token `b`
foo!(a, b, c, d e);
//~^ ERROR no rules expected the token `e`
foo!(a, b, c d, e);
//~^ ERROR no rules expected the token `d`
foo!(a, b, c d e);
//~^ ERROR no rules expected the token `d`
} }

View File

@ -1,16 +1,38 @@
error: expected token: `,` error: expected token: `,`
--> $DIR/missing-comma.rs:16:19 --> $DIR/missing-comma.rs:20:19
| |
LL | println!("{}" a); LL | println!("{}" a);
| ^ | ^
error: no rules expected the token `b` error: no rules expected the token `b`
--> $DIR/missing-comma.rs:18:12 --> $DIR/missing-comma.rs:22:12
| |
LL | foo!(a b); LL | foo!(a b);
| -^ | -^
| | | |
| help: missing comma here | help: missing comma here
error: aborting due to 2 previous errors error: no rules expected the token `e`
--> $DIR/missing-comma.rs:24:21
|
LL | foo!(a, b, c, d e);
| -^
| |
| help: missing comma here
error: no rules expected the token `d`
--> $DIR/missing-comma.rs:26:18
|
LL | foo!(a, b, c d, e);
| -^
| |
| help: missing comma here
error: no rules expected the token `d`
--> $DIR/missing-comma.rs:28:18
|
LL | foo!(a, b, c d e);
| ^
error: aborting due to 5 previous errors