1 use crate::context
::{CheckLintNameResult, LintStore}
;
2 use crate::late
::unerased_lint_store
;
5 use rustc_ast
::unwrap_or
;
6 use rustc_ast_pretty
::pprust
;
7 use rustc_data_structures
::fx
::FxHashMap
;
8 use rustc_errors
::{struct_span_err, Applicability}
;
10 use rustc_hir
::def_id
::{CrateNum, LOCAL_CRATE}
;
11 use rustc_hir
::{intravisit, HirId}
;
12 use rustc_middle
::hir
::map
::Map
;
13 use rustc_middle
::lint
::LintDiagnosticBuilder
;
14 use rustc_middle
::lint
::{struct_lint_level, LintLevelMap, LintLevelSets, LintSet, LintSource}
;
15 use rustc_middle
::ty
::query
::Providers
;
16 use rustc_middle
::ty
::TyCtxt
;
17 use rustc_session
::lint
::{builtin, Level, Lint, LintId}
;
18 use rustc_session
::parse
::feature_err
;
19 use rustc_session
::Session
;
20 use rustc_span
::symbol
::{sym, Symbol}
;
21 use rustc_span
::{source_map::MultiSpan, Span, DUMMY_SP}
;
25 fn lint_levels(tcx
: TyCtxt
<'_
>, cnum
: CrateNum
) -> LintLevelMap
{
26 assert_eq
!(cnum
, LOCAL_CRATE
);
27 let store
= unerased_lint_store(tcx
);
28 let levels
= LintLevelsBuilder
::new(tcx
.sess
, false, &store
);
29 let mut builder
= LintLevelMapBuilder { levels, tcx, store }
;
30 let krate
= tcx
.hir().krate();
32 let push
= builder
.levels
.push(&krate
.item
.attrs
, &store
, true);
33 builder
.levels
.register_id(hir
::CRATE_HIR_ID
);
34 for macro_def
in krate
.exported_macros
{
35 builder
.levels
.register_id(macro_def
.hir_id
);
37 intravisit
::walk_crate(&mut builder
, krate
);
38 builder
.levels
.pop(push
);
40 builder
.levels
.build_map()
43 pub struct LintLevelsBuilder
<'s
> {
46 id_to_set
: FxHashMap
<HirId
, u32>,
48 warn_about_weird_lints
: bool
,
51 pub struct BuilderPush
{
56 impl<'s
> LintLevelsBuilder
<'s
> {
57 pub fn new(sess
: &'s Session
, warn_about_weird_lints
: bool
, store
: &LintStore
) -> Self {
58 let mut builder
= LintLevelsBuilder
{
60 sets
: LintLevelSets
::new(),
62 id_to_set
: Default
::default(),
63 warn_about_weird_lints
,
65 builder
.process_command_line(sess
, store
);
66 assert_eq
!(builder
.sets
.list
.len(), 1);
70 fn process_command_line(&mut self, sess
: &Session
, store
: &LintStore
) {
71 let mut specs
= FxHashMap
::default();
72 self.sets
.lint_cap
= sess
.opts
.lint_cap
.unwrap_or(Level
::Forbid
);
74 for &(ref lint_name
, level
) in &sess
.opts
.lint_opts
{
75 store
.check_lint_name_cmdline(sess
, &lint_name
, level
);
76 let orig_level
= level
;
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
);
83 let lint_flag_val
= Symbol
::intern(lint_name
);
85 let ids
= match store
.find_lints(&lint_name
) {
87 Err(_
) => continue, // errors handled in check_lint_name_cmdline above
90 self.check_gated_lint(id
, DUMMY_SP
);
91 let src
= LintSource
::CommandLine(lint_flag_val
, orig_level
);
92 specs
.insert(id
, (level
, src
));
96 self.sets
.list
.push(LintSet
::CommandLine { specs }
);
99 /// Pushes a list of AST lint attributes onto this context.
101 /// This function will return a `BuilderPush` object which should be passed
102 /// to `pop` when this scope for the attributes provided is exited.
104 /// This function will perform a number of tasks:
106 /// * It'll validate all lint-related attributes in `attrs`
107 /// * It'll mark all lint-related attributes as used
108 /// * Lint levels will be updated based on the attributes provided
109 /// * Lint attributes are validated, e.g., a `#[forbid]` can't be switched to
112 /// Don't forget to call `pop`!
115 attrs
: &[ast
::Attribute
],
119 let mut specs
= FxHashMap
::default();
120 let sess
= self.sess
;
121 let bad_attr
= |span
| struct_span_err
!(sess
, span
, E0452
, "malformed lint attribute input");
123 let level
= match Level
::from_symbol(attr
.name_or_empty()) {
128 let meta
= unwrap_or
!(attr
.meta(), continue);
129 self.sess
.mark_attr_used(attr
);
131 let mut metas
= unwrap_or
!(meta
.meta_item_list(), continue);
133 if metas
.is_empty() {
134 // FIXME (#55112): issue unused-attributes lint for `#[level()]`
138 // Before processing the lint names, look for a reason (RFC 2383)
140 let mut reason
= None
;
141 let tail_li
= &metas
[metas
.len() - 1];
142 if let Some(item
) = tail_li
.meta_item() {
144 ast
::MetaItemKind
::Word
=> {}
// actual lint names handled later
145 ast
::MetaItemKind
::NameValue(ref name_value
) => {
146 if item
.path
== sym
::reason
{
147 // found reason, reslice meta list to exclude it
148 metas
= &metas
[0..metas
.len() - 1];
149 // FIXME (#55112): issue unused-attributes lint if we thereby
150 // don't have any lint names (`#[level(reason = "foo")]`)
151 if let ast
::LitKind
::Str(rationale
, _
) = name_value
.kind
{
152 if !self.sess
.features_untracked().lint_reasons
{
154 &self.sess
.parse_sess
,
157 "lint reasons are experimental",
161 reason
= Some(rationale
);
163 bad_attr(name_value
.span
)
164 .span_label(name_value
.span
, "reason must be a string literal")
169 .span_label(item
.span
, "bad attribute argument")
173 ast
::MetaItemKind
::List(_
) => {
174 bad_attr(item
.span
).span_label(item
.span
, "bad attribute argument").emit();
180 let meta_item
= match li
.meta_item() {
181 Some(meta_item
) if meta_item
.is_word() => meta_item
,
184 let mut err
= bad_attr(sp
);
185 let mut add_label
= true;
186 if let Some(item
) = li
.meta_item() {
187 if let ast
::MetaItemKind
::NameValue(_
) = item
.kind
{
188 if item
.path
== sym
::reason
{
189 err
.span_label(sp
, "reason in lint attribute must come last");
195 err
.span_label(sp
, "bad attribute argument");
201 let tool_name
= if meta_item
.path
.segments
.len() > 1 {
202 let tool_ident
= meta_item
.path
.segments
[0].ident
;
203 if !attr
::is_known_lint_tool(tool_ident
) {
208 "an unknown tool name found in scoped lint: `{}`",
209 pprust
::path_to_string(&meta_item
.path
),
215 Some(tool_ident
.name
)
219 let name
= meta_item
.path
.segments
.last().expect("empty lint name").ident
.name
;
220 match store
.check_lint_name(&name
.as_str(), tool_name
) {
221 CheckLintNameResult
::Ok(ids
) => {
222 let src
= LintSource
::Node(name
, li
.span(), reason
);
224 self.check_gated_lint(id
, attr
.span
);
225 specs
.insert(id
, (level
, src
));
229 CheckLintNameResult
::Tool(result
) => {
232 let complete_name
= &format
!("{}::{}", tool_name
.unwrap(), name
);
233 let src
= LintSource
::Node(
234 Symbol
::intern(complete_name
),
239 specs
.insert(*id
, (level
, src
));
242 Err((Some(ids
), new_lint_name
)) => {
243 let lint
= builtin
::RENAMED_AND_REMOVED_LINTS
;
245 self.sets
.get_lint_level(lint
, self.cur
, Some(&specs
), &sess
);
251 Some(li
.span().into()),
254 "lint name `{}` is deprecated \
255 and may not have an effect in the future. \
256 Also `cfg_attr(cargo-clippy)` won't be necessary anymore",
263 new_lint_name
.to_string(),
264 Applicability
::MachineApplicable
,
270 let src
= LintSource
::Node(
271 Symbol
::intern(&new_lint_name
),
276 specs
.insert(*id
, (level
, src
));
280 // If Tool(Err(None, _)) is returned, then either the lint does not
281 // exist in the tool or the code was not compiled with the tool and
282 // therefore the lint was never added to the `LintStore`. To detect
283 // this is the responsibility of the lint tool.
288 _
if !self.warn_about_weird_lints
=> {}
290 CheckLintNameResult
::Warning(msg
, renamed
) => {
291 let lint
= builtin
::RENAMED_AND_REMOVED_LINTS
;
293 self.sets
.get_lint_level(lint
, self.cur
, Some(&specs
), &sess
);
299 Some(li
.span().into()),
301 let mut err
= lint
.build(&msg
);
302 if let Some(new_name
) = renamed
{
307 Applicability
::MachineApplicable
,
314 CheckLintNameResult
::NoLint(suggestion
) => {
315 let lint
= builtin
::UNKNOWN_LINTS
;
317 self.sets
.get_lint_level(lint
, self.cur
, Some(&specs
), self.sess
);
323 Some(li
.span().into()),
325 let mut db
= lint
.build(&format
!("unknown lint: `{}`", name
));
326 if let Some(suggestion
) = suggestion
{
330 suggestion
.to_string(),
331 Applicability
::MachineApplicable
,
343 for (id
, &(level
, ref src
)) in specs
.iter() {
344 if !id
.lint
.crate_level_only
{
348 let (lint_attr_name
, lint_attr_span
) = match *src
{
349 LintSource
::Node(name
, span
, _
) => (name
, span
),
353 let lint
= builtin
::UNUSED_ATTRIBUTES
;
354 let (lint_level
, lint_src
) =
355 self.sets
.get_lint_level(lint
, self.cur
, Some(&specs
), self.sess
);
361 Some(lint_attr_span
.into()),
363 let mut db
= lint
.build(&format
!(
364 "{}({}) is ignored unless specified at crate level",
371 // don't set a separate error for every lint in the group
376 for (id
, &(level
, ref src
)) in specs
.iter() {
377 if level
== Level
::Forbid
{
380 let forbid_src
= match self.sets
.get_lint_id_level(*id
, self.cur
, None
) {
381 (Some(Level
::Forbid
), src
) => src
,
384 let forbidden_lint_name
= match forbid_src
{
385 LintSource
::Default
=> id
.to_string(),
386 LintSource
::Node(name
, _
, _
) => name
.to_string(),
387 LintSource
::CommandLine(name
, _
) => name
.to_string(),
389 let (lint_attr_name
, lint_attr_span
) = match *src
{
390 LintSource
::Node(name
, span
, _
) => (name
, span
),
393 let mut diag_builder
= struct_span_err
!(
397 "{}({}) overruled by outer forbid({})",
402 diag_builder
.span_label(lint_attr_span
, "overruled by previous forbid");
404 LintSource
::Default
=> {}
405 LintSource
::Node(_
, forbid_source_span
, reason
) => {
406 diag_builder
.span_label(forbid_source_span
, "`forbid` level set here");
407 if let Some(rationale
) = reason
{
408 diag_builder
.note(&rationale
.as_str());
411 LintSource
::CommandLine(_
, _
) => {
412 diag_builder
.note("`forbid` lint level was set on command line");
416 // don't set a separate error for every lint in the group
421 if !specs
.is_empty() {
422 self.cur
= self.sets
.list
.len() as u32;
423 self.sets
.list
.push(LintSet
::Node { specs, parent: prev }
);
426 BuilderPush { prev, changed: prev != self.cur }
429 /// Checks if the lint is gated on a feature that is not enabled.
430 fn check_gated_lint(&self, lint_id
: LintId
, span
: Span
) {
431 if let Some(feature
) = lint_id
.lint
.feature_gate
{
432 if !self.sess
.features_untracked().enabled(feature
) {
434 &self.sess
.parse_sess
,
437 &format
!("the `{}` lint is unstable", lint_id
.lint
.name_lower()),
444 /// Called after `push` when the scope of a set of attributes are exited.
445 pub fn pop(&mut self, push
: BuilderPush
) {
446 self.cur
= push
.prev
;
449 /// Find the lint level for a lint.
450 pub fn lint_level(&self, lint
: &'
static Lint
) -> (Level
, LintSource
) {
451 self.sets
.get_lint_level(lint
, self.cur
, None
, self.sess
)
454 /// Used to emit a lint-related diagnostic based on the current state of
455 /// this lint context.
459 span
: Option
<MultiSpan
>,
460 decorate
: impl for<'a
> FnOnce(LintDiagnosticBuilder
<'a
>),
462 let (level
, src
) = self.lint_level(lint
);
463 struct_lint_level(self.sess
, lint
, level
, src
, span
, decorate
)
466 /// Registers the ID provided with the current set of lints stored in
468 pub fn register_id(&mut self, id
: HirId
) {
469 self.id_to_set
.insert(id
, self.cur
);
472 pub fn build(self) -> LintLevelSets
{
476 pub fn build_map(self) -> LintLevelMap
{
477 LintLevelMap { sets: self.sets, id_to_set: self.id_to_set }
481 struct LintLevelMapBuilder
<'a
, 'tcx
> {
482 levels
: LintLevelsBuilder
<'tcx
>,
484 store
: &'a LintStore
,
487 impl LintLevelMapBuilder
<'_
, '_
> {
488 fn with_lint_attrs
<F
>(&mut self, id
: hir
::HirId
, attrs
: &[ast
::Attribute
], f
: F
)
490 F
: FnOnce(&mut Self),
492 let is_crate_hir
= id
== hir
::CRATE_HIR_ID
;
493 let push
= self.levels
.push(attrs
, self.store
, is_crate_hir
);
495 self.levels
.register_id(id
);
498 self.levels
.pop(push
);
502 impl<'tcx
> intravisit
::Visitor
<'tcx
> for LintLevelMapBuilder
<'_
, 'tcx
> {
503 type Map
= Map
<'tcx
>;
505 fn nested_visit_map(&mut self) -> intravisit
::NestedVisitorMap
<Self::Map
> {
506 intravisit
::NestedVisitorMap
::All(self.tcx
.hir())
509 fn visit_param(&mut self, param
: &'tcx hir
::Param
<'tcx
>) {
510 self.with_lint_attrs(param
.hir_id
, ¶m
.attrs
, |builder
| {
511 intravisit
::walk_param(builder
, param
);
515 fn visit_item(&mut self, it
: &'tcx hir
::Item
<'tcx
>) {
516 self.with_lint_attrs(it
.hir_id
, &it
.attrs
, |builder
| {
517 intravisit
::walk_item(builder
, it
);
521 fn visit_foreign_item(&mut self, it
: &'tcx hir
::ForeignItem
<'tcx
>) {
522 self.with_lint_attrs(it
.hir_id
, &it
.attrs
, |builder
| {
523 intravisit
::walk_foreign_item(builder
, it
);
527 fn visit_stmt(&mut self, e
: &'tcx hir
::Stmt
<'tcx
>) {
528 // We will call `with_lint_attrs` when we walk
529 // the `StmtKind`. The outer statement itself doesn't
530 // define the lint levels.
531 intravisit
::walk_stmt(self, e
);
534 fn visit_expr(&mut self, e
: &'tcx hir
::Expr
<'tcx
>) {
535 self.with_lint_attrs(e
.hir_id
, &e
.attrs
, |builder
| {
536 intravisit
::walk_expr(builder
, e
);
540 fn visit_struct_field(&mut self, s
: &'tcx hir
::StructField
<'tcx
>) {
541 self.with_lint_attrs(s
.hir_id
, &s
.attrs
, |builder
| {
542 intravisit
::walk_struct_field(builder
, s
);
548 v
: &'tcx hir
::Variant
<'tcx
>,
549 g
: &'tcx hir
::Generics
<'tcx
>,
552 self.with_lint_attrs(v
.id
, &v
.attrs
, |builder
| {
553 intravisit
::walk_variant(builder
, v
, g
, item_id
);
557 fn visit_local(&mut self, l
: &'tcx hir
::Local
<'tcx
>) {
558 self.with_lint_attrs(l
.hir_id
, &l
.attrs
, |builder
| {
559 intravisit
::walk_local(builder
, l
);
563 fn visit_arm(&mut self, a
: &'tcx hir
::Arm
<'tcx
>) {
564 self.with_lint_attrs(a
.hir_id
, &a
.attrs
, |builder
| {
565 intravisit
::walk_arm(builder
, a
);
569 fn visit_trait_item(&mut self, trait_item
: &'tcx hir
::TraitItem
<'tcx
>) {
570 self.with_lint_attrs(trait_item
.hir_id
, &trait_item
.attrs
, |builder
| {
571 intravisit
::walk_trait_item(builder
, trait_item
);
575 fn visit_impl_item(&mut self, impl_item
: &'tcx hir
::ImplItem
<'tcx
>) {
576 self.with_lint_attrs(impl_item
.hir_id
, &impl_item
.attrs
, |builder
| {
577 intravisit
::walk_impl_item(builder
, impl_item
);
582 pub fn provide(providers
: &mut Providers
) {
583 providers
.lint_levels
= lint_levels
;