]> git.lizzy.rs Git - rust.git/blob - src/librustc/lint/levels.rs
Rollup merge of #61698 - davidtwco:ice-const-generic-length, r=varkor
[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::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,
12                                            StableHasher, StableHasherResult};
13 use syntax::ast;
14 use syntax::attr;
15 use syntax::feature_gate;
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) -> LintLevelSets {
39         let mut me = LintLevelSets {
40             list: Vec::new(),
41             lint_cap: Level::Forbid,
42         };
43         me.process_command_line(sess);
44         return me
45     }
46
47     pub fn builder(sess: &Session) -> LintLevelsBuilder<'_> {
48         LintLevelsBuilder::new(sess, LintLevelSets::new(sess))
49     }
50
51     fn process_command_line(&mut self, sess: &Session) {
52         let store = sess.lint_store.borrow();
53         let mut specs = FxHashMap::default();
54         self.lint_cap = sess.opts.lint_cap.unwrap_or(Level::Forbid);
55
56         for &(ref lint_name, level) in &sess.opts.lint_opts {
57             store.check_lint_name_cmdline(sess, &lint_name, level);
58
59             // If the cap is less than this specified level, e.g., if we've got
60             // `--cap-lints allow` but we've also got `-D foo` then we ignore
61             // this specification as the lint cap will set it to allow anyway.
62             let level = cmp::min(level, self.lint_cap);
63
64             let lint_flag_val = Symbol::intern(lint_name);
65             let ids = match store.find_lints(&lint_name) {
66                 Ok(ids) => ids,
67                 Err(_) => continue, // errors handled in check_lint_name_cmdline above
68             };
69             for id in ids {
70                 let src = LintSource::CommandLine(lint_flag_val);
71                 specs.insert(id, (level, src));
72             }
73         }
74
75         self.list.push(LintSet::CommandLine {
76             specs: specs,
77         });
78     }
79
80     fn get_lint_level(&self,
81                       lint: &'static Lint,
82                       idx: u32,
83                       aux: Option<&FxHashMap<LintId, (Level, LintSource)>>,
84                       sess: &Session)
85         -> (Level, LintSource)
86     {
87         let (level, mut src) = self.get_lint_id_level(LintId::of(lint), idx, aux);
88
89         // If `level` is none then we actually assume the default level for this
90         // lint.
91         let mut level = level.unwrap_or_else(|| lint.default_level(sess));
92
93         // If we're about to issue a warning, check at the last minute for any
94         // directives against the warnings "lint". If, for example, there's an
95         // `allow(warnings)` in scope then we want to respect that instead.
96         if level == Level::Warn {
97             let (warnings_level, warnings_src) =
98                 self.get_lint_id_level(LintId::of(lint::builtin::WARNINGS),
99                                        idx,
100                                        aux);
101             if let Some(configured_warning_level) = warnings_level {
102                 if configured_warning_level != Level::Warn {
103                     level = configured_warning_level;
104                     src = warnings_src;
105                 }
106             }
107         }
108
109         // Ensure that we never exceed the `--cap-lints` argument.
110         level = cmp::min(level, self.lint_cap);
111
112         if let Some(driver_level) = sess.driver_lint_caps.get(&LintId::of(lint)) {
113             // Ensure that we never exceed driver level.
114             level = cmp::min(*driver_level, level);
115         }
116
117         return (level, src)
118     }
119
120     fn get_lint_id_level(&self,
121                          id: LintId,
122                          mut idx: u32,
123                          aux: Option<&FxHashMap<LintId, (Level, LintSource)>>)
124         -> (Option<Level>, LintSource)
125     {
126         if let Some(specs) = aux {
127             if let Some(&(level, src)) = specs.get(&id) {
128                 return (Some(level), src)
129             }
130         }
131         loop {
132             match self.list[idx as usize] {
133                 LintSet::CommandLine { ref specs } => {
134                     if let Some(&(level, src)) = specs.get(&id) {
135                         return (Some(level), src)
136                     }
137                     return (None, LintSource::Default)
138                 }
139                 LintSet::Node { ref specs, parent } => {
140                     if let Some(&(level, src)) = specs.get(&id) {
141                         return (Some(level), src)
142                     }
143                     idx = parent;
144                 }
145             }
146         }
147     }
148 }
149
150 pub struct LintLevelsBuilder<'a> {
151     sess: &'a Session,
152     sets: LintLevelSets,
153     id_to_set: FxHashMap<HirId, u32>,
154     cur: u32,
155     warn_about_weird_lints: bool,
156 }
157
158 pub struct BuilderPush {
159     prev: u32,
160     pub(super) changed: bool,
161 }
162
163 impl<'a> LintLevelsBuilder<'a> {
164     pub fn new(sess: &'a Session, sets: LintLevelSets) -> LintLevelsBuilder<'a> {
165         assert_eq!(sets.list.len(), 1);
166         LintLevelsBuilder {
167             sess,
168             sets,
169             cur: 0,
170             id_to_set: Default::default(),
171             warn_about_weird_lints: sess.buffered_lints.borrow().is_some(),
172         }
173     }
174
175     /// Pushes a list of AST lint attributes onto this context.
176     ///
177     /// This function will return a `BuilderPush` object which should be passed
178     /// to `pop` when this scope for the attributes provided is exited.
179     ///
180     /// This function will perform a number of tasks:
181     ///
182     /// * It'll validate all lint-related attributes in `attrs`
183     /// * It'll mark all lint-related attributes as used
184     /// * Lint levels will be updated based on the attributes provided
185     /// * Lint attributes are validated, e.g., a #[forbid] can't be switched to
186     ///   #[allow]
187     ///
188     /// Don't forget to call `pop`!
189     pub fn push(&mut self, attrs: &[ast::Attribute]) -> BuilderPush {
190         let mut specs = FxHashMap::default();
191         let store = self.sess.lint_store.borrow();
192         let sess = self.sess;
193         let bad_attr = |span| {
194             struct_span_err!(sess, span, E0452, "malformed lint attribute input")
195         };
196         for attr in attrs {
197             let level = match Level::from_symbol(attr.name_or_empty()) {
198                 None => continue,
199                 Some(lvl) => lvl,
200             };
201
202             let meta = unwrap_or!(attr.meta(), continue);
203             attr::mark_used(attr);
204
205             let mut metas = if let Some(metas) = meta.meta_item_list() {
206                 metas
207             } else {
208                 continue;
209             };
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.node {
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.node {
230                                 if !self.sess.features_untracked().lint_reasons {
231                                     feature_gate::emit_feature_err(
232                                         &self.sess.parse_sess,
233                                         sym::lint_reasons,
234                                         item.span,
235                                         feature_gate::GateIssue::Language,
236                                         "lint reasons are experimental"
237                                     );
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)
253                             .span_label(item.span, "bad attribute argument")
254                             .emit();
255                     }
256                 }
257             }
258
259             for li in metas {
260                 let meta_item = match li.meta_item() {
261                     Some(meta_item) if meta_item.is_word() => meta_item,
262                     _ => {
263                         let sp = li.span();
264                         let mut err = bad_attr(sp);
265                         let mut add_label = true;
266                         if let Some(item) = li.meta_item() {
267                             if let ast::MetaItemKind::NameValue(_) = item.node {
268                                 if item.path == sym::reason {
269                                     err.span_label(sp, "reason in lint attribute must come last");
270                                     add_label = false;
271                                 }
272                             }
273                         }
274                         if add_label {
275                             err.span_label(sp, "bad attribute argument");
276                         }
277                         err.emit();
278                         continue;
279                     }
280                 };
281                 let tool_name = if meta_item.path.segments.len() > 1 {
282                     let tool_ident = meta_item.path.segments[0].ident;
283                     if !attr::is_known_lint_tool(tool_ident) {
284                         span_err!(
285                             sess,
286                             tool_ident.span,
287                             E0710,
288                             "an unknown tool name found in scoped lint: `{}`",
289                             meta_item.path
290                         );
291                         continue;
292                     }
293
294                     Some(tool_ident.as_str())
295                 } else {
296                     None
297                 };
298                 let name = meta_item.path.segments.last().expect("empty lint name").ident.name;
299                 match store.check_lint_name(&name.as_str(), tool_name) {
300                     CheckLintNameResult::Ok(ids) => {
301                         let src = LintSource::Node(name, li.span(), reason);
302                         for id in ids {
303                             specs.insert(*id, (level, src));
304                         }
305                     }
306
307                     CheckLintNameResult::Tool(result) => {
308                         match result {
309                             Ok(ids) => {
310                                 let complete_name = &format!("{}::{}", tool_name.unwrap(), name);
311                                 let src = LintSource::Node(
312                                     Symbol::intern(complete_name), li.span(), reason
313                                 );
314                                 for id in ids {
315                                     specs.insert(*id, (level, src));
316                                 }
317                             }
318                             Err((Some(ids), new_lint_name)) => {
319                                 let lint = builtin::RENAMED_AND_REMOVED_LINTS;
320                                 let (lvl, src) =
321                                     self.sets
322                                         .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                                 ).span_suggestion(
337                                     li.span(),
338                                     "change it to",
339                                     new_lint_name.to_string(),
340                                     Applicability::MachineApplicable,
341                                 ).emit();
342
343                                 let src = LintSource::Node(
344                                     Symbol::intern(&new_lint_name), li.span(), reason
345                                 );
346                                 for id in ids {
347                                     specs.insert(*id, (level, src));
348                                 }
349                             }
350                             Err((None, _)) => {
351                                 // If Tool(Err(None, _)) is returned, then either the lint does not
352                                 // exist in the tool or the code was not compiled with the tool and
353                                 // therefore the lint was never added to the `LintStore`. To detect
354                                 // this is the responsibility of the lint tool.
355                             }
356                         }
357                     }
358
359                     _ if !self.warn_about_weird_lints => {}
360
361                     CheckLintNameResult::Warning(msg, renamed) => {
362                         let lint = builtin::RENAMED_AND_REMOVED_LINTS;
363                         let (level, src) = self.sets.get_lint_level(lint,
364                                                                     self.cur,
365                                                                     Some(&specs),
366                                                                     &sess);
367                         let mut err = lint::struct_lint_level(self.sess,
368                                                               lint,
369                                                               level,
370                                                               src,
371                                                               Some(li.span().into()),
372                                                               &msg);
373                         if let Some(new_name) = renamed {
374                             err.span_suggestion(
375                                 li.span(),
376                                 "use the new name",
377                                 new_name,
378                                 Applicability::MachineApplicable
379                             );
380                         }
381                         err.emit();
382                     }
383                     CheckLintNameResult::NoLint(suggestion) => {
384                         let lint = builtin::UNKNOWN_LINTS;
385                         let (level, src) = self.sets.get_lint_level(lint,
386                                                                     self.cur,
387                                                                     Some(&specs),
388                                                                     self.sess);
389                         let msg = format!("unknown lint: `{}`", name);
390                         let mut db = lint::struct_lint_level(self.sess,
391                                                 lint,
392                                                 level,
393                                                 src,
394                                                 Some(li.span().into()),
395                                                 &msg);
396
397                         if let Some(suggestion) = suggestion {
398                             db.span_suggestion(
399                                 li.span(),
400                                 "did you mean",
401                                 suggestion.to_string(),
402                                 Applicability::MachineApplicable,
403                             );
404                         }
405
406                         db.emit();
407                     }
408                 }
409             }
410         }
411
412         for (id, &(level, ref src)) in specs.iter() {
413             if level == Level::Forbid {
414                 continue
415             }
416             let forbid_src = match self.sets.get_lint_id_level(*id, self.cur, None) {
417                 (Some(Level::Forbid), src) => src,
418                 _ => continue,
419             };
420             let forbidden_lint_name = match forbid_src {
421                 LintSource::Default => id.to_string(),
422                 LintSource::Node(name, _, _) => name.to_string(),
423                 LintSource::CommandLine(name) => name.to_string(),
424             };
425             let (lint_attr_name, lint_attr_span) = match *src {
426                 LintSource::Node(name, span, _) => (name, span),
427                 _ => continue,
428             };
429             let mut diag_builder = struct_span_err!(self.sess,
430                                                     lint_attr_span,
431                                                     E0453,
432                                                     "{}({}) overruled by outer forbid({})",
433                                                     level.as_str(),
434                                                     lint_attr_name,
435                                                     forbidden_lint_name);
436             diag_builder.span_label(lint_attr_span, "overruled by previous forbid");
437             match forbid_src {
438                 LintSource::Default => {},
439                 LintSource::Node(_, forbid_source_span, reason) => {
440                     diag_builder.span_label(forbid_source_span,
441                                             "`forbid` level set here");
442                     if let Some(rationale) = reason {
443                         diag_builder.note(&rationale.as_str());
444                     }
445                 },
446                 LintSource::CommandLine(_) => {
447                     diag_builder.note("`forbid` lint level was set on command line");
448                 }
449             }
450             diag_builder.emit();
451             // don't set a separate error for every lint in the group
452             break
453         }
454
455         let prev = self.cur;
456         if specs.len() > 0 {
457             self.cur = self.sets.list.len() as u32;
458             self.sets.list.push(LintSet::Node {
459                 specs: specs,
460                 parent: prev,
461             });
462         }
463
464         BuilderPush {
465             prev: prev,
466             changed: prev != self.cur,
467         }
468     }
469
470     /// Called after `push` when the scope of a set of attributes are exited.
471     pub fn pop(&mut self, push: BuilderPush) {
472         self.cur = push.prev;
473     }
474
475     /// Used to emit a lint-related diagnostic based on the current state of
476     /// this lint context.
477     pub fn struct_lint(&self,
478                        lint: &'static Lint,
479                        span: Option<MultiSpan>,
480                        msg: &str)
481         -> DiagnosticBuilder<'a>
482     {
483         let (level, src) = self.sets.get_lint_level(lint, self.cur, None, self.sess);
484         lint::struct_lint_level(self.sess, lint, level, src, span, msg)
485     }
486
487     /// Registers the ID provided with the current set of lints stored in
488     /// this context.
489     pub fn register_id(&mut self, id: HirId) {
490         self.id_to_set.insert(id, self.cur);
491     }
492
493     pub fn build(self) -> LintLevelSets {
494         self.sets
495     }
496
497     pub fn build_map(self) -> LintLevelMap {
498         LintLevelMap {
499             sets: self.sets,
500             id_to_set: self.id_to_set,
501         }
502     }
503 }
504
505 pub struct LintLevelMap {
506     sets: LintLevelSets,
507     id_to_set: FxHashMap<HirId, u32>,
508 }
509
510 impl LintLevelMap {
511     /// If the `id` was previously registered with `register_id` when building
512     /// this `LintLevelMap` this returns the corresponding lint level and source
513     /// of the lint level for the lint provided.
514     ///
515     /// If the `id` was not previously registered, returns `None`. If `None` is
516     /// returned then the parent of `id` should be acquired and this function
517     /// should be called again.
518     pub fn level_and_source(&self, lint: &'static Lint, id: HirId, session: &Session)
519         -> Option<(Level, LintSource)>
520     {
521         self.id_to_set.get(&id).map(|idx| {
522             self.sets.get_lint_level(lint, *idx, None, session)
523         })
524     }
525 }
526
527 impl<'a> HashStable<StableHashingContext<'a>> for LintLevelMap {
528     #[inline]
529     fn hash_stable<W: StableHasherResult>(&self,
530                                           hcx: &mut StableHashingContext<'a>,
531                                           hasher: &mut StableHasher<W>) {
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<W: StableHasherResult>(&self,
571                                           hcx: &mut HCX,
572                                           hasher: &mut StableHasher<W>) {
573         self.lint_name_raw().hash_stable(hcx, hasher);
574     }
575 }
576
577 impl<HCX> ToStableHashKey<HCX> for LintId {
578     type KeyType = &'static str;
579
580     #[inline]
581     fn to_stable_hash_key(&self, _: &HCX) -> &'static str {
582         self.lint_name_raw()
583     }
584 }