Skip to content

Commit fe7254f

Browse files
committed
Auto merge of #8576 - smoelius:crate_in_macro_def, r=llogiq
Add `crate_in_macro_def` lint This PR adds a lint to check for `crate` as opposed to `$crate` used in a macro definition. I think this can close #4798. That issue focused on the case where the macro author "imports something into said macro." But I think use of `crate` is likely to be a bug whether it appears in a `use` statement or not. There could be some use case I am failing to see, though. (cc: `@nilscript` `@flip1995)` changelog: `crate_in_macro_def`
2 parents df7c253 + aaf04dc commit fe7254f

9 files changed

+253
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3229,6 +3229,7 @@ Released 2018-09-13
32293229
[`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain
32303230
[`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty
32313231
[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator
3232+
[`crate_in_macro_def`]: https://rust-lang.github.io/rust-clippy/master/index.html#crate_in_macro_def
32323233
[`create_dir`]: https://rust-lang.github.io/rust-clippy/master/index.html#create_dir
32333234
[`crosspointer_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#crosspointer_transmute
32343235
[`dbg_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#dbg_macro
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use rustc_ast::ast::{AttrKind, Attribute, Item, ItemKind};
3+
use rustc_ast::token::{Token, TokenKind};
4+
use rustc_ast::tokenstream::{TokenStream, TokenTree};
5+
use rustc_errors::Applicability;
6+
use rustc_lint::{EarlyContext, EarlyLintPass};
7+
use rustc_session::{declare_lint_pass, declare_tool_lint};
8+
use rustc_span::{symbol::sym, Span};
9+
10+
declare_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+
}
51+
declare_lint_pass!(CrateInMacroDef => [CRATE_IN_MACRO_DEF]);
52+
53+
impl 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+
75+
fn 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+
87+
fn 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;
93+
if let Some(span) = is_crate_keyword(&curr);
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+
}
106+
prev_is_dollar = is_token(&curr, &TokenKind::Dollar);
107+
}
108+
None
109+
}
110+
111+
fn 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+
119+
fn 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+
}

clippy_lints/src/lib.register_all.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
3737
LintId::of(comparison_chain::COMPARISON_CHAIN),
3838
LintId::of(copies::IFS_SAME_COND),
3939
LintId::of(copies::IF_SAME_THEN_ELSE),
40+
LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF),
4041
LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
4142
LintId::of(dereference::NEEDLESS_BORROW),
4243
LintId::of(derivable_impls::DERIVABLE_IMPLS),

clippy_lints/src/lib.register_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ store.register_lints(&[
9797
copies::IF_SAME_THEN_ELSE,
9898
copies::SAME_FUNCTIONS_IN_IF_CONDITION,
9999
copy_iterator::COPY_ITERATOR,
100+
crate_in_macro_def::CRATE_IN_MACRO_DEF,
100101
create_dir::CREATE_DIR,
101102
dbg_macro::DBG_MACRO,
102103
default::DEFAULT_TRAIT_ACCESS,

clippy_lints/src/lib.register_suspicious.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec!
99
LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF),
1010
LintId::of(casts::CAST_ENUM_CONSTRUCTOR),
1111
LintId::of(casts::CAST_ENUM_TRUNCATION),
12+
LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF),
1213
LintId::of(eval_order_dependence::EVAL_ORDER_DEPENDENCE),
1314
LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
1415
LintId::of(format_impl::PRINT_IN_FORMAT_IMPL),

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ mod collapsible_match;
190190
mod comparison_chain;
191191
mod copies;
192192
mod copy_iterator;
193+
mod crate_in_macro_def;
193194
mod create_dir;
194195
mod dbg_macro;
195196
mod default;
@@ -867,6 +868,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
867868
ignore_publish: cargo_ignore_publish,
868869
})
869870
});
871+
store.register_early_pass(|| Box::new(crate_in_macro_def::CrateInMacroDef));
870872
// add lints here, do not remove this comment, it's used in `new_lint`
871873
}
872874

tests/ui/crate_in_macro_def.fixed

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// run-rustfix
2+
#![warn(clippy::crate_in_macro_def)]
3+
4+
mod hygienic {
5+
#[macro_export]
6+
macro_rules! print_message_hygienic {
7+
() => {
8+
println!("{}", $crate::hygienic::MESSAGE);
9+
};
10+
}
11+
12+
pub const MESSAGE: &str = "Hello!";
13+
}
14+
15+
mod unhygienic {
16+
#[macro_export]
17+
macro_rules! print_message_unhygienic {
18+
() => {
19+
println!("{}", $crate::unhygienic::MESSAGE);
20+
};
21+
}
22+
23+
pub const MESSAGE: &str = "Hello!";
24+
}
25+
26+
mod unhygienic_intentionally {
27+
// For cases where the use of `crate` is intentional, applying `allow` to the macro definition
28+
// should suppress the lint.
29+
#[allow(clippy::crate_in_macro_def)]
30+
#[macro_export]
31+
macro_rules! print_message_unhygienic_intentionally {
32+
() => {
33+
println!("{}", crate::CALLER_PROVIDED_MESSAGE);
34+
};
35+
}
36+
}
37+
38+
#[macro_use]
39+
mod not_exported {
40+
macro_rules! print_message_not_exported {
41+
() => {
42+
println!("{}", crate::not_exported::MESSAGE);
43+
};
44+
}
45+
46+
pub const MESSAGE: &str = "Hello!";
47+
}
48+
49+
fn main() {
50+
print_message_hygienic!();
51+
print_message_unhygienic!();
52+
print_message_unhygienic_intentionally!();
53+
print_message_not_exported!();
54+
}
55+
56+
pub const CALLER_PROVIDED_MESSAGE: &str = "Hello!";

tests/ui/crate_in_macro_def.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// run-rustfix
2+
#![warn(clippy::crate_in_macro_def)]
3+
4+
mod hygienic {
5+
#[macro_export]
6+
macro_rules! print_message_hygienic {
7+
() => {
8+
println!("{}", $crate::hygienic::MESSAGE);
9+
};
10+
}
11+
12+
pub const MESSAGE: &str = "Hello!";
13+
}
14+
15+
mod unhygienic {
16+
#[macro_export]
17+
macro_rules! print_message_unhygienic {
18+
() => {
19+
println!("{}", crate::unhygienic::MESSAGE);
20+
};
21+
}
22+
23+
pub const MESSAGE: &str = "Hello!";
24+
}
25+
26+
mod unhygienic_intentionally {
27+
// For cases where the use of `crate` is intentional, applying `allow` to the macro definition
28+
// should suppress the lint.
29+
#[allow(clippy::crate_in_macro_def)]
30+
#[macro_export]
31+
macro_rules! print_message_unhygienic_intentionally {
32+
() => {
33+
println!("{}", crate::CALLER_PROVIDED_MESSAGE);
34+
};
35+
}
36+
}
37+
38+
#[macro_use]
39+
mod not_exported {
40+
macro_rules! print_message_not_exported {
41+
() => {
42+
println!("{}", crate::not_exported::MESSAGE);
43+
};
44+
}
45+
46+
pub const MESSAGE: &str = "Hello!";
47+
}
48+
49+
fn main() {
50+
print_message_hygienic!();
51+
print_message_unhygienic!();
52+
print_message_unhygienic_intentionally!();
53+
print_message_not_exported!();
54+
}
55+
56+
pub const CALLER_PROVIDED_MESSAGE: &str = "Hello!";

tests/ui/crate_in_macro_def.stderr

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: `crate` references the macro call's crate
2+
--> $DIR/crate_in_macro_def.rs:19:28
3+
|
4+
LL | println!("{}", crate::unhygienic::MESSAGE);
5+
| ^^^^^ help: to reference the macro definition's crate, use: `$crate`
6+
|
7+
= note: `-D clippy::crate-in-macro-def` implied by `-D warnings`
8+
9+
error: aborting due to previous error
10+

0 commit comments

Comments
 (0)