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