]> git.lizzy.rs Git - rust.git/blob - src/librustc_session/lint.rs
Rollup merge of #71976 - mibac138:let-recovery, r=estebank
[rust.git] / src / librustc_session / lint.rs
1 pub use self::Level::*;
2 use rustc_ast::node_id::{NodeId, NodeMap};
3 use rustc_data_structures::stable_hasher::{HashStable, StableHasher, ToStableHashKey};
4 use rustc_errors::{pluralize, Applicability, DiagnosticBuilder};
5 use rustc_span::edition::Edition;
6 use rustc_span::{sym, symbol::Ident, MultiSpan, Span, Symbol};
7
8 pub mod builtin;
9
10 /// Setting for how to handle a lint.
11 #[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
12 pub enum Level {
13     Allow,
14     Warn,
15     Deny,
16     Forbid,
17 }
18
19 rustc_data_structures::impl_stable_hash_via_hash!(Level);
20
21 impl Level {
22     /// Converts a level to a lower-case string.
23     pub fn as_str(self) -> &'static str {
24         match self {
25             Level::Allow => "allow",
26             Level::Warn => "warn",
27             Level::Deny => "deny",
28             Level::Forbid => "forbid",
29         }
30     }
31
32     /// Converts a lower-case string to a level.
33     pub fn from_str(x: &str) -> Option<Level> {
34         match x {
35             "allow" => Some(Level::Allow),
36             "warn" => Some(Level::Warn),
37             "deny" => Some(Level::Deny),
38             "forbid" => Some(Level::Forbid),
39             _ => None,
40         }
41     }
42
43     /// Converts a symbol to a level.
44     pub fn from_symbol(x: Symbol) -> Option<Level> {
45         match x {
46             sym::allow => Some(Level::Allow),
47             sym::warn => Some(Level::Warn),
48             sym::deny => Some(Level::Deny),
49             sym::forbid => Some(Level::Forbid),
50             _ => None,
51         }
52     }
53 }
54
55 /// Specification of a single lint.
56 #[derive(Copy, Clone, Debug)]
57 pub struct Lint {
58     /// A string identifier for the lint.
59     ///
60     /// This identifies the lint in attributes and in command-line arguments.
61     /// In those contexts it is always lowercase, but this field is compared
62     /// in a way which is case-insensitive for ASCII characters. This allows
63     /// `declare_lint!()` invocations to follow the convention of upper-case
64     /// statics without repeating the name.
65     ///
66     /// The name is written with underscores, e.g., "unused_imports".
67     /// On the command line, underscores become dashes.
68     pub name: &'static str,
69
70     /// Default level for the lint.
71     pub default_level: Level,
72
73     /// Description of the lint or the issue it detects.
74     ///
75     /// e.g., "imports that are never used"
76     pub desc: &'static str,
77
78     /// Starting at the given edition, default to the given lint level. If this is `None`, then use
79     /// `default_level`.
80     pub edition_lint_opts: Option<(Edition, Level)>,
81
82     /// `true` if this lint is reported even inside expansions of external macros.
83     pub report_in_external_macro: bool,
84
85     pub future_incompatible: Option<FutureIncompatibleInfo>,
86
87     pub is_plugin: bool,
88
89     /// `Some` if this lint is feature gated, otherwise `None`.
90     pub feature_gate: Option<Symbol>,
91 }
92
93 /// Extra information for a future incompatibility lint.
94 #[derive(Copy, Clone, Debug)]
95 pub struct FutureIncompatibleInfo {
96     /// e.g., a URL for an issue/PR/RFC or error code
97     pub reference: &'static str,
98     /// If this is an edition fixing lint, the edition in which
99     /// this lint becomes obsolete
100     pub edition: Option<Edition>,
101 }
102
103 impl Lint {
104     pub const fn default_fields_for_macro() -> Self {
105         Lint {
106             name: "",
107             default_level: Level::Forbid,
108             desc: "",
109             edition_lint_opts: None,
110             is_plugin: false,
111             report_in_external_macro: false,
112             future_incompatible: None,
113             feature_gate: None,
114         }
115     }
116
117     /// Gets the lint's name, with ASCII letters converted to lowercase.
118     pub fn name_lower(&self) -> String {
119         self.name.to_ascii_lowercase()
120     }
121
122     pub fn default_level(&self, edition: Edition) -> Level {
123         self.edition_lint_opts
124             .filter(|(e, _)| *e <= edition)
125             .map(|(_, l)| l)
126             .unwrap_or(self.default_level)
127     }
128 }
129
130 /// Identifies a lint known to the compiler.
131 #[derive(Clone, Copy, Debug)]
132 pub struct LintId {
133     // Identity is based on pointer equality of this field.
134     pub lint: &'static Lint,
135 }
136
137 impl PartialEq for LintId {
138     fn eq(&self, other: &LintId) -> bool {
139         std::ptr::eq(self.lint, other.lint)
140     }
141 }
142
143 impl Eq for LintId {}
144
145 impl std::hash::Hash for LintId {
146     fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
147         let ptr = self.lint as *const Lint;
148         ptr.hash(state);
149     }
150 }
151
152 impl LintId {
153     /// Gets the `LintId` for a `Lint`.
154     pub fn of(lint: &'static Lint) -> LintId {
155         LintId { lint }
156     }
157
158     pub fn lint_name_raw(&self) -> &'static str {
159         self.lint.name
160     }
161
162     /// Gets the name of the lint.
163     pub fn to_string(&self) -> String {
164         self.lint.name_lower()
165     }
166 }
167
168 impl<HCX> HashStable<HCX> for LintId {
169     #[inline]
170     fn hash_stable(&self, hcx: &mut HCX, hasher: &mut StableHasher) {
171         self.lint_name_raw().hash_stable(hcx, hasher);
172     }
173 }
174
175 impl<HCX> ToStableHashKey<HCX> for LintId {
176     type KeyType = &'static str;
177
178     #[inline]
179     fn to_stable_hash_key(&self, _: &HCX) -> &'static str {
180         self.lint_name_raw()
181     }
182 }
183
184 // This could be a closure, but then implementing derive trait
185 // becomes hacky (and it gets allocated).
186 #[derive(PartialEq)]
187 pub enum BuiltinLintDiagnostics {
188     Normal,
189     BareTraitObject(Span, /* is_global */ bool),
190     AbsPathWithModule(Span),
191     ProcMacroDeriveResolutionFallback(Span),
192     MacroExpandedMacroExportsAccessedByAbsolutePaths(Span),
193     ElidedLifetimesInPaths(usize, Span, bool, Span, String),
194     UnknownCrateTypes(Span, String, String),
195     UnusedImports(String, Vec<(Span, String)>),
196     RedundantImport(Vec<(Span, bool)>, Ident),
197     DeprecatedMacro(Option<Symbol>, Span),
198     UnusedDocComment(Span),
199 }
200
201 /// Lints that are buffered up early on in the `Session` before the
202 /// `LintLevels` is calculated.
203 #[derive(PartialEq)]
204 pub struct BufferedEarlyLint {
205     /// The span of code that we are linting on.
206     pub span: MultiSpan,
207
208     /// The lint message.
209     pub msg: String,
210
211     /// The `NodeId` of the AST node that generated the lint.
212     pub node_id: NodeId,
213
214     /// A lint Id that can be passed to
215     /// `rustc_lint::early::EarlyContextAndPass::check_id`.
216     pub lint_id: LintId,
217
218     /// Customization of the `DiagnosticBuilder<'_>` for the lint.
219     pub diagnostic: BuiltinLintDiagnostics,
220 }
221
222 #[derive(Default)]
223 pub struct LintBuffer {
224     pub map: NodeMap<Vec<BufferedEarlyLint>>,
225 }
226
227 impl LintBuffer {
228     pub fn add_early_lint(&mut self, early_lint: BufferedEarlyLint) {
229         let arr = self.map.entry(early_lint.node_id).or_default();
230         if !arr.contains(&early_lint) {
231             arr.push(early_lint);
232         }
233     }
234
235     pub fn add_lint(
236         &mut self,
237         lint: &'static Lint,
238         node_id: NodeId,
239         span: MultiSpan,
240         msg: &str,
241         diagnostic: BuiltinLintDiagnostics,
242     ) {
243         let lint_id = LintId::of(lint);
244         let msg = msg.to_string();
245         self.add_early_lint(BufferedEarlyLint { lint_id, node_id, span, msg, diagnostic });
246     }
247
248     pub fn take(&mut self, id: NodeId) -> Vec<BufferedEarlyLint> {
249         self.map.remove(&id).unwrap_or_default()
250     }
251
252     pub fn buffer_lint(
253         &mut self,
254         lint: &'static Lint,
255         id: NodeId,
256         sp: impl Into<MultiSpan>,
257         msg: &str,
258     ) {
259         self.add_lint(lint, id, sp.into(), msg, BuiltinLintDiagnostics::Normal)
260     }
261
262     pub fn buffer_lint_with_diagnostic(
263         &mut self,
264         lint: &'static Lint,
265         id: NodeId,
266         sp: impl Into<MultiSpan>,
267         msg: &str,
268         diagnostic: BuiltinLintDiagnostics,
269     ) {
270         self.add_lint(lint, id, sp.into(), msg, diagnostic)
271     }
272 }
273
274 /// Declares a static item of type `&'static Lint`.
275 #[macro_export]
276 macro_rules! declare_lint {
277     ($vis: vis $NAME: ident, $Level: ident, $desc: expr) => (
278         $crate::declare_lint!(
279             $vis $NAME, $Level, $desc,
280         );
281     );
282     ($vis: vis $NAME: ident, $Level: ident, $desc: expr,
283      $(@future_incompatible = $fi:expr;)?
284      $(@feature_gate = $gate:expr;)?
285      $($v:ident),*) => (
286         $vis static $NAME: &$crate::lint::Lint = &$crate::lint::Lint {
287             name: stringify!($NAME),
288             default_level: $crate::lint::$Level,
289             desc: $desc,
290             edition_lint_opts: None,
291             is_plugin: false,
292             $($v: true,)*
293             $(future_incompatible: Some($fi),)*
294             $(feature_gate: Some($gate),)*
295             ..$crate::lint::Lint::default_fields_for_macro()
296         };
297     );
298     ($vis: vis $NAME: ident, $Level: ident, $desc: expr,
299      $lint_edition: expr => $edition_level: ident
300     ) => (
301         $vis static $NAME: &$crate::lint::Lint = &$crate::lint::Lint {
302             name: stringify!($NAME),
303             default_level: $crate::lint::$Level,
304             desc: $desc,
305             edition_lint_opts: Some(($lint_edition, $crate::lint::Level::$edition_level)),
306             report_in_external_macro: false,
307             is_plugin: false,
308         };
309     );
310 }
311
312 #[macro_export]
313 macro_rules! declare_tool_lint {
314     (
315         $(#[$attr:meta])* $vis:vis $tool:ident ::$NAME:ident, $Level: ident, $desc: expr
316     ) => (
317         $crate::declare_tool_lint!{$(#[$attr])* $vis $tool::$NAME, $Level, $desc, false}
318     );
319     (
320         $(#[$attr:meta])* $vis:vis $tool:ident ::$NAME:ident, $Level:ident, $desc:expr,
321         report_in_external_macro: $rep:expr
322     ) => (
323          $crate::declare_tool_lint!{$(#[$attr])* $vis $tool::$NAME, $Level, $desc, $rep}
324     );
325     (
326         $(#[$attr:meta])* $vis:vis $tool:ident ::$NAME:ident, $Level:ident, $desc:expr,
327         $external:expr
328     ) => (
329         $(#[$attr])*
330         $vis static $NAME: &$crate::lint::Lint = &$crate::lint::Lint {
331             name: &concat!(stringify!($tool), "::", stringify!($NAME)),
332             default_level: $crate::lint::$Level,
333             desc: $desc,
334             edition_lint_opts: None,
335             report_in_external_macro: $external,
336             future_incompatible: None,
337             is_plugin: true,
338             feature_gate: None,
339         };
340     );
341 }
342
343 /// Declares a static `LintArray` and return it as an expression.
344 #[macro_export]
345 macro_rules! lint_array {
346     ($( $lint:expr ),* ,) => { lint_array!( $($lint),* ) };
347     ($( $lint:expr ),*) => {{
348         vec![$($lint),*]
349     }}
350 }
351
352 pub type LintArray = Vec<&'static Lint>;
353
354 pub trait LintPass {
355     fn name(&self) -> &'static str;
356 }
357
358 /// Implements `LintPass for $ty` with the given list of `Lint` statics.
359 #[macro_export]
360 macro_rules! impl_lint_pass {
361     ($ty:ty => [$($lint:expr),* $(,)?]) => {
362         impl $crate::lint::LintPass for $ty {
363             fn name(&self) -> &'static str { stringify!($ty) }
364         }
365         impl $ty {
366             pub fn get_lints() -> $crate::lint::LintArray { $crate::lint_array!($($lint),*) }
367         }
368     };
369 }
370
371 /// Declares a type named `$name` which implements `LintPass`.
372 /// To the right of `=>` a comma separated list of `Lint` statics is given.
373 #[macro_export]
374 macro_rules! declare_lint_pass {
375     ($(#[$m:meta])* $name:ident => [$($lint:expr),* $(,)?]) => {
376         $(#[$m])* #[derive(Copy, Clone)] pub struct $name;
377         $crate::impl_lint_pass!($name => [$($lint),*]);
378     };
379 }
380
381 pub fn add_elided_lifetime_in_path_suggestion(
382     sess: &crate::Session,
383     db: &mut DiagnosticBuilder<'_>,
384     n: usize,
385     path_span: Span,
386     incl_angl_brckt: bool,
387     insertion_span: Span,
388     anon_lts: String,
389 ) {
390     let (replace_span, suggestion) = if incl_angl_brckt {
391         (insertion_span, anon_lts)
392     } else {
393         // When possible, prefer a suggestion that replaces the whole
394         // `Path<T>` expression with `Path<'_, T>`, rather than inserting `'_, `
395         // at a point (which makes for an ugly/confusing label)
396         if let Ok(snippet) = sess.source_map().span_to_snippet(path_span) {
397             // But our spans can get out of whack due to macros; if the place we think
398             // we want to insert `'_` isn't even within the path expression's span, we
399             // should bail out of making any suggestion rather than panicking on a
400             // subtract-with-overflow or string-slice-out-out-bounds (!)
401             // FIXME: can we do better?
402             if insertion_span.lo().0 < path_span.lo().0 {
403                 return;
404             }
405             let insertion_index = (insertion_span.lo().0 - path_span.lo().0) as usize;
406             if insertion_index > snippet.len() {
407                 return;
408             }
409             let (before, after) = snippet.split_at(insertion_index);
410             (path_span, format!("{}{}{}", before, anon_lts, after))
411         } else {
412             (insertion_span, anon_lts)
413         }
414     };
415     db.span_suggestion(
416         replace_span,
417         &format!("indicate the anonymous lifetime{}", pluralize!(n)),
418         suggestion,
419         Applicability::MachineApplicable,
420     );
421 }