]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/std_instead_of_core.rs
d6b336bef943ec9510ec68221d34da9599a411e0
[rust.git] / src / tools / clippy / clippy_lints / src / std_instead_of_core.rs
1 use clippy_utils::diagnostics::span_lint_and_help;
2 use rustc_hir::def_id::DefId;
3 use rustc_hir::{def::Res, HirId, Path, PathSegment};
4 use rustc_lint::{LateContext, LateLintPass};
5 use rustc_middle::ty::DefIdTree;
6 use rustc_session::{declare_tool_lint, impl_lint_pass};
7 use rustc_span::{sym, symbol::kw, Span};
8
9 declare_clippy_lint! {
10     /// ### What it does
11     ///
12     /// Finds items imported through `std` when available through `core`.
13     ///
14     /// ### Why is this bad?
15     ///
16     /// Crates which have `no_std` compatibility may wish to ensure types are imported from core to ensure
17     /// disabling `std` does not cause the crate to fail to compile. This lint is also useful for crates
18     /// migrating to become `no_std` compatible.
19     ///
20     /// ### Example
21     /// ```rust
22     /// use std::hash::Hasher;
23     /// ```
24     /// Use instead:
25     /// ```rust
26     /// use core::hash::Hasher;
27     /// ```
28     #[clippy::version = "1.64.0"]
29     pub STD_INSTEAD_OF_CORE,
30     restriction,
31     "type is imported from std when available in core"
32 }
33
34 declare_clippy_lint! {
35     /// ### What it does
36     ///
37     /// Finds items imported through `std` when available through `alloc`.
38     ///
39     /// ### Why is this bad?
40     ///
41     /// Crates which have `no_std` compatibility and require alloc may wish to ensure types are imported from
42     /// alloc to ensure disabling `std` does not cause the crate to fail to compile. This lint is also useful
43     /// for crates migrating to become `no_std` compatible.
44     ///
45     /// ### Example
46     /// ```rust
47     /// use std::vec::Vec;
48     /// ```
49     /// Use instead:
50     /// ```rust
51     /// # extern crate alloc;
52     /// use alloc::vec::Vec;
53     /// ```
54     #[clippy::version = "1.64.0"]
55     pub STD_INSTEAD_OF_ALLOC,
56     restriction,
57     "type is imported from std when available in alloc"
58 }
59
60 declare_clippy_lint! {
61     /// ### What it does
62     ///
63     /// Finds items imported through `alloc` when available through `core`.
64     ///
65     /// ### Why is this bad?
66     ///
67     /// Crates which have `no_std` compatibility and may optionally require alloc may wish to ensure types are
68     /// imported from core to ensure disabling `alloc` does not cause the crate to fail to compile. This lint
69     /// is also useful for crates migrating to become `no_std` compatible.
70     ///
71     /// ### Example
72     /// ```rust
73     /// # extern crate alloc;
74     /// use alloc::slice::from_ref;
75     /// ```
76     /// Use instead:
77     /// ```rust
78     /// use core::slice::from_ref;
79     /// ```
80     #[clippy::version = "1.64.0"]
81     pub ALLOC_INSTEAD_OF_CORE,
82     restriction,
83     "type is imported from alloc when available in core"
84 }
85
86 #[derive(Default)]
87 pub struct StdReexports {
88     // Paths which can be either a module or a macro (e.g. `std::env`) will cause this check to happen
89     // twice. First for the mod, second for the macro. This is used to avoid the lint reporting for the macro
90     // when the path could be also be used to access the module.
91     prev_span: Span,
92 }
93 impl_lint_pass!(StdReexports => [STD_INSTEAD_OF_CORE, STD_INSTEAD_OF_ALLOC, ALLOC_INSTEAD_OF_CORE]);
94
95 impl<'tcx> LateLintPass<'tcx> for StdReexports {
96     fn check_path(&mut self, cx: &LateContext<'tcx>, path: &Path<'tcx>, _: HirId) {
97         if let Res::Def(_, def_id) = path.res
98             && let Some(first_segment) = get_first_segment(path)
99             && is_stable(cx, def_id)
100         {
101             let (lint, msg, help) = match first_segment.ident.name {
102                 sym::std => match cx.tcx.crate_name(def_id.krate) {
103                     sym::core => (
104                         STD_INSTEAD_OF_CORE,
105                         "used import from `std` instead of `core`",
106                         "consider importing the item from `core`",
107                     ),
108                     sym::alloc => (
109                         STD_INSTEAD_OF_ALLOC,
110                         "used import from `std` instead of `alloc`",
111                         "consider importing the item from `alloc`",
112                     ),
113                     _ => {
114                         self.prev_span = path.span;
115                         return;
116                     },
117                 },
118                 sym::alloc => {
119                     if cx.tcx.crate_name(def_id.krate) == sym::core {
120                         (
121                             ALLOC_INSTEAD_OF_CORE,
122                             "used import from `alloc` instead of `core`",
123                             "consider importing the item from `core`",
124                         )
125                     } else {
126                         self.prev_span = path.span;
127                         return;
128                     }
129                 },
130                 _ => return,
131             };
132             if path.span != self.prev_span {
133                 span_lint_and_help(cx, lint, path.span, msg, None, help);
134                 self.prev_span = path.span;
135             }
136         }
137     }
138 }
139
140 /// Returns the first named segment of a [`Path`].
141 ///
142 /// If this is a global path (such as `::std::fmt::Debug`), then the segment after [`kw::PathRoot`]
143 /// is returned.
144 fn get_first_segment<'tcx>(path: &Path<'tcx>) -> Option<&'tcx PathSegment<'tcx>> {
145     match path.segments {
146         // A global path will have PathRoot as the first segment. In this case, return the segment after.
147         [x, y, ..] if x.ident.name == kw::PathRoot => Some(y),
148         [x, ..] => Some(x),
149         _ => None,
150     }
151 }
152
153 /// Checks if all ancestors of `def_id` are stable, to avoid linting
154 /// [unstable moves](https://github.com/rust-lang/rust/pull/95956)
155 fn is_stable(cx: &LateContext<'_>, mut def_id: DefId) -> bool {
156     loop {
157         if cx
158             .tcx
159             .lookup_stability(def_id)
160             .map_or(false, |stability| stability.is_unstable())
161         {
162             return false;
163         }
164
165         match cx.tcx.opt_parent(def_id) {
166             Some(parent) => def_id = parent,
167             None => return true,
168         }
169     }
170 }