]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/crate_in_macro_def.rs
New upstream version 1.63.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / crate_in_macro_def.rs
CommitLineData
04454e1e
FG
1use clippy_utils::diagnostics::span_lint_and_sugg;
2use rustc_ast::ast::{AttrKind, Attribute, Item, ItemKind};
3use rustc_ast::token::{Token, TokenKind};
4use rustc_ast::tokenstream::{TokenStream, TokenTree};
5use rustc_errors::Applicability;
6use rustc_lint::{EarlyContext, EarlyLintPass};
7use rustc_session::{declare_lint_pass, declare_tool_lint};
8use rustc_span::{symbol::sym, Span};
9
10declare_clippy_lint! {
11 /// ### What it does
12 /// Checks for use of `crate` as opposed to `$crate` in a macro definition.
13 ///
14 /// ### Why is this bad?
15 /// `crate` refers to the macro call's crate, whereas `$crate` refers to the macro definition's
16 /// crate. Rarely is the former intended. See:
17 /// https://doc.rust-lang.org/reference/macros-by-example.html#hygiene
18 ///
19 /// ### Example
20 /// ```rust
21 /// #[macro_export]
22 /// macro_rules! print_message {
23 /// () => {
24 /// println!("{}", crate::MESSAGE);
25 /// };
26 /// }
27 /// pub const MESSAGE: &str = "Hello!";
28 /// ```
29 /// Use instead:
30 /// ```rust
31 /// #[macro_export]
32 /// macro_rules! print_message {
33 /// () => {
34 /// println!("{}", $crate::MESSAGE);
35 /// };
36 /// }
37 /// pub const MESSAGE: &str = "Hello!";
38 /// ```
39 ///
40 /// Note that if the use of `crate` is intentional, an `allow` attribute can be applied to the
41 /// macro definition, e.g.:
42 /// ```rust,ignore
43 /// #[allow(clippy::crate_in_macro_def)]
44 /// macro_rules! ok { ... crate::foo ... }
45 /// ```
46 #[clippy::version = "1.61.0"]
47 pub CRATE_IN_MACRO_DEF,
48 suspicious,
49 "using `crate` in a macro definition"
50}
51declare_lint_pass!(CrateInMacroDef => [CRATE_IN_MACRO_DEF]);
52
53impl EarlyLintPass for CrateInMacroDef {
54 fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
55 if_chain! {
56 if item.attrs.iter().any(is_macro_export);
57 if let ItemKind::MacroDef(macro_def) = &item.kind;
58 let tts = macro_def.body.inner_tokens();
59 if let Some(span) = contains_unhygienic_crate_reference(&tts);
60 then {
61 span_lint_and_sugg(
62 cx,
63 CRATE_IN_MACRO_DEF,
64 span,
65 "`crate` references the macro call's crate",
66 "to reference the macro definition's crate, use",
67 String::from("$crate"),
68 Applicability::MachineApplicable,
69 );
70 }
71 }
72 }
73}
74
75fn is_macro_export(attr: &Attribute) -> bool {
76 if_chain! {
77 if let AttrKind::Normal(attr_item, _) = &attr.kind;
78 if let [segment] = attr_item.path.segments.as_slice();
79 then {
80 segment.ident.name == sym::macro_export
81 } else {
82 false
83 }
84 }
85}
86
87fn contains_unhygienic_crate_reference(tts: &TokenStream) -> Option<Span> {
88 let mut prev_is_dollar = false;
89 let mut cursor = tts.trees();
90 while let Some(curr) = cursor.next() {
91 if_chain! {
92 if !prev_is_dollar;
923072b8 93 if let Some(span) = is_crate_keyword(curr);
04454e1e
FG
94 if let Some(next) = cursor.look_ahead(0);
95 if is_token(next, &TokenKind::ModSep);
96 then {
97 return Some(span);
98 }
99 }
100 if let TokenTree::Delimited(_, _, tts) = &curr {
101 let span = contains_unhygienic_crate_reference(tts);
102 if span.is_some() {
103 return span;
104 }
105 }
923072b8 106 prev_is_dollar = is_token(curr, &TokenKind::Dollar);
04454e1e
FG
107 }
108 None
109}
110
111fn is_crate_keyword(tt: &TokenTree) -> Option<Span> {
112 if_chain! {
113 if let TokenTree::Token(Token { kind: TokenKind::Ident(symbol, _), span }) = tt;
114 if symbol.as_str() == "crate";
115 then { Some(*span) } else { None }
116 }
117}
118
119fn is_token(tt: &TokenTree, kind: &TokenKind) -> bool {
120 if let TokenTree::Token(Token { kind: other, .. }) = tt {
121 kind == other
122 } else {
123 false
124 }
125}