3 use crate::ich::StableHashingContext;
4 use crate::lint::context::CheckLintNameResult;
5 use rustc_data_structures::fx::FxHashMap;
6 use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
7 use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder, DiagnosticId};
9 pub use rustc_session::lint::{builtin, Level, Lint, LintId, LintPass};
10 use rustc_session::{DiagnosticMessageId, Session};
11 use rustc_span::hygiene::MacroKind;
12 use rustc_span::source_map::{DesugaringKind, ExpnKind, MultiSpan};
13 use rustc_span::symbol::{sym, Symbol};
17 use syntax::print::pprust;
18 use syntax::sess::feature_err;
20 use rustc_error_codes::*;
25 pub use context::add_elided_lifetime_in_path_suggestion;
26 pub use context::{EarlyContext, LateContext, LintContext, LintStore};
27 pub use passes::{EarlyLintPass, EarlyLintPassObject, LateLintPass, LateLintPassObject};
29 /// How a lint level was set.
30 #[derive(Clone, Copy, PartialEq, Eq, HashStable)]
32 /// Lint is at the default level as declared
33 /// in rustc or a plugin.
36 /// Lint level was set by an attribute.
37 Node(Symbol, Span, Option<Symbol> /* RFC 2383 reason */),
39 /// Lint level was set by a command-line flag.
43 pub type LevelSource = (Level, LintSource);
45 pub struct LintLevelSets {
52 // -A,-W,-D flags, a `Symbol` for the flag itself and `Level` for which
54 specs: FxHashMap<LintId, LevelSource>,
58 specs: FxHashMap<LintId, LevelSource>,
64 pub fn new() -> Self {
65 LintLevelSets { list: Vec::new(), lint_cap: Level::Forbid }
68 pub fn get_lint_level(
72 aux: Option<&FxHashMap<LintId, LevelSource>>,
75 let (level, mut src) = self.get_lint_id_level(LintId::of(lint), idx, aux);
77 // If `level` is none then we actually assume the default level for this
79 let mut level = level.unwrap_or_else(|| lint.default_level(sess.edition()));
81 // If we're about to issue a warning, check at the last minute for any
82 // directives against the warnings "lint". If, for example, there's an
83 // `allow(warnings)` in scope then we want to respect that instead.
84 if level == Level::Warn {
85 let (warnings_level, warnings_src) =
86 self.get_lint_id_level(LintId::of(builtin::WARNINGS), idx, aux);
87 if let Some(configured_warning_level) = warnings_level {
88 if configured_warning_level != Level::Warn {
89 level = configured_warning_level;
95 // Ensure that we never exceed the `--cap-lints` argument.
96 level = cmp::min(level, self.lint_cap);
98 if let Some(driver_level) = sess.driver_lint_caps.get(&LintId::of(lint)) {
99 // Ensure that we never exceed driver level.
100 level = cmp::min(*driver_level, level);
106 pub fn get_lint_id_level(
110 aux: Option<&FxHashMap<LintId, LevelSource>>,
111 ) -> (Option<Level>, LintSource) {
112 if let Some(specs) = aux {
113 if let Some(&(level, src)) = specs.get(&id) {
114 return (Some(level), src);
118 match self.list[idx as usize] {
119 LintSet::CommandLine { ref specs } => {
120 if let Some(&(level, src)) = specs.get(&id) {
121 return (Some(level), src);
123 return (None, LintSource::Default);
125 LintSet::Node { ref specs, parent } => {
126 if let Some(&(level, src)) = specs.get(&id) {
127 return (Some(level), src);
136 pub struct LintLevelsBuilder<'a> {
139 id_to_set: FxHashMap<HirId, u32>,
141 warn_about_weird_lints: bool,
144 pub struct BuilderPush {
149 impl<'a> LintLevelsBuilder<'a> {
150 pub fn new(sess: &'a Session, warn_about_weird_lints: bool, store: &LintStore) -> Self {
151 let mut builder = LintLevelsBuilder {
153 sets: LintLevelSets::new(),
155 id_to_set: Default::default(),
156 warn_about_weird_lints,
158 builder.process_command_line(sess, store);
159 assert_eq!(builder.sets.list.len(), 1);
163 fn process_command_line(&mut self, sess: &Session, store: &LintStore) {
164 let mut specs = FxHashMap::default();
165 self.sets.lint_cap = sess.opts.lint_cap.unwrap_or(Level::Forbid);
167 for &(ref lint_name, level) in &sess.opts.lint_opts {
168 store.check_lint_name_cmdline(sess, &lint_name, level);
170 // If the cap is less than this specified level, e.g., if we've got
171 // `--cap-lints allow` but we've also got `-D foo` then we ignore
172 // this specification as the lint cap will set it to allow anyway.
173 let level = cmp::min(level, self.sets.lint_cap);
175 let lint_flag_val = Symbol::intern(lint_name);
176 let ids = match store.find_lints(&lint_name) {
178 Err(_) => continue, // errors handled in check_lint_name_cmdline above
181 let src = LintSource::CommandLine(lint_flag_val);
182 specs.insert(id, (level, src));
186 self.sets.list.push(LintSet::CommandLine { specs });
189 /// Pushes a list of AST lint attributes onto this context.
191 /// This function will return a `BuilderPush` object which should be passed
192 /// to `pop` when this scope for the attributes provided is exited.
194 /// This function will perform a number of tasks:
196 /// * It'll validate all lint-related attributes in `attrs`
197 /// * It'll mark all lint-related attributes as used
198 /// * Lint levels will be updated based on the attributes provided
199 /// * Lint attributes are validated, e.g., a #[forbid] can't be switched to
202 /// Don't forget to call `pop`!
203 pub fn push(&mut self, attrs: &[ast::Attribute], store: &LintStore) -> BuilderPush {
204 let mut specs = FxHashMap::default();
205 let sess = self.sess;
206 let bad_attr = |span| struct_span_err!(sess, span, E0452, "malformed lint attribute input");
208 let level = match Level::from_symbol(attr.name_or_empty()) {
213 let meta = unwrap_or!(attr.meta(), continue);
214 attr::mark_used(attr);
216 let mut metas = unwrap_or!(meta.meta_item_list(), continue);
218 if metas.is_empty() {
219 // FIXME (#55112): issue unused-attributes lint for `#[level()]`
223 // Before processing the lint names, look for a reason (RFC 2383)
225 let mut reason = None;
226 let tail_li = &metas[metas.len() - 1];
227 if let Some(item) = tail_li.meta_item() {
229 ast::MetaItemKind::Word => {} // actual lint names handled later
230 ast::MetaItemKind::NameValue(ref name_value) => {
231 if item.path == sym::reason {
232 // found reason, reslice meta list to exclude it
233 metas = &metas[0..metas.len() - 1];
234 // FIXME (#55112): issue unused-attributes lint if we thereby
235 // don't have any lint names (`#[level(reason = "foo")]`)
236 if let ast::LitKind::Str(rationale, _) = name_value.kind {
237 if !self.sess.features_untracked().lint_reasons {
239 &self.sess.parse_sess,
242 "lint reasons are experimental",
246 reason = Some(rationale);
248 bad_attr(name_value.span)
249 .span_label(name_value.span, "reason must be a string literal")
254 .span_label(item.span, "bad attribute argument")
258 ast::MetaItemKind::List(_) => {
259 bad_attr(item.span).span_label(item.span, "bad attribute argument").emit();
265 let meta_item = match li.meta_item() {
266 Some(meta_item) if meta_item.is_word() => meta_item,
269 let mut err = bad_attr(sp);
270 let mut add_label = true;
271 if let Some(item) = li.meta_item() {
272 if let ast::MetaItemKind::NameValue(_) = item.kind {
273 if item.path == sym::reason {
274 err.span_label(sp, "reason in lint attribute must come last");
280 err.span_label(sp, "bad attribute argument");
286 let tool_name = if meta_item.path.segments.len() > 1 {
287 let tool_ident = meta_item.path.segments[0].ident;
288 if !attr::is_known_lint_tool(tool_ident) {
293 "an unknown tool name found in scoped lint: `{}`",
294 pprust::path_to_string(&meta_item.path),
300 Some(tool_ident.name)
304 let name = meta_item.path.segments.last().expect("empty lint name").ident.name;
305 match store.check_lint_name(&name.as_str(), tool_name) {
306 CheckLintNameResult::Ok(ids) => {
307 let src = LintSource::Node(name, li.span(), reason);
309 specs.insert(*id, (level, src));
313 CheckLintNameResult::Tool(result) => {
316 let complete_name = &format!("{}::{}", tool_name.unwrap(), name);
317 let src = LintSource::Node(
318 Symbol::intern(complete_name),
323 specs.insert(*id, (level, src));
326 Err((Some(ids), new_lint_name)) => {
327 let lint = builtin::RENAMED_AND_REMOVED_LINTS;
329 self.sets.get_lint_level(lint, self.cur, Some(&specs), &sess);
331 "lint name `{}` is deprecated \
332 and may not have an effect in the future. \
333 Also `cfg_attr(cargo-clippy)` won't be necessary anymore",
341 Some(li.span().into()),
347 new_lint_name.to_string(),
348 Applicability::MachineApplicable,
352 let src = LintSource::Node(
353 Symbol::intern(&new_lint_name),
358 specs.insert(*id, (level, src));
362 // If Tool(Err(None, _)) is returned, then either the lint does not
363 // exist in the tool or the code was not compiled with the tool and
364 // therefore the lint was never added to the `LintStore`. To detect
365 // this is the responsibility of the lint tool.
370 _ if !self.warn_about_weird_lints => {}
372 CheckLintNameResult::Warning(msg, renamed) => {
373 let lint = builtin::RENAMED_AND_REMOVED_LINTS;
375 self.sets.get_lint_level(lint, self.cur, Some(&specs), &sess);
376 let mut err = struct_lint_level(
381 Some(li.span().into()),
384 if let Some(new_name) = renamed {
389 Applicability::MachineApplicable,
394 CheckLintNameResult::NoLint(suggestion) => {
395 let lint = builtin::UNKNOWN_LINTS;
397 self.sets.get_lint_level(lint, self.cur, Some(&specs), self.sess);
398 let msg = format!("unknown lint: `{}`", name);
399 let mut db = struct_lint_level(
404 Some(li.span().into()),
408 if let Some(suggestion) = suggestion {
412 suggestion.to_string(),
413 Applicability::MachineApplicable,
423 for (id, &(level, ref src)) in specs.iter() {
424 if level == Level::Forbid {
427 let forbid_src = match self.sets.get_lint_id_level(*id, self.cur, None) {
428 (Some(Level::Forbid), src) => src,
431 let forbidden_lint_name = match forbid_src {
432 LintSource::Default => id.to_string(),
433 LintSource::Node(name, _, _) => name.to_string(),
434 LintSource::CommandLine(name) => name.to_string(),
436 let (lint_attr_name, lint_attr_span) = match *src {
437 LintSource::Node(name, span, _) => (name, span),
440 let mut diag_builder = struct_span_err!(
444 "{}({}) overruled by outer forbid({})",
449 diag_builder.span_label(lint_attr_span, "overruled by previous forbid");
451 LintSource::Default => {}
452 LintSource::Node(_, forbid_source_span, reason) => {
453 diag_builder.span_label(forbid_source_span, "`forbid` level set here");
454 if let Some(rationale) = reason {
455 diag_builder.note(&rationale.as_str());
458 LintSource::CommandLine(_) => {
459 diag_builder.note("`forbid` lint level was set on command line");
463 // don't set a separate error for every lint in the group
469 self.cur = self.sets.list.len() as u32;
470 self.sets.list.push(LintSet::Node { specs: specs, parent: prev });
473 BuilderPush { prev: prev, changed: prev != self.cur }
476 /// Called after `push` when the scope of a set of attributes are exited.
477 pub fn pop(&mut self, push: BuilderPush) {
478 self.cur = push.prev;
481 /// Used to emit a lint-related diagnostic based on the current state of
482 /// this lint context.
486 span: Option<MultiSpan>,
488 ) -> DiagnosticBuilder<'a> {
489 let (level, src) = self.sets.get_lint_level(lint, self.cur, None, self.sess);
490 struct_lint_level(self.sess, lint, level, src, span, msg)
493 /// Registers the ID provided with the current set of lints stored in
495 pub fn register_id(&mut self, id: HirId) {
496 self.id_to_set.insert(id, self.cur);
499 pub fn build(self) -> LintLevelSets {
503 pub fn build_map(self) -> LintLevelMap {
504 LintLevelMap { sets: self.sets, id_to_set: self.id_to_set }
508 pub struct LintLevelMap {
510 id_to_set: FxHashMap<HirId, u32>,
514 /// If the `id` was previously registered with `register_id` when building
515 /// this `LintLevelMap` this returns the corresponding lint level and source
516 /// of the lint level for the lint provided.
518 /// If the `id` was not previously registered, returns `None`. If `None` is
519 /// returned then the parent of `id` should be acquired and this function
520 /// should be called again.
521 pub fn level_and_source(
526 ) -> Option<LevelSource> {
527 self.id_to_set.get(&id).map(|idx| self.sets.get_lint_level(lint, *idx, None, session))
531 impl<'a> HashStable<StableHashingContext<'a>> for LintLevelMap {
533 fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) {
534 let LintLevelMap { ref sets, ref id_to_set } = *self;
536 id_to_set.hash_stable(hcx, hasher);
538 let LintLevelSets { ref list, lint_cap } = *sets;
540 lint_cap.hash_stable(hcx, hasher);
542 hcx.while_hashing_spans(true, |hcx| {
543 list.len().hash_stable(hcx, hasher);
545 // We are working under the assumption here that the list of
546 // lint-sets is built in a deterministic order.
547 for lint_set in list {
548 ::std::mem::discriminant(lint_set).hash_stable(hcx, hasher);
551 LintSet::CommandLine { ref specs } => {
552 specs.hash_stable(hcx, hasher);
554 LintSet::Node { ref specs, parent } => {
555 specs.hash_stable(hcx, hasher);
556 parent.hash_stable(hcx, hasher);
564 pub fn struct_lint_level<'a>(
569 span: Option<MultiSpan>,
571 ) -> DiagnosticBuilder<'a> {
572 let mut err = match (level, span) {
573 (Level::Allow, _) => return sess.diagnostic().struct_dummy(),
574 (Level::Warn, Some(span)) => sess.struct_span_warn(span, msg),
575 (Level::Warn, None) => sess.struct_warn(msg),
576 (Level::Deny, Some(span)) | (Level::Forbid, Some(span)) => sess.struct_span_err(span, msg),
577 (Level::Deny, None) | (Level::Forbid, None) => sess.struct_err(msg),
580 // Check for future incompatibility lints and issue a stronger warning.
581 let lint_id = LintId::of(lint);
582 let future_incompatible = lint.future_incompatible;
584 // If this code originates in a foreign macro, aka something that this crate
585 // did not itself author, then it's likely that there's nothing this crate
586 // can do about it. We probably want to skip the lint entirely.
587 if err.span.primary_spans().iter().any(|s| in_external_macro(sess, *s)) {
588 // Any suggestions made here are likely to be incorrect, so anything we
589 // emit shouldn't be automatically fixed by rustfix.
590 err.allow_suggestions(false);
592 // If this is a future incompatible lint it'll become a hard error, so
593 // we have to emit *something*. Also allow lints to whitelist themselves
594 // on a case-by-case basis for emission in a foreign macro.
595 if future_incompatible.is_none() && !lint.report_in_external_macro {
597 // Don't continue further, since we don't want to have
598 // `diag_span_note_once` called for a diagnostic that isn't emitted.
603 let name = lint.name_lower();
605 LintSource::Default => {
608 DiagnosticMessageId::from(lint),
609 &format!("`#[{}({})]` on by default", level.as_str(), name),
612 LintSource::CommandLine(lint_flag_val) => {
613 let flag = match level {
616 Level::Forbid => "-F",
617 Level::Allow => panic!(),
619 let hyphen_case_lint_name = name.replace("_", "-");
620 if lint_flag_val.as_str() == name {
623 DiagnosticMessageId::from(lint),
625 "requested on the command line with `{} {}`",
626 flag, hyphen_case_lint_name
630 let hyphen_case_flag_val = lint_flag_val.as_str().replace("_", "-");
633 DiagnosticMessageId::from(lint),
635 "`{} {}` implied by `{} {}`",
636 flag, hyphen_case_lint_name, flag, hyphen_case_flag_val
641 LintSource::Node(lint_attr_name, src, reason) => {
642 if let Some(rationale) = reason {
643 err.note(&rationale.as_str());
645 sess.diag_span_note_once(
647 DiagnosticMessageId::from(lint),
649 "lint level defined here",
651 if lint_attr_name.as_str() != name {
652 let level_str = level.as_str();
655 DiagnosticMessageId::from(lint),
657 "`#[{}({})]` implied by `#[{}({})]`",
658 level_str, name, level_str, lint_attr_name
665 err.code(DiagnosticId::Lint(name));
667 if let Some(future_incompatible) = future_incompatible {
668 const STANDARD_MESSAGE: &str = "this was previously accepted by the compiler but is being phased out; \
669 it will become a hard error";
671 let explanation = if lint_id == LintId::of(builtin::UNSTABLE_NAME_COLLISIONS) {
672 "once this method is added to the standard library, \
673 the ambiguity may cause an error or change in behavior!"
675 } else if lint_id == LintId::of(builtin::MUTABLE_BORROW_RESERVATION_CONFLICT) {
676 "this borrowing pattern was not meant to be accepted, \
677 and may become a hard error in the future"
679 } else if let Some(edition) = future_incompatible.edition {
680 format!("{} in the {} edition!", STANDARD_MESSAGE, edition)
682 format!("{} in a future release!", STANDARD_MESSAGE)
684 let citation = format!("for more information, see {}", future_incompatible.reference);
685 err.warn(&explanation);
692 /// Returns whether `span` originates in a foreign crate's external macro.
694 /// This is used to test whether a lint should not even begin to figure out whether it should
695 /// be reported on the current node.
696 pub fn in_external_macro(sess: &Session, span: Span) -> bool {
697 let expn_data = span.ctxt().outer_expn_data();
698 match expn_data.kind {
699 ExpnKind::Root | ExpnKind::Desugaring(DesugaringKind::ForLoop) => false,
700 ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) => true, // well, it's "external"
701 ExpnKind::Macro(MacroKind::Bang, _) => {
702 if expn_data.def_site.is_dummy() {
703 // Dummy span for the `def_site` means it's an external macro.
706 match sess.source_map().span_to_snippet(expn_data.def_site) {
707 Ok(code) => !code.starts_with("macro_rules"),
708 // No snippet means external macro or compiler-builtin expansion.
712 ExpnKind::Macro(..) => true, // definitely a plugin