]> git.lizzy.rs Git - rust.git/blob - src/librustc/lint/levels.rs
d43bc57ea7a605bac88a983dfce277ddc2cdfc7b
[rust.git] / src / librustc / lint / levels.rs
1 use std::cmp;
2
3 use crate::ich::StableHashingContext;
4 use crate::lint;
5 use crate::lint::context::{CheckLintNameResult, LintStore};
6 use rustc_data_structures::fx::FxHashMap;
7 use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
8 use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder};
9 use rustc_hir::HirId;
10 use rustc_session::lint::{builtin, Level, Lint, LintId};
11 use rustc_session::Session;
12 use rustc_span::source_map::MultiSpan;
13 use rustc_span::symbol::{sym, Symbol};
14 use rustc_span::Span;
15 use syntax::ast;
16 use syntax::attr;
17 use syntax::print::pprust;
18 use syntax::sess::feature_err;
19
20 use rustc_error_codes::*;
21
22 /// How a lint level was set.
23 #[derive(Clone, Copy, PartialEq, Eq, HashStable)]
24 pub enum LintSource {
25     /// Lint is at the default level as declared
26     /// in rustc or a plugin.
27     Default,
28
29     /// Lint level was set by an attribute.
30     Node(Symbol, Span, Option<Symbol> /* RFC 2383 reason */),
31
32     /// Lint level was set by a command-line flag.
33     CommandLine(Symbol),
34 }
35
36 pub type LevelSource = (Level, LintSource);
37
38 pub struct LintLevelSets {
39     list: Vec<LintSet>,
40     lint_cap: Level,
41 }
42
43 enum LintSet {
44     CommandLine {
45         // -A,-W,-D flags, a `Symbol` for the flag itself and `Level` for which
46         // flag.
47         specs: FxHashMap<LintId, LevelSource>,
48     },
49
50     Node {
51         specs: FxHashMap<LintId, LevelSource>,
52         parent: u32,
53     },
54 }
55
56 impl LintLevelSets {
57     pub fn new() -> Self {
58         LintLevelSets { list: Vec::new(), lint_cap: Level::Forbid }
59     }
60
61     pub fn get_lint_level(
62         &self,
63         lint: &'static Lint,
64         idx: u32,
65         aux: Option<&FxHashMap<LintId, LevelSource>>,
66         sess: &Session,
67     ) -> LevelSource {
68         let (level, mut src) = self.get_lint_id_level(LintId::of(lint), idx, aux);
69
70         // If `level` is none then we actually assume the default level for this
71         // lint.
72         let mut level = level.unwrap_or_else(|| lint.default_level(sess.edition()));
73
74         // If we're about to issue a warning, check at the last minute for any
75         // directives against the warnings "lint". If, for example, there's an
76         // `allow(warnings)` in scope then we want to respect that instead.
77         if level == Level::Warn {
78             let (warnings_level, warnings_src) =
79                 self.get_lint_id_level(LintId::of(builtin::WARNINGS), idx, aux);
80             if let Some(configured_warning_level) = warnings_level {
81                 if configured_warning_level != Level::Warn {
82                     level = configured_warning_level;
83                     src = warnings_src;
84                 }
85             }
86         }
87
88         // Ensure that we never exceed the `--cap-lints` argument.
89         level = cmp::min(level, self.lint_cap);
90
91         if let Some(driver_level) = sess.driver_lint_caps.get(&LintId::of(lint)) {
92             // Ensure that we never exceed driver level.
93             level = cmp::min(*driver_level, level);
94         }
95
96         return (level, src);
97     }
98
99     pub fn get_lint_id_level(
100         &self,
101         id: LintId,
102         mut idx: u32,
103         aux: Option<&FxHashMap<LintId, LevelSource>>,
104     ) -> (Option<Level>, LintSource) {
105         if let Some(specs) = aux {
106             if let Some(&(level, src)) = specs.get(&id) {
107                 return (Some(level), src);
108             }
109         }
110         loop {
111             match self.list[idx as usize] {
112                 LintSet::CommandLine { ref specs } => {
113                     if let Some(&(level, src)) = specs.get(&id) {
114                         return (Some(level), src);
115                     }
116                     return (None, LintSource::Default);
117                 }
118                 LintSet::Node { ref specs, parent } => {
119                     if let Some(&(level, src)) = specs.get(&id) {
120                         return (Some(level), src);
121                     }
122                     idx = parent;
123                 }
124             }
125         }
126     }
127 }
128
129 pub struct LintLevelsBuilder<'a> {
130     sess: &'a Session,
131     sets: LintLevelSets,
132     id_to_set: FxHashMap<HirId, u32>,
133     cur: u32,
134     warn_about_weird_lints: bool,
135 }
136
137 pub struct BuilderPush {
138     prev: u32,
139     pub changed: bool,
140 }
141
142 impl<'a> LintLevelsBuilder<'a> {
143     pub fn new(sess: &'a Session, warn_about_weird_lints: bool, store: &LintStore) -> Self {
144         let mut builder = LintLevelsBuilder {
145             sess,
146             sets: LintLevelSets::new(),
147             cur: 0,
148             id_to_set: Default::default(),
149             warn_about_weird_lints,
150         };
151         builder.process_command_line(sess, store);
152         assert_eq!(builder.sets.list.len(), 1);
153         builder
154     }
155
156     fn process_command_line(&mut self, sess: &Session, store: &LintStore) {
157         let mut specs = FxHashMap::default();
158         self.sets.lint_cap = sess.opts.lint_cap.unwrap_or(Level::Forbid);
159
160         for &(ref lint_name, level) in &sess.opts.lint_opts {
161             store.check_lint_name_cmdline(sess, &lint_name, level);
162
163             // If the cap is less than this specified level, e.g., if we've got
164             // `--cap-lints allow` but we've also got `-D foo` then we ignore
165             // this specification as the lint cap will set it to allow anyway.
166             let level = cmp::min(level, self.sets.lint_cap);
167
168             let lint_flag_val = Symbol::intern(lint_name);
169             let ids = match store.find_lints(&lint_name) {
170                 Ok(ids) => ids,
171                 Err(_) => continue, // errors handled in check_lint_name_cmdline above
172             };
173             for id in ids {
174                 let src = LintSource::CommandLine(lint_flag_val);
175                 specs.insert(id, (level, src));
176             }
177         }
178
179         self.sets.list.push(LintSet::CommandLine { specs });
180     }
181
182     /// Pushes a list of AST lint attributes onto this context.
183     ///
184     /// This function will return a `BuilderPush` object which should be passed
185     /// to `pop` when this scope for the attributes provided is exited.
186     ///
187     /// This function will perform a number of tasks:
188     ///
189     /// * It'll validate all lint-related attributes in `attrs`
190     /// * It'll mark all lint-related attributes as used
191     /// * Lint levels will be updated based on the attributes provided
192     /// * Lint attributes are validated, e.g., a #[forbid] can't be switched to
193     ///   #[allow]
194     ///
195     /// Don't forget to call `pop`!
196     pub fn push(&mut self, attrs: &[ast::Attribute], store: &LintStore) -> BuilderPush {
197         let mut specs = FxHashMap::default();
198         let sess = self.sess;
199         let bad_attr = |span| struct_span_err!(sess, span, E0452, "malformed lint attribute input");
200         for attr in attrs {
201             let level = match Level::from_symbol(attr.name_or_empty()) {
202                 None => continue,
203                 Some(lvl) => lvl,
204             };
205
206             let meta = unwrap_or!(attr.meta(), continue);
207             attr::mark_used(attr);
208
209             let mut metas = unwrap_or!(meta.meta_item_list(), continue);
210
211             if metas.is_empty() {
212                 // FIXME (#55112): issue unused-attributes lint for `#[level()]`
213                 continue;
214             }
215
216             // Before processing the lint names, look for a reason (RFC 2383)
217             // at the end.
218             let mut reason = None;
219             let tail_li = &metas[metas.len() - 1];
220             if let Some(item) = tail_li.meta_item() {
221                 match item.kind {
222                     ast::MetaItemKind::Word => {} // actual lint names handled later
223                     ast::MetaItemKind::NameValue(ref name_value) => {
224                         if item.path == sym::reason {
225                             // found reason, reslice meta list to exclude it
226                             metas = &metas[0..metas.len() - 1];
227                             // FIXME (#55112): issue unused-attributes lint if we thereby
228                             // don't have any lint names (`#[level(reason = "foo")]`)
229                             if let ast::LitKind::Str(rationale, _) = name_value.kind {
230                                 if !self.sess.features_untracked().lint_reasons {
231                                     feature_err(
232                                         &self.sess.parse_sess,
233                                         sym::lint_reasons,
234                                         item.span,
235                                         "lint reasons are experimental",
236                                     )
237                                     .emit();
238                                 }
239                                 reason = Some(rationale);
240                             } else {
241                                 bad_attr(name_value.span)
242                                     .span_label(name_value.span, "reason must be a string literal")
243                                     .emit();
244                             }
245                         } else {
246                             bad_attr(item.span)
247                                 .span_label(item.span, "bad attribute argument")
248                                 .emit();
249                         }
250                     }
251                     ast::MetaItemKind::List(_) => {
252                         bad_attr(item.span).span_label(item.span, "bad attribute argument").emit();
253                     }
254                 }
255             }
256
257             for li in metas {
258                 let meta_item = match li.meta_item() {
259                     Some(meta_item) if meta_item.is_word() => meta_item,
260                     _ => {
261                         let sp = li.span();
262                         let mut err = bad_attr(sp);
263                         let mut add_label = true;
264                         if let Some(item) = li.meta_item() {
265                             if let ast::MetaItemKind::NameValue(_) = item.kind {
266                                 if item.path == sym::reason {
267                                     err.span_label(sp, "reason in lint attribute must come last");
268                                     add_label = false;
269                                 }
270                             }
271                         }
272                         if add_label {
273                             err.span_label(sp, "bad attribute argument");
274                         }
275                         err.emit();
276                         continue;
277                     }
278                 };
279                 let tool_name = if meta_item.path.segments.len() > 1 {
280                     let tool_ident = meta_item.path.segments[0].ident;
281                     if !attr::is_known_lint_tool(tool_ident) {
282                         struct_span_err!(
283                             sess,
284                             tool_ident.span,
285                             E0710,
286                             "an unknown tool name found in scoped lint: `{}`",
287                             pprust::path_to_string(&meta_item.path),
288                         )
289                         .emit();
290                         continue;
291                     }
292
293                     Some(tool_ident.name)
294                 } else {
295                     None
296                 };
297                 let name = meta_item.path.segments.last().expect("empty lint name").ident.name;
298                 match store.check_lint_name(&name.as_str(), tool_name) {
299                     CheckLintNameResult::Ok(ids) => {
300                         let src = LintSource::Node(name, li.span(), reason);
301                         for id in ids {
302                             specs.insert(*id, (level, src));
303                         }
304                     }
305
306                     CheckLintNameResult::Tool(result) => {
307                         match result {
308                             Ok(ids) => {
309                                 let complete_name = &format!("{}::{}", tool_name.unwrap(), name);
310                                 let src = LintSource::Node(
311                                     Symbol::intern(complete_name),
312                                     li.span(),
313                                     reason,
314                                 );
315                                 for id in ids {
316                                     specs.insert(*id, (level, src));
317                                 }
318                             }
319                             Err((Some(ids), new_lint_name)) => {
320                                 let lint = builtin::RENAMED_AND_REMOVED_LINTS;
321                                 let (lvl, src) =
322                                     self.sets.get_lint_level(lint, self.cur, Some(&specs), &sess);
323                                 let msg = format!(
324                                     "lint name `{}` is deprecated \
325                                      and may not have an effect in the future. \
326                                      Also `cfg_attr(cargo-clippy)` won't be necessary anymore",
327                                     name
328                                 );
329                                 lint::struct_lint_level(
330                                     self.sess,
331                                     lint,
332                                     lvl,
333                                     src,
334                                     Some(li.span().into()),
335                                     &msg,
336                                 )
337                                 .span_suggestion(
338                                     li.span(),
339                                     "change it to",
340                                     new_lint_name.to_string(),
341                                     Applicability::MachineApplicable,
342                                 )
343                                 .emit();
344
345                                 let src = LintSource::Node(
346                                     Symbol::intern(&new_lint_name),
347                                     li.span(),
348                                     reason,
349                                 );
350                                 for id in ids {
351                                     specs.insert(*id, (level, src));
352                                 }
353                             }
354                             Err((None, _)) => {
355                                 // If Tool(Err(None, _)) is returned, then either the lint does not
356                                 // exist in the tool or the code was not compiled with the tool and
357                                 // therefore the lint was never added to the `LintStore`. To detect
358                                 // this is the responsibility of the lint tool.
359                             }
360                         }
361                     }
362
363                     _ if !self.warn_about_weird_lints => {}
364
365                     CheckLintNameResult::Warning(msg, renamed) => {
366                         let lint = builtin::RENAMED_AND_REMOVED_LINTS;
367                         let (level, src) =
368                             self.sets.get_lint_level(lint, self.cur, Some(&specs), &sess);
369                         let mut err = lint::struct_lint_level(
370                             self.sess,
371                             lint,
372                             level,
373                             src,
374                             Some(li.span().into()),
375                             &msg,
376                         );
377                         if let Some(new_name) = renamed {
378                             err.span_suggestion(
379                                 li.span(),
380                                 "use the new name",
381                                 new_name,
382                                 Applicability::MachineApplicable,
383                             );
384                         }
385                         err.emit();
386                     }
387                     CheckLintNameResult::NoLint(suggestion) => {
388                         let lint = builtin::UNKNOWN_LINTS;
389                         let (level, src) =
390                             self.sets.get_lint_level(lint, self.cur, Some(&specs), self.sess);
391                         let msg = format!("unknown lint: `{}`", name);
392                         let mut db = lint::struct_lint_level(
393                             self.sess,
394                             lint,
395                             level,
396                             src,
397                             Some(li.span().into()),
398                             &msg,
399                         );
400
401                         if let Some(suggestion) = suggestion {
402                             db.span_suggestion(
403                                 li.span(),
404                                 "did you mean",
405                                 suggestion.to_string(),
406                                 Applicability::MachineApplicable,
407                             );
408                         }
409
410                         db.emit();
411                     }
412                 }
413             }
414         }
415
416         for (id, &(level, ref src)) in specs.iter() {
417             if level == Level::Forbid {
418                 continue;
419             }
420             let forbid_src = match self.sets.get_lint_id_level(*id, self.cur, None) {
421                 (Some(Level::Forbid), src) => src,
422                 _ => continue,
423             };
424             let forbidden_lint_name = match forbid_src {
425                 LintSource::Default => id.to_string(),
426                 LintSource::Node(name, _, _) => name.to_string(),
427                 LintSource::CommandLine(name) => name.to_string(),
428             };
429             let (lint_attr_name, lint_attr_span) = match *src {
430                 LintSource::Node(name, span, _) => (name, span),
431                 _ => continue,
432             };
433             let mut diag_builder = struct_span_err!(
434                 self.sess,
435                 lint_attr_span,
436                 E0453,
437                 "{}({}) overruled by outer forbid({})",
438                 level.as_str(),
439                 lint_attr_name,
440                 forbidden_lint_name
441             );
442             diag_builder.span_label(lint_attr_span, "overruled by previous forbid");
443             match forbid_src {
444                 LintSource::Default => {}
445                 LintSource::Node(_, forbid_source_span, reason) => {
446                     diag_builder.span_label(forbid_source_span, "`forbid` level set here");
447                     if let Some(rationale) = reason {
448                         diag_builder.note(&rationale.as_str());
449                     }
450                 }
451                 LintSource::CommandLine(_) => {
452                     diag_builder.note("`forbid` lint level was set on command line");
453                 }
454             }
455             diag_builder.emit();
456             // don't set a separate error for every lint in the group
457             break;
458         }
459
460         let prev = self.cur;
461         if specs.len() > 0 {
462             self.cur = self.sets.list.len() as u32;
463             self.sets.list.push(LintSet::Node { specs: specs, parent: prev });
464         }
465
466         BuilderPush { prev: prev, changed: prev != self.cur }
467     }
468
469     /// Called after `push` when the scope of a set of attributes are exited.
470     pub fn pop(&mut self, push: BuilderPush) {
471         self.cur = push.prev;
472     }
473
474     /// Used to emit a lint-related diagnostic based on the current state of
475     /// this lint context.
476     pub fn struct_lint(
477         &self,
478         lint: &'static Lint,
479         span: Option<MultiSpan>,
480         msg: &str,
481     ) -> DiagnosticBuilder<'a> {
482         let (level, src) = self.sets.get_lint_level(lint, self.cur, None, self.sess);
483         lint::struct_lint_level(self.sess, lint, level, src, span, msg)
484     }
485
486     /// Registers the ID provided with the current set of lints stored in
487     /// this context.
488     pub fn register_id(&mut self, id: HirId) {
489         self.id_to_set.insert(id, self.cur);
490     }
491
492     pub fn build(self) -> LintLevelSets {
493         self.sets
494     }
495
496     pub fn build_map(self) -> LintLevelMap {
497         LintLevelMap { sets: self.sets, id_to_set: self.id_to_set }
498     }
499 }
500
501 pub struct LintLevelMap {
502     sets: LintLevelSets,
503     id_to_set: FxHashMap<HirId, u32>,
504 }
505
506 impl LintLevelMap {
507     /// If the `id` was previously registered with `register_id` when building
508     /// this `LintLevelMap` this returns the corresponding lint level and source
509     /// of the lint level for the lint provided.
510     ///
511     /// If the `id` was not previously registered, returns `None`. If `None` is
512     /// returned then the parent of `id` should be acquired and this function
513     /// should be called again.
514     pub fn level_and_source(
515         &self,
516         lint: &'static Lint,
517         id: HirId,
518         session: &Session,
519     ) -> Option<LevelSource> {
520         self.id_to_set.get(&id).map(|idx| self.sets.get_lint_level(lint, *idx, None, session))
521     }
522 }
523
524 impl<'a> HashStable<StableHashingContext<'a>> for LintLevelMap {
525     #[inline]
526     fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) {
527         let LintLevelMap { ref sets, ref id_to_set } = *self;
528
529         id_to_set.hash_stable(hcx, hasher);
530
531         let LintLevelSets { ref list, lint_cap } = *sets;
532
533         lint_cap.hash_stable(hcx, hasher);
534
535         hcx.while_hashing_spans(true, |hcx| {
536             list.len().hash_stable(hcx, hasher);
537
538             // We are working under the assumption here that the list of
539             // lint-sets is built in a deterministic order.
540             for lint_set in list {
541                 ::std::mem::discriminant(lint_set).hash_stable(hcx, hasher);
542
543                 match *lint_set {
544                     LintSet::CommandLine { ref specs } => {
545                         specs.hash_stable(hcx, hasher);
546                     }
547                     LintSet::Node { ref specs, parent } => {
548                         specs.hash_stable(hcx, hasher);
549                         parent.hash_stable(hcx, hasher);
550                     }
551                 }
552             }
553         })
554     }
555 }