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
);
77 // If the cap is less than this specified level, e.g., if we've got
78 // `--cap-lints allow` but we've also got `-D foo` then we ignore
79 // this specification as the lint cap will set it to allow anyway.
80 let level
= cmp
::min(level
, self.sets
.lint_cap
);
82 let lint_flag_val
= Symbol
::intern(lint_name
);
84 let ids
= match store
.find_lints(&lint_name
) {
86 Err(_
) => continue, // errors handled in check_lint_name_cmdline above
89 self.check_gated_lint(id
, DUMMY_SP
);
90 let src
= LintSource
::CommandLine(lint_flag_val
);
91 specs
.insert(id
, (level
, src
));
95 self.sets
.list
.push(LintSet
::CommandLine { specs }
);
98 /// Pushes a list of AST lint attributes onto this context.
100 /// This function will return a `BuilderPush` object which should be passed
101 /// to `pop` when this scope for the attributes provided is exited.
103 /// This function will perform a number of tasks:
105 /// * It'll validate all lint-related attributes in `attrs`
106 /// * It'll mark all lint-related attributes as used
107 /// * Lint levels will be updated based on the attributes provided
108 /// * Lint attributes are validated, e.g., a `#[forbid]` can't be switched to
111 /// Don't forget to call `pop`!
114 attrs
: &[ast
::Attribute
],
118 let mut specs
= FxHashMap
::default();
119 let sess
= self.sess
;
120 let bad_attr
= |span
| struct_span_err
!(sess
, span
, E0452
, "malformed lint attribute input");
122 let level
= match Level
::from_symbol(attr
.name_or_empty()) {
127 let meta
= unwrap_or
!(attr
.meta(), continue);
128 self.sess
.mark_attr_used(attr
);
130 let mut metas
= unwrap_or
!(meta
.meta_item_list(), continue);
132 if metas
.is_empty() {
133 // FIXME (#55112): issue unused-attributes lint for `#[level()]`
137 // Before processing the lint names, look for a reason (RFC 2383)
139 let mut reason
= None
;
140 let tail_li
= &metas
[metas
.len() - 1];
141 if let Some(item
) = tail_li
.meta_item() {
143 ast
::MetaItemKind
::Word
=> {}
// actual lint names handled later
144 ast
::MetaItemKind
::NameValue(ref name_value
) => {
145 if item
.path
== sym
::reason
{
146 // found reason, reslice meta list to exclude it
147 metas
= &metas
[0..metas
.len() - 1];
148 // FIXME (#55112): issue unused-attributes lint if we thereby
149 // don't have any lint names (`#[level(reason = "foo")]`)
150 if let ast
::LitKind
::Str(rationale
, _
) = name_value
.kind
{
151 if !self.sess
.features_untracked().lint_reasons
{
153 &self.sess
.parse_sess
,
156 "lint reasons are experimental",
160 reason
= Some(rationale
);
162 bad_attr(name_value
.span
)
163 .span_label(name_value
.span
, "reason must be a string literal")
168 .span_label(item
.span
, "bad attribute argument")
172 ast
::MetaItemKind
::List(_
) => {
173 bad_attr(item
.span
).span_label(item
.span
, "bad attribute argument").emit();
179 let meta_item
= match li
.meta_item() {
180 Some(meta_item
) if meta_item
.is_word() => meta_item
,
183 let mut err
= bad_attr(sp
);
184 let mut add_label
= true;
185 if let Some(item
) = li
.meta_item() {
186 if let ast
::MetaItemKind
::NameValue(_
) = item
.kind
{
187 if item
.path
== sym
::reason
{
188 err
.span_label(sp
, "reason in lint attribute must come last");
194 err
.span_label(sp
, "bad attribute argument");
200 let tool_name
= if meta_item
.path
.segments
.len() > 1 {
201 let tool_ident
= meta_item
.path
.segments
[0].ident
;
202 if !attr
::is_known_lint_tool(tool_ident
) {
207 "an unknown tool name found in scoped lint: `{}`",
208 pprust
::path_to_string(&meta_item
.path
),
214 Some(tool_ident
.name
)
218 let name
= meta_item
.path
.segments
.last().expect("empty lint name").ident
.name
;
219 match store
.check_lint_name(&name
.as_str(), tool_name
) {
220 CheckLintNameResult
::Ok(ids
) => {
221 let src
= LintSource
::Node(name
, li
.span(), reason
);
223 self.check_gated_lint(id
, attr
.span
);
224 specs
.insert(id
, (level
, src
));
228 CheckLintNameResult
::Tool(result
) => {
231 let complete_name
= &format
!("{}::{}", tool_name
.unwrap(), name
);
232 let src
= LintSource
::Node(
233 Symbol
::intern(complete_name
),
238 specs
.insert(*id
, (level
, src
));
241 Err((Some(ids
), new_lint_name
)) => {
242 let lint
= builtin
::RENAMED_AND_REMOVED_LINTS
;
244 self.sets
.get_lint_level(lint
, self.cur
, Some(&specs
), &sess
);
250 Some(li
.span().into()),
253 "lint name `{}` is deprecated \
254 and may not have an effect in the future. \
255 Also `cfg_attr(cargo-clippy)` won't be necessary anymore",
262 new_lint_name
.to_string(),
263 Applicability
::MachineApplicable
,
269 let src
= LintSource
::Node(
270 Symbol
::intern(&new_lint_name
),
275 specs
.insert(*id
, (level
, src
));
279 // If Tool(Err(None, _)) is returned, then either the lint does not
280 // exist in the tool or the code was not compiled with the tool and
281 // therefore the lint was never added to the `LintStore`. To detect
282 // this is the responsibility of the lint tool.
287 _
if !self.warn_about_weird_lints
=> {}
289 CheckLintNameResult
::Warning(msg
, renamed
) => {
290 let lint
= builtin
::RENAMED_AND_REMOVED_LINTS
;
292 self.sets
.get_lint_level(lint
, self.cur
, Some(&specs
), &sess
);
298 Some(li
.span().into()),
300 let mut err
= lint
.build(&msg
);
301 if let Some(new_name
) = renamed
{
306 Applicability
::MachineApplicable
,
313 CheckLintNameResult
::NoLint(suggestion
) => {
314 let lint
= builtin
::UNKNOWN_LINTS
;
316 self.sets
.get_lint_level(lint
, self.cur
, Some(&specs
), self.sess
);
322 Some(li
.span().into()),
324 let mut db
= lint
.build(&format
!("unknown lint: `{}`", name
));
325 if let Some(suggestion
) = suggestion
{
329 suggestion
.to_string(),
330 Applicability
::MachineApplicable
,
342 for (id
, &(level
, ref src
)) in specs
.iter() {
343 if !id
.lint
.crate_level_only
{
347 let (lint_attr_name
, lint_attr_span
) = match *src
{
348 LintSource
::Node(name
, span
, _
) => (name
, span
),
352 let lint
= builtin
::UNUSED_ATTRIBUTES
;
353 let (lint_level
, lint_src
) =
354 self.sets
.get_lint_level(lint
, self.cur
, Some(&specs
), self.sess
);
360 Some(lint_attr_span
.into()),
362 let mut db
= lint
.build(&format
!(
363 "{}({}) is ignored unless specified at crate level",
370 // don't set a separate error for every lint in the group
375 for (id
, &(level
, ref src
)) in specs
.iter() {
376 if level
== Level
::Forbid
{
379 let forbid_src
= match self.sets
.get_lint_id_level(*id
, self.cur
, None
) {
380 (Some(Level
::Forbid
), src
) => src
,
383 let forbidden_lint_name
= match forbid_src
{
384 LintSource
::Default
=> id
.to_string(),
385 LintSource
::Node(name
, _
, _
) => name
.to_string(),
386 LintSource
::CommandLine(name
) => name
.to_string(),
388 let (lint_attr_name
, lint_attr_span
) = match *src
{
389 LintSource
::Node(name
, span
, _
) => (name
, span
),
392 let mut diag_builder
= struct_span_err
!(
396 "{}({}) overruled by outer forbid({})",
401 diag_builder
.span_label(lint_attr_span
, "overruled by previous forbid");
403 LintSource
::Default
=> {}
404 LintSource
::Node(_
, forbid_source_span
, reason
) => {
405 diag_builder
.span_label(forbid_source_span
, "`forbid` level set here");
406 if let Some(rationale
) = reason
{
407 diag_builder
.note(&rationale
.as_str());
410 LintSource
::CommandLine(_
) => {
411 diag_builder
.note("`forbid` lint level was set on command line");
415 // don't set a separate error for every lint in the group
420 if !specs
.is_empty() {
421 self.cur
= self.sets
.list
.len() as u32;
422 self.sets
.list
.push(LintSet
::Node { specs, parent: prev }
);
425 BuilderPush { prev, changed: prev != self.cur }
428 /// Checks if the lint is gated on a feature that is not enabled.
429 fn check_gated_lint(&self, lint_id
: LintId
, span
: Span
) {
430 if let Some(feature
) = lint_id
.lint
.feature_gate
{
431 if !self.sess
.features_untracked().enabled(feature
) {
433 &self.sess
.parse_sess
,
436 &format
!("the `{}` lint is unstable", lint_id
.lint
.name_lower()),
443 /// Called after `push` when the scope of a set of attributes are exited.
444 pub fn pop(&mut self, push
: BuilderPush
) {
445 self.cur
= push
.prev
;
448 /// Find the lint level for a lint.
449 pub fn lint_level(&self, lint
: &'
static Lint
) -> (Level
, LintSource
) {
450 self.sets
.get_lint_level(lint
, self.cur
, None
, self.sess
)
453 /// Used to emit a lint-related diagnostic based on the current state of
454 /// this lint context.
458 span
: Option
<MultiSpan
>,
459 decorate
: impl for<'a
> FnOnce(LintDiagnosticBuilder
<'a
>),
461 let (level
, src
) = self.lint_level(lint
);
462 struct_lint_level(self.sess
, lint
, level
, src
, span
, decorate
)
465 /// Registers the ID provided with the current set of lints stored in
467 pub fn register_id(&mut self, id
: HirId
) {
468 self.id_to_set
.insert(id
, self.cur
);
471 pub fn build(self) -> LintLevelSets
{
475 pub fn build_map(self) -> LintLevelMap
{
476 LintLevelMap { sets: self.sets, id_to_set: self.id_to_set }
480 struct LintLevelMapBuilder
<'a
, 'tcx
> {
481 levels
: LintLevelsBuilder
<'tcx
>,
483 store
: &'a LintStore
,
486 impl LintLevelMapBuilder
<'_
, '_
> {
487 fn with_lint_attrs
<F
>(&mut self, id
: hir
::HirId
, attrs
: &[ast
::Attribute
], f
: F
)
489 F
: FnOnce(&mut Self),
491 let is_crate_hir
= id
== hir
::CRATE_HIR_ID
;
492 let push
= self.levels
.push(attrs
, self.store
, is_crate_hir
);
494 self.levels
.register_id(id
);
497 self.levels
.pop(push
);
501 impl<'tcx
> intravisit
::Visitor
<'tcx
> for LintLevelMapBuilder
<'_
, 'tcx
> {
502 type Map
= Map
<'tcx
>;
504 fn nested_visit_map(&mut self) -> intravisit
::NestedVisitorMap
<Self::Map
> {
505 intravisit
::NestedVisitorMap
::All(self.tcx
.hir())
508 fn visit_param(&mut self, param
: &'tcx hir
::Param
<'tcx
>) {
509 self.with_lint_attrs(param
.hir_id
, ¶m
.attrs
, |builder
| {
510 intravisit
::walk_param(builder
, param
);
514 fn visit_item(&mut self, it
: &'tcx hir
::Item
<'tcx
>) {
515 self.with_lint_attrs(it
.hir_id
, &it
.attrs
, |builder
| {
516 intravisit
::walk_item(builder
, it
);
520 fn visit_foreign_item(&mut self, it
: &'tcx hir
::ForeignItem
<'tcx
>) {
521 self.with_lint_attrs(it
.hir_id
, &it
.attrs
, |builder
| {
522 intravisit
::walk_foreign_item(builder
, it
);
526 fn visit_expr(&mut self, e
: &'tcx hir
::Expr
<'tcx
>) {
527 self.with_lint_attrs(e
.hir_id
, &e
.attrs
, |builder
| {
528 intravisit
::walk_expr(builder
, e
);
532 fn visit_struct_field(&mut self, s
: &'tcx hir
::StructField
<'tcx
>) {
533 self.with_lint_attrs(s
.hir_id
, &s
.attrs
, |builder
| {
534 intravisit
::walk_struct_field(builder
, s
);
540 v
: &'tcx hir
::Variant
<'tcx
>,
541 g
: &'tcx hir
::Generics
<'tcx
>,
544 self.with_lint_attrs(v
.id
, &v
.attrs
, |builder
| {
545 intravisit
::walk_variant(builder
, v
, g
, item_id
);
549 fn visit_local(&mut self, l
: &'tcx hir
::Local
<'tcx
>) {
550 self.with_lint_attrs(l
.hir_id
, &l
.attrs
, |builder
| {
551 intravisit
::walk_local(builder
, l
);
555 fn visit_arm(&mut self, a
: &'tcx hir
::Arm
<'tcx
>) {
556 self.with_lint_attrs(a
.hir_id
, &a
.attrs
, |builder
| {
557 intravisit
::walk_arm(builder
, a
);
561 fn visit_trait_item(&mut self, trait_item
: &'tcx hir
::TraitItem
<'tcx
>) {
562 self.with_lint_attrs(trait_item
.hir_id
, &trait_item
.attrs
, |builder
| {
563 intravisit
::walk_trait_item(builder
, trait_item
);
567 fn visit_impl_item(&mut self, impl_item
: &'tcx hir
::ImplItem
<'tcx
>) {
568 self.with_lint_attrs(impl_item
.hir_id
, &impl_item
.attrs
, |builder
| {
569 intravisit
::walk_impl_item(builder
, impl_item
);
574 pub fn provide(providers
: &mut Providers
) {
575 providers
.lint_levels
= lint_levels
;