3 use crate::ich::StableHashingContext;
5 use crate::lint::context::{CheckLintNameResult, LintStore};
6 use rustc_data_structures::fx::FxHashMap;
7 use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
8 use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder};
10 use rustc_session::lint::{builtin, Level, Lint, LintId};
11 use rustc_session::Session;
12 use rustc_span::source_map::MultiSpan;
13 use rustc_span::symbol::{sym, Symbol};
17 use syntax::print::pprust;
18 use syntax::sess::feature_err;
20 use rustc_error_codes::*;
22 /// How a lint level was set.
23 #[derive(Clone, Copy, PartialEq, Eq, HashStable)]
25 /// Lint is at the default level as declared
26 /// in rustc or a plugin.
29 /// Lint level was set by an attribute.
30 Node(Symbol, Span, Option<Symbol> /* RFC 2383 reason */),
32 /// Lint level was set by a command-line flag.
36 pub type LevelSource = (Level, LintSource);
38 pub struct LintLevelSets {
45 // -A,-W,-D flags, a `Symbol` for the flag itself and `Level` for which
47 specs: FxHashMap<LintId, LevelSource>,
51 specs: FxHashMap<LintId, LevelSource>,
57 pub fn new() -> Self {
58 LintLevelSets { list: Vec::new(), lint_cap: Level::Forbid }
61 pub fn get_lint_level(
65 aux: Option<&FxHashMap<LintId, LevelSource>>,
68 let (level, mut src) = self.get_lint_id_level(LintId::of(lint), idx, aux);
70 // If `level` is none then we actually assume the default level for this
72 let mut level = level.unwrap_or_else(|| lint.default_level(sess.edition()));
74 // If we're about to issue a warning, check at the last minute for any
75 // directives against the warnings "lint". If, for example, there's an
76 // `allow(warnings)` in scope then we want to respect that instead.
77 if level == Level::Warn {
78 let (warnings_level, warnings_src) =
79 self.get_lint_id_level(LintId::of(builtin::WARNINGS), idx, aux);
80 if let Some(configured_warning_level) = warnings_level {
81 if configured_warning_level != Level::Warn {
82 level = configured_warning_level;
88 // Ensure that we never exceed the `--cap-lints` argument.
89 level = cmp::min(level, self.lint_cap);
91 if let Some(driver_level) = sess.driver_lint_caps.get(&LintId::of(lint)) {
92 // Ensure that we never exceed driver level.
93 level = cmp::min(*driver_level, level);
99 pub fn get_lint_id_level(
103 aux: Option<&FxHashMap<LintId, LevelSource>>,
104 ) -> (Option<Level>, LintSource) {
105 if let Some(specs) = aux {
106 if let Some(&(level, src)) = specs.get(&id) {
107 return (Some(level), src);
111 match self.list[idx as usize] {
112 LintSet::CommandLine { ref specs } => {
113 if let Some(&(level, src)) = specs.get(&id) {
114 return (Some(level), src);
116 return (None, LintSource::Default);
118 LintSet::Node { ref specs, parent } => {
119 if let Some(&(level, src)) = specs.get(&id) {
120 return (Some(level), src);
129 pub struct LintLevelsBuilder<'a> {
132 id_to_set: FxHashMap<HirId, u32>,
134 warn_about_weird_lints: bool,
137 pub struct BuilderPush {
142 impl<'a> LintLevelsBuilder<'a> {
143 pub fn new(sess: &'a Session, warn_about_weird_lints: bool, store: &LintStore) -> Self {
144 let mut builder = LintLevelsBuilder {
146 sets: LintLevelSets::new(),
148 id_to_set: Default::default(),
149 warn_about_weird_lints,
151 builder.process_command_line(sess, store);
152 assert_eq!(builder.sets.list.len(), 1);
156 fn process_command_line(&mut self, sess: &Session, store: &LintStore) {
157 let mut specs = FxHashMap::default();
158 self.sets.lint_cap = sess.opts.lint_cap.unwrap_or(Level::Forbid);
160 for &(ref lint_name, level) in &sess.opts.lint_opts {
161 store.check_lint_name_cmdline(sess, &lint_name, level);
163 // If the cap is less than this specified level, e.g., if we've got
164 // `--cap-lints allow` but we've also got `-D foo` then we ignore
165 // this specification as the lint cap will set it to allow anyway.
166 let level = cmp::min(level, self.sets.lint_cap);
168 let lint_flag_val = Symbol::intern(lint_name);
169 let ids = match store.find_lints(&lint_name) {
171 Err(_) => continue, // errors handled in check_lint_name_cmdline above
174 let src = LintSource::CommandLine(lint_flag_val);
175 specs.insert(id, (level, src));
179 self.sets.list.push(LintSet::CommandLine { specs });
182 /// Pushes a list of AST lint attributes onto this context.
184 /// This function will return a `BuilderPush` object which should be passed
185 /// to `pop` when this scope for the attributes provided is exited.
187 /// This function will perform a number of tasks:
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
195 /// Don't forget to call `pop`!
196 pub fn push(&mut self, attrs: &[ast::Attribute], store: &LintStore) -> BuilderPush {
197 let mut specs = FxHashMap::default();
198 let sess = self.sess;
199 let bad_attr = |span| struct_span_err!(sess, span, E0452, "malformed lint attribute input");
201 let level = match Level::from_symbol(attr.name_or_empty()) {
206 let meta = unwrap_or!(attr.meta(), continue);
207 attr::mark_used(attr);
209 let mut metas = unwrap_or!(meta.meta_item_list(), continue);
211 if metas.is_empty() {
212 // FIXME (#55112): issue unused-attributes lint for `#[level()]`
216 // Before processing the lint names, look for a reason (RFC 2383)
218 let mut reason = None;
219 let tail_li = &metas[metas.len() - 1];
220 if let Some(item) = tail_li.meta_item() {
222 ast::MetaItemKind::Word => {} // actual lint names handled later
223 ast::MetaItemKind::NameValue(ref name_value) => {
224 if item.path == sym::reason {
225 // found reason, reslice meta list to exclude it
226 metas = &metas[0..metas.len() - 1];
227 // FIXME (#55112): issue unused-attributes lint if we thereby
228 // don't have any lint names (`#[level(reason = "foo")]`)
229 if let ast::LitKind::Str(rationale, _) = name_value.kind {
230 if !self.sess.features_untracked().lint_reasons {
232 &self.sess.parse_sess,
235 "lint reasons are experimental",
239 reason = Some(rationale);
241 bad_attr(name_value.span)
242 .span_label(name_value.span, "reason must be a string literal")
247 .span_label(item.span, "bad attribute argument")
251 ast::MetaItemKind::List(_) => {
252 bad_attr(item.span).span_label(item.span, "bad attribute argument").emit();
258 let meta_item = match li.meta_item() {
259 Some(meta_item) if meta_item.is_word() => meta_item,
262 let mut err = bad_attr(sp);
263 let mut add_label = true;
264 if let Some(item) = li.meta_item() {
265 if let ast::MetaItemKind::NameValue(_) = item.kind {
266 if item.path == sym::reason {
267 err.span_label(sp, "reason in lint attribute must come last");
273 err.span_label(sp, "bad attribute argument");
279 let tool_name = if meta_item.path.segments.len() > 1 {
280 let tool_ident = meta_item.path.segments[0].ident;
281 if !attr::is_known_lint_tool(tool_ident) {
286 "an unknown tool name found in scoped lint: `{}`",
287 pprust::path_to_string(&meta_item.path),
293 Some(tool_ident.name)
297 let name = meta_item.path.segments.last().expect("empty lint name").ident.name;
298 match store.check_lint_name(&name.as_str(), tool_name) {
299 CheckLintNameResult::Ok(ids) => {
300 let src = LintSource::Node(name, li.span(), reason);
302 specs.insert(*id, (level, src));
306 CheckLintNameResult::Tool(result) => {
309 let complete_name = &format!("{}::{}", tool_name.unwrap(), name);
310 let src = LintSource::Node(
311 Symbol::intern(complete_name),
316 specs.insert(*id, (level, src));
319 Err((Some(ids), new_lint_name)) => {
320 let lint = builtin::RENAMED_AND_REMOVED_LINTS;
322 self.sets.get_lint_level(lint, self.cur, Some(&specs), &sess);
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",
329 lint::struct_lint_level(
334 Some(li.span().into()),
340 new_lint_name.to_string(),
341 Applicability::MachineApplicable,
345 let src = LintSource::Node(
346 Symbol::intern(&new_lint_name),
351 specs.insert(*id, (level, src));
355 // If Tool(Err(None, _)) is returned, then either the lint does not
356 // exist in the tool or the code was not compiled with the tool and
357 // therefore the lint was never added to the `LintStore`. To detect
358 // this is the responsibility of the lint tool.
363 _ if !self.warn_about_weird_lints => {}
365 CheckLintNameResult::Warning(msg, renamed) => {
366 let lint = builtin::RENAMED_AND_REMOVED_LINTS;
368 self.sets.get_lint_level(lint, self.cur, Some(&specs), &sess);
369 let mut err = lint::struct_lint_level(
374 Some(li.span().into()),
377 if let Some(new_name) = renamed {
382 Applicability::MachineApplicable,
387 CheckLintNameResult::NoLint(suggestion) => {
388 let lint = builtin::UNKNOWN_LINTS;
390 self.sets.get_lint_level(lint, self.cur, Some(&specs), self.sess);
391 let msg = format!("unknown lint: `{}`", name);
392 let mut db = lint::struct_lint_level(
397 Some(li.span().into()),
401 if let Some(suggestion) = suggestion {
405 suggestion.to_string(),
406 Applicability::MachineApplicable,
416 for (id, &(level, ref src)) in specs.iter() {
417 if level == Level::Forbid {
420 let forbid_src = match self.sets.get_lint_id_level(*id, self.cur, None) {
421 (Some(Level::Forbid), src) => src,
424 let forbidden_lint_name = match forbid_src {
425 LintSource::Default => id.to_string(),
426 LintSource::Node(name, _, _) => name.to_string(),
427 LintSource::CommandLine(name) => name.to_string(),
429 let (lint_attr_name, lint_attr_span) = match *src {
430 LintSource::Node(name, span, _) => (name, span),
433 let mut diag_builder = struct_span_err!(
437 "{}({}) overruled by outer forbid({})",
442 diag_builder.span_label(lint_attr_span, "overruled by previous forbid");
444 LintSource::Default => {}
445 LintSource::Node(_, forbid_source_span, reason) => {
446 diag_builder.span_label(forbid_source_span, "`forbid` level set here");
447 if let Some(rationale) = reason {
448 diag_builder.note(&rationale.as_str());
451 LintSource::CommandLine(_) => {
452 diag_builder.note("`forbid` lint level was set on command line");
456 // don't set a separate error for every lint in the group
462 self.cur = self.sets.list.len() as u32;
463 self.sets.list.push(LintSet::Node { specs: specs, parent: prev });
466 BuilderPush { prev: prev, changed: prev != self.cur }
469 /// Called after `push` when the scope of a set of attributes are exited.
470 pub fn pop(&mut self, push: BuilderPush) {
471 self.cur = push.prev;
474 /// Used to emit a lint-related diagnostic based on the current state of
475 /// this lint context.
479 span: Option<MultiSpan>,
481 ) -> DiagnosticBuilder<'a> {
482 let (level, src) = self.sets.get_lint_level(lint, self.cur, None, self.sess);
483 lint::struct_lint_level(self.sess, lint, level, src, span, msg)
486 /// Registers the ID provided with the current set of lints stored in
488 pub fn register_id(&mut self, id: HirId) {
489 self.id_to_set.insert(id, self.cur);
492 pub fn build(self) -> LintLevelSets {
496 pub fn build_map(self) -> LintLevelMap {
497 LintLevelMap { sets: self.sets, id_to_set: self.id_to_set }
501 pub struct LintLevelMap {
503 id_to_set: FxHashMap<HirId, u32>,
507 /// If the `id` was previously registered with `register_id` when building
508 /// this `LintLevelMap` this returns the corresponding lint level and source
509 /// of the lint level for the lint provided.
511 /// If the `id` was not previously registered, returns `None`. If `None` is
512 /// returned then the parent of `id` should be acquired and this function
513 /// should be called again.
514 pub fn level_and_source(
519 ) -> Option<LevelSource> {
520 self.id_to_set.get(&id).map(|idx| self.sets.get_lint_level(lint, *idx, None, session))
524 impl<'a> HashStable<StableHashingContext<'a>> for LintLevelMap {
526 fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) {
527 let LintLevelMap { ref sets, ref id_to_set } = *self;
529 id_to_set.hash_stable(hcx, hasher);
531 let LintLevelSets { ref list, lint_cap } = *sets;
533 lint_cap.hash_stable(hcx, hasher);
535 hcx.while_hashing_spans(true, |hcx| {
536 list.len().hash_stable(hcx, hasher);
538 // We are working under the assumption here that the list of
539 // lint-sets is built in a deterministic order.
540 for lint_set in list {
541 ::std::mem::discriminant(lint_set).hash_stable(hcx, hasher);
544 LintSet::CommandLine { ref specs } => {
545 specs.hash_stable(hcx, hasher);
547 LintSet::Node { ref specs, parent } => {
548 specs.hash_stable(hcx, hasher);
549 parent.hash_stable(hcx, hasher);