]>
Commit | Line | Data |
---|---|---|
dfeec247 XL |
1 | //! Implementation of lint checking. |
2 | //! | |
3 | //! The lint checking is mostly consolidated into one pass which runs | |
4 | //! after all other analyses. Throughout compilation, lint warnings | |
5 | //! can be added via the `add_lint` method on the Session structure. This | |
6 | //! requires a span and an ID of the node that the lint is being added to. The | |
7 | //! lint isn't actually emitted at that time because it is unknown what the | |
8 | //! actual lint level at that location is. | |
9 | //! | |
10 | //! To actually emit lint warnings/errors, a separate pass is used. | |
11 | //! A context keeps track of the current state of all lint levels. | |
12 | //! Upon entering a node of the ast which can modify the lint settings, the | |
13 | //! previous lint state is pushed onto a stack and the ast is then recursed | |
14 | //! upon. As the ast is traversed, this keeps track of the current lint level | |
15 | //! for all lint attributes. | |
16 | ||
17 | use self::TargetLint::*; | |
18 | ||
f2b60f7d FG |
19 | use crate::errors::{ |
20 | CheckNameDeprecated, CheckNameUnknown, CheckNameUnknownTool, CheckNameWarning, RequestedLevel, | |
21 | UnsupportedGroup, | |
22 | }; | |
5099ac24 | 23 | use crate::levels::LintLevelsBuilder; |
dfeec247 | 24 | use crate::passes::{EarlyLintPassObject, LateLintPassObject}; |
5099ac24 | 25 | use rustc_ast::util::unicode::TEXT_FLOW_CONTROL_CHARS; |
dfeec247 XL |
26 | use rustc_data_structures::fx::FxHashMap; |
27 | use rustc_data_structures::sync; | |
2b03887a FG |
28 | use rustc_errors::{add_elided_lifetime_in_path_suggestion, DiagnosticBuilder, DiagnosticMessage}; |
29 | use rustc_errors::{Applicability, DecorateLint, MultiSpan, SuggestionStyle}; | |
dfeec247 | 30 | use rustc_hir as hir; |
f035d41b | 31 | use rustc_hir::def::Res; |
dfeec247 | 32 | use rustc_hir::def_id::{CrateNum, DefId}; |
ba9703b0 | 33 | use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData}; |
2b03887a | 34 | use rustc_middle::middle::privacy::EffectiveVisibilities; |
ba9703b0 | 35 | use rustc_middle::middle::stability; |
c295e0f8 | 36 | use rustc_middle::ty::layout::{LayoutError, LayoutOfHelpers, TyAndLayout}; |
1b1a35ee | 37 | use rustc_middle::ty::print::with_no_trimmed_paths; |
5099ac24 | 38 | use rustc_middle::ty::{self, print::Printer, subst::GenericArg, RegisteredTools, Ty, TyCtxt}; |
064997fb | 39 | use rustc_session::lint::{BuiltinLintDiagnostics, LintExpectationId}; |
dfeec247 XL |
40 | use rustc_session::lint::{FutureIncompatibleInfo, Level, Lint, LintBuffer, LintId}; |
41 | use rustc_session::Session; | |
fc512014 | 42 | use rustc_span::lev_distance::find_best_match_for_name; |
5099ac24 | 43 | use rustc_span::symbol::{sym, Ident, Symbol}; |
f2b60f7d | 44 | use rustc_span::{BytePos, Span}; |
c295e0f8 | 45 | use rustc_target::abi; |
dfeec247 | 46 | |
f035d41b | 47 | use std::cell::Cell; |
cdc7bbd5 | 48 | use std::iter; |
dfeec247 XL |
49 | use std::slice; |
50 | ||
f2b60f7d FG |
51 | type EarlyLintPassFactory = dyn Fn() -> EarlyLintPassObject + sync::Send + sync::Sync; |
52 | type LateLintPassFactory = | |
53 | dyn for<'tcx> Fn(TyCtxt<'tcx>) -> LateLintPassObject<'tcx> + sync::Send + sync::Sync; | |
54 | ||
dfeec247 XL |
55 | /// Information about the registered lints. |
56 | /// | |
57 | /// This is basically the subset of `Context` that we can | |
58 | /// build early in the compile pipeline. | |
59 | pub struct LintStore { | |
60 | /// Registered lints. | |
61 | lints: Vec<&'static Lint>, | |
62 | ||
63 | /// Constructor functions for each variety of lint pass. | |
64 | /// | |
65 | /// These should only be called once, but since we want to avoid locks or | |
66 | /// interior mutability, we don't enforce this (and lints should, in theory, | |
67 | /// be compatible with being constructed more than once, though not | |
68 | /// necessarily in a sane manner. This is safe though.) | |
f2b60f7d FG |
69 | pub pre_expansion_passes: Vec<Box<EarlyLintPassFactory>>, |
70 | pub early_passes: Vec<Box<EarlyLintPassFactory>>, | |
71 | pub late_passes: Vec<Box<LateLintPassFactory>>, | |
dfeec247 | 72 | /// This is unique in that we construct them per-module, so not once. |
f2b60f7d | 73 | pub late_module_passes: Vec<Box<LateLintPassFactory>>, |
dfeec247 XL |
74 | |
75 | /// Lints indexed by name. | |
76 | by_name: FxHashMap<String, TargetLint>, | |
77 | ||
78 | /// Map of registered lint groups to what lints they expand to. | |
79 | lint_groups: FxHashMap<&'static str, LintGroup>, | |
80 | } | |
81 | ||
82 | /// The target of the `by_name` map, which accounts for renaming/deprecation. | |
6a06907d | 83 | #[derive(Debug)] |
dfeec247 XL |
84 | enum TargetLint { |
85 | /// A direct lint target | |
86 | Id(LintId), | |
87 | ||
88 | /// Temporary renaming, used for easing migration pain; see #16545 | |
89 | Renamed(String, LintId), | |
90 | ||
91 | /// Lint with this name existed previously, but has been removed/deprecated. | |
92 | /// The string argument is the reason for removal. | |
93 | Removed(String), | |
6a06907d XL |
94 | |
95 | /// A lint name that should give no warnings and have no effect. | |
96 | /// | |
97 | /// This is used by rustc to avoid warning about old rustdoc lints before rustdoc registers them as tool lints. | |
98 | Ignored, | |
dfeec247 XL |
99 | } |
100 | ||
101 | pub enum FindLintError { | |
102 | NotFound, | |
103 | Removed, | |
104 | } | |
105 | ||
106 | struct LintAlias { | |
107 | name: &'static str, | |
108 | /// Whether deprecation warnings should be suppressed for this alias. | |
109 | silent: bool, | |
110 | } | |
111 | ||
112 | struct LintGroup { | |
113 | lint_ids: Vec<LintId>, | |
114 | from_plugin: bool, | |
115 | depr: Option<LintAlias>, | |
116 | } | |
117 | ||
5e7ed085 | 118 | #[derive(Debug)] |
dfeec247 XL |
119 | pub enum CheckLintNameResult<'a> { |
120 | Ok(&'a [LintId]), | |
121 | /// Lint doesn't exist. Potentially contains a suggestion for a correct lint name. | |
122 | NoLint(Option<Symbol>), | |
136023e0 XL |
123 | /// The lint refers to a tool that has not been registered. |
124 | NoTool, | |
dfeec247 XL |
125 | /// The lint is either renamed or removed. This is the warning |
126 | /// message, and an optional new name (`None` if removed). | |
127 | Warning(String, Option<String>), | |
128 | /// The lint is from a tool. If the Option is None, then either | |
129 | /// the lint does not exist in the tool or the code was not | |
130 | /// compiled with the tool and therefore the lint was never | |
131 | /// added to the `LintStore`. Otherwise the `LintId` will be | |
132 | /// returned as if it where a rustc lint. | |
133 | Tool(Result<&'a [LintId], (Option<&'a [LintId]>, String)>), | |
134 | } | |
135 | ||
136 | impl LintStore { | |
137 | pub fn new() -> LintStore { | |
138 | LintStore { | |
139 | lints: vec![], | |
140 | pre_expansion_passes: vec![], | |
141 | early_passes: vec![], | |
142 | late_passes: vec![], | |
143 | late_module_passes: vec![], | |
144 | by_name: Default::default(), | |
145 | lint_groups: Default::default(), | |
146 | } | |
147 | } | |
148 | ||
149 | pub fn get_lints<'t>(&'t self) -> &'t [&'static Lint] { | |
150 | &self.lints | |
151 | } | |
152 | ||
5099ac24 FG |
153 | pub fn get_lint_groups<'t>( |
154 | &'t self, | |
155 | ) -> impl Iterator<Item = (&'static str, Vec<LintId>, bool)> + 't { | |
156 | // This function is not used in a way which observes the order of lints. | |
5e7ed085 | 157 | #[allow(rustc::potential_query_instability)] |
dfeec247 XL |
158 | self.lint_groups |
159 | .iter() | |
160 | .filter(|(_, LintGroup { depr, .. })| { | |
161 | // Don't display deprecated lint groups. | |
162 | depr.is_none() | |
163 | }) | |
164 | .map(|(k, LintGroup { lint_ids, from_plugin, .. })| { | |
165 | (*k, lint_ids.clone(), *from_plugin) | |
166 | }) | |
dfeec247 XL |
167 | } |
168 | ||
169 | pub fn register_early_pass( | |
170 | &mut self, | |
171 | pass: impl Fn() -> EarlyLintPassObject + 'static + sync::Send + sync::Sync, | |
172 | ) { | |
173 | self.early_passes.push(Box::new(pass)); | |
174 | } | |
175 | ||
5e7ed085 FG |
176 | /// This lint pass is softly deprecated. It misses expanded code and has caused a few |
177 | /// errors in the past. Currently, it is only used in Clippy. New implementations | |
178 | /// should avoid using this interface, as it might be removed in the future. | |
179 | /// | |
180 | /// * See [rust#69838](https://github.com/rust-lang/rust/pull/69838) | |
181 | /// * See [rust-clippy#5518](https://github.com/rust-lang/rust-clippy/pull/5518) | |
dfeec247 XL |
182 | pub fn register_pre_expansion_pass( |
183 | &mut self, | |
184 | pass: impl Fn() -> EarlyLintPassObject + 'static + sync::Send + sync::Sync, | |
185 | ) { | |
186 | self.pre_expansion_passes.push(Box::new(pass)); | |
187 | } | |
188 | ||
189 | pub fn register_late_pass( | |
190 | &mut self, | |
f2b60f7d FG |
191 | pass: impl for<'tcx> Fn(TyCtxt<'tcx>) -> LateLintPassObject<'tcx> |
192 | + 'static | |
193 | + sync::Send | |
194 | + sync::Sync, | |
dfeec247 XL |
195 | ) { |
196 | self.late_passes.push(Box::new(pass)); | |
197 | } | |
198 | ||
199 | pub fn register_late_mod_pass( | |
200 | &mut self, | |
f2b60f7d FG |
201 | pass: impl for<'tcx> Fn(TyCtxt<'tcx>) -> LateLintPassObject<'tcx> |
202 | + 'static | |
203 | + sync::Send | |
204 | + sync::Sync, | |
dfeec247 XL |
205 | ) { |
206 | self.late_module_passes.push(Box::new(pass)); | |
207 | } | |
208 | ||
487cf647 | 209 | /// Helper method for register_early/late_pass |
dfeec247 XL |
210 | pub fn register_lints(&mut self, lints: &[&'static Lint]) { |
211 | for lint in lints { | |
212 | self.lints.push(lint); | |
213 | ||
214 | let id = LintId::of(lint); | |
215 | if self.by_name.insert(lint.name_lower(), Id(id)).is_some() { | |
216 | bug!("duplicate specification of lint {}", lint.name_lower()) | |
217 | } | |
218 | ||
136023e0 XL |
219 | if let Some(FutureIncompatibleInfo { reason, .. }) = lint.future_incompatible { |
220 | if let Some(edition) = reason.edition() { | |
dfeec247 XL |
221 | self.lint_groups |
222 | .entry(edition.lint_name()) | |
223 | .or_insert(LintGroup { | |
224 | lint_ids: vec![], | |
225 | from_plugin: lint.is_plugin, | |
226 | depr: None, | |
227 | }) | |
228 | .lint_ids | |
229 | .push(id); | |
136023e0 XL |
230 | } else { |
231 | // Lints belonging to the `future_incompatible` lint group are lints where a | |
232 | // future version of rustc will cause existing code to stop compiling. | |
233 | // Lints tied to an edition don't count because they are opt-in. | |
234 | self.lint_groups | |
235 | .entry("future_incompatible") | |
236 | .or_insert(LintGroup { | |
237 | lint_ids: vec![], | |
238 | from_plugin: lint.is_plugin, | |
239 | depr: None, | |
240 | }) | |
241 | .lint_ids | |
242 | .push(id); | |
dfeec247 | 243 | } |
dfeec247 XL |
244 | } |
245 | } | |
246 | } | |
247 | ||
248 | pub fn register_group_alias(&mut self, lint_name: &'static str, alias: &'static str) { | |
249 | self.lint_groups.insert( | |
250 | alias, | |
251 | LintGroup { | |
252 | lint_ids: vec![], | |
253 | from_plugin: false, | |
254 | depr: Some(LintAlias { name: lint_name, silent: true }), | |
255 | }, | |
256 | ); | |
257 | } | |
258 | ||
259 | pub fn register_group( | |
260 | &mut self, | |
261 | from_plugin: bool, | |
262 | name: &'static str, | |
263 | deprecated_name: Option<&'static str>, | |
264 | to: Vec<LintId>, | |
265 | ) { | |
266 | let new = self | |
267 | .lint_groups | |
268 | .insert(name, LintGroup { lint_ids: to, from_plugin, depr: None }) | |
269 | .is_none(); | |
270 | if let Some(deprecated) = deprecated_name { | |
271 | self.lint_groups.insert( | |
272 | deprecated, | |
273 | LintGroup { | |
274 | lint_ids: vec![], | |
275 | from_plugin, | |
276 | depr: Some(LintAlias { name, silent: false }), | |
277 | }, | |
278 | ); | |
279 | } | |
280 | ||
281 | if !new { | |
282 | bug!("duplicate specification of lint group {}", name); | |
283 | } | |
284 | } | |
285 | ||
6a06907d XL |
286 | /// This lint should give no warning and have no effect. |
287 | /// | |
288 | /// This is used by rustc to avoid warning about old rustdoc lints before rustdoc registers them as tool lints. | |
289 | #[track_caller] | |
290 | pub fn register_ignored(&mut self, name: &str) { | |
291 | if self.by_name.insert(name.to_string(), Ignored).is_some() { | |
292 | bug!("duplicate specification of lint {}", name); | |
293 | } | |
294 | } | |
295 | ||
296 | /// This lint has been renamed; warn about using the new name and apply the lint. | |
5869c6ff | 297 | #[track_caller] |
dfeec247 | 298 | pub fn register_renamed(&mut self, old_name: &str, new_name: &str) { |
5e7ed085 FG |
299 | let Some(&Id(target)) = self.by_name.get(new_name) else { |
300 | bug!("invalid lint renaming of {} to {}", old_name, new_name); | |
dfeec247 XL |
301 | }; |
302 | self.by_name.insert(old_name.to_string(), Renamed(new_name.to_string(), target)); | |
303 | } | |
304 | ||
305 | pub fn register_removed(&mut self, name: &str, reason: &str) { | |
306 | self.by_name.insert(name.into(), Removed(reason.into())); | |
307 | } | |
308 | ||
309 | pub fn find_lints(&self, mut lint_name: &str) -> Result<Vec<LintId>, FindLintError> { | |
310 | match self.by_name.get(lint_name) { | |
311 | Some(&Id(lint_id)) => Ok(vec![lint_id]), | |
312 | Some(&Renamed(_, lint_id)) => Ok(vec![lint_id]), | |
313 | Some(&Removed(_)) => Err(FindLintError::Removed), | |
6a06907d | 314 | Some(&Ignored) => Ok(vec![]), |
dfeec247 XL |
315 | None => loop { |
316 | return match self.lint_groups.get(lint_name) { | |
317 | Some(LintGroup { lint_ids, depr, .. }) => { | |
318 | if let Some(LintAlias { name, .. }) = depr { | |
319 | lint_name = name; | |
320 | continue; | |
321 | } | |
322 | Ok(lint_ids.clone()) | |
323 | } | |
324 | None => Err(FindLintError::Removed), | |
325 | }; | |
326 | }, | |
327 | } | |
328 | } | |
329 | ||
136023e0 | 330 | /// Checks the validity of lint names derived from the command line. |
17df50a5 XL |
331 | pub fn check_lint_name_cmdline( |
332 | &self, | |
333 | sess: &Session, | |
334 | lint_name: &str, | |
136023e0 | 335 | level: Level, |
5099ac24 | 336 | registered_tools: &RegisteredTools, |
136023e0 XL |
337 | ) { |
338 | let (tool_name, lint_name_only) = parse_lint_and_tool_name(lint_name); | |
923072b8 | 339 | if lint_name_only == crate::WARNINGS.name_lower() && matches!(level, Level::ForceWarn(_)) { |
f2b60f7d | 340 | sess.emit_err(UnsupportedGroup { lint_group: crate::WARNINGS.name_lower() }); |
5e7ed085 | 341 | return; |
94222f64 | 342 | } |
f2b60f7d FG |
343 | let lint_name = lint_name.to_string(); |
344 | match self.check_lint_name(lint_name_only, tool_name, registered_tools) { | |
345 | CheckLintNameResult::Warning(msg, _) => { | |
346 | sess.emit_warning(CheckNameWarning { | |
347 | msg, | |
348 | sub: RequestedLevel { level, lint_name }, | |
349 | }); | |
350 | } | |
dfeec247 | 351 | CheckLintNameResult::NoLint(suggestion) => { |
f2b60f7d FG |
352 | sess.emit_err(CheckNameUnknown { |
353 | lint_name: lint_name.clone(), | |
354 | suggestion, | |
355 | sub: RequestedLevel { level, lint_name }, | |
356 | }); | |
357 | } | |
f25598a0 FG |
358 | CheckLintNameResult::Tool(Err((Some(_), new_name))) => { |
359 | sess.emit_warning(CheckNameDeprecated { | |
360 | lint_name: lint_name.clone(), | |
361 | new_name, | |
362 | sub: RequestedLevel { level, lint_name }, | |
363 | }); | |
dfeec247 | 364 | } |
f2b60f7d FG |
365 | CheckLintNameResult::NoTool => { |
366 | sess.emit_err(CheckNameUnknownTool { | |
367 | tool_name: tool_name.unwrap(), | |
368 | sub: RequestedLevel { level, lint_name }, | |
369 | }); | |
370 | } | |
371 | _ => {} | |
dfeec247 | 372 | }; |
dfeec247 XL |
373 | } |
374 | ||
fc512014 XL |
375 | /// True if this symbol represents a lint group name. |
376 | pub fn is_lint_group(&self, lint_name: Symbol) -> bool { | |
377 | debug!( | |
378 | "is_lint_group(lint_name={:?}, lint_groups={:?})", | |
379 | lint_name, | |
380 | self.lint_groups.keys().collect::<Vec<_>>() | |
381 | ); | |
a2a8927a XL |
382 | let lint_name_str = lint_name.as_str(); |
383 | self.lint_groups.contains_key(lint_name_str) || { | |
fc512014 | 384 | let warnings_name_str = crate::WARNINGS.name_lower(); |
a2a8927a | 385 | lint_name_str == warnings_name_str |
fc512014 XL |
386 | } |
387 | } | |
388 | ||
dfeec247 XL |
389 | /// Checks the name of a lint for its existence, and whether it was |
390 | /// renamed or removed. Generates a DiagnosticBuilder containing a | |
391 | /// warning for renamed and removed lints. This is over both lint | |
392 | /// names from attributes and those passed on the command line. Since | |
393 | /// it emits non-fatal warnings and there are *two* lint passes that | |
394 | /// inspect attributes, this is only run from the late pass to avoid | |
395 | /// printing duplicate warnings. | |
396 | pub fn check_lint_name( | |
397 | &self, | |
398 | lint_name: &str, | |
399 | tool_name: Option<Symbol>, | |
5099ac24 | 400 | registered_tools: &RegisteredTools, |
dfeec247 | 401 | ) -> CheckLintNameResult<'_> { |
136023e0 | 402 | if let Some(tool_name) = tool_name { |
5099ac24 FG |
403 | // FIXME: rustc and rustdoc are considered tools for lints, but not for attributes. |
404 | if tool_name != sym::rustc | |
405 | && tool_name != sym::rustdoc | |
406 | && !registered_tools.contains(&Ident::with_dummy_span(tool_name)) | |
407 | { | |
136023e0 XL |
408 | return CheckLintNameResult::NoTool; |
409 | } | |
410 | } | |
411 | ||
dfeec247 XL |
412 | let complete_name = if let Some(tool_name) = tool_name { |
413 | format!("{}::{}", tool_name, lint_name) | |
414 | } else { | |
415 | lint_name.to_string() | |
416 | }; | |
417 | // If the lint was scoped with `tool::` check if the tool lint exists | |
5869c6ff | 418 | if let Some(tool_name) = tool_name { |
dfeec247 XL |
419 | match self.by_name.get(&complete_name) { |
420 | None => match self.lint_groups.get(&*complete_name) { | |
5869c6ff XL |
421 | // If the lint isn't registered, there are two possibilities: |
422 | None => { | |
423 | // 1. The tool is currently running, so this lint really doesn't exist. | |
424 | // FIXME: should this handle tools that never register a lint, like rustfmt? | |
f2b60f7d | 425 | debug!("lints={:?}", self.by_name.keys().collect::<Vec<_>>()); |
5869c6ff XL |
426 | let tool_prefix = format!("{}::", tool_name); |
427 | return if self.by_name.keys().any(|lint| lint.starts_with(&tool_prefix)) { | |
428 | self.no_lint_suggestion(&complete_name) | |
429 | } else { | |
430 | // 2. The tool isn't currently running, so no lints will be registered. | |
431 | // To avoid giving a false positive, ignore all unknown lints. | |
432 | CheckLintNameResult::Tool(Err((None, String::new()))) | |
433 | }; | |
434 | } | |
dfeec247 XL |
435 | Some(LintGroup { lint_ids, .. }) => { |
436 | return CheckLintNameResult::Tool(Ok(&lint_ids)); | |
437 | } | |
438 | }, | |
f25598a0 | 439 | Some(Id(id)) => return CheckLintNameResult::Tool(Ok(slice::from_ref(id))), |
dfeec247 XL |
440 | // If the lint was registered as removed or renamed by the lint tool, we don't need |
441 | // to treat tool_lints and rustc lints different and can use the code below. | |
442 | _ => {} | |
443 | } | |
444 | } | |
445 | match self.by_name.get(&complete_name) { | |
f25598a0 | 446 | Some(Renamed(new_name, _)) => CheckLintNameResult::Warning( |
dfeec247 XL |
447 | format!("lint `{}` has been renamed to `{}`", complete_name, new_name), |
448 | Some(new_name.to_owned()), | |
449 | ), | |
f25598a0 | 450 | Some(Removed(reason)) => CheckLintNameResult::Warning( |
5869c6ff | 451 | format!("lint `{}` has been removed: {}", complete_name, reason), |
dfeec247 XL |
452 | None, |
453 | ), | |
454 | None => match self.lint_groups.get(&*complete_name) { | |
455 | // If neither the lint, nor the lint group exists check if there is a `clippy::` | |
456 | // variant of this lint | |
457 | None => self.check_tool_name_for_backwards_compat(&complete_name, "clippy"), | |
458 | Some(LintGroup { lint_ids, depr, .. }) => { | |
459 | // Check if the lint group name is deprecated | |
460 | if let Some(LintAlias { name, silent }) = depr { | |
461 | let LintGroup { lint_ids, .. } = self.lint_groups.get(name).unwrap(); | |
462 | return if *silent { | |
463 | CheckLintNameResult::Ok(&lint_ids) | |
464 | } else { | |
74b04a01 | 465 | CheckLintNameResult::Tool(Err((Some(&lint_ids), (*name).to_string()))) |
dfeec247 XL |
466 | }; |
467 | } | |
468 | CheckLintNameResult::Ok(&lint_ids) | |
469 | } | |
470 | }, | |
f25598a0 | 471 | Some(Id(id)) => CheckLintNameResult::Ok(slice::from_ref(id)), |
6a06907d | 472 | Some(&Ignored) => CheckLintNameResult::Ok(&[]), |
dfeec247 XL |
473 | } |
474 | } | |
475 | ||
5869c6ff XL |
476 | fn no_lint_suggestion(&self, lint_name: &str) -> CheckLintNameResult<'_> { |
477 | let name_lower = lint_name.to_lowercase(); | |
5869c6ff XL |
478 | |
479 | if lint_name.chars().any(char::is_uppercase) && self.find_lints(&name_lower).is_ok() { | |
480 | // First check if the lint name is (partly) in upper case instead of lower case... | |
136023e0 | 481 | return CheckLintNameResult::NoLint(Some(Symbol::intern(&name_lower))); |
5869c6ff | 482 | } |
136023e0 | 483 | // ...if not, search for lints with a similar name |
f25598a0 FG |
484 | // Note: find_best_match_for_name depends on the sort order of its input vector. |
485 | // To ensure deterministic output, sort elements of the lint_groups hash map. | |
486 | // Also, never suggest deprecated lint groups. | |
487 | let mut groups: Vec<_> = self | |
488 | .lint_groups | |
489 | .iter() | |
490 | .filter_map(|(k, LintGroup { depr, .. })| if depr.is_none() { Some(k) } else { None }) | |
491 | .collect(); | |
492 | groups.sort(); | |
493 | let groups = groups.iter().map(|k| Symbol::intern(k)); | |
136023e0 XL |
494 | let lints = self.lints.iter().map(|l| Symbol::intern(&l.name_lower())); |
495 | let names: Vec<Symbol> = groups.chain(lints).collect(); | |
496 | let suggestion = find_best_match_for_name(&names, Symbol::intern(&name_lower), None); | |
497 | CheckLintNameResult::NoLint(suggestion) | |
5869c6ff XL |
498 | } |
499 | ||
dfeec247 XL |
500 | fn check_tool_name_for_backwards_compat( |
501 | &self, | |
502 | lint_name: &str, | |
503 | tool_name: &str, | |
504 | ) -> CheckLintNameResult<'_> { | |
505 | let complete_name = format!("{}::{}", tool_name, lint_name); | |
506 | match self.by_name.get(&complete_name) { | |
507 | None => match self.lint_groups.get(&*complete_name) { | |
508 | // Now we are sure, that this lint exists nowhere | |
5869c6ff | 509 | None => self.no_lint_suggestion(lint_name), |
dfeec247 XL |
510 | Some(LintGroup { lint_ids, depr, .. }) => { |
511 | // Reaching this would be weird, but let's cover this case anyway | |
512 | if let Some(LintAlias { name, silent }) = depr { | |
513 | let LintGroup { lint_ids, .. } = self.lint_groups.get(name).unwrap(); | |
514 | return if *silent { | |
515 | CheckLintNameResult::Tool(Err((Some(&lint_ids), complete_name))) | |
516 | } else { | |
74b04a01 | 517 | CheckLintNameResult::Tool(Err((Some(&lint_ids), (*name).to_string()))) |
dfeec247 XL |
518 | }; |
519 | } | |
520 | CheckLintNameResult::Tool(Err((Some(&lint_ids), complete_name))) | |
521 | } | |
522 | }, | |
f25598a0 | 523 | Some(Id(id)) => { |
dfeec247 XL |
524 | CheckLintNameResult::Tool(Err((Some(slice::from_ref(id)), complete_name))) |
525 | } | |
6a06907d | 526 | Some(other) => { |
f2b60f7d | 527 | debug!("got renamed lint {:?}", other); |
6a06907d XL |
528 | CheckLintNameResult::NoLint(None) |
529 | } | |
dfeec247 XL |
530 | } |
531 | } | |
532 | } | |
533 | ||
5099ac24 | 534 | /// Context for lint checking outside of type inference. |
f035d41b | 535 | pub struct LateContext<'tcx> { |
dfeec247 XL |
536 | /// Type context we're checking in. |
537 | pub tcx: TyCtxt<'tcx>, | |
538 | ||
f035d41b XL |
539 | /// Current body, or `None` if outside a body. |
540 | pub enclosing_body: Option<hir::BodyId>, | |
541 | ||
3dfed10e XL |
542 | /// Type-checking results for the current body. Access using the `typeck_results` |
543 | /// and `maybe_typeck_results` methods, which handle querying the typeck results on demand. | |
f035d41b XL |
544 | // FIXME(eddyb) move all the code accessing internal fields like this, |
545 | // to this module, to avoid exposing it to lint logic. | |
3dfed10e | 546 | pub(super) cached_typeck_results: Cell<Option<&'tcx ty::TypeckResults<'tcx>>>, |
dfeec247 XL |
547 | |
548 | /// Parameter environment for the item we are in. | |
549 | pub param_env: ty::ParamEnv<'tcx>, | |
550 | ||
551 | /// Items accessible from the crate being checked. | |
2b03887a | 552 | pub effective_visibilities: &'tcx EffectiveVisibilities, |
dfeec247 XL |
553 | |
554 | /// The store of registered lints and the lint levels. | |
555 | pub lint_store: &'tcx LintStore, | |
556 | ||
557 | pub last_node_with_lint_attrs: hir::HirId, | |
558 | ||
559 | /// Generic type parameters in scope for the item we are in. | |
560 | pub generics: Option<&'tcx hir::Generics<'tcx>>, | |
561 | ||
562 | /// We are only looking at one module | |
563 | pub only_module: bool, | |
564 | } | |
565 | ||
5099ac24 | 566 | /// Context for lint checking of the AST, after expansion, before lowering to HIR. |
dfeec247 | 567 | pub struct EarlyContext<'a> { |
2b03887a | 568 | pub builder: LintLevelsBuilder<'a, crate::levels::TopDown>, |
dfeec247 XL |
569 | pub buffered: LintBuffer, |
570 | } | |
571 | ||
572 | pub trait LintPassObject: Sized {} | |
573 | ||
574 | impl LintPassObject for EarlyLintPassObject {} | |
575 | ||
f2b60f7d | 576 | impl LintPassObject for LateLintPassObject<'_> {} |
dfeec247 XL |
577 | |
578 | pub trait LintContext: Sized { | |
579 | type PassObject: LintPassObject; | |
580 | ||
581 | fn sess(&self) -> &Session; | |
582 | fn lints(&self) -> &LintStore; | |
583 | ||
2b03887a FG |
584 | /// Emit a lint at the appropriate level, with an optional associated span and an existing diagnostic. |
585 | /// | |
586 | /// Return value of the `decorate` closure is ignored, see [`struct_lint_level`] for a detailed explanation. | |
587 | /// | |
588 | /// [`struct_lint_level`]: rustc_middle::lint::struct_lint_level#decorate-signature | |
487cf647 | 589 | #[rustc_lint_diagnostics] |
74b04a01 | 590 | fn lookup_with_diagnostics( |
dfeec247 XL |
591 | &self, |
592 | lint: &'static Lint, | |
74b04a01 | 593 | span: Option<impl Into<MultiSpan>>, |
2b03887a FG |
594 | msg: impl Into<DiagnosticMessage>, |
595 | decorate: impl for<'a, 'b> FnOnce( | |
596 | &'b mut DiagnosticBuilder<'a, ()>, | |
597 | ) -> &'b mut DiagnosticBuilder<'a, ()>, | |
dfeec247 XL |
598 | diagnostic: BuiltinLintDiagnostics, |
599 | ) { | |
2b03887a FG |
600 | // We first generate a blank diagnostic. |
601 | self.lookup(lint, span, msg,|db| { | |
74b04a01 XL |
602 | // Now, set up surrounding context. |
603 | let sess = self.sess(); | |
604 | match diagnostic { | |
c295e0f8 XL |
605 | BuiltinLintDiagnostics::UnicodeTextFlow(span, content) => { |
606 | let spans: Vec<_> = content | |
607 | .char_indices() | |
608 | .filter_map(|(i, c)| { | |
3c0e092e | 609 | TEXT_FLOW_CONTROL_CHARS.contains(&c).then(|| { |
c295e0f8 XL |
610 | let lo = span.lo() + BytePos(2 + i as u32); |
611 | (c, span.with_lo(lo).with_hi(lo + BytePos(c.len_utf8() as u32))) | |
612 | }) | |
613 | }) | |
614 | .collect(); | |
615 | let (an, s) = match spans.len() { | |
616 | 1 => ("an ", ""), | |
617 | _ => ("", "s"), | |
618 | }; | |
619 | db.span_label(span, &format!( | |
620 | "this comment contains {}invisible unicode text flow control codepoint{}", | |
621 | an, | |
622 | s, | |
623 | )); | |
624 | for (c, span) in &spans { | |
625 | db.span_label(*span, format!("{:?}", c)); | |
626 | } | |
627 | db.note( | |
628 | "these kind of unicode codepoints change the way text flows on \ | |
629 | applications that support them, but can cause confusion because they \ | |
630 | change the order of characters on the screen", | |
631 | ); | |
632 | if !spans.is_empty() { | |
633 | db.multipart_suggestion_with_style( | |
634 | "if their presence wasn't intentional, you can remove them", | |
635 | spans.into_iter().map(|(_, span)| (span, "".to_string())).collect(), | |
636 | Applicability::MachineApplicable, | |
637 | SuggestionStyle::HideCodeAlways, | |
638 | ); | |
639 | } | |
640 | }, | |
74b04a01 | 641 | BuiltinLintDiagnostics::Normal => (), |
74b04a01 XL |
642 | BuiltinLintDiagnostics::AbsPathWithModule(span) => { |
643 | let (sugg, app) = match sess.source_map().span_to_snippet(span) { | |
644 | Ok(ref s) => { | |
645 | // FIXME(Manishearth) ideally the emitting code | |
646 | // can tell us whether or not this is global | |
647 | let opt_colon = | |
648 | if s.trim_start().starts_with("::") { "" } else { "::" }; | |
649 | ||
650 | (format!("crate{}{}", opt_colon, s), Applicability::MachineApplicable) | |
651 | } | |
652 | Err(_) => ("crate::<path>".to_string(), Applicability::HasPlaceholders), | |
653 | }; | |
654 | db.span_suggestion(span, "use `crate`", sugg, app); | |
655 | } | |
656 | BuiltinLintDiagnostics::ProcMacroDeriveResolutionFallback(span) => { | |
657 | db.span_label( | |
658 | span, | |
659 | "names from parent modules are not accessible without an explicit import", | |
660 | ); | |
661 | } | |
662 | BuiltinLintDiagnostics::MacroExpandedMacroExportsAccessedByAbsolutePaths( | |
663 | span_def, | |
664 | ) => { | |
665 | db.span_note(span_def, "the macro is defined here"); | |
666 | } | |
04454e1e FG |
667 | BuiltinLintDiagnostics::ElidedLifetimesInPaths( |
668 | n, | |
669 | path_span, | |
670 | incl_angl_brckt, | |
671 | insertion_span, | |
672 | ) => { | |
673 | add_elided_lifetime_in_path_suggestion( | |
674 | sess.source_map(), | |
2b03887a | 675 | db, |
04454e1e FG |
676 | n, |
677 | path_span, | |
678 | incl_angl_brckt, | |
679 | insertion_span, | |
680 | ); | |
681 | } | |
74b04a01 XL |
682 | BuiltinLintDiagnostics::UnknownCrateTypes(span, note, sugg) => { |
683 | db.span_suggestion(span, ¬e, sugg, Applicability::MaybeIncorrect); | |
684 | } | |
a2a8927a | 685 | BuiltinLintDiagnostics::UnusedImports(message, replaces, in_test_module) => { |
74b04a01 XL |
686 | if !replaces.is_empty() { |
687 | db.tool_only_multipart_suggestion( | |
688 | &message, | |
689 | replaces, | |
690 | Applicability::MachineApplicable, | |
691 | ); | |
692 | } | |
a2a8927a XL |
693 | |
694 | if let Some(span) = in_test_module { | |
a2a8927a | 695 | db.span_help( |
064997fb | 696 | self.sess().source_map().guess_head_span(span), |
a2a8927a XL |
697 | "consider adding a `#[cfg(test)]` to the containing module", |
698 | ); | |
699 | } | |
74b04a01 XL |
700 | } |
701 | BuiltinLintDiagnostics::RedundantImport(spans, ident) => { | |
702 | for (span, is_imported) in spans { | |
703 | let introduced = if is_imported { "imported" } else { "defined" }; | |
704 | db.span_label( | |
705 | span, | |
706 | format!("the item `{}` is already {} here", ident, introduced), | |
707 | ); | |
708 | } | |
709 | } | |
710 | BuiltinLintDiagnostics::DeprecatedMacro(suggestion, span) => { | |
2b03887a | 711 | stability::deprecation_suggestion(db, "macro", suggestion, span) |
74b04a01 XL |
712 | } |
713 | BuiltinLintDiagnostics::UnusedDocComment(span) => { | |
f9f354fc | 714 | db.span_label(span, "rustdoc does not generate documentation for macro invocations"); |
74b04a01 XL |
715 | db.help("to document an item produced by a macro, \ |
716 | the macro must produce the documentation as part of its expansion"); | |
dfeec247 | 717 | } |
5869c6ff | 718 | BuiltinLintDiagnostics::PatternsInFnsWithoutBody(span, ident) => { |
923072b8 | 719 | db.span_suggestion(span, "remove `mut` from the parameter", ident, Applicability::MachineApplicable); |
5869c6ff XL |
720 | } |
721 | BuiltinLintDiagnostics::MissingAbi(span, default_abi) => { | |
722 | db.span_label(span, "ABI should be specified here"); | |
723 | db.help(&format!("the default ABI is {}", default_abi.name())); | |
724 | } | |
6a06907d XL |
725 | BuiltinLintDiagnostics::LegacyDeriveHelpers(span) => { |
726 | db.span_label(span, "the attribute is introduced here"); | |
727 | } | |
6a06907d XL |
728 | BuiltinLintDiagnostics::ProcMacroBackCompat(note) => { |
729 | db.note(¬e); | |
730 | } | |
cdc7bbd5 XL |
731 | BuiltinLintDiagnostics::OrPatternsBackCompat(span,suggestion) => { |
732 | db.span_suggestion(span, "use pat_param to preserve semantics", suggestion, Applicability::MachineApplicable); | |
733 | } | |
136023e0 XL |
734 | BuiltinLintDiagnostics::ReservedPrefix(span) => { |
735 | db.span_label(span, "unknown prefix"); | |
736 | db.span_suggestion_verbose( | |
737 | span.shrink_to_hi(), | |
738 | "insert whitespace here to avoid this being parsed as a prefix in Rust 2021", | |
04454e1e | 739 | " ", |
136023e0 XL |
740 | Applicability::MachineApplicable, |
741 | ); | |
742 | } | |
94222f64 XL |
743 | BuiltinLintDiagnostics::UnusedBuiltinAttribute { |
744 | attr_name, | |
745 | macro_name, | |
746 | invoc_span | |
747 | } => { | |
748 | db.span_note( | |
749 | invoc_span, | |
750 | &format!("the built-in attribute `{attr_name}` will be ignored, since it's applied to the macro invocation `{macro_name}`") | |
751 | ); | |
752 | } | |
753 | BuiltinLintDiagnostics::TrailingMacro(is_trailing, name) => { | |
754 | if is_trailing { | |
755 | db.note("macro invocations at the end of a block are treated as expressions"); | |
756 | db.note(&format!("to ignore the value produced by the macro, add a semicolon after the invocation of `{name}`")); | |
757 | } | |
758 | } | |
759 | BuiltinLintDiagnostics::BreakWithLabelAndLoop(span) => { | |
760 | db.multipart_suggestion( | |
761 | "wrap this expression in parentheses", | |
762 | vec![(span.shrink_to_lo(), "(".to_string()), | |
763 | (span.shrink_to_hi(), ")".to_string())], | |
764 | Applicability::MachineApplicable | |
765 | ); | |
766 | } | |
767 | BuiltinLintDiagnostics::NamedAsmLabel(help) => { | |
768 | db.help(&help); | |
a2a8927a | 769 | db.note("see the asm section of Rust By Example <https://doc.rust-lang.org/nightly/rust-by-example/unsafe/asm.html#labels> for more information"); |
5e7ed085 FG |
770 | }, |
771 | BuiltinLintDiagnostics::UnexpectedCfg((name, name_span), None) => { | |
772 | let Some(names_valid) = &sess.parse_sess.check_config.names_valid else { | |
773 | bug!("it shouldn't be possible to have a diagnostic on a name if name checking is not enabled"); | |
774 | }; | |
775 | let possibilities: Vec<Symbol> = names_valid.iter().map(|s| *s).collect(); | |
776 | ||
777 | // Suggest the most probable if we found one | |
778 | if let Some(best_match) = find_best_match_for_name(&possibilities, name, None) { | |
923072b8 | 779 | db.span_suggestion(name_span, "did you mean", best_match, Applicability::MaybeIncorrect); |
5e7ed085 FG |
780 | } |
781 | }, | |
782 | BuiltinLintDiagnostics::UnexpectedCfg((name, name_span), Some((value, value_span))) => { | |
783 | let Some(values) = &sess.parse_sess.check_config.values_valid.get(&name) else { | |
784 | bug!("it shouldn't be possible to have a diagnostic on a value whose name is not in values"); | |
785 | }; | |
786 | let possibilities: Vec<Symbol> = values.iter().map(|&s| s).collect(); | |
787 | ||
788 | // Show the full list if all possible values for a given name, but don't do it | |
789 | // for names as the possibilities could be very long | |
790 | if !possibilities.is_empty() { | |
791 | { | |
792 | let mut possibilities = possibilities.iter().map(Symbol::as_str).collect::<Vec<_>>(); | |
793 | possibilities.sort(); | |
794 | ||
795 | let possibilities = possibilities.join(", "); | |
796 | db.note(&format!("expected values for `{name}` are: {possibilities}")); | |
797 | } | |
798 | ||
799 | // Suggest the most probable if we found one | |
800 | if let Some(best_match) = find_best_match_for_name(&possibilities, value, None) { | |
801 | db.span_suggestion(value_span, "did you mean", format!("\"{best_match}\""), Applicability::MaybeIncorrect); | |
802 | } | |
803 | } else { | |
804 | db.note(&format!("no expected value for `{name}`")); | |
805 | if name != sym::feature { | |
923072b8 | 806 | db.span_suggestion(name_span.shrink_to_hi().to(value_span), "remove the value", "", Applicability::MaybeIncorrect); |
5e7ed085 FG |
807 | } |
808 | } | |
809 | }, | |
810 | BuiltinLintDiagnostics::DeprecatedWhereclauseLocation(new_span, suggestion) => { | |
811 | db.multipart_suggestion( | |
812 | "move it to the end of the type declaration", | |
813 | vec![(db.span.primary_span().unwrap(), "".to_string()), (new_span, suggestion)], | |
814 | Applicability::MachineApplicable, | |
815 | ); | |
816 | db.note( | |
817 | "see issue #89122 <https://github.com/rust-lang/rust/issues/89122> for more information", | |
818 | ); | |
819 | }, | |
923072b8 FG |
820 | BuiltinLintDiagnostics::SingleUseLifetime { |
821 | param_span, | |
822 | use_span: Some((use_span, elide)), | |
823 | deletion_span, | |
824 | } => { | |
825 | debug!(?param_span, ?use_span, ?deletion_span); | |
826 | db.span_label(param_span, "this lifetime..."); | |
827 | db.span_label(use_span, "...is used only here"); | |
f25598a0 FG |
828 | if let Some(deletion_span) = deletion_span { |
829 | let msg = "elide the single-use lifetime"; | |
830 | let (use_span, replace_lt) = if elide { | |
831 | let use_span = sess.source_map().span_extend_while( | |
832 | use_span, | |
833 | char::is_whitespace, | |
834 | ).unwrap_or(use_span); | |
835 | (use_span, String::new()) | |
836 | } else { | |
837 | (use_span, "'_".to_owned()) | |
838 | }; | |
839 | debug!(?deletion_span, ?use_span); | |
840 | db.multipart_suggestion( | |
841 | msg, | |
842 | vec![(deletion_span, String::new()), (use_span, replace_lt)], | |
843 | Applicability::MachineApplicable, | |
844 | ); | |
845 | } | |
923072b8 FG |
846 | }, |
847 | BuiltinLintDiagnostics::SingleUseLifetime { | |
848 | param_span: _, | |
849 | use_span: None, | |
850 | deletion_span, | |
851 | } => { | |
852 | debug!(?deletion_span); | |
f25598a0 FG |
853 | if let Some(deletion_span) = deletion_span { |
854 | db.span_suggestion( | |
855 | deletion_span, | |
856 | "elide the unused lifetime", | |
857 | "", | |
858 | Applicability::MachineApplicable, | |
859 | ); | |
860 | } | |
923072b8 | 861 | }, |
064997fb FG |
862 | BuiltinLintDiagnostics::NamedArgumentUsedPositionally{ position_sp_to_replace, position_sp_for_msg, named_arg_sp, named_arg_name, is_formatting_arg} => { |
863 | db.span_label(named_arg_sp, "this named argument is referred to by position in formatting string"); | |
864 | if let Some(positional_arg_for_msg) = position_sp_for_msg { | |
865 | let msg = format!("this formatting argument uses named argument `{}` by position", named_arg_name); | |
866 | db.span_label(positional_arg_for_msg, msg); | |
867 | } | |
868 | ||
869 | if let Some(positional_arg_to_replace) = position_sp_to_replace { | |
870 | let name = if is_formatting_arg { named_arg_name + "$" } else { named_arg_name }; | |
f2b60f7d FG |
871 | let span_to_replace = if let Ok(positional_arg_content) = |
872 | self.sess().source_map().span_to_snippet(positional_arg_to_replace) && positional_arg_content.starts_with(':') { | |
873 | positional_arg_to_replace.shrink_to_lo() | |
874 | } else { | |
875 | positional_arg_to_replace | |
876 | }; | |
064997fb | 877 | db.span_suggestion_verbose( |
f2b60f7d | 878 | span_to_replace, |
064997fb FG |
879 | "use the named argument by name to avoid ambiguity", |
880 | name, | |
881 | Applicability::MaybeIncorrect, | |
882 | ); | |
883 | } | |
884 | } | |
dfeec247 | 885 | } |
74b04a01 | 886 | // Rewrap `db`, and pass control to the user. |
2b03887a | 887 | decorate(db) |
74b04a01 | 888 | }); |
dfeec247 XL |
889 | } |
890 | ||
74b04a01 XL |
891 | // FIXME: These methods should not take an Into<MultiSpan> -- instead, callers should need to |
892 | // set the span in their `decorate` function (preferably using set_span). | |
2b03887a FG |
893 | /// Emit a lint at the appropriate level, with an optional associated span. |
894 | /// | |
895 | /// Return value of the `decorate` closure is ignored, see [`struct_lint_level`] for a detailed explanation. | |
896 | /// | |
897 | /// [`struct_lint_level`]: rustc_middle::lint::struct_lint_level#decorate-signature | |
487cf647 | 898 | #[rustc_lint_diagnostics] |
dfeec247 XL |
899 | fn lookup<S: Into<MultiSpan>>( |
900 | &self, | |
901 | lint: &'static Lint, | |
902 | span: Option<S>, | |
2b03887a FG |
903 | msg: impl Into<DiagnosticMessage>, |
904 | decorate: impl for<'a, 'b> FnOnce( | |
905 | &'b mut DiagnosticBuilder<'a, ()>, | |
906 | ) -> &'b mut DiagnosticBuilder<'a, ()>, | |
74b04a01 | 907 | ); |
dfeec247 | 908 | |
064997fb FG |
909 | /// Emit a lint at `span` from a lint struct (some type that implements `DecorateLint`, |
910 | /// typically generated by `#[derive(LintDiagnostic)]`). | |
911 | fn emit_spanned_lint<S: Into<MultiSpan>>( | |
912 | &self, | |
913 | lint: &'static Lint, | |
914 | span: S, | |
915 | decorator: impl for<'a> DecorateLint<'a, ()>, | |
916 | ) { | |
2b03887a | 917 | self.lookup(lint, Some(span), decorator.msg(), |diag| decorator.decorate_lint(diag)); |
064997fb FG |
918 | } |
919 | ||
2b03887a FG |
920 | /// Emit a lint at the appropriate level, with an associated span. |
921 | /// | |
922 | /// Return value of the `decorate` closure is ignored, see [`struct_lint_level`] for a detailed explanation. | |
923 | /// | |
924 | /// [`struct_lint_level`]: rustc_middle::lint::struct_lint_level#decorate-signature | |
487cf647 | 925 | #[rustc_lint_diagnostics] |
dfeec247 XL |
926 | fn struct_span_lint<S: Into<MultiSpan>>( |
927 | &self, | |
928 | lint: &'static Lint, | |
929 | span: S, | |
2b03887a FG |
930 | msg: impl Into<DiagnosticMessage>, |
931 | decorate: impl for<'a, 'b> FnOnce( | |
932 | &'b mut DiagnosticBuilder<'a, ()>, | |
933 | ) -> &'b mut DiagnosticBuilder<'a, ()>, | |
dfeec247 | 934 | ) { |
2b03887a | 935 | self.lookup(lint, Some(span), msg, decorate); |
dfeec247 | 936 | } |
064997fb FG |
937 | |
938 | /// Emit a lint from a lint struct (some type that implements `DecorateLint`, typically | |
939 | /// generated by `#[derive(LintDiagnostic)]`). | |
940 | fn emit_lint(&self, lint: &'static Lint, decorator: impl for<'a> DecorateLint<'a, ()>) { | |
2b03887a FG |
941 | self.lookup(lint, None as Option<Span>, decorator.msg(), |diag| { |
942 | decorator.decorate_lint(diag) | |
943 | }); | |
064997fb FG |
944 | } |
945 | ||
dfeec247 | 946 | /// Emit a lint at the appropriate level, with no associated span. |
2b03887a FG |
947 | /// |
948 | /// Return value of the `decorate` closure is ignored, see [`struct_lint_level`] for a detailed explanation. | |
949 | /// | |
950 | /// [`struct_lint_level`]: rustc_middle::lint::struct_lint_level#decorate-signature | |
487cf647 | 951 | #[rustc_lint_diagnostics] |
5e7ed085 FG |
952 | fn lint( |
953 | &self, | |
954 | lint: &'static Lint, | |
2b03887a FG |
955 | msg: impl Into<DiagnosticMessage>, |
956 | decorate: impl for<'a, 'b> FnOnce( | |
957 | &'b mut DiagnosticBuilder<'a, ()>, | |
958 | ) -> &'b mut DiagnosticBuilder<'a, ()>, | |
5e7ed085 | 959 | ) { |
2b03887a | 960 | self.lookup(lint, None as Option<Span>, msg, decorate); |
dfeec247 | 961 | } |
064997fb FG |
962 | |
963 | /// This returns the lint level for the given lint at the current location. | |
964 | fn get_lint_level(&self, lint: &'static Lint) -> Level; | |
965 | ||
966 | /// This function can be used to manually fulfill an expectation. This can | |
967 | /// be used for lints which contain several spans, and should be suppressed, | |
968 | /// if either location was marked with an expectation. | |
969 | /// | |
970 | /// Note that this function should only be called for [`LintExpectationId`]s | |
971 | /// retrieved from the current lint pass. Buffered or manually created ids can | |
972 | /// cause ICEs. | |
f25598a0 | 973 | #[rustc_lint_diagnostics] |
064997fb FG |
974 | fn fulfill_expectation(&self, expectation: LintExpectationId) { |
975 | // We need to make sure that submitted expectation ids are correctly fulfilled suppressed | |
976 | // and stored between compilation sessions. To not manually do these steps, we simply create | |
977 | // a dummy diagnostic and emit is as usual, which will be suppressed and stored like a normal | |
978 | // expected lint diagnostic. | |
979 | self.sess() | |
980 | .struct_expect( | |
981 | "this is a dummy diagnostic, to submit and store an expectation", | |
982 | expectation, | |
983 | ) | |
984 | .emit(); | |
985 | } | |
dfeec247 XL |
986 | } |
987 | ||
988 | impl<'a> EarlyContext<'a> { | |
5099ac24 | 989 | pub(crate) fn new( |
dfeec247 | 990 | sess: &'a Session, |
5099ac24 | 991 | warn_about_weird_lints: bool, |
dfeec247 | 992 | lint_store: &'a LintStore, |
5099ac24 | 993 | registered_tools: &'a RegisteredTools, |
dfeec247 | 994 | buffered: LintBuffer, |
dfeec247 XL |
995 | ) -> EarlyContext<'a> { |
996 | EarlyContext { | |
5099ac24 FG |
997 | builder: LintLevelsBuilder::new( |
998 | sess, | |
999 | warn_about_weird_lints, | |
1000 | lint_store, | |
1001 | registered_tools, | |
1002 | ), | |
dfeec247 XL |
1003 | buffered, |
1004 | } | |
1005 | } | |
1006 | } | |
1007 | ||
f2b60f7d FG |
1008 | impl<'tcx> LintContext for LateContext<'tcx> { |
1009 | type PassObject = LateLintPassObject<'tcx>; | |
dfeec247 XL |
1010 | |
1011 | /// Gets the overall compiler `Session` object. | |
1012 | fn sess(&self) -> &Session { | |
1013 | &self.tcx.sess | |
1014 | } | |
1015 | ||
1016 | fn lints(&self) -> &LintStore { | |
1017 | &*self.lint_store | |
1018 | } | |
1019 | ||
f25598a0 | 1020 | #[rustc_lint_diagnostics] |
dfeec247 XL |
1021 | fn lookup<S: Into<MultiSpan>>( |
1022 | &self, | |
1023 | lint: &'static Lint, | |
1024 | span: Option<S>, | |
2b03887a FG |
1025 | msg: impl Into<DiagnosticMessage>, |
1026 | decorate: impl for<'a, 'b> FnOnce( | |
1027 | &'b mut DiagnosticBuilder<'a, ()>, | |
1028 | ) -> &'b mut DiagnosticBuilder<'a, ()>, | |
74b04a01 | 1029 | ) { |
dfeec247 XL |
1030 | let hir_id = self.last_node_with_lint_attrs; |
1031 | ||
1032 | match span { | |
2b03887a FG |
1033 | Some(s) => self.tcx.struct_span_lint_hir(lint, hir_id, s, msg, decorate), |
1034 | None => self.tcx.struct_lint_node(lint, hir_id, msg, decorate), | |
dfeec247 XL |
1035 | } |
1036 | } | |
064997fb FG |
1037 | |
1038 | fn get_lint_level(&self, lint: &'static Lint) -> Level { | |
1039 | self.tcx.lint_level_at_node(lint, self.last_node_with_lint_attrs).0 | |
1040 | } | |
dfeec247 XL |
1041 | } |
1042 | ||
1043 | impl LintContext for EarlyContext<'_> { | |
1044 | type PassObject = EarlyLintPassObject; | |
1045 | ||
1046 | /// Gets the overall compiler `Session` object. | |
1047 | fn sess(&self) -> &Session { | |
5099ac24 | 1048 | &self.builder.sess() |
dfeec247 XL |
1049 | } |
1050 | ||
1051 | fn lints(&self) -> &LintStore { | |
5099ac24 | 1052 | self.builder.lint_store() |
dfeec247 XL |
1053 | } |
1054 | ||
f25598a0 | 1055 | #[rustc_lint_diagnostics] |
dfeec247 XL |
1056 | fn lookup<S: Into<MultiSpan>>( |
1057 | &self, | |
1058 | lint: &'static Lint, | |
1059 | span: Option<S>, | |
2b03887a FG |
1060 | msg: impl Into<DiagnosticMessage>, |
1061 | decorate: impl for<'a, 'b> FnOnce( | |
1062 | &'b mut DiagnosticBuilder<'a, ()>, | |
1063 | ) -> &'b mut DiagnosticBuilder<'a, ()>, | |
74b04a01 | 1064 | ) { |
2b03887a | 1065 | self.builder.struct_lint(lint, span.map(|s| s.into()), msg, decorate) |
dfeec247 | 1066 | } |
064997fb FG |
1067 | |
1068 | fn get_lint_level(&self, lint: &'static Lint) -> Level { | |
1069 | self.builder.lint_level(lint).0 | |
1070 | } | |
dfeec247 XL |
1071 | } |
1072 | ||
f035d41b | 1073 | impl<'tcx> LateContext<'tcx> { |
3dfed10e | 1074 | /// Gets the type-checking results for the current body, |
f035d41b | 1075 | /// or `None` if outside a body. |
3dfed10e XL |
1076 | pub fn maybe_typeck_results(&self) -> Option<&'tcx ty::TypeckResults<'tcx>> { |
1077 | self.cached_typeck_results.get().or_else(|| { | |
f035d41b | 1078 | self.enclosing_body.map(|body| { |
3dfed10e XL |
1079 | let typeck_results = self.tcx.typeck_body(body); |
1080 | self.cached_typeck_results.set(Some(typeck_results)); | |
1081 | typeck_results | |
f035d41b XL |
1082 | }) |
1083 | }) | |
1084 | } | |
1085 | ||
3dfed10e | 1086 | /// Gets the type-checking results for the current body. |
f035d41b XL |
1087 | /// As this will ICE if called outside bodies, only call when working with |
1088 | /// `Expr` or `Pat` nodes (they are guaranteed to be found only in bodies). | |
1089 | #[track_caller] | |
3dfed10e XL |
1090 | pub fn typeck_results(&self) -> &'tcx ty::TypeckResults<'tcx> { |
1091 | self.maybe_typeck_results().expect("`LateContext::typeck_results` called outside of body") | |
f035d41b XL |
1092 | } |
1093 | ||
1094 | /// Returns the final resolution of a `QPath`, or `Res::Err` if unavailable. | |
3dfed10e | 1095 | /// Unlike `.typeck_results().qpath_res(qpath, id)`, this can be used even outside |
f035d41b XL |
1096 | /// bodies (e.g. for paths in `hir::Ty`), without any risk of ICE-ing. |
1097 | pub fn qpath_res(&self, qpath: &hir::QPath<'_>, id: hir::HirId) -> Res { | |
1098 | match *qpath { | |
1099 | hir::QPath::Resolved(_, ref path) => path.res, | |
3dfed10e XL |
1100 | hir::QPath::TypeRelative(..) | hir::QPath::LangItem(..) => self |
1101 | .maybe_typeck_results() | |
5869c6ff XL |
1102 | .filter(|typeck_results| typeck_results.hir_owner == id.owner) |
1103 | .or_else(|| { | |
1104 | if self.tcx.has_typeck_results(id.owner.to_def_id()) { | |
2b03887a | 1105 | Some(self.tcx.typeck(id.owner.def_id)) |
5869c6ff XL |
1106 | } else { |
1107 | None | |
1108 | } | |
1109 | }) | |
3dfed10e | 1110 | .and_then(|typeck_results| typeck_results.type_dependent_def(id)) |
f035d41b XL |
1111 | .map_or(Res::Err, |(kind, def_id)| Res::Def(kind, def_id)), |
1112 | } | |
1113 | } | |
1114 | ||
dfeec247 XL |
1115 | /// Check if a `DefId`'s path matches the given absolute type path usage. |
1116 | /// | |
5869c6ff | 1117 | /// Anonymous scopes such as `extern` imports are matched with `kw::Empty`; |
dfeec247 XL |
1118 | /// inherent `impl` blocks are matched with the name of the type. |
1119 | /// | |
1b1a35ee XL |
1120 | /// Instead of using this method, it is often preferable to instead use |
1121 | /// `rustc_diagnostic_item` or a `lang_item`. This is less prone to errors | |
1122 | /// as paths get invalidated if the target definition moves. | |
1123 | /// | |
dfeec247 XL |
1124 | /// # Examples |
1125 | /// | |
1126 | /// ```rust,ignore (no context or def id available) | |
1127 | /// if cx.match_def_path(def_id, &[sym::core, sym::option, sym::Option]) { | |
1128 | /// // The given `def_id` is that of an `Option` type | |
1129 | /// } | |
1130 | /// ``` | |
cdc7bbd5 XL |
1131 | /// |
1132 | /// Used by clippy, but should be replaced by diagnostic items eventually. | |
dfeec247 XL |
1133 | pub fn match_def_path(&self, def_id: DefId, path: &[Symbol]) -> bool { |
1134 | let names = self.get_def_path(def_id); | |
1135 | ||
cdc7bbd5 | 1136 | names.len() == path.len() && iter::zip(names, path).all(|(a, &b)| a == b) |
dfeec247 XL |
1137 | } |
1138 | ||
1139 | /// Gets the absolute path of `def_id` as a vector of `Symbol`. | |
1140 | /// | |
1141 | /// # Examples | |
1142 | /// | |
1143 | /// ```rust,ignore (no context or def id available) | |
1144 | /// let def_path = cx.get_def_path(def_id); | |
1145 | /// if let &[sym::core, sym::option, sym::Option] = &def_path[..] { | |
1146 | /// // The given `def_id` is that of an `Option` type | |
1147 | /// } | |
1148 | /// ``` | |
1149 | pub fn get_def_path(&self, def_id: DefId) -> Vec<Symbol> { | |
1150 | pub struct AbsolutePathPrinter<'tcx> { | |
1151 | pub tcx: TyCtxt<'tcx>, | |
1152 | } | |
1153 | ||
1154 | impl<'tcx> Printer<'tcx> for AbsolutePathPrinter<'tcx> { | |
1155 | type Error = !; | |
1156 | ||
1157 | type Path = Vec<Symbol>; | |
1158 | type Region = (); | |
1159 | type Type = (); | |
1160 | type DynExistential = (); | |
1161 | type Const = (); | |
1162 | ||
1163 | fn tcx(&self) -> TyCtxt<'tcx> { | |
1164 | self.tcx | |
1165 | } | |
1166 | ||
1167 | fn print_region(self, _region: ty::Region<'_>) -> Result<Self::Region, Self::Error> { | |
1168 | Ok(()) | |
1169 | } | |
1170 | ||
1171 | fn print_type(self, _ty: Ty<'tcx>) -> Result<Self::Type, Self::Error> { | |
1172 | Ok(()) | |
1173 | } | |
1174 | ||
1175 | fn print_dyn_existential( | |
1176 | self, | |
487cf647 | 1177 | _predicates: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>, |
dfeec247 XL |
1178 | ) -> Result<Self::DynExistential, Self::Error> { |
1179 | Ok(()) | |
1180 | } | |
1181 | ||
5099ac24 | 1182 | fn print_const(self, _ct: ty::Const<'tcx>) -> Result<Self::Const, Self::Error> { |
dfeec247 XL |
1183 | Ok(()) |
1184 | } | |
1185 | ||
1186 | fn path_crate(self, cnum: CrateNum) -> Result<Self::Path, Self::Error> { | |
17df50a5 | 1187 | Ok(vec![self.tcx.crate_name(cnum)]) |
dfeec247 XL |
1188 | } |
1189 | ||
1190 | fn path_qualified( | |
1191 | self, | |
1192 | self_ty: Ty<'tcx>, | |
1193 | trait_ref: Option<ty::TraitRef<'tcx>>, | |
1194 | ) -> Result<Self::Path, Self::Error> { | |
1195 | if trait_ref.is_none() { | |
1b1a35ee | 1196 | if let ty::Adt(def, substs) = self_ty.kind() { |
5e7ed085 | 1197 | return self.print_def_path(def.did(), substs); |
dfeec247 XL |
1198 | } |
1199 | } | |
1200 | ||
1201 | // This shouldn't ever be needed, but just in case: | |
5e7ed085 | 1202 | with_no_trimmed_paths!({ |
1b1a35ee XL |
1203 | Ok(vec![match trait_ref { |
1204 | Some(trait_ref) => Symbol::intern(&format!("{:?}", trait_ref)), | |
1205 | None => Symbol::intern(&format!("<{}>", self_ty)), | |
1206 | }]) | |
1207 | }) | |
dfeec247 XL |
1208 | } |
1209 | ||
1210 | fn path_append_impl( | |
1211 | self, | |
1212 | print_prefix: impl FnOnce(Self) -> Result<Self::Path, Self::Error>, | |
1213 | _disambiguated_data: &DisambiguatedDefPathData, | |
1214 | self_ty: Ty<'tcx>, | |
1215 | trait_ref: Option<ty::TraitRef<'tcx>>, | |
1216 | ) -> Result<Self::Path, Self::Error> { | |
1217 | let mut path = print_prefix(self)?; | |
1218 | ||
1219 | // This shouldn't ever be needed, but just in case: | |
1220 | path.push(match trait_ref { | |
5e7ed085 FG |
1221 | Some(trait_ref) => { |
1222 | with_no_trimmed_paths!(Symbol::intern(&format!( | |
1b1a35ee XL |
1223 | "<impl {} for {}>", |
1224 | trait_ref.print_only_trait_path(), | |
1225 | self_ty | |
5e7ed085 FG |
1226 | ))) |
1227 | } | |
1b1a35ee | 1228 | None => { |
5e7ed085 | 1229 | with_no_trimmed_paths!(Symbol::intern(&format!("<impl {}>", self_ty))) |
1b1a35ee | 1230 | } |
dfeec247 XL |
1231 | }); |
1232 | ||
1233 | Ok(path) | |
1234 | } | |
1235 | ||
1236 | fn path_append( | |
1237 | self, | |
1238 | print_prefix: impl FnOnce(Self) -> Result<Self::Path, Self::Error>, | |
1239 | disambiguated_data: &DisambiguatedDefPathData, | |
1240 | ) -> Result<Self::Path, Self::Error> { | |
1241 | let mut path = print_prefix(self)?; | |
1242 | ||
a2a8927a XL |
1243 | // Skip `::{{extern}}` blocks and `::{{constructor}}` on tuple/unit structs. |
1244 | if let DefPathData::ForeignMod | DefPathData::Ctor = disambiguated_data.data { | |
ba9703b0 | 1245 | return Ok(path); |
dfeec247 XL |
1246 | } |
1247 | ||
1b1a35ee | 1248 | path.push(Symbol::intern(&disambiguated_data.data.to_string())); |
dfeec247 XL |
1249 | Ok(path) |
1250 | } | |
1251 | ||
1252 | fn path_generic_args( | |
1253 | self, | |
1254 | print_prefix: impl FnOnce(Self) -> Result<Self::Path, Self::Error>, | |
1255 | _args: &[GenericArg<'tcx>], | |
1256 | ) -> Result<Self::Path, Self::Error> { | |
1257 | print_prefix(self) | |
1258 | } | |
1259 | } | |
1260 | ||
1261 | AbsolutePathPrinter { tcx: self.tcx }.print_def_path(def_id, &[]).unwrap() | |
1262 | } | |
487cf647 FG |
1263 | |
1264 | /// Returns the associated type `name` for `self_ty` as an implementation of `trait_id`. | |
1265 | /// Do not invoke without first verifying that the type implements the trait. | |
1266 | pub fn get_associated_type( | |
1267 | &self, | |
1268 | self_ty: Ty<'tcx>, | |
1269 | trait_id: DefId, | |
1270 | name: &str, | |
1271 | ) -> Option<Ty<'tcx>> { | |
1272 | let tcx = self.tcx; | |
1273 | tcx.associated_items(trait_id) | |
1274 | .find_by_name_and_kind(tcx, Ident::from_str(name), ty::AssocKind::Type, trait_id) | |
1275 | .and_then(|assoc| { | |
f25598a0 | 1276 | let proj = tcx.mk_projection(assoc.def_id, [self_ty]); |
487cf647 FG |
1277 | tcx.try_normalize_erasing_regions(self.param_env, proj).ok() |
1278 | }) | |
1279 | } | |
dfeec247 XL |
1280 | } |
1281 | ||
94222f64 XL |
1282 | impl<'tcx> abi::HasDataLayout for LateContext<'tcx> { |
1283 | #[inline] | |
1284 | fn data_layout(&self) -> &abi::TargetDataLayout { | |
1285 | &self.tcx.data_layout | |
1286 | } | |
1287 | } | |
1288 | ||
1289 | impl<'tcx> ty::layout::HasTyCtxt<'tcx> for LateContext<'tcx> { | |
1290 | #[inline] | |
1291 | fn tcx(&self) -> TyCtxt<'tcx> { | |
1292 | self.tcx | |
1293 | } | |
1294 | } | |
1295 | ||
1296 | impl<'tcx> ty::layout::HasParamEnv<'tcx> for LateContext<'tcx> { | |
1297 | #[inline] | |
1298 | fn param_env(&self) -> ty::ParamEnv<'tcx> { | |
1299 | self.param_env | |
1300 | } | |
1301 | } | |
1302 | ||
c295e0f8 XL |
1303 | impl<'tcx> LayoutOfHelpers<'tcx> for LateContext<'tcx> { |
1304 | type LayoutOfResult = Result<TyAndLayout<'tcx>, LayoutError<'tcx>>; | |
dfeec247 | 1305 | |
c295e0f8 XL |
1306 | #[inline] |
1307 | fn handle_layout_err(&self, err: LayoutError<'tcx>, _: Span, _: Ty<'tcx>) -> LayoutError<'tcx> { | |
1308 | err | |
dfeec247 XL |
1309 | } |
1310 | } | |
136023e0 XL |
1311 | |
1312 | pub fn parse_lint_and_tool_name(lint_name: &str) -> (Option<Symbol>, &str) { | |
1313 | match lint_name.split_once("::") { | |
1314 | Some((tool_name, lint_name)) => { | |
1315 | let tool_name = Symbol::intern(tool_name); | |
1316 | ||
1317 | (Some(tool_name), lint_name) | |
1318 | } | |
1319 | None => (None, lint_name), | |
1320 | } | |
1321 | } |