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};
15 use syntax::feature_gate;
16 use syntax::source_map::MultiSpan;
17 use syntax::symbol::Symbol;
19 pub struct LintLevelSets {
26 // -A,-W,-D flags, a `Symbol` for the flag itself and `Level` for which
28 specs: FxHashMap<LintId, (Level, LintSource)>,
32 specs: FxHashMap<LintId, (Level, LintSource)>,
38 pub fn new(sess: &Session) -> LintLevelSets {
39 let mut me = LintLevelSets {
41 lint_cap: Level::Forbid,
43 me.process_command_line(sess);
47 pub fn builder(sess: &Session) -> LintLevelsBuilder<'_> {
48 LintLevelsBuilder::new(sess, LintLevelSets::new(sess))
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);
56 for &(ref lint_name, level) in &sess.opts.lint_opts {
57 store.check_lint_name_cmdline(sess, &lint_name, level);
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);
64 let lint_flag_val = Symbol::intern(lint_name);
65 let ids = match store.find_lints(&lint_name) {
67 Err(_) => continue, // errors handled in check_lint_name_cmdline above
70 let src = LintSource::CommandLine(lint_flag_val);
71 specs.insert(id, (level, src));
75 self.list.push(LintSet::CommandLine {
80 fn get_lint_level(&self,
83 aux: Option<&FxHashMap<LintId, (Level, LintSource)>>,
85 -> (Level, LintSource)
87 let (level, mut src) = self.get_lint_id_level(LintId::of(lint), idx, aux);
89 // If `level` is none then we actually assume the default level for this
91 let mut level = level.unwrap_or_else(|| lint.default_level(sess));
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),
101 if let Some(configured_warning_level) = warnings_level {
102 if configured_warning_level != Level::Warn {
103 level = configured_warning_level;
109 // Ensure that we never exceed the `--cap-lints` argument.
110 level = cmp::min(level, self.lint_cap);
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);
120 fn get_lint_id_level(&self,
123 aux: Option<&FxHashMap<LintId, (Level, LintSource)>>)
124 -> (Option<Level>, LintSource)
126 if let Some(specs) = aux {
127 if let Some(&(level, src)) = specs.get(&id) {
128 return (Some(level), src)
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)
137 return (None, LintSource::Default)
139 LintSet::Node { ref specs, parent } => {
140 if let Some(&(level, src)) = specs.get(&id) {
141 return (Some(level), src)
150 pub struct LintLevelsBuilder<'a> {
153 id_to_set: FxHashMap<HirId, u32>,
155 warn_about_weird_lints: bool,
158 pub struct BuilderPush {
160 pub(super) changed: bool,
163 impl<'a> LintLevelsBuilder<'a> {
164 pub fn new(sess: &'a Session, sets: LintLevelSets) -> LintLevelsBuilder<'a> {
165 assert_eq!(sets.list.len(), 1);
170 id_to_set: Default::default(),
171 warn_about_weird_lints: sess.buffered_lints.borrow().is_some(),
175 /// Pushes a list of AST lint attributes onto this context.
177 /// This function will return a `BuilderPush` object which should be passed
178 /// to `pop` when this scope for the attributes provided is exited.
180 /// This function will perform a number of tasks:
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
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")
197 let level = match Level::from_str(&attr.name_or_empty()) {
202 let meta = unwrap_or!(attr.meta(), continue);
203 attr::mark_used(attr);
205 let mut metas = if let Some(metas) = meta.meta_item_list() {
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 == "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,
235 feature_gate::GateIssue::Language,
236 "lint reasons are experimental"
239 reason = Some(rationale);
241 let mut err = bad_attr(name_value.span);
242 err.help("reason must be a string literal");
246 let mut err = bad_attr(item.span);
250 ast::MetaItemKind::List(_) => {
251 let mut err = bad_attr(item.span);
258 let meta_item = match li.meta_item() {
259 Some(meta_item) if meta_item.is_word() => meta_item,
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 == "reason" {
265 err.help("reason in lint attribute must come last");
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) {
280 "an unknown tool name found in scoped lint: `{}`",
286 Some(tool_ident.as_str())
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);
295 specs.insert(*id, (level, src));
299 CheckLintNameResult::Tool(result) => {
302 let complete_name = &format!("{}::{}", tool_name.unwrap(), name);
303 let src = LintSource::Node(
304 Symbol::intern(complete_name), li.span(), reason
307 specs.insert(*id, (level, src));
310 Err((Some(ids), new_lint_name)) => {
311 let lint = builtin::RENAMED_AND_REMOVED_LINTS;
314 .get_lint_level(lint, self.cur, Some(&specs), &sess);
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",
321 let mut err = lint::struct_lint_level(
326 Some(li.span().into()),
332 new_lint_name.to_string(),
333 Applicability::MachineApplicable,
336 let src = LintSource::Node(
337 Symbol::intern(&new_lint_name), li.span(), reason
340 specs.insert(*id, (level, src));
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.
352 _ if !self.warn_about_weird_lints => {}
354 CheckLintNameResult::Warning(msg, renamed) => {
355 let lint = builtin::RENAMED_AND_REMOVED_LINTS;
356 let (level, src) = self.sets.get_lint_level(lint,
360 let mut err = lint::struct_lint_level(self.sess,
364 Some(li.span().into()),
366 if let Some(new_name) = renamed {
371 Applicability::MachineApplicable
376 CheckLintNameResult::NoLint(suggestion) => {
377 let lint = builtin::UNKNOWN_LINTS;
378 let (level, src) = self.sets.get_lint_level(lint,
382 let msg = format!("unknown lint: `{}`", name);
383 let mut db = lint::struct_lint_level(self.sess,
387 Some(li.span().into()),
390 if let Some(suggestion) = suggestion {
394 suggestion.to_string(),
395 Applicability::MachineApplicable,
405 for (id, &(level, ref src)) in specs.iter() {
406 if level == Level::Forbid {
409 let forbid_src = match self.sets.get_lint_id_level(*id, self.cur, None) {
410 (Some(Level::Forbid), src) => src,
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(),
418 let (lint_attr_name, lint_attr_span) = match *src {
419 LintSource::Node(name, span, _) => (name, span),
422 let mut diag_builder = struct_span_err!(self.sess,
425 "{}({}) overruled by outer forbid({})",
428 forbidden_lint_name);
429 diag_builder.span_label(lint_attr_span, "overruled by previous forbid");
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());
439 LintSource::CommandLine(_) => {
440 diag_builder.note("`forbid` lint level was set on command line");
444 // don't set a separate error for every lint in the group
450 self.cur = self.sets.list.len() as u32;
451 self.sets.list.push(LintSet::Node {
459 changed: prev != self.cur,
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;
468 /// Used to emit a lint-related diagnostic based on the current state of
469 /// this lint context.
470 pub fn struct_lint(&self,
472 span: Option<MultiSpan>,
474 -> DiagnosticBuilder<'a>
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)
480 /// Registers the ID provided with the current set of lints stored in
482 pub fn register_id(&mut self, id: HirId) {
483 self.id_to_set.insert(id, self.cur);
486 pub fn build(self) -> LintLevelSets {
490 pub fn build_map(self) -> LintLevelMap {
493 id_to_set: self.id_to_set,
498 pub struct LintLevelMap {
500 id_to_set: FxHashMap<HirId, u32>,
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.
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)>
514 self.id_to_set.get(&id).map(|idx| {
515 self.sets.get_lint_level(lint, *idx, None, session)
520 impl<'a> HashStable<StableHashingContext<'a>> for LintLevelMap {
522 fn hash_stable<W: StableHasherResult>(&self,
523 hcx: &mut StableHashingContext<'a>,
524 hasher: &mut StableHasher<W>) {
530 id_to_set.hash_stable(hcx, hasher);
537 lint_cap.hash_stable(hcx, hasher);
539 hcx.while_hashing_spans(true, |hcx| {
540 list.len().hash_stable(hcx, hasher);
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);
548 LintSet::CommandLine { ref specs } => {
549 specs.hash_stable(hcx, hasher);
551 LintSet::Node { ref specs, parent } => {
552 specs.hash_stable(hcx, hasher);
553 parent.hash_stable(hcx, hasher);
561 impl<HCX> HashStable<HCX> for LintId {
563 fn hash_stable<W: StableHasherResult>(&self,
565 hasher: &mut StableHasher<W>) {
566 self.lint_name_raw().hash_stable(hcx, hasher);
570 impl<HCX> ToStableHashKey<HCX> for LintId {
571 type KeyType = &'static str;
574 fn to_stable_hash_key(&self, _: &HCX) -> &'static str {