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