48b10feedb
`MacArgs` is an enum with three variants: `Empty`, `Delimited`, and `Eq`. It's used in two ways: - For representing attribute macro arguments (e.g. in `AttrItem`), where all three variants are used. - For representing function-like macros (e.g. in `MacCall` and `MacroDef`), where only the `Delimited` variant is used. In other words, `MacArgs` is used in two quite different places due to them having partial overlap. I find this makes the code hard to read. It also leads to various unreachable code paths, and allows invalid values (such as accidentally using `MacArgs::Empty` in a `MacCall`). This commit splits `MacArgs` in two: - `DelimArgs` is a new struct just for the "delimited arguments" case. It is now used in `MacCall` and `MacroDef`. - `AttrArgs` is a renaming of the old `MacArgs` enum for the attribute macro case. Its `Delimited` variant now contains a `DelimArgs`. Various other related things are renamed as well. These changes make the code clearer, avoids several unreachable paths, and disallows the invalid values.
125 lines
3.9 KiB
Rust
125 lines
3.9 KiB
Rust
use clippy_utils::diagnostics::span_lint_and_sugg;
|
|
use rustc_ast::ast::{AttrKind, Attribute, Item, ItemKind};
|
|
use rustc_ast::token::{Token, TokenKind};
|
|
use rustc_ast::tokenstream::{TokenStream, TokenTree};
|
|
use rustc_errors::Applicability;
|
|
use rustc_lint::{EarlyContext, EarlyLintPass};
|
|
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
|
use rustc_span::{symbol::sym, Span};
|
|
|
|
declare_clippy_lint! {
|
|
/// ### What it does
|
|
/// Checks for use of `crate` as opposed to `$crate` in a macro definition.
|
|
///
|
|
/// ### Why is this bad?
|
|
/// `crate` refers to the macro call's crate, whereas `$crate` refers to the macro definition's
|
|
/// crate. Rarely is the former intended. See:
|
|
/// https://doc.rust-lang.org/reference/macros-by-example.html#hygiene
|
|
///
|
|
/// ### Example
|
|
/// ```rust
|
|
/// #[macro_export]
|
|
/// macro_rules! print_message {
|
|
/// () => {
|
|
/// println!("{}", crate::MESSAGE);
|
|
/// };
|
|
/// }
|
|
/// pub const MESSAGE: &str = "Hello!";
|
|
/// ```
|
|
/// Use instead:
|
|
/// ```rust
|
|
/// #[macro_export]
|
|
/// macro_rules! print_message {
|
|
/// () => {
|
|
/// println!("{}", $crate::MESSAGE);
|
|
/// };
|
|
/// }
|
|
/// pub const MESSAGE: &str = "Hello!";
|
|
/// ```
|
|
///
|
|
/// Note that if the use of `crate` is intentional, an `allow` attribute can be applied to the
|
|
/// macro definition, e.g.:
|
|
/// ```rust,ignore
|
|
/// #[allow(clippy::crate_in_macro_def)]
|
|
/// macro_rules! ok { ... crate::foo ... }
|
|
/// ```
|
|
#[clippy::version = "1.62.0"]
|
|
pub CRATE_IN_MACRO_DEF,
|
|
suspicious,
|
|
"using `crate` in a macro definition"
|
|
}
|
|
declare_lint_pass!(CrateInMacroDef => [CRATE_IN_MACRO_DEF]);
|
|
|
|
impl EarlyLintPass for CrateInMacroDef {
|
|
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
|
|
if_chain! {
|
|
if item.attrs.iter().any(is_macro_export);
|
|
if let ItemKind::MacroDef(macro_def) = &item.kind;
|
|
let tts = macro_def.body.tokens.clone();
|
|
if let Some(span) = contains_unhygienic_crate_reference(&tts);
|
|
then {
|
|
span_lint_and_sugg(
|
|
cx,
|
|
CRATE_IN_MACRO_DEF,
|
|
span,
|
|
"`crate` references the macro call's crate",
|
|
"to reference the macro definition's crate, use",
|
|
String::from("$crate"),
|
|
Applicability::MachineApplicable,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn is_macro_export(attr: &Attribute) -> bool {
|
|
if_chain! {
|
|
if let AttrKind::Normal(normal) = &attr.kind;
|
|
if let [segment] = normal.item.path.segments.as_slice();
|
|
then {
|
|
segment.ident.name == sym::macro_export
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
}
|
|
|
|
fn contains_unhygienic_crate_reference(tts: &TokenStream) -> Option<Span> {
|
|
let mut prev_is_dollar = false;
|
|
let mut cursor = tts.trees();
|
|
while let Some(curr) = cursor.next() {
|
|
if_chain! {
|
|
if !prev_is_dollar;
|
|
if let Some(span) = is_crate_keyword(curr);
|
|
if let Some(next) = cursor.look_ahead(0);
|
|
if is_token(next, &TokenKind::ModSep);
|
|
then {
|
|
return Some(span);
|
|
}
|
|
}
|
|
if let TokenTree::Delimited(_, _, tts) = &curr {
|
|
let span = contains_unhygienic_crate_reference(tts);
|
|
if span.is_some() {
|
|
return span;
|
|
}
|
|
}
|
|
prev_is_dollar = is_token(curr, &TokenKind::Dollar);
|
|
}
|
|
None
|
|
}
|
|
|
|
fn is_crate_keyword(tt: &TokenTree) -> Option<Span> {
|
|
if_chain! {
|
|
if let TokenTree::Token(Token { kind: TokenKind::Ident(symbol, _), span }, _) = tt;
|
|
if symbol.as_str() == "crate";
|
|
then { Some(*span) } else { None }
|
|
}
|
|
}
|
|
|
|
fn is_token(tt: &TokenTree, kind: &TokenKind) -> bool {
|
|
if let TokenTree::Token(Token { kind: other, .. }, _) = tt {
|
|
kind == other
|
|
} else {
|
|
false
|
|
}
|
|
}
|