]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_middle/src/lint.rs
Auto merge of #86009 - cjgillot:fwarn, r=davidtwco
[rust.git] / compiler / rustc_middle / src / lint.rs
1 use std::cmp;
2
3 use crate::ich::StableHashingContext;
4 use rustc_data_structures::fx::FxHashMap;
5 use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
6 use rustc_errors::{DiagnosticBuilder, DiagnosticId};
7 use rustc_hir::HirId;
8 use rustc_session::lint::{
9     builtin::{self, FORBIDDEN_LINT_GROUPS},
10     FutureIncompatibilityReason, Level, Lint, LintId,
11 };
12 use rustc_session::{DiagnosticMessageId, Session};
13 use rustc_span::hygiene::MacroKind;
14 use rustc_span::source_map::{DesugaringKind, ExpnKind, MultiSpan};
15 use rustc_span::{symbol, Span, Symbol, DUMMY_SP};
16
17 /// How a lint level was set.
18 #[derive(Clone, Copy, PartialEq, Eq, HashStable, Debug)]
19 pub enum LintLevelSource {
20     /// Lint is at the default level as declared
21     /// in rustc or a plugin.
22     Default,
23
24     /// Lint level was set by an attribute.
25     Node(Symbol, Span, Option<Symbol> /* RFC 2383 reason */),
26
27     /// Lint level was set by a command-line flag.
28     /// The provided `Level` is the level specified on the command line.
29     /// (The actual level may be lower due to `--cap-lints`.)
30     CommandLine(Symbol, Level),
31 }
32
33 impl LintLevelSource {
34     pub fn name(&self) -> Symbol {
35         match *self {
36             LintLevelSource::Default => symbol::kw::Default,
37             LintLevelSource::Node(name, _, _) => name,
38             LintLevelSource::CommandLine(name, _) => name,
39         }
40     }
41
42     pub fn span(&self) -> Span {
43         match *self {
44             LintLevelSource::Default => DUMMY_SP,
45             LintLevelSource::Node(_, span, _) => span,
46             LintLevelSource::CommandLine(_, _) => DUMMY_SP,
47         }
48     }
49 }
50
51 /// A tuple of a lint level and its source.
52 pub type LevelAndSource = (Level, LintLevelSource);
53
54 #[derive(Debug)]
55 pub struct LintLevelSets {
56     pub list: Vec<LintSet>,
57     pub lint_cap: Level,
58 }
59
60 #[derive(Debug)]
61 pub enum LintSet {
62     CommandLine {
63         // -A,-W,-D flags, a `Symbol` for the flag itself and `Level` for which
64         // flag.
65         specs: FxHashMap<LintId, LevelAndSource>,
66     },
67
68     Node {
69         specs: FxHashMap<LintId, LevelAndSource>,
70         parent: u32,
71     },
72 }
73
74 impl LintLevelSets {
75     pub fn new() -> Self {
76         LintLevelSets { list: Vec::new(), lint_cap: Level::Forbid }
77     }
78
79     pub fn get_lint_level(
80         &self,
81         lint: &'static Lint,
82         idx: u32,
83         aux: Option<&FxHashMap<LintId, LevelAndSource>>,
84         sess: &Session,
85     ) -> LevelAndSource {
86         let (level, mut src) = self.get_lint_id_level(LintId::of(lint), idx, aux);
87
88         // If `level` is none then we actually assume the default level for this
89         // lint.
90         let mut level = level.unwrap_or_else(|| lint.default_level(sess.edition()));
91
92         // If we're about to issue a warning, check at the last minute for any
93         // directives against the warnings "lint". If, for example, there's an
94         // `allow(warnings)` in scope then we want to respect that instead.
95         //
96         // We exempt `FORBIDDEN_LINT_GROUPS` from this because it specifically
97         // triggers in cases (like #80988) where you have `forbid(warnings)`,
98         // and so if we turned that into an error, it'd defeat the purpose of the
99         // future compatibility warning.
100         if level == Level::Warn && LintId::of(lint) != LintId::of(FORBIDDEN_LINT_GROUPS) {
101             let (warnings_level, warnings_src) =
102                 self.get_lint_id_level(LintId::of(builtin::WARNINGS), idx, aux);
103             if let Some(configured_warning_level) = warnings_level {
104                 if configured_warning_level != Level::Warn {
105                     level = configured_warning_level;
106                     src = warnings_src;
107                 }
108             }
109         }
110
111         // Ensure that we never exceed the `--cap-lints` argument.
112         level = cmp::min(level, self.lint_cap);
113
114         if let Some(driver_level) = sess.driver_lint_caps.get(&LintId::of(lint)) {
115             // Ensure that we never exceed driver level.
116             level = cmp::min(*driver_level, level);
117         }
118
119         (level, src)
120     }
121
122     pub fn get_lint_id_level(
123         &self,
124         id: LintId,
125         mut idx: u32,
126         aux: Option<&FxHashMap<LintId, LevelAndSource>>,
127     ) -> (Option<Level>, LintLevelSource) {
128         if let Some(specs) = aux {
129             if let Some(&(level, src)) = specs.get(&id) {
130                 return (Some(level), src);
131             }
132         }
133         loop {
134             match self.list[idx as usize] {
135                 LintSet::CommandLine { ref specs } => {
136                     if let Some(&(level, src)) = specs.get(&id) {
137                         return (Some(level), src);
138                     }
139                     return (None, LintLevelSource::Default);
140                 }
141                 LintSet::Node { ref specs, parent } => {
142                     if let Some(&(level, src)) = specs.get(&id) {
143                         return (Some(level), src);
144                     }
145                     idx = parent;
146                 }
147             }
148         }
149     }
150 }
151
152 #[derive(Debug)]
153 pub struct LintLevelMap {
154     pub sets: LintLevelSets,
155     pub id_to_set: FxHashMap<HirId, u32>,
156 }
157
158 impl LintLevelMap {
159     /// If the `id` was previously registered with `register_id` when building
160     /// this `LintLevelMap` this returns the corresponding lint level and source
161     /// of the lint level for the lint provided.
162     ///
163     /// If the `id` was not previously registered, returns `None`. If `None` is
164     /// returned then the parent of `id` should be acquired and this function
165     /// should be called again.
166     pub fn level_and_source(
167         &self,
168         lint: &'static Lint,
169         id: HirId,
170         session: &Session,
171     ) -> Option<LevelAndSource> {
172         self.id_to_set.get(&id).map(|idx| self.sets.get_lint_level(lint, *idx, None, session))
173     }
174 }
175
176 impl<'a> HashStable<StableHashingContext<'a>> for LintLevelMap {
177     #[inline]
178     fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) {
179         let LintLevelMap { ref sets, ref id_to_set } = *self;
180
181         id_to_set.hash_stable(hcx, hasher);
182
183         let LintLevelSets { ref list, lint_cap } = *sets;
184
185         lint_cap.hash_stable(hcx, hasher);
186
187         hcx.while_hashing_spans(true, |hcx| {
188             list.len().hash_stable(hcx, hasher);
189
190             // We are working under the assumption here that the list of
191             // lint-sets is built in a deterministic order.
192             for lint_set in list {
193                 ::std::mem::discriminant(lint_set).hash_stable(hcx, hasher);
194
195                 match *lint_set {
196                     LintSet::CommandLine { ref specs } => {
197                         specs.hash_stable(hcx, hasher);
198                     }
199                     LintSet::Node { ref specs, parent } => {
200                         specs.hash_stable(hcx, hasher);
201                         parent.hash_stable(hcx, hasher);
202                     }
203                 }
204             }
205         })
206     }
207 }
208
209 pub struct LintDiagnosticBuilder<'a>(DiagnosticBuilder<'a>);
210
211 impl<'a> LintDiagnosticBuilder<'a> {
212     /// Return the inner DiagnosticBuilder, first setting the primary message to `msg`.
213     pub fn build(mut self, msg: &str) -> DiagnosticBuilder<'a> {
214         self.0.set_primary_message(msg);
215         self.0
216     }
217
218     /// Create a LintDiagnosticBuilder from some existing DiagnosticBuilder.
219     pub fn new(err: DiagnosticBuilder<'a>) -> LintDiagnosticBuilder<'a> {
220         LintDiagnosticBuilder(err)
221     }
222 }
223
224 pub fn struct_lint_level<'s, 'd>(
225     sess: &'s Session,
226     lint: &'static Lint,
227     level: Level,
228     src: LintLevelSource,
229     span: Option<MultiSpan>,
230     decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>) + 'd,
231 ) {
232     // Avoid codegen bloat from monomorphization by immediately doing dyn dispatch of `decorate` to
233     // the "real" work.
234     fn struct_lint_level_impl(
235         sess: &'s Session,
236         lint: &'static Lint,
237         level: Level,
238         src: LintLevelSource,
239         span: Option<MultiSpan>,
240         decorate: Box<dyn for<'b> FnOnce(LintDiagnosticBuilder<'b>) + 'd>,
241     ) {
242         // Check for future incompatibility lints and issue a stronger warning.
243         let lint_id = LintId::of(lint);
244         let future_incompatible = lint.future_incompatible;
245
246         let has_future_breakage =
247             future_incompatible.map_or(false, |incompat| incompat.future_breakage.is_some());
248
249         let mut err = match (level, span) {
250             (Level::Allow, span) => {
251                 if has_future_breakage {
252                     if let Some(span) = span {
253                         sess.struct_span_allow(span, "")
254                     } else {
255                         sess.struct_allow("")
256                     }
257                 } else {
258                     return;
259                 }
260             }
261             (Level::Warn | Level::ForceWarn, Some(span)) => sess.struct_span_warn(span, ""),
262             (Level::Warn | Level::ForceWarn, None) => sess.struct_warn(""),
263             (Level::Deny | Level::Forbid, Some(span)) => sess.struct_span_err(span, ""),
264             (Level::Deny | Level::Forbid, None) => sess.struct_err(""),
265         };
266
267         // If this code originates in a foreign macro, aka something that this crate
268         // did not itself author, then it's likely that there's nothing this crate
269         // can do about it. We probably want to skip the lint entirely.
270         if err.span.primary_spans().iter().any(|s| in_external_macro(sess, *s)) {
271             // Any suggestions made here are likely to be incorrect, so anything we
272             // emit shouldn't be automatically fixed by rustfix.
273             err.allow_suggestions(false);
274
275             // If this is a future incompatible that is not an edition fixing lint
276             // it'll become a hard error, so we have to emit *something*. Also,
277             // if this lint occurs in the expansion of a macro from an external crate,
278             // allow individual lints to opt-out from being reported.
279             let not_future_incompatible =
280                 future_incompatible.map(|f| f.reason.edition().is_some()).unwrap_or(true);
281             if not_future_incompatible && !lint.report_in_external_macro {
282                 err.cancel();
283                 // Don't continue further, since we don't want to have
284                 // `diag_span_note_once` called for a diagnostic that isn't emitted.
285                 return;
286             }
287         }
288
289         let name = lint.name_lower();
290         match src {
291             LintLevelSource::Default => {
292                 sess.diag_note_once(
293                     &mut err,
294                     DiagnosticMessageId::from(lint),
295                     &format!("`#[{}({})]` on by default", level.as_str(), name),
296                 );
297             }
298             LintLevelSource::CommandLine(lint_flag_val, orig_level) => {
299                 let flag = match orig_level {
300                     Level::Warn => "-W",
301                     Level::Deny => "-D",
302                     Level::Forbid => "-F",
303                     Level::Allow => "-A",
304                     Level::ForceWarn => "--force-warns",
305                 };
306                 let hyphen_case_lint_name = name.replace("_", "-");
307                 if lint_flag_val.as_str() == name {
308                     sess.diag_note_once(
309                         &mut err,
310                         DiagnosticMessageId::from(lint),
311                         &format!(
312                             "requested on the command line with `{} {}`",
313                             flag, hyphen_case_lint_name
314                         ),
315                     );
316                 } else {
317                     let hyphen_case_flag_val = lint_flag_val.as_str().replace("_", "-");
318                     sess.diag_note_once(
319                         &mut err,
320                         DiagnosticMessageId::from(lint),
321                         &format!(
322                             "`{} {}` implied by `{} {}`",
323                             flag, hyphen_case_lint_name, flag, hyphen_case_flag_val
324                         ),
325                     );
326                 }
327             }
328             LintLevelSource::Node(lint_attr_name, src, reason) => {
329                 if let Some(rationale) = reason {
330                     err.note(&rationale.as_str());
331                 }
332                 sess.diag_span_note_once(
333                     &mut err,
334                     DiagnosticMessageId::from(lint),
335                     src,
336                     "the lint level is defined here",
337                 );
338                 if lint_attr_name.as_str() != name {
339                     let level_str = level.as_str();
340                     sess.diag_note_once(
341                         &mut err,
342                         DiagnosticMessageId::from(lint),
343                         &format!(
344                             "`#[{}({})]` implied by `#[{}({})]`",
345                             level_str, name, level_str, lint_attr_name
346                         ),
347                     );
348                 }
349             }
350         }
351
352         err.code(DiagnosticId::Lint { name, has_future_breakage });
353
354         if let Some(future_incompatible) = future_incompatible {
355             let explanation = if lint_id == LintId::of(builtin::UNSTABLE_NAME_COLLISIONS) {
356                 "once this associated item is added to the standard library, the ambiguity may \
357                  cause an error or change in behavior!"
358                     .to_owned()
359             } else if lint_id == LintId::of(builtin::MUTABLE_BORROW_RESERVATION_CONFLICT) {
360                 "this borrowing pattern was not meant to be accepted, and may become a hard error \
361                  in the future"
362                     .to_owned()
363             } else if let FutureIncompatibilityReason::EditionError(edition) =
364                 future_incompatible.reason
365             {
366                 let current_edition = sess.edition();
367                 format!(
368                     "this is accepted in the current edition (Rust {}) but is a hard error in Rust {}!",
369                     current_edition, edition
370                 )
371             } else if let FutureIncompatibilityReason::EditionSemanticsChange(edition) =
372                 future_incompatible.reason
373             {
374                 format!("this changes meaning in Rust {}", edition)
375             } else {
376                 "this was previously accepted by the compiler but is being phased out; \
377                  it will become a hard error in a future release!"
378                     .to_owned()
379             };
380             if future_incompatible.explain_reason {
381                 err.warn(&explanation);
382             }
383             if !future_incompatible.reference.is_empty() {
384                 let citation =
385                     format!("for more information, see {}", future_incompatible.reference);
386                 err.note(&citation);
387             }
388         }
389
390         // Finally, run `decorate`. This function is also responsible for emitting the diagnostic.
391         decorate(LintDiagnosticBuilder::new(err));
392     }
393     struct_lint_level_impl(sess, lint, level, src, span, Box::new(decorate))
394 }
395
396 /// Returns whether `span` originates in a foreign crate's external macro.
397 ///
398 /// This is used to test whether a lint should not even begin to figure out whether it should
399 /// be reported on the current node.
400 pub fn in_external_macro(sess: &Session, span: Span) -> bool {
401     let expn_data = span.ctxt().outer_expn_data();
402     match expn_data.kind {
403         ExpnKind::Inlined | ExpnKind::Root | ExpnKind::Desugaring(DesugaringKind::ForLoop(_)) => {
404             false
405         }
406         ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) => true, // well, it's "external"
407         ExpnKind::Macro { kind: MacroKind::Bang, name: _, proc_macro: _ } => {
408             // Dummy span for the `def_site` means it's an external macro.
409             expn_data.def_site.is_dummy() || sess.source_map().is_imported(expn_data.def_site)
410         }
411         ExpnKind::Macro { .. } => true, // definitely a plugin
412     }
413 }