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