]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_middle/src/lint.rs
Compute `lint_levels` by definition
[rust.git] / compiler / rustc_middle / src / lint.rs
1 use std::cmp;
2
3 use rustc_data_structures::fx::FxHashMap;
4 use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
5 use rustc_errors::{Diagnostic, DiagnosticId, LintDiagnosticBuilder, MultiSpan};
6 use rustc_hir::HirId;
7 use rustc_index::vec::IndexVec;
8 use rustc_query_system::ich::StableHashingContext;
9 use rustc_session::lint::{
10     builtin::{self, FORBIDDEN_LINT_GROUPS},
11     FutureIncompatibilityReason, Level, Lint, LintExpectationId, LintId,
12 };
13 use rustc_session::Session;
14 use rustc_span::hygiene::MacroKind;
15 use rustc_span::source_map::{DesugaringKind, ExpnKind};
16 use rustc_span::{symbol, Span, Symbol, DUMMY_SP};
17
18 use crate::ty::TyCtxt;
19
20 /// How a lint level was set.
21 #[derive(Clone, Copy, PartialEq, Eq, HashStable, Debug)]
22 pub enum LintLevelSource {
23     /// Lint is at the default level as declared
24     /// in rustc or a plugin.
25     Default,
26
27     /// Lint level was set by an attribute.
28     Node {
29         name: Symbol,
30         span: Span,
31         /// RFC 2383 reason
32         reason: Option<Symbol>,
33         /// The lint tool. (e.g. rustdoc, clippy)
34         tool: Option<Symbol>,
35     },
36
37     /// Lint level was set by a command-line flag.
38     /// The provided `Level` is the level specified on the command line.
39     /// (The actual level may be lower due to `--cap-lints`.)
40     CommandLine(Symbol, Level),
41 }
42
43 impl LintLevelSource {
44     pub fn name(&self) -> Symbol {
45         match *self {
46             LintLevelSource::Default => symbol::kw::Default,
47             LintLevelSource::Node { name, .. } => name,
48             LintLevelSource::CommandLine(name, _) => name,
49         }
50     }
51
52     pub fn span(&self) -> Span {
53         match *self {
54             LintLevelSource::Default => DUMMY_SP,
55             LintLevelSource::Node { span, .. } => span,
56             LintLevelSource::CommandLine(_, _) => DUMMY_SP,
57         }
58     }
59 }
60
61 /// A tuple of a lint level and its source.
62 pub type LevelAndSource = (Level, LintLevelSource);
63
64 #[derive(Debug, HashStable)]
65 pub struct LintLevelSets {
66     pub list: IndexVec<LintStackIndex, LintSet>,
67 }
68
69 rustc_index::newtype_index! {
70     #[derive(HashStable)]
71     pub struct LintStackIndex {
72         const COMMAND_LINE = 0,
73     }
74 }
75
76 #[derive(Debug, HashStable)]
77 pub struct LintSet {
78     // -A,-W,-D flags, a `Symbol` for the flag itself and `Level` for which
79     // flag.
80     pub specs: FxHashMap<LintId, LevelAndSource>,
81
82     pub parent: LintStackIndex,
83 }
84
85 impl LintLevelSets {
86     pub fn new() -> Self {
87         LintLevelSets { list: IndexVec::new() }
88     }
89
90     pub fn actual_level(
91         level: Option<Level>,
92         src: &mut LintLevelSource,
93         sess: &Session,
94         lint: &'static Lint,
95         get_lint_id_level: impl FnOnce(LintId) -> (Option<Level>, LintLevelSource),
96     ) -> Level {
97         // If `level` is none then we actually assume the default level for this
98         // lint.
99         let mut level = level.unwrap_or_else(|| lint.default_level(sess.edition()));
100
101         // If we're about to issue a warning, check at the last minute for any
102         // directives against the warnings "lint". If, for example, there's an
103         // `allow(warnings)` in scope then we want to respect that instead.
104         //
105         // We exempt `FORBIDDEN_LINT_GROUPS` from this because it specifically
106         // triggers in cases (like #80988) where you have `forbid(warnings)`,
107         // and so if we turned that into an error, it'd defeat the purpose of the
108         // future compatibility warning.
109         if level == Level::Warn && LintId::of(lint) != LintId::of(FORBIDDEN_LINT_GROUPS) {
110             let (warnings_level, warnings_src) = get_lint_id_level(LintId::of(builtin::WARNINGS));
111             if let Some(configured_warning_level) = warnings_level {
112                 if configured_warning_level != Level::Warn {
113                     level = configured_warning_level;
114                     *src = warnings_src;
115                 }
116             }
117         }
118
119         // Ensure that we never exceed the `--cap-lints` argument
120         // unless the source is a --force-warn
121         level = if let LintLevelSource::CommandLine(_, Level::ForceWarn(_)) = src {
122             level
123         } else {
124             cmp::min(level, sess.opts.lint_cap.unwrap_or(Level::Forbid))
125         };
126
127         if let Some(driver_level) = sess.driver_lint_caps.get(&LintId::of(lint)) {
128             // Ensure that we never exceed driver level.
129             level = cmp::min(*driver_level, level);
130         }
131
132         level
133     }
134
135     pub fn get_lint_level(
136         &self,
137         lint: &'static Lint,
138         idx: LintStackIndex,
139         aux: Option<&FxHashMap<LintId, LevelAndSource>>,
140         sess: &Session,
141     ) -> LevelAndSource {
142         let (level, mut src) = self.get_lint_id_level(LintId::of(lint), idx, aux);
143
144         let level = Self::actual_level(level, &mut src, sess, lint, |id| {
145             self.get_lint_id_level(id, idx, aux)
146         });
147
148         (level, src)
149     }
150
151     pub fn get_lint_id_level(
152         &self,
153         id: LintId,
154         mut idx: LintStackIndex,
155         aux: Option<&FxHashMap<LintId, LevelAndSource>>,
156     ) -> (Option<Level>, LintLevelSource) {
157         if let Some(specs) = aux {
158             if let Some(&(level, src)) = specs.get(&id) {
159                 return (Some(level), src);
160             }
161         }
162         loop {
163             let LintSet { ref specs, parent } = self.list[idx];
164             if let Some(&(level, src)) = specs.get(&id) {
165                 return (Some(level), src);
166             }
167             if idx == COMMAND_LINE {
168                 return (None, LintLevelSource::Default);
169             }
170             idx = parent;
171         }
172     }
173 }
174
175 #[derive(Debug)]
176 pub struct LintLevelMap {
177     /// This is a collection of lint expectations as described in RFC 2383, that
178     /// can be fulfilled during this compilation session. This means that at least
179     /// one expected lint is currently registered in the lint store.
180     ///
181     /// The [`LintExpectationId`] is stored as a part of the [`Expect`](Level::Expect)
182     /// lint level.
183     pub lint_expectations: Vec<(LintExpectationId, LintExpectation)>,
184     pub sets: LintLevelSets,
185     pub id_to_set: FxHashMap<HirId, LintStackIndex>,
186 }
187
188 impl LintLevelMap {
189     /// If the `id` was previously registered with `register_id` when building
190     /// this `LintLevelMap` this returns the corresponding lint level and source
191     /// of the lint level for the lint provided.
192     ///
193     /// If the `id` was not previously registered, returns `None`. If `None` is
194     /// returned then the parent of `id` should be acquired and this function
195     /// should be called again.
196     pub fn level_and_source(
197         &self,
198         lint: &'static Lint,
199         id: HirId,
200         session: &Session,
201     ) -> Option<LevelAndSource> {
202         self.id_to_set.get(&id).map(|idx| self.sets.get_lint_level(lint, *idx, None, session))
203     }
204 }
205
206 impl<'a> HashStable<StableHashingContext<'a>> for LintLevelMap {
207     #[inline]
208     fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) {
209         let LintLevelMap { ref sets, ref id_to_set, ref lint_expectations } = *self;
210
211         id_to_set.hash_stable(hcx, hasher);
212         lint_expectations.hash_stable(hcx, hasher);
213
214         hcx.while_hashing_spans(true, |hcx| sets.hash_stable(hcx, hasher))
215     }
216 }
217 pub struct LintLevelQueryMap<'tcx> {
218     pub tcx: TyCtxt<'tcx>,
219     pub cur: HirId,
220     pub specs: FxHashMap<LintId, LevelAndSource>,
221 }
222
223 impl<'tcx> LintLevelQueryMap<'tcx> {
224     pub fn lint_id_level(&self, id: LintId) -> (Option<Level>, LintLevelSource) {
225         Self::get_lint_id_level(id, self.cur, self.tcx, &self.specs)
226     }
227
228     pub fn lint_level(&self, lint: &'static Lint) -> LevelAndSource {
229         Self::get_lint_level(LintId::of(lint), self.cur, self.tcx, &self.specs)
230     }
231
232     pub fn get_lint_id_level(
233         id: LintId,
234         cur: HirId,
235         tcx: TyCtxt<'tcx>,
236         specs: &FxHashMap<LintId, LevelAndSource>,
237     ) -> (Option<Level>, LintLevelSource) {
238         if let Some(&(level, src)) = specs.get(&id) {
239             return (Some(level), src);
240         }
241         let mut cur = cur;
242
243         loop {
244             let parent = tcx.hir().get_parent_node(cur);
245             if cur == parent {
246                 return (None, LintLevelSource::Default);
247             }
248             let specs = tcx.lint_levels_on(parent);
249             if let Some(&(level, src)) = specs.get(&id) {
250                 return (Some(level), src);
251             }
252             cur = parent
253         }
254     }
255
256     pub fn get_lint_level(
257         id: LintId,
258         cur: HirId,
259         tcx: TyCtxt<'tcx>,
260         specs: &FxHashMap<LintId, LevelAndSource>,
261     ) -> (Level, LintLevelSource) {
262         let (level, mut src) = Self::get_lint_id_level(id, cur, tcx, specs);
263         let level = LintLevelSets::actual_level(level, &mut src, tcx.sess, id.lint, |id| {
264             Self::get_lint_id_level(id, cur, tcx, specs)
265         });
266         (level, src)
267     }
268 }
269
270 /// This struct represents a lint expectation and holds all required information
271 /// to emit the `unfulfilled_lint_expectations` lint if it is unfulfilled after
272 /// the `LateLintPass` has completed.
273 #[derive(Clone, Debug, HashStable)]
274 pub struct LintExpectation {
275     /// The reason for this expectation that can optionally be added as part of
276     /// the attribute. It will be displayed as part of the lint message.
277     pub reason: Option<Symbol>,
278     /// The [`Span`] of the attribute that this expectation originated from.
279     pub emission_span: Span,
280     /// Lint messages for the `unfulfilled_lint_expectations` lint will be
281     /// adjusted to include an additional note. Therefore, we have to track if
282     /// the expectation is for the lint.
283     pub is_unfulfilled_lint_expectations: bool,
284     /// This will hold the name of the tool that this lint belongs to. For
285     /// the lint `clippy::some_lint` the tool would be `clippy`, the same
286     /// goes for `rustdoc`. This will be `None` for rustc lints
287     pub lint_tool: Option<Symbol>,
288 }
289
290 impl LintExpectation {
291     pub fn new(
292         reason: Option<Symbol>,
293         emission_span: Span,
294         is_unfulfilled_lint_expectations: bool,
295         lint_tool: Option<Symbol>,
296     ) -> Self {
297         Self { reason, emission_span, is_unfulfilled_lint_expectations, lint_tool }
298     }
299 }
300
301 pub fn explain_lint_level_source(
302     lint: &'static Lint,
303     level: Level,
304     src: LintLevelSource,
305     err: &mut Diagnostic,
306 ) {
307     let name = lint.name_lower();
308     match src {
309         LintLevelSource::Default => {
310             err.note_once(&format!("`#[{}({})]` on by default", level.as_str(), name));
311         }
312         LintLevelSource::CommandLine(lint_flag_val, orig_level) => {
313             let flag = match orig_level {
314                 Level::Warn => "-W",
315                 Level::Deny => "-D",
316                 Level::Forbid => "-F",
317                 Level::Allow => "-A",
318                 Level::ForceWarn(_) => "--force-warn",
319                 Level::Expect(_) => {
320                     unreachable!("the expect level does not have a commandline flag")
321                 }
322             };
323             let hyphen_case_lint_name = name.replace('_', "-");
324             if lint_flag_val.as_str() == name {
325                 err.note_once(&format!(
326                     "requested on the command line with `{} {}`",
327                     flag, hyphen_case_lint_name
328                 ));
329             } else {
330                 let hyphen_case_flag_val = lint_flag_val.as_str().replace('_', "-");
331                 err.note_once(&format!(
332                     "`{} {}` implied by `{} {}`",
333                     flag, hyphen_case_lint_name, flag, hyphen_case_flag_val
334                 ));
335             }
336         }
337         LintLevelSource::Node { name: lint_attr_name, span, reason, .. } => {
338             if let Some(rationale) = reason {
339                 err.note(rationale.as_str());
340             }
341             err.span_note_once(span, "the lint level is defined here");
342             if lint_attr_name.as_str() != name {
343                 let level_str = level.as_str();
344                 err.note_once(&format!(
345                     "`#[{}({})]` implied by `#[{}({})]`",
346                     level_str, name, level_str, lint_attr_name
347                 ));
348             }
349         }
350     }
351 }
352
353 pub fn struct_lint_level<'s, 'd>(
354     sess: &'s Session,
355     lint: &'static Lint,
356     level: Level,
357     src: LintLevelSource,
358     span: Option<MultiSpan>,
359     decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>) + 'd,
360 ) {
361     // Avoid codegen bloat from monomorphization by immediately doing dyn dispatch of `decorate` to
362     // the "real" work.
363     fn struct_lint_level_impl<'s, 'd>(
364         sess: &'s Session,
365         lint: &'static Lint,
366         level: Level,
367         src: LintLevelSource,
368         span: Option<MultiSpan>,
369         decorate: Box<dyn for<'b> FnOnce(LintDiagnosticBuilder<'b, ()>) + 'd>,
370     ) {
371         // Check for future incompatibility lints and issue a stronger warning.
372         let future_incompatible = lint.future_incompatible;
373
374         let has_future_breakage = future_incompatible.map_or(
375             // Default allow lints trigger too often for testing.
376             sess.opts.unstable_opts.future_incompat_test && lint.default_level != Level::Allow,
377             |incompat| {
378                 matches!(incompat.reason, FutureIncompatibilityReason::FutureReleaseErrorReportNow)
379             },
380         );
381
382         let mut err = match (level, span) {
383             (Level::Allow, span) => {
384                 if has_future_breakage {
385                     if let Some(span) = span {
386                         sess.struct_span_allow(span, "")
387                     } else {
388                         sess.struct_allow("")
389                     }
390                 } else {
391                     return;
392                 }
393             }
394             (Level::Expect(expect_id), _) => {
395                 // This case is special as we actually allow the lint itself in this context, but
396                 // we can't return early like in the case for `Level::Allow` because we still
397                 // need the lint diagnostic to be emitted to `rustc_error::HandlerInner`.
398                 //
399                 // We can also not mark the lint expectation as fulfilled here right away, as it
400                 // can still be cancelled in the decorate function. All of this means that we simply
401                 // create a `DiagnosticBuilder` and continue as we would for warnings.
402                 sess.struct_expect("", expect_id)
403             }
404             (Level::ForceWarn(Some(expect_id)), Some(span)) => {
405                 sess.struct_span_warn_with_expectation(span, "", expect_id)
406             }
407             (Level::ForceWarn(Some(expect_id)), None) => {
408                 sess.struct_warn_with_expectation("", expect_id)
409             }
410             (Level::Warn | Level::ForceWarn(None), Some(span)) => sess.struct_span_warn(span, ""),
411             (Level::Warn | Level::ForceWarn(None), None) => sess.struct_warn(""),
412             (Level::Deny | Level::Forbid, Some(span)) => {
413                 let mut builder = sess.diagnostic().struct_err_lint("");
414                 builder.set_span(span);
415                 builder
416             }
417             (Level::Deny | Level::Forbid, None) => sess.diagnostic().struct_err_lint(""),
418         };
419
420         // If this code originates in a foreign macro, aka something that this crate
421         // did not itself author, then it's likely that there's nothing this crate
422         // can do about it. We probably want to skip the lint entirely.
423         if err.span.primary_spans().iter().any(|s| in_external_macro(sess, *s)) {
424             // Any suggestions made here are likely to be incorrect, so anything we
425             // emit shouldn't be automatically fixed by rustfix.
426             err.disable_suggestions();
427
428             // If this is a future incompatible that is not an edition fixing lint
429             // it'll become a hard error, so we have to emit *something*. Also,
430             // if this lint occurs in the expansion of a macro from an external crate,
431             // allow individual lints to opt-out from being reported.
432             let not_future_incompatible =
433                 future_incompatible.map(|f| f.reason.edition().is_some()).unwrap_or(true);
434             if not_future_incompatible && !lint.report_in_external_macro {
435                 err.cancel();
436                 // Don't continue further, since we don't want to have
437                 // `diag_span_note_once` called for a diagnostic that isn't emitted.
438                 return;
439             }
440         }
441
442         // Lint diagnostics that are covered by the expect level will not be emitted outside
443         // the compiler. It is therefore not necessary to add any information for the user.
444         // This will therefore directly call the decorate function which will in turn emit
445         // the `Diagnostic`.
446         if let Level::Expect(_) = level {
447             let name = lint.name_lower();
448             err.code(DiagnosticId::Lint { name, has_future_breakage, is_force_warn: false });
449             decorate(LintDiagnosticBuilder::new(err));
450             return;
451         }
452
453         explain_lint_level_source(lint, level, src, &mut err);
454
455         let name = lint.name_lower();
456         let is_force_warn = matches!(level, Level::ForceWarn(_));
457         err.code(DiagnosticId::Lint { name, has_future_breakage, is_force_warn });
458
459         if let Some(future_incompatible) = future_incompatible {
460             let explanation = match future_incompatible.reason {
461                 FutureIncompatibilityReason::FutureReleaseError
462                 | FutureIncompatibilityReason::FutureReleaseErrorReportNow => {
463                     "this was previously accepted by the compiler but is being phased out; \
464                          it will become a hard error in a future release!"
465                         .to_owned()
466                 }
467                 FutureIncompatibilityReason::FutureReleaseSemanticsChange => {
468                     "this will change its meaning in a future release!".to_owned()
469                 }
470                 FutureIncompatibilityReason::EditionError(edition) => {
471                     let current_edition = sess.edition();
472                     format!(
473                         "this is accepted in the current edition (Rust {}) but is a hard error in Rust {}!",
474                         current_edition, edition
475                     )
476                 }
477                 FutureIncompatibilityReason::EditionSemanticsChange(edition) => {
478                     format!("this changes meaning in Rust {}", edition)
479                 }
480                 FutureIncompatibilityReason::Custom(reason) => reason.to_owned(),
481             };
482
483             if future_incompatible.explain_reason {
484                 err.warn(&explanation);
485             }
486             if !future_incompatible.reference.is_empty() {
487                 let citation =
488                     format!("for more information, see {}", future_incompatible.reference);
489                 err.note(&citation);
490             }
491         }
492
493         // Finally, run `decorate`. This function is also responsible for emitting the diagnostic.
494         decorate(LintDiagnosticBuilder::new(err));
495     }
496     struct_lint_level_impl(sess, lint, level, src, span, Box::new(decorate))
497 }
498
499 /// Returns whether `span` originates in a foreign crate's external macro.
500 ///
501 /// This is used to test whether a lint should not even begin to figure out whether it should
502 /// be reported on the current node.
503 pub fn in_external_macro(sess: &Session, span: Span) -> bool {
504     let expn_data = span.ctxt().outer_expn_data();
505     match expn_data.kind {
506         ExpnKind::Inlined
507         | ExpnKind::Root
508         | ExpnKind::Desugaring(DesugaringKind::ForLoop | DesugaringKind::WhileLoop) => false,
509         ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) => true, // well, it's "external"
510         ExpnKind::Macro(MacroKind::Bang, _) => {
511             // Dummy span for the `def_site` means it's an external macro.
512             expn_data.def_site.is_dummy() || sess.source_map().is_imported(expn_data.def_site)
513         }
514         ExpnKind::Macro { .. } => true, // definitely a plugin
515     }
516 }