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