]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | //! checks for attributes |
2 | ||
cdc7bbd5 | 3 | use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; |
5099ac24 | 4 | use clippy_utils::macros::{is_panic, macro_backtrace}; |
487cf647 | 5 | use clippy_utils::msrvs::{self, Msrv}; |
cdc7bbd5 | 6 | use clippy_utils::source::{first_line_of_span, is_present_in_source, snippet_opt, without_block_comments}; |
f20569fa | 7 | use if_chain::if_chain; |
487cf647 | 8 | use rustc_ast::{AttrKind, AttrStyle, Attribute, LitKind, MetaItemKind, MetaItemLit, NestedMetaItem}; |
f20569fa XL |
9 | use rustc_errors::Applicability; |
10 | use rustc_hir::{ | |
11 | Block, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, StmtKind, TraitFn, TraitItem, TraitItemKind, | |
12 | }; | |
487cf647 | 13 | use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, Level, LintContext}; |
f20569fa XL |
14 | use rustc_middle::lint::in_external_macro; |
15 | use rustc_middle::ty; | |
a2a8927a | 16 | use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass}; |
f20569fa | 17 | use rustc_span::source_map::Span; |
a2a8927a | 18 | use rustc_span::symbol::Symbol; |
487cf647 | 19 | use rustc_span::{sym, DUMMY_SP}; |
f20569fa XL |
20 | use semver::Version; |
21 | ||
22 | static UNIX_SYSTEMS: &[&str] = &[ | |
23 | "android", | |
24 | "dragonfly", | |
25 | "emscripten", | |
26 | "freebsd", | |
27 | "fuchsia", | |
28 | "haiku", | |
29 | "illumos", | |
30 | "ios", | |
31 | "l4re", | |
32 | "linux", | |
33 | "macos", | |
34 | "netbsd", | |
35 | "openbsd", | |
36 | "redox", | |
37 | "solaris", | |
38 | "vxworks", | |
39 | ]; | |
40 | ||
41 | // NOTE: windows is excluded from the list because it's also a valid target family. | |
42 | static NON_UNIX_SYSTEMS: &[&str] = &["hermit", "none", "wasi"]; | |
43 | ||
44 | declare_clippy_lint! { | |
94222f64 XL |
45 | /// ### What it does |
46 | /// Checks for items annotated with `#[inline(always)]`, | |
f20569fa XL |
47 | /// unless the annotated function is empty or simply panics. |
48 | /// | |
94222f64 XL |
49 | /// ### Why is this bad? |
50 | /// While there are valid uses of this annotation (and once | |
f20569fa XL |
51 | /// you know when to use it, by all means `allow` this lint), it's a common |
52 | /// newbie-mistake to pepper one's code with it. | |
53 | /// | |
54 | /// As a rule of thumb, before slapping `#[inline(always)]` on a function, | |
55 | /// measure if that additional function call really affects your runtime profile | |
56 | /// sufficiently to make up for the increase in compile time. | |
57 | /// | |
94222f64 XL |
58 | /// ### Known problems |
59 | /// False positives, big time. This lint is meant to be | |
f20569fa XL |
60 | /// deactivated by everyone doing serious performance work. This means having |
61 | /// done the measurement. | |
62 | /// | |
94222f64 | 63 | /// ### Example |
f20569fa XL |
64 | /// ```ignore |
65 | /// #[inline(always)] | |
66 | /// fn not_quite_hot_code(..) { ... } | |
67 | /// ``` | |
a2a8927a | 68 | #[clippy::version = "pre 1.29.0"] |
f20569fa XL |
69 | pub INLINE_ALWAYS, |
70 | pedantic, | |
71 | "use of `#[inline(always)]`" | |
72 | } | |
73 | ||
74 | declare_clippy_lint! { | |
94222f64 XL |
75 | /// ### What it does |
76 | /// Checks for `extern crate` and `use` items annotated with | |
f20569fa XL |
77 | /// lint attributes. |
78 | /// | |
064997fb FG |
79 | /// This lint permits lint attributes for lints emitted on the items themself. |
80 | /// For `use` items these lints are: | |
81 | /// * deprecated | |
82 | /// * unreachable_pub | |
83 | /// * unused_imports | |
84 | /// * clippy::enum_glob_use | |
85 | /// * clippy::macro_use_imports | |
86 | /// * clippy::wildcard_imports | |
87 | /// | |
88 | /// For `extern crate` items these lints are: | |
89 | /// * `unused_imports` on items with `#[macro_use]` | |
f20569fa | 90 | /// |
94222f64 XL |
91 | /// ### Why is this bad? |
92 | /// Lint attributes have no effect on crate imports. Most | |
f20569fa XL |
93 | /// likely a `!` was forgotten. |
94 | /// | |
94222f64 | 95 | /// ### Example |
f20569fa | 96 | /// ```ignore |
f20569fa XL |
97 | /// #[deny(dead_code)] |
98 | /// extern crate foo; | |
99 | /// #[forbid(dead_code)] | |
100 | /// use foo::bar; | |
923072b8 | 101 | /// ``` |
f20569fa | 102 | /// |
923072b8 FG |
103 | /// Use instead: |
104 | /// ```rust,ignore | |
f20569fa XL |
105 | /// #[allow(unused_imports)] |
106 | /// use foo::baz; | |
107 | /// #[allow(unused_imports)] | |
108 | /// #[macro_use] | |
109 | /// extern crate baz; | |
110 | /// ``` | |
a2a8927a | 111 | #[clippy::version = "pre 1.29.0"] |
f20569fa XL |
112 | pub USELESS_ATTRIBUTE, |
113 | correctness, | |
114 | "use of lint attributes on `extern crate` items" | |
115 | } | |
116 | ||
117 | declare_clippy_lint! { | |
94222f64 XL |
118 | /// ### What it does |
119 | /// Checks for `#[deprecated]` annotations with a `since` | |
f20569fa XL |
120 | /// field that is not a valid semantic version. |
121 | /// | |
94222f64 XL |
122 | /// ### Why is this bad? |
123 | /// For checking the version of the deprecation, it must be | |
f20569fa XL |
124 | /// a valid semver. Failing that, the contained information is useless. |
125 | /// | |
94222f64 | 126 | /// ### Example |
f20569fa XL |
127 | /// ```rust |
128 | /// #[deprecated(since = "forever")] | |
129 | /// fn something_else() { /* ... */ } | |
130 | /// ``` | |
a2a8927a | 131 | #[clippy::version = "pre 1.29.0"] |
f20569fa XL |
132 | pub DEPRECATED_SEMVER, |
133 | correctness, | |
134 | "use of `#[deprecated(since = \"x\")]` where x is not semver" | |
135 | } | |
136 | ||
137 | declare_clippy_lint! { | |
94222f64 XL |
138 | /// ### What it does |
139 | /// Checks for empty lines after outer attributes | |
f20569fa | 140 | /// |
94222f64 | 141 | /// ### Why is this bad? |
f20569fa XL |
142 | /// Most likely the attribute was meant to be an inner attribute using a '!'. |
143 | /// If it was meant to be an outer attribute, then the following item | |
144 | /// should not be separated by empty lines. | |
145 | /// | |
94222f64 XL |
146 | /// ### Known problems |
147 | /// Can cause false positives. | |
f20569fa XL |
148 | /// |
149 | /// From the clippy side it's difficult to detect empty lines between an attributes and the | |
150 | /// following item because empty lines and comments are not part of the AST. The parsing | |
151 | /// currently works for basic cases but is not perfect. | |
152 | /// | |
94222f64 | 153 | /// ### Example |
f20569fa | 154 | /// ```rust |
923072b8 FG |
155 | /// #[allow(dead_code)] |
156 | /// | |
157 | /// fn not_quite_good_code() { } | |
158 | /// ``` | |
159 | /// | |
160 | /// Use instead: | |
161 | /// ```rust | |
f20569fa XL |
162 | /// // Good (as inner attribute) |
163 | /// #![allow(dead_code)] | |
164 | /// | |
165 | /// fn this_is_fine() { } | |
166 | /// | |
923072b8 | 167 | /// // or |
f20569fa XL |
168 | /// |
169 | /// // Good (as outer attribute) | |
170 | /// #[allow(dead_code)] | |
171 | /// fn this_is_fine_too() { } | |
172 | /// ``` | |
a2a8927a | 173 | #[clippy::version = "pre 1.29.0"] |
f20569fa XL |
174 | pub EMPTY_LINE_AFTER_OUTER_ATTR, |
175 | nursery, | |
176 | "empty line after outer attribute" | |
177 | } | |
178 | ||
179 | declare_clippy_lint! { | |
94222f64 XL |
180 | /// ### What it does |
181 | /// Checks for `warn`/`deny`/`forbid` attributes targeting the whole clippy::restriction category. | |
f20569fa | 182 | /// |
94222f64 XL |
183 | /// ### Why is this bad? |
184 | /// Restriction lints sometimes are in contrast with other lints or even go against idiomatic rust. | |
f20569fa XL |
185 | /// These lints should only be enabled on a lint-by-lint basis and with careful consideration. |
186 | /// | |
94222f64 | 187 | /// ### Example |
f20569fa XL |
188 | /// ```rust |
189 | /// #![deny(clippy::restriction)] | |
190 | /// ``` | |
191 | /// | |
923072b8 | 192 | /// Use instead: |
f20569fa XL |
193 | /// ```rust |
194 | /// #![deny(clippy::as_conversions)] | |
195 | /// ``` | |
a2a8927a | 196 | #[clippy::version = "1.47.0"] |
f20569fa | 197 | pub BLANKET_CLIPPY_RESTRICTION_LINTS, |
136023e0 | 198 | suspicious, |
f20569fa XL |
199 | "enabling the complete restriction group" |
200 | } | |
201 | ||
202 | declare_clippy_lint! { | |
94222f64 XL |
203 | /// ### What it does |
204 | /// Checks for `#[cfg_attr(rustfmt, rustfmt_skip)]` and suggests to replace it | |
f20569fa XL |
205 | /// with `#[rustfmt::skip]`. |
206 | /// | |
94222f64 XL |
207 | /// ### Why is this bad? |
208 | /// Since tool_attributes ([rust-lang/rust#44690](https://github.com/rust-lang/rust/issues/44690)) | |
f20569fa XL |
209 | /// are stable now, they should be used instead of the old `cfg_attr(rustfmt)` attributes. |
210 | /// | |
94222f64 XL |
211 | /// ### Known problems |
212 | /// This lint doesn't detect crate level inner attributes, because they get | |
f20569fa XL |
213 | /// processed before the PreExpansionPass lints get executed. See |
214 | /// [#3123](https://github.com/rust-lang/rust-clippy/pull/3123#issuecomment-422321765) | |
215 | /// | |
94222f64 | 216 | /// ### Example |
f20569fa XL |
217 | /// ```rust |
218 | /// #[cfg_attr(rustfmt, rustfmt_skip)] | |
219 | /// fn main() { } | |
220 | /// ``` | |
221 | /// | |
923072b8 | 222 | /// Use instead: |
f20569fa XL |
223 | /// ```rust |
224 | /// #[rustfmt::skip] | |
225 | /// fn main() { } | |
226 | /// ``` | |
a2a8927a | 227 | #[clippy::version = "1.32.0"] |
f20569fa XL |
228 | pub DEPRECATED_CFG_ATTR, |
229 | complexity, | |
230 | "usage of `cfg_attr(rustfmt)` instead of tool attributes" | |
231 | } | |
232 | ||
233 | declare_clippy_lint! { | |
94222f64 XL |
234 | /// ### What it does |
235 | /// Checks for cfg attributes having operating systems used in target family position. | |
f20569fa | 236 | /// |
94222f64 XL |
237 | /// ### Why is this bad? |
238 | /// The configuration option will not be recognised and the related item will not be included | |
f20569fa XL |
239 | /// by the conditional compilation engine. |
240 | /// | |
94222f64 | 241 | /// ### Example |
f20569fa XL |
242 | /// ```rust |
243 | /// #[cfg(linux)] | |
244 | /// fn conditional() { } | |
245 | /// ``` | |
246 | /// | |
923072b8 | 247 | /// Use instead: |
f20569fa | 248 | /// ```rust |
923072b8 | 249 | /// # mod hidden { |
f20569fa XL |
250 | /// #[cfg(target_os = "linux")] |
251 | /// fn conditional() { } | |
923072b8 FG |
252 | /// # } |
253 | /// | |
254 | /// // or | |
f20569fa | 255 | /// |
f20569fa XL |
256 | /// #[cfg(unix)] |
257 | /// fn conditional() { } | |
258 | /// ``` | |
259 | /// Check the [Rust Reference](https://doc.rust-lang.org/reference/conditional-compilation.html#target_os) for more details. | |
a2a8927a | 260 | #[clippy::version = "1.45.0"] |
f20569fa XL |
261 | pub MISMATCHED_TARGET_OS, |
262 | correctness, | |
263 | "usage of `cfg(operating_system)` instead of `cfg(target_os = \"operating_system\")`" | |
264 | } | |
265 | ||
5e7ed085 FG |
266 | declare_clippy_lint! { |
267 | /// ### What it does | |
268 | /// Checks for attributes that allow lints without a reason. | |
269 | /// | |
270 | /// (This requires the `lint_reasons` feature) | |
271 | /// | |
272 | /// ### Why is this bad? | |
273 | /// Allowing a lint should always have a reason. This reason should be documented to | |
274 | /// ensure that others understand the reasoning | |
275 | /// | |
276 | /// ### Example | |
5e7ed085 FG |
277 | /// ```rust |
278 | /// #![feature(lint_reasons)] | |
279 | /// | |
280 | /// #![allow(clippy::some_lint)] | |
281 | /// ``` | |
282 | /// | |
923072b8 | 283 | /// Use instead: |
5e7ed085 FG |
284 | /// ```rust |
285 | /// #![feature(lint_reasons)] | |
286 | /// | |
287 | /// #![allow(clippy::some_lint, reason = "False positive rust-lang/rust-clippy#1002020")] | |
288 | /// ``` | |
289 | #[clippy::version = "1.61.0"] | |
290 | pub ALLOW_ATTRIBUTES_WITHOUT_REASON, | |
291 | restriction, | |
292 | "ensures that all `allow` and `expect` attributes have a reason" | |
293 | } | |
294 | ||
f20569fa | 295 | declare_lint_pass!(Attributes => [ |
5e7ed085 | 296 | ALLOW_ATTRIBUTES_WITHOUT_REASON, |
f20569fa XL |
297 | INLINE_ALWAYS, |
298 | DEPRECATED_SEMVER, | |
299 | USELESS_ATTRIBUTE, | |
300 | BLANKET_CLIPPY_RESTRICTION_LINTS, | |
301 | ]); | |
302 | ||
303 | impl<'tcx> LateLintPass<'tcx> for Attributes { | |
487cf647 FG |
304 | fn check_crate(&mut self, cx: &LateContext<'tcx>) { |
305 | for (name, level) in &cx.sess().opts.lint_opts { | |
306 | if name == "clippy::restriction" && *level > Level::Allow { | |
307 | span_lint_and_then( | |
308 | cx, | |
309 | BLANKET_CLIPPY_RESTRICTION_LINTS, | |
310 | DUMMY_SP, | |
311 | "`clippy::restriction` is not meant to be enabled as a group", | |
312 | |diag| { | |
313 | diag.note(format!( | |
314 | "because of the command line `--{} clippy::restriction`", | |
315 | level.as_str() | |
316 | )); | |
317 | diag.help("enable the restriction lints you need individually"); | |
318 | }, | |
319 | ); | |
320 | } | |
321 | } | |
322 | } | |
323 | ||
f20569fa XL |
324 | fn check_attribute(&mut self, cx: &LateContext<'tcx>, attr: &'tcx Attribute) { |
325 | if let Some(items) = &attr.meta_item_list() { | |
326 | if let Some(ident) = attr.ident() { | |
cdc7bbd5 XL |
327 | if is_lint_level(ident.name) { |
328 | check_clippy_lint_names(cx, ident.name, items); | |
f20569fa | 329 | } |
5e7ed085 FG |
330 | if matches!(ident.name, sym::allow | sym::expect) { |
331 | check_lint_reason(cx, ident.name, items, attr); | |
332 | } | |
f20569fa XL |
333 | if items.is_empty() || !attr.has_name(sym::deprecated) { |
334 | return; | |
335 | } | |
336 | for item in items { | |
337 | if_chain! { | |
338 | if let NestedMetaItem::MetaItem(mi) = &item; | |
339 | if let MetaItemKind::NameValue(lit) = &mi.kind; | |
340 | if mi.has_name(sym::since); | |
341 | then { | |
342 | check_semver(cx, item.span(), lit); | |
343 | } | |
344 | } | |
345 | } | |
346 | } | |
347 | } | |
348 | } | |
349 | ||
350 | fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { | |
351 | let attrs = cx.tcx.hir().attrs(item.hir_id()); | |
352 | if is_relevant_item(cx, item) { | |
17df50a5 | 353 | check_attrs(cx, item.span, item.ident.name, attrs); |
f20569fa XL |
354 | } |
355 | match item.kind { | |
356 | ItemKind::ExternCrate(..) | ItemKind::Use(..) => { | |
357 | let skip_unused_imports = attrs.iter().any(|attr| attr.has_name(sym::macro_use)); | |
358 | ||
359 | for attr in attrs { | |
360 | if in_external_macro(cx.sess(), attr.span) { | |
361 | return; | |
362 | } | |
363 | if let Some(lint_list) = &attr.meta_item_list() { | |
cdc7bbd5 | 364 | if attr.ident().map_or(false, |ident| is_lint_level(ident.name)) { |
cdc7bbd5 XL |
365 | for lint in lint_list { |
366 | match item.kind { | |
367 | ItemKind::Use(..) => { | |
923072b8 | 368 | if is_word(lint, sym::unused_imports) |
cdc7bbd5 XL |
369 | || is_word(lint, sym::deprecated) |
370 | || is_word(lint, sym!(unreachable_pub)) | |
371 | || is_word(lint, sym!(unused)) | |
04454e1e FG |
372 | || extract_clippy_lint(lint).map_or(false, |s| { |
373 | matches!( | |
374 | s.as_str(), | |
064997fb FG |
375 | "wildcard_imports" |
376 | | "enum_glob_use" | |
377 | | "redundant_pub_crate" | |
2b03887a | 378 | | "macro_use_imports" |
487cf647 FG |
379 | | "unsafe_removed_from_name" |
380 | | "module_name_repetitions" | |
381 | | "single_component_path_imports" | |
04454e1e FG |
382 | ) |
383 | }) | |
cdc7bbd5 XL |
384 | { |
385 | return; | |
386 | } | |
387 | }, | |
388 | ItemKind::ExternCrate(..) => { | |
923072b8 | 389 | if is_word(lint, sym::unused_imports) && skip_unused_imports { |
cdc7bbd5 XL |
390 | return; |
391 | } | |
392 | if is_word(lint, sym!(unused_extern_crates)) { | |
393 | return; | |
f20569fa | 394 | } |
cdc7bbd5 XL |
395 | }, |
396 | _ => {}, | |
397 | } | |
398 | } | |
399 | let line_span = first_line_of_span(cx, attr.span); | |
400 | ||
401 | if let Some(mut sugg) = snippet_opt(cx, line_span) { | |
402 | if sugg.contains("#[") { | |
403 | span_lint_and_then( | |
404 | cx, | |
405 | USELESS_ATTRIBUTE, | |
406 | line_span, | |
407 | "useless lint attribute", | |
408 | |diag| { | |
409 | sugg = sugg.replacen("#[", "#![", 1); | |
410 | diag.span_suggestion( | |
f20569fa | 411 | line_span, |
cdc7bbd5 XL |
412 | "if you just forgot a `!`, use", |
413 | sugg, | |
414 | Applicability::MaybeIncorrect, | |
f20569fa | 415 | ); |
cdc7bbd5 XL |
416 | }, |
417 | ); | |
418 | } | |
f20569fa XL |
419 | } |
420 | } | |
421 | } | |
422 | } | |
423 | }, | |
424 | _ => {}, | |
425 | } | |
426 | } | |
427 | ||
428 | fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) { | |
429 | if is_relevant_impl(cx, item) { | |
17df50a5 | 430 | check_attrs(cx, item.span, item.ident.name, cx.tcx.hir().attrs(item.hir_id())); |
f20569fa XL |
431 | } |
432 | } | |
433 | ||
434 | fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { | |
435 | if is_relevant_trait(cx, item) { | |
17df50a5 | 436 | check_attrs(cx, item.span, item.ident.name, cx.tcx.hir().attrs(item.hir_id())); |
f20569fa XL |
437 | } |
438 | } | |
439 | } | |
440 | ||
441 | /// Returns the lint name if it is clippy lint. | |
a2a8927a | 442 | fn extract_clippy_lint(lint: &NestedMetaItem) -> Option<Symbol> { |
f20569fa XL |
443 | if_chain! { |
444 | if let Some(meta_item) = lint.meta_item(); | |
445 | if meta_item.path.segments.len() > 1; | |
446 | if let tool_name = meta_item.path.segments[0].ident; | |
447 | if tool_name.name == sym::clippy; | |
f20569fa | 448 | then { |
cdc7bbd5 | 449 | let lint_name = meta_item.path.segments.last().unwrap().ident.name; |
a2a8927a | 450 | return Some(lint_name); |
f20569fa XL |
451 | } |
452 | } | |
453 | None | |
454 | } | |
455 | ||
cdc7bbd5 | 456 | fn check_clippy_lint_names(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem]) { |
f20569fa XL |
457 | for lint in items { |
458 | if let Some(lint_name) = extract_clippy_lint(lint) { | |
a2a8927a | 459 | if lint_name.as_str() == "restriction" && name != sym::allow { |
f20569fa XL |
460 | span_lint_and_help( |
461 | cx, | |
462 | BLANKET_CLIPPY_RESTRICTION_LINTS, | |
463 | lint.span(), | |
487cf647 | 464 | "`clippy::restriction` is not meant to be enabled as a group", |
f20569fa | 465 | None, |
487cf647 | 466 | "enable the restriction lints you need individually", |
f20569fa XL |
467 | ); |
468 | } | |
469 | } | |
470 | } | |
471 | } | |
472 | ||
5e7ed085 FG |
473 | fn check_lint_reason(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem], attr: &'_ Attribute) { |
474 | // Check for the feature | |
f25598a0 | 475 | if !cx.tcx.features().lint_reasons { |
5e7ed085 FG |
476 | return; |
477 | } | |
478 | ||
479 | // Check if the reason is present | |
480 | if let Some(item) = items.last().and_then(NestedMetaItem::meta_item) | |
481 | && let MetaItemKind::NameValue(_) = &item.kind | |
482 | && item.path == sym::reason | |
483 | { | |
484 | return; | |
485 | } | |
486 | ||
487cf647 FG |
487 | // Check if the attribute is in an external macro and therefore out of the developer's control |
488 | if in_external_macro(cx.sess(), attr.span) { | |
489 | return; | |
490 | } | |
491 | ||
5e7ed085 FG |
492 | span_lint_and_help( |
493 | cx, | |
494 | ALLOW_ATTRIBUTES_WITHOUT_REASON, | |
495 | attr.span, | |
496 | &format!("`{}` attribute without specifying a reason", name.as_str()), | |
497 | None, | |
498 | "try adding a reason at the end with `, reason = \"..\"`", | |
499 | ); | |
500 | } | |
501 | ||
f20569fa XL |
502 | fn is_relevant_item(cx: &LateContext<'_>, item: &Item<'_>) -> bool { |
503 | if let ItemKind::Fn(_, _, eid) = item.kind { | |
f2b60f7d | 504 | is_relevant_expr(cx, cx.tcx.typeck_body(eid), cx.tcx.hir().body(eid).value) |
f20569fa XL |
505 | } else { |
506 | true | |
507 | } | |
508 | } | |
509 | ||
510 | fn is_relevant_impl(cx: &LateContext<'_>, item: &ImplItem<'_>) -> bool { | |
511 | match item.kind { | |
f2b60f7d | 512 | ImplItemKind::Fn(_, eid) => is_relevant_expr(cx, cx.tcx.typeck_body(eid), cx.tcx.hir().body(eid).value), |
f20569fa XL |
513 | _ => false, |
514 | } | |
515 | } | |
516 | ||
517 | fn is_relevant_trait(cx: &LateContext<'_>, item: &TraitItem<'_>) -> bool { | |
518 | match item.kind { | |
519 | TraitItemKind::Fn(_, TraitFn::Required(_)) => true, | |
520 | TraitItemKind::Fn(_, TraitFn::Provided(eid)) => { | |
f2b60f7d | 521 | is_relevant_expr(cx, cx.tcx.typeck_body(eid), cx.tcx.hir().body(eid).value) |
f20569fa XL |
522 | }, |
523 | _ => false, | |
524 | } | |
525 | } | |
526 | ||
527 | fn is_relevant_block(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>, block: &Block<'_>) -> bool { | |
528 | block.stmts.first().map_or( | |
529 | block | |
530 | .expr | |
531 | .as_ref() | |
532 | .map_or(false, |e| is_relevant_expr(cx, typeck_results, e)), | |
533 | |stmt| match &stmt.kind { | |
534 | StmtKind::Local(_) => true, | |
535 | StmtKind::Expr(expr) | StmtKind::Semi(expr) => is_relevant_expr(cx, typeck_results, expr), | |
cdc7bbd5 | 536 | StmtKind::Item(_) => false, |
f20569fa XL |
537 | }, |
538 | ) | |
539 | } | |
540 | ||
541 | fn is_relevant_expr(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>, expr: &Expr<'_>) -> bool { | |
5099ac24 FG |
542 | if macro_backtrace(expr.span).last().map_or(false, |macro_call| { |
543 | is_panic(cx, macro_call.def_id) || cx.tcx.item_name(macro_call.def_id) == sym::unreachable | |
544 | }) { | |
545 | return false; | |
546 | } | |
f20569fa XL |
547 | match &expr.kind { |
548 | ExprKind::Block(block, _) => is_relevant_block(cx, typeck_results, block), | |
549 | ExprKind::Ret(Some(e)) => is_relevant_expr(cx, typeck_results, e), | |
550 | ExprKind::Ret(None) | ExprKind::Break(_, None) => false, | |
f20569fa XL |
551 | _ => true, |
552 | } | |
553 | } | |
554 | ||
555 | fn check_attrs(cx: &LateContext<'_>, span: Span, name: Symbol, attrs: &[Attribute]) { | |
556 | if span.from_expansion() { | |
557 | return; | |
558 | } | |
559 | ||
560 | for attr in attrs { | |
561 | if let Some(values) = attr.meta_item_list() { | |
562 | if values.len() != 1 || !attr.has_name(sym::inline) { | |
563 | continue; | |
564 | } | |
565 | if is_word(&values[0], sym::always) { | |
566 | span_lint( | |
567 | cx, | |
568 | INLINE_ALWAYS, | |
569 | attr.span, | |
2b03887a | 570 | &format!("you have declared `#[inline(always)]` on `{name}`. This is usually a bad idea"), |
f20569fa XL |
571 | ); |
572 | } | |
573 | } | |
574 | } | |
575 | } | |
576 | ||
487cf647 | 577 | fn check_semver(cx: &LateContext<'_>, span: Span, lit: &MetaItemLit) { |
f20569fa | 578 | if let LitKind::Str(is, _) = lit.kind { |
a2a8927a | 579 | if Version::parse(is.as_str()).is_ok() { |
f20569fa XL |
580 | return; |
581 | } | |
582 | } | |
583 | span_lint( | |
584 | cx, | |
585 | DEPRECATED_SEMVER, | |
586 | span, | |
587 | "the since field must contain a semver-compliant version", | |
588 | ); | |
589 | } | |
590 | ||
591 | fn is_word(nmi: &NestedMetaItem, expected: Symbol) -> bool { | |
592 | if let NestedMetaItem::MetaItem(mi) = &nmi { | |
593 | mi.is_word() && mi.has_name(expected) | |
594 | } else { | |
595 | false | |
596 | } | |
597 | } | |
598 | ||
a2a8927a | 599 | pub struct EarlyAttributes { |
487cf647 | 600 | pub msrv: Msrv, |
a2a8927a XL |
601 | } |
602 | ||
603 | impl_lint_pass!(EarlyAttributes => [ | |
f20569fa XL |
604 | DEPRECATED_CFG_ATTR, |
605 | MISMATCHED_TARGET_OS, | |
606 | EMPTY_LINE_AFTER_OUTER_ATTR, | |
607 | ]); | |
608 | ||
609 | impl EarlyLintPass for EarlyAttributes { | |
610 | fn check_item(&mut self, cx: &EarlyContext<'_>, item: &rustc_ast::Item) { | |
611 | check_empty_line_after_outer_attr(cx, item); | |
612 | } | |
613 | ||
614 | fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) { | |
487cf647 | 615 | check_deprecated_cfg_attr(cx, attr, &self.msrv); |
f20569fa XL |
616 | check_mismatched_target_os(cx, attr); |
617 | } | |
a2a8927a XL |
618 | |
619 | extract_msrv_attr!(EarlyContext); | |
f20569fa XL |
620 | } |
621 | ||
622 | fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::Item) { | |
923072b8 FG |
623 | let mut iter = item.attrs.iter().peekable(); |
624 | while let Some(attr) = iter.next() { | |
625 | if matches!(attr.kind, AttrKind::Normal(..)) | |
626 | && attr.style == AttrStyle::Outer | |
627 | && is_present_in_source(cx, attr.span) | |
628 | { | |
c295e0f8 | 629 | let begin_of_attr_to_item = Span::new(attr.span.lo(), item.span.lo(), item.span.ctxt(), item.span.parent()); |
923072b8 FG |
630 | let end_of_attr_to_next_attr_or_item = Span::new( |
631 | attr.span.hi(), | |
632 | iter.peek().map_or(item.span.lo(), |next_attr| next_attr.span.lo()), | |
633 | item.span.ctxt(), | |
634 | item.span.parent(), | |
635 | ); | |
f20569fa | 636 | |
923072b8 | 637 | if let Some(snippet) = snippet_opt(cx, end_of_attr_to_next_attr_or_item) { |
f20569fa XL |
638 | let lines = snippet.split('\n').collect::<Vec<_>>(); |
639 | let lines = without_block_comments(lines); | |
640 | ||
641 | if lines.iter().filter(|l| l.trim().is_empty()).count() > 2 { | |
642 | span_lint( | |
643 | cx, | |
644 | EMPTY_LINE_AFTER_OUTER_ATTR, | |
645 | begin_of_attr_to_item, | |
646 | "found an empty line after an outer attribute. \ | |
647 | Perhaps you forgot to add a `!` to make it an inner attribute?", | |
648 | ); | |
649 | } | |
650 | } | |
651 | } | |
652 | } | |
653 | } | |
654 | ||
487cf647 | 655 | fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &Msrv) { |
f20569fa | 656 | if_chain! { |
487cf647 | 657 | if msrv.meets(msrvs::TOOL_ATTRIBUTES); |
f20569fa XL |
658 | // check cfg_attr |
659 | if attr.has_name(sym::cfg_attr); | |
660 | if let Some(items) = attr.meta_item_list(); | |
661 | if items.len() == 2; | |
662 | // check for `rustfmt` | |
663 | if let Some(feature_item) = items[0].meta_item(); | |
664 | if feature_item.has_name(sym::rustfmt); | |
665 | // check for `rustfmt_skip` and `rustfmt::skip` | |
666 | if let Some(skip_item) = &items[1].meta_item(); | |
923072b8 FG |
667 | if skip_item.has_name(sym!(rustfmt_skip)) |
668 | || skip_item | |
669 | .path | |
670 | .segments | |
671 | .last() | |
672 | .expect("empty path in attribute") | |
673 | .ident | |
674 | .name | |
675 | == sym::skip; | |
f20569fa XL |
676 | // Only lint outer attributes, because custom inner attributes are unstable |
677 | // Tracking issue: https://github.com/rust-lang/rust/issues/54726 | |
c295e0f8 | 678 | if attr.style == AttrStyle::Outer; |
f20569fa XL |
679 | then { |
680 | span_lint_and_sugg( | |
681 | cx, | |
682 | DEPRECATED_CFG_ATTR, | |
683 | attr.span, | |
684 | "`cfg_attr` is deprecated for rustfmt and got replaced by tool attributes", | |
685 | "use", | |
686 | "#[rustfmt::skip]".to_string(), | |
687 | Applicability::MachineApplicable, | |
688 | ); | |
689 | } | |
690 | } | |
691 | } | |
692 | ||
693 | fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) { | |
694 | fn find_os(name: &str) -> Option<&'static str> { | |
695 | UNIX_SYSTEMS | |
696 | .iter() | |
697 | .chain(NON_UNIX_SYSTEMS.iter()) | |
698 | .find(|&&os| os == name) | |
699 | .copied() | |
700 | } | |
701 | ||
702 | fn is_unix(name: &str) -> bool { | |
703 | UNIX_SYSTEMS.iter().any(|&os| os == name) | |
704 | } | |
705 | ||
706 | fn find_mismatched_target_os(items: &[NestedMetaItem]) -> Vec<(&str, Span)> { | |
707 | let mut mismatched = Vec::new(); | |
708 | ||
709 | for item in items { | |
710 | if let NestedMetaItem::MetaItem(meta) = item { | |
711 | match &meta.kind { | |
712 | MetaItemKind::List(list) => { | |
cdc7bbd5 | 713 | mismatched.extend(find_mismatched_target_os(list)); |
f20569fa XL |
714 | }, |
715 | MetaItemKind::Word => { | |
716 | if_chain! { | |
717 | if let Some(ident) = meta.ident(); | |
a2a8927a | 718 | if let Some(os) = find_os(ident.name.as_str()); |
f20569fa XL |
719 | then { |
720 | mismatched.push((os, ident.span)); | |
721 | } | |
722 | } | |
723 | }, | |
cdc7bbd5 | 724 | MetaItemKind::NameValue(..) => {}, |
f20569fa XL |
725 | } |
726 | } | |
727 | } | |
728 | ||
729 | mismatched | |
730 | } | |
731 | ||
732 | if_chain! { | |
733 | if attr.has_name(sym::cfg); | |
734 | if let Some(list) = attr.meta_item_list(); | |
735 | let mismatched = find_mismatched_target_os(&list); | |
736 | if !mismatched.is_empty(); | |
737 | then { | |
738 | let mess = "operating system used in target family position"; | |
739 | ||
cdc7bbd5 | 740 | span_lint_and_then(cx, MISMATCHED_TARGET_OS, attr.span, mess, |diag| { |
f20569fa XL |
741 | // Avoid showing the unix suggestion multiple times in case |
742 | // we have more than one mismatch for unix-like systems | |
743 | let mut unix_suggested = false; | |
744 | ||
745 | for (os, span) in mismatched { | |
2b03887a | 746 | let sugg = format!("target_os = \"{os}\""); |
f20569fa XL |
747 | diag.span_suggestion(span, "try", sugg, Applicability::MaybeIncorrect); |
748 | ||
749 | if !unix_suggested && is_unix(os) { | |
750 | diag.help("did you mean `unix`?"); | |
751 | unix_suggested = true; | |
752 | } | |
753 | } | |
754 | }); | |
755 | } | |
756 | } | |
757 | } | |
cdc7bbd5 XL |
758 | |
759 | fn is_lint_level(symbol: Symbol) -> bool { | |
5e7ed085 | 760 | matches!(symbol, sym::allow | sym::expect | sym::warn | sym::deny | sym::forbid) |
cdc7bbd5 | 761 | } |