]> git.lizzy.rs Git - rust.git/blob - src/librustc/lint/levels.rs
Rollup merge of #60511 - taiki-e:libstd-intra-doc, r=Dylan-DPC
[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")
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                                 let mut err = bad_attr(name_value.span);
242                                 err.help("reason must be a string literal");
243                                 err.emit();
244                             }
245                         } else {
246                             let mut err = bad_attr(item.span);
247                             err.emit();
248                         }
249                     },
250                     ast::MetaItemKind::List(_) => {
251                         let mut err = bad_attr(item.span);
252                         err.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 mut err = bad_attr(li.span());
262                         if let Some(item) = li.meta_item() {
263                             if let ast::MetaItemKind::NameValue(_) = item.node {
264                                 if item.path == sym::reason {
265                                     err.help("reason in lint attribute must come last");
266                                 }
267                             }
268                         }
269                         err.emit();
270                         continue;
271                     }
272                 };
273                 let tool_name = if meta_item.path.segments.len() > 1 {
274                     let tool_ident = meta_item.path.segments[0].ident;
275                     if !attr::is_known_lint_tool(tool_ident) {
276                         span_err!(
277                             sess,
278                             tool_ident.span,
279                             E0710,
280                             "an unknown tool name found in scoped lint: `{}`",
281                             meta_item.path
282                         );
283                         continue;
284                     }
285
286                     Some(tool_ident.as_str())
287                 } else {
288                     None
289                 };
290                 let name = meta_item.path.segments.last().expect("empty lint name").ident.name;
291                 match store.check_lint_name(&name.as_str(), tool_name) {
292                     CheckLintNameResult::Ok(ids) => {
293                         let src = LintSource::Node(name, li.span(), reason);
294                         for id in ids {
295                             specs.insert(*id, (level, src));
296                         }
297                     }
298
299                     CheckLintNameResult::Tool(result) => {
300                         match result {
301                             Ok(ids) => {
302                                 let complete_name = &format!("{}::{}", tool_name.unwrap(), name);
303                                 let src = LintSource::Node(
304                                     Symbol::intern(complete_name), li.span(), reason
305                                 );
306                                 for id in ids {
307                                     specs.insert(*id, (level, src));
308                                 }
309                             }
310                             Err((Some(ids), new_lint_name)) => {
311                                 let lint = builtin::RENAMED_AND_REMOVED_LINTS;
312                                 let (lvl, src) =
313                                     self.sets
314                                         .get_lint_level(lint, self.cur, Some(&specs), &sess);
315                                 let msg = format!(
316                                     "lint name `{}` is deprecated \
317                                      and may not have an effect in the future. \
318                                      Also `cfg_attr(cargo-clippy)` won't be necessary anymore",
319                                     name
320                                 );
321                                 let mut err = lint::struct_lint_level(
322                                     self.sess,
323                                     lint,
324                                     lvl,
325                                     src,
326                                     Some(li.span().into()),
327                                     &msg,
328                                 );
329                                 err.span_suggestion(
330                                     li.span(),
331                                     "change it to",
332                                     new_lint_name.to_string(),
333                                     Applicability::MachineApplicable,
334                                 ).emit();
335
336                                 let src = LintSource::Node(
337                                     Symbol::intern(&new_lint_name), li.span(), reason
338                                 );
339                                 for id in ids {
340                                     specs.insert(*id, (level, src));
341                                 }
342                             }
343                             Err((None, _)) => {
344                                 // If Tool(Err(None, _)) is returned, then either the lint does not
345                                 // exist in the tool or the code was not compiled with the tool and
346                                 // therefore the lint was never added to the `LintStore`. To detect
347                                 // this is the responsibility of the lint tool.
348                             }
349                         }
350                     }
351
352                     _ if !self.warn_about_weird_lints => {}
353
354                     CheckLintNameResult::Warning(msg, renamed) => {
355                         let lint = builtin::RENAMED_AND_REMOVED_LINTS;
356                         let (level, src) = self.sets.get_lint_level(lint,
357                                                                     self.cur,
358                                                                     Some(&specs),
359                                                                     &sess);
360                         let mut err = lint::struct_lint_level(self.sess,
361                                                               lint,
362                                                               level,
363                                                               src,
364                                                               Some(li.span().into()),
365                                                               &msg);
366                         if let Some(new_name) = renamed {
367                             err.span_suggestion(
368                                 li.span(),
369                                 "use the new name",
370                                 new_name,
371                                 Applicability::MachineApplicable
372                             );
373                         }
374                         err.emit();
375                     }
376                     CheckLintNameResult::NoLint(suggestion) => {
377                         let lint = builtin::UNKNOWN_LINTS;
378                         let (level, src) = self.sets.get_lint_level(lint,
379                                                                     self.cur,
380                                                                     Some(&specs),
381                                                                     self.sess);
382                         let msg = format!("unknown lint: `{}`", name);
383                         let mut db = lint::struct_lint_level(self.sess,
384                                                 lint,
385                                                 level,
386                                                 src,
387                                                 Some(li.span().into()),
388                                                 &msg);
389
390                         if let Some(suggestion) = suggestion {
391                             db.span_suggestion(
392                                 li.span(),
393                                 "did you mean",
394                                 suggestion.to_string(),
395                                 Applicability::MachineApplicable,
396                             );
397                         }
398
399                         db.emit();
400                     }
401                 }
402             }
403         }
404
405         for (id, &(level, ref src)) in specs.iter() {
406             if level == Level::Forbid {
407                 continue
408             }
409             let forbid_src = match self.sets.get_lint_id_level(*id, self.cur, None) {
410                 (Some(Level::Forbid), src) => src,
411                 _ => continue,
412             };
413             let forbidden_lint_name = match forbid_src {
414                 LintSource::Default => id.to_string(),
415                 LintSource::Node(name, _, _) => name.to_string(),
416                 LintSource::CommandLine(name) => name.to_string(),
417             };
418             let (lint_attr_name, lint_attr_span) = match *src {
419                 LintSource::Node(name, span, _) => (name, span),
420                 _ => continue,
421             };
422             let mut diag_builder = struct_span_err!(self.sess,
423                                                     lint_attr_span,
424                                                     E0453,
425                                                     "{}({}) overruled by outer forbid({})",
426                                                     level.as_str(),
427                                                     lint_attr_name,
428                                                     forbidden_lint_name);
429             diag_builder.span_label(lint_attr_span, "overruled by previous forbid");
430             match forbid_src {
431                 LintSource::Default => {},
432                 LintSource::Node(_, forbid_source_span, reason) => {
433                     diag_builder.span_label(forbid_source_span,
434                                             "`forbid` level set here");
435                     if let Some(rationale) = reason {
436                         diag_builder.note(&rationale.as_str());
437                     }
438                 },
439                 LintSource::CommandLine(_) => {
440                     diag_builder.note("`forbid` lint level was set on command line");
441                 }
442             }
443             diag_builder.emit();
444             // don't set a separate error for every lint in the group
445             break
446         }
447
448         let prev = self.cur;
449         if specs.len() > 0 {
450             self.cur = self.sets.list.len() as u32;
451             self.sets.list.push(LintSet::Node {
452                 specs: specs,
453                 parent: prev,
454             });
455         }
456
457         BuilderPush {
458             prev: prev,
459             changed: prev != self.cur,
460         }
461     }
462
463     /// Called after `push` when the scope of a set of attributes are exited.
464     pub fn pop(&mut self, push: BuilderPush) {
465         self.cur = push.prev;
466     }
467
468     /// Used to emit a lint-related diagnostic based on the current state of
469     /// this lint context.
470     pub fn struct_lint(&self,
471                        lint: &'static Lint,
472                        span: Option<MultiSpan>,
473                        msg: &str)
474         -> DiagnosticBuilder<'a>
475     {
476         let (level, src) = self.sets.get_lint_level(lint, self.cur, None, self.sess);
477         lint::struct_lint_level(self.sess, lint, level, src, span, msg)
478     }
479
480     /// Registers the ID provided with the current set of lints stored in
481     /// this context.
482     pub fn register_id(&mut self, id: HirId) {
483         self.id_to_set.insert(id, self.cur);
484     }
485
486     pub fn build(self) -> LintLevelSets {
487         self.sets
488     }
489
490     pub fn build_map(self) -> LintLevelMap {
491         LintLevelMap {
492             sets: self.sets,
493             id_to_set: self.id_to_set,
494         }
495     }
496 }
497
498 pub struct LintLevelMap {
499     sets: LintLevelSets,
500     id_to_set: FxHashMap<HirId, u32>,
501 }
502
503 impl LintLevelMap {
504     /// If the `id` was previously registered with `register_id` when building
505     /// this `LintLevelMap` this returns the corresponding lint level and source
506     /// of the lint level for the lint provided.
507     ///
508     /// If the `id` was not previously registered, returns `None`. If `None` is
509     /// returned then the parent of `id` should be acquired and this function
510     /// should be called again.
511     pub fn level_and_source(&self, lint: &'static Lint, id: HirId, session: &Session)
512         -> Option<(Level, LintSource)>
513     {
514         self.id_to_set.get(&id).map(|idx| {
515             self.sets.get_lint_level(lint, *idx, None, session)
516         })
517     }
518 }
519
520 impl<'a> HashStable<StableHashingContext<'a>> for LintLevelMap {
521     #[inline]
522     fn hash_stable<W: StableHasherResult>(&self,
523                                           hcx: &mut StableHashingContext<'a>,
524                                           hasher: &mut StableHasher<W>) {
525         let LintLevelMap {
526             ref sets,
527             ref id_to_set,
528         } = *self;
529
530         id_to_set.hash_stable(hcx, hasher);
531
532         let LintLevelSets {
533             ref list,
534             lint_cap,
535         } = *sets;
536
537         lint_cap.hash_stable(hcx, hasher);
538
539         hcx.while_hashing_spans(true, |hcx| {
540             list.len().hash_stable(hcx, hasher);
541
542             // We are working under the assumption here that the list of
543             // lint-sets is built in a deterministic order.
544             for lint_set in list {
545                 ::std::mem::discriminant(lint_set).hash_stable(hcx, hasher);
546
547                 match *lint_set {
548                     LintSet::CommandLine { ref specs } => {
549                         specs.hash_stable(hcx, hasher);
550                     }
551                     LintSet::Node { ref specs, parent } => {
552                         specs.hash_stable(hcx, hasher);
553                         parent.hash_stable(hcx, hasher);
554                     }
555                 }
556             }
557         })
558     }
559 }
560
561 impl<HCX> HashStable<HCX> for LintId {
562     #[inline]
563     fn hash_stable<W: StableHasherResult>(&self,
564                                           hcx: &mut HCX,
565                                           hasher: &mut StableHasher<W>) {
566         self.lint_name_raw().hash_stable(hcx, hasher);
567     }
568 }
569
570 impl<HCX> ToStableHashKey<HCX> for LintId {
571     type KeyType = &'static str;
572
573     #[inline]
574     fn to_stable_hash_key(&self, _: &HCX) -> &'static str {
575         self.lint_name_raw()
576     }
577 }