|
1 | 1 | use clippy_config::Conf;
|
2 |
| -use clippy_utils::diagnostics::span_lint_and_sugg; |
| 2 | +use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; |
3 | 3 | use clippy_utils::is_from_proc_macro;
|
4 | 4 | use clippy_utils::msrvs::Msrv;
|
5 | 5 | use rustc_attr_data_structures::{StabilityLevel, StableSince};
|
6 | 6 | use rustc_errors::Applicability;
|
7 |
| -use rustc_hir::def::Res; |
| 7 | +use rustc_hir::def::{DefKind, Res}; |
8 | 8 | use rustc_hir::def_id::DefId;
|
9 | 9 | use rustc_hir::{Block, Body, HirId, Path, PathSegment};
|
10 | 10 | use rustc_lint::{LateContext, LateLintPass, Lint, LintContext};
|
@@ -88,94 +88,137 @@ declare_clippy_lint! {
|
88 | 88 | }
|
89 | 89 |
|
90 | 90 | pub struct StdReexports {
|
91 |
| - lint_point: (Span, Option<LintPoint>), |
| 91 | + lint_points: Option<(Span, Vec<LintPoint>)>, |
92 | 92 | msrv: Msrv,
|
93 | 93 | }
|
94 | 94 |
|
95 | 95 | impl StdReexports {
|
96 | 96 | pub fn new(conf: &'static Conf) -> Self {
|
97 | 97 | Self {
|
98 |
| - lint_point: Default::default(), |
| 98 | + lint_points: Option::default(), |
99 | 99 | msrv: conf.msrv,
|
100 | 100 | }
|
101 | 101 | }
|
102 | 102 |
|
103 |
| - fn lint_if_finish(&mut self, cx: &LateContext<'_>, (span, item): (Span, Option<LintPoint>)) { |
104 |
| - if span.source_equal(self.lint_point.0) { |
105 |
| - return; |
| 103 | + fn lint_if_finish(&mut self, cx: &LateContext<'_>, krate: Span, lint_point: LintPoint) { |
| 104 | + match &mut self.lint_points { |
| 105 | + Some((prev_krate, prev_lints)) if prev_krate.overlaps(krate) => { |
| 106 | + prev_lints.push(lint_point); |
| 107 | + }, |
| 108 | + _ => emit_lints(cx, self.lint_points.replace((krate, vec![lint_point]))), |
106 | 109 | }
|
107 |
| - |
108 |
| - if !self.lint_point.0.is_dummy() { |
109 |
| - emit_lints(cx, &self.lint_point); |
110 |
| - } |
111 |
| - |
112 |
| - self.lint_point = (span, item); |
113 | 110 | }
|
114 | 111 | }
|
115 | 112 |
|
116 | 113 | impl_lint_pass!(StdReexports => [STD_INSTEAD_OF_CORE, STD_INSTEAD_OF_ALLOC, ALLOC_INSTEAD_OF_CORE]);
|
117 | 114 |
|
118 |
| -type LintPoint = (&'static Lint, &'static str, &'static str); |
| 115 | +#[derive(Debug)] |
| 116 | +enum LintPoint { |
| 117 | + Available(Span, &'static Lint, &'static str, &'static str), |
| 118 | + Conflict, |
| 119 | +} |
119 | 120 |
|
120 | 121 | impl<'tcx> LateLintPass<'tcx> for StdReexports {
|
121 | 122 | fn check_path(&mut self, cx: &LateContext<'tcx>, path: &Path<'tcx>, _: HirId) {
|
122 |
| - if let Res::Def(_, def_id) = path.res |
| 123 | + if let Res::Def(def_kind, def_id) = path.res |
123 | 124 | && let Some(first_segment) = get_first_segment(path)
|
124 | 125 | && is_stable(cx, def_id, self.msrv)
|
125 | 126 | && !path.span.in_external_macro(cx.sess().source_map())
|
126 | 127 | && !is_from_proc_macro(cx, &first_segment.ident)
|
| 128 | + && !matches!(def_kind, DefKind::Macro(_)) |
| 129 | + && let Some(last_segment) = path.segments.last() |
127 | 130 | {
|
128 | 131 | let (lint, used_mod, replace_with) = match first_segment.ident.name {
|
129 | 132 | sym::std => match cx.tcx.crate_name(def_id.krate) {
|
130 | 133 | sym::core => (STD_INSTEAD_OF_CORE, "std", "core"),
|
131 | 134 | sym::alloc => (STD_INSTEAD_OF_ALLOC, "std", "alloc"),
|
132 | 135 | _ => {
|
133 |
| - self.lint_if_finish(cx, (first_segment.ident.span, None)); |
| 136 | + self.lint_if_finish(cx, first_segment.ident.span, LintPoint::Conflict); |
134 | 137 | return;
|
135 | 138 | },
|
136 | 139 | },
|
137 | 140 | sym::alloc => {
|
138 | 141 | if cx.tcx.crate_name(def_id.krate) == sym::core {
|
139 | 142 | (ALLOC_INSTEAD_OF_CORE, "alloc", "core")
|
140 | 143 | } else {
|
141 |
| - self.lint_if_finish(cx, (first_segment.ident.span, None)); |
| 144 | + self.lint_if_finish(cx, first_segment.ident.span, LintPoint::Conflict); |
142 | 145 | return;
|
143 | 146 | }
|
144 | 147 | },
|
145 |
| - _ => return, |
| 148 | + _ => { |
| 149 | + self.lint_if_finish(cx, first_segment.ident.span, LintPoint::Conflict); |
| 150 | + return; |
| 151 | + }, |
146 | 152 | };
|
147 | 153 |
|
148 |
| - self.lint_if_finish(cx, (first_segment.ident.span, Some((lint, used_mod, replace_with)))); |
| 154 | + self.lint_if_finish( |
| 155 | + cx, |
| 156 | + first_segment.ident.span, |
| 157 | + LintPoint::Available(last_segment.ident.span, lint, used_mod, replace_with), |
| 158 | + ); |
149 | 159 | }
|
150 | 160 | }
|
151 | 161 |
|
152 | 162 | fn check_block_post(&mut self, cx: &LateContext<'tcx>, _: &Block<'tcx>) {
|
153 |
| - self.lint_if_finish(cx, Default::default()); |
| 163 | + emit_lints(cx, self.lint_points.take()); |
154 | 164 | }
|
155 | 165 |
|
156 | 166 | fn check_body_post(&mut self, cx: &LateContext<'tcx>, _: &Body<'tcx>) {
|
157 |
| - self.lint_if_finish(cx, Default::default()); |
| 167 | + emit_lints(cx, self.lint_points.take()); |
158 | 168 | }
|
159 | 169 |
|
160 | 170 | fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
|
161 |
| - self.lint_if_finish(cx, Default::default()); |
| 171 | + emit_lints(cx, self.lint_points.take()); |
162 | 172 | }
|
163 | 173 | }
|
164 | 174 |
|
165 |
| -fn emit_lints(cx: &LateContext<'_>, (span, item): &(Span, Option<LintPoint>)) { |
166 |
| - let Some((lint, used_mod, replace_with)) = item else { |
| 175 | +fn emit_lints(cx: &LateContext<'_>, lint_points: Option<(Span, Vec<LintPoint>)>) { |
| 176 | + let Some((krate_span, lint_points)) = lint_points else { |
167 | 177 | return;
|
168 | 178 | };
|
169 | 179 |
|
170 |
| - span_lint_and_sugg( |
171 |
| - cx, |
172 |
| - lint, |
173 |
| - *span, |
174 |
| - format!("used import from `{used_mod}` instead of `{replace_with}`"), |
175 |
| - format!("consider importing the item from `{replace_with}`"), |
176 |
| - (*replace_with).to_string(), |
177 |
| - Applicability::MachineApplicable, |
178 |
| - ); |
| 180 | + let mut lint: Option<(&'static Lint, &'static str, &'static str)> = None; |
| 181 | + let mut has_conflict = false; |
| 182 | + for lint_point in &lint_points { |
| 183 | + match lint_point { |
| 184 | + LintPoint::Available(_, l, used_mod, replace_with) |
| 185 | + if lint.is_none_or(|(prev_l, ..)| l.name == prev_l.name) => |
| 186 | + { |
| 187 | + lint = Some((l, used_mod, replace_with)); |
| 188 | + }, |
| 189 | + _ => { |
| 190 | + has_conflict = true; |
| 191 | + break; |
| 192 | + }, |
| 193 | + } |
| 194 | + } |
| 195 | + |
| 196 | + if !has_conflict && let Some((lint, used_mod, replace_with)) = lint { |
| 197 | + span_lint_and_sugg( |
| 198 | + cx, |
| 199 | + lint, |
| 200 | + krate_span, |
| 201 | + format!("used import from `{used_mod}` instead of `{replace_with}`"), |
| 202 | + format!("consider importing the item from `{replace_with}`"), |
| 203 | + (*replace_with).to_string(), |
| 204 | + Applicability::MachineApplicable, |
| 205 | + ); |
| 206 | + return; |
| 207 | + } |
| 208 | + |
| 209 | + for lint_point in lint_points { |
| 210 | + let LintPoint::Available(span, lint, used_mod, replace_with) = lint_point else { |
| 211 | + continue; |
| 212 | + }; |
| 213 | + span_lint_and_help( |
| 214 | + cx, |
| 215 | + lint, |
| 216 | + span, |
| 217 | + format!("used import from `{used_mod}` instead of `{replace_with}`"), |
| 218 | + None, |
| 219 | + format!("consider importing the item from `{replace_with}`"), |
| 220 | + ); |
| 221 | + } |
179 | 222 | }
|
180 | 223 |
|
181 | 224 | /// Returns the first named segment of a [`Path`].
|
|
0 commit comments