1 use crate::context
::{CheckLintNameResult, LintStore}
;
2 use crate::late
::unerased_lint_store
;
4 use rustc_ast_pretty
::pprust
;
5 use rustc_data_structures
::fx
::FxHashMap
;
6 use rustc_errors
::{struct_span_err, Applicability, Diagnostic, MultiSpan}
;
8 use rustc_hir
::{intravisit, HirId}
;
9 use rustc_middle
::hir
::nested_filter
;
10 use rustc_middle
::lint
::{
11 struct_lint_level
, LevelAndSource
, LintDiagnosticBuilder
, LintExpectation
, LintLevelMap
,
12 LintLevelSets
, LintLevelSource
, LintSet
, LintStackIndex
, COMMAND_LINE
,
14 use rustc_middle
::ty
::query
::Providers
;
15 use rustc_middle
::ty
::{RegisteredTools, TyCtxt}
;
16 use rustc_session
::lint
::{
17 builtin
::{self, FORBIDDEN_LINT_GROUPS, UNFULFILLED_LINT_EXPECTATIONS}
,
18 Level
, Lint
, LintExpectationId
, LintId
,
20 use rustc_session
::parse
::{add_feature_diagnostics, feature_err}
;
21 use rustc_session
::Session
;
22 use rustc_span
::symbol
::{sym, Symbol}
;
23 use rustc_span
::{Span, DUMMY_SP}
;
26 fn lint_levels(tcx
: TyCtxt
<'_
>, (): ()) -> LintLevelMap
{
27 let store
= unerased_lint_store(tcx
);
29 LintLevelsBuilder
::new(tcx
.sess
, false, &store
, &tcx
.resolutions(()).registered_tools
);
30 let mut builder
= LintLevelMapBuilder { levels, tcx }
;
31 let krate
= tcx
.hir().krate();
33 builder
.levels
.id_to_set
.reserve(krate
.owners
.len() + 1);
36 builder
.levels
.push(tcx
.hir().attrs(hir
::CRATE_HIR_ID
), true, Some(hir
::CRATE_HIR_ID
));
38 builder
.levels
.register_id(hir
::CRATE_HIR_ID
);
39 tcx
.hir().walk_toplevel_module(&mut builder
);
40 builder
.levels
.pop(push
);
42 builder
.levels
.update_unstable_expectation_ids();
43 builder
.levels
.build_map()
46 pub struct LintLevelsBuilder
<'s
> {
48 lint_expectations
: Vec
<(LintExpectationId
, LintExpectation
)>,
49 /// Each expectation has a stable and an unstable identifier. This map
50 /// is used to map from unstable to stable [`LintExpectationId`]s.
51 expectation_id_map
: FxHashMap
<LintExpectationId
, LintExpectationId
>,
53 id_to_set
: FxHashMap
<HirId
, LintStackIndex
>,
55 warn_about_weird_lints
: bool
,
57 registered_tools
: &'s RegisteredTools
,
60 pub struct BuilderPush
{
65 impl<'s
> LintLevelsBuilder
<'s
> {
68 warn_about_weird_lints
: bool
,
70 registered_tools
: &'s RegisteredTools
,
72 let mut builder
= LintLevelsBuilder
{
74 lint_expectations
: Default
::default(),
75 expectation_id_map
: Default
::default(),
76 sets
: LintLevelSets
::new(),
78 id_to_set
: Default
::default(),
79 warn_about_weird_lints
,
83 builder
.process_command_line(sess
, store
);
84 assert_eq
!(builder
.sets
.list
.len(), 1);
88 pub(crate) fn sess(&self) -> &Session
{
92 pub(crate) fn lint_store(&self) -> &LintStore
{
96 fn current_specs(&self) -> &FxHashMap
<LintId
, LevelAndSource
> {
97 &self.sets
.list
[self.cur
].specs
100 fn current_specs_mut(&mut self) -> &mut FxHashMap
<LintId
, LevelAndSource
> {
101 &mut self.sets
.list
[self.cur
].specs
104 fn process_command_line(&mut self, sess
: &Session
, store
: &LintStore
) {
105 self.sets
.lint_cap
= sess
.opts
.lint_cap
.unwrap_or(Level
::Forbid
);
108 self.sets
.list
.push(LintSet { specs: FxHashMap::default(), parent: COMMAND_LINE }
);
109 for &(ref lint_name
, level
) in &sess
.opts
.lint_opts
{
110 store
.check_lint_name_cmdline(sess
, &lint_name
, level
, self.registered_tools
);
111 let orig_level
= level
;
112 let lint_flag_val
= Symbol
::intern(lint_name
);
114 let Ok(ids
) = store
.find_lints(&lint_name
) else {
115 // errors handled in check_lint_name_cmdline above
119 // ForceWarn and Forbid cannot be overridden
120 if let Some((Level
::ForceWarn
| Level
::Forbid
, _
)) = self.current_specs().get(&id
) {
124 if self.check_gated_lint(id
, DUMMY_SP
) {
125 let src
= LintLevelSource
::CommandLine(lint_flag_val
, orig_level
);
126 self.current_specs_mut().insert(id
, (level
, src
));
132 /// Attempts to insert the `id` to `level_src` map entry. If unsuccessful
133 /// (e.g. if a forbid was already inserted on the same scope), then emits a
134 /// diagnostic with no change to `specs`.
135 fn insert_spec(&mut self, id
: LintId
, (level
, src
): LevelAndSource
) {
136 let (old_level
, old_src
) =
137 self.sets
.get_lint_level(id
.lint
, self.cur
, Some(self.current_specs()), &self.sess
);
138 // Setting to a non-forbid level is an error if the lint previously had
139 // a forbid level. Note that this is not necessarily true even with a
140 // `#[forbid(..)]` attribute present, as that is overridden by `--cap-lints`.
142 // This means that this only errors if we're truly lowering the lint
143 // level from forbid.
144 if level
!= Level
::Forbid
{
145 if let Level
::Forbid
= old_level
{
146 // Backwards compatibility check:
148 // We used to not consider `forbid(lint_group)`
149 // as preventing `allow(lint)` for some lint `lint` in
150 // `lint_group`. For now, issue a future-compatibility
151 // warning for this case.
152 let id_name
= id
.lint
.name_lower();
153 let fcw_warning
= match old_src
{
154 LintLevelSource
::Default
=> false,
155 LintLevelSource
::Node(symbol
, _
, _
) => self.store
.is_lint_group(symbol
),
156 LintLevelSource
::CommandLine(symbol
, _
) => self.store
.is_lint_group(symbol
),
159 "fcw_warning={:?}, specs.get(&id) = {:?}, old_src={:?}, id_name={:?}",
161 self.current_specs(),
166 let decorate_diag
= |diag
: &mut Diagnostic
| {
167 diag
.span_label(src
.span(), "overruled by previous forbid");
169 LintLevelSource
::Default
=> {
171 "`forbid` lint level is the default for {}",
175 LintLevelSource
::Node(_
, forbid_source_span
, reason
) => {
176 diag
.span_label(forbid_source_span
, "`forbid` level set here");
177 if let Some(rationale
) = reason
{
178 diag
.note(rationale
.as_str());
181 LintLevelSource
::CommandLine(_
, _
) => {
182 diag
.note("`forbid` lint level was set on command line");
187 let mut diag_builder
= struct_span_err
!(
191 "{}({}) incompatible with previous forbid",
195 decorate_diag(&mut diag_builder
);
199 FORBIDDEN_LINT_GROUPS
,
200 Some(src
.span().into()),
202 let mut diag_builder
= diag_builder
.build(&format
!(
203 "{}({}) incompatible with previous forbid",
207 decorate_diag(&mut diag_builder
);
213 // Retain the forbid lint level, unless we are
214 // issuing a FCW. In the FCW case, we want to
215 // respect the new setting.
222 // The lint `unfulfilled_lint_expectations` can't be expected, as it would suppress itself.
223 // Handling expectations of this lint would add additional complexity with little to no
224 // benefit. The expect level for this lint will therefore be ignored.
225 if let Level
::Expect(_
) = level
&& id
== LintId
::of(UNFULFILLED_LINT_EXPECTATIONS
) {
229 if let Level
::ForceWarn
= old_level
{
230 self.current_specs_mut().insert(id
, (old_level
, old_src
));
232 self.current_specs_mut().insert(id
, (level
, src
));
236 /// Pushes a list of AST lint attributes onto this context.
238 /// This function will return a `BuilderPush` object which should be passed
239 /// to `pop` when this scope for the attributes provided is exited.
241 /// This function will perform a number of tasks:
243 /// * It'll validate all lint-related attributes in `attrs`
244 /// * It'll mark all lint-related attributes as used
245 /// * Lint levels will be updated based on the attributes provided
246 /// * Lint attributes are validated, e.g., a `#[forbid]` can't be switched to
249 /// Don't forget to call `pop`!
252 attrs
: &[ast
::Attribute
],
254 source_hir_id
: Option
<HirId
>,
257 self.cur
= self.sets
.list
.push(LintSet { specs: FxHashMap::default(), parent: prev }
);
259 let sess
= self.sess
;
260 let bad_attr
= |span
| struct_span_err
!(sess
, span
, E0452
, "malformed lint attribute input");
261 for (attr_index
, attr
) in attrs
.iter().enumerate() {
262 let level
= match Level
::from_attr(attr
) {
264 Some(Level
::Expect(unstable_id
)) if let Some(hir_id
) = source_hir_id
=> {
265 let stable_id
= self.create_stable_id(unstable_id
, hir_id
, attr_index
);
267 Level
::Expect(stable_id
)
272 let Some(mut metas
) = attr
.meta_item_list() else {
276 if metas
.is_empty() {
277 // This emits the unused_attributes lint for `#[level()]`
281 // Before processing the lint names, look for a reason (RFC 2383)
283 let mut reason
= None
;
284 let tail_li
= &metas
[metas
.len() - 1];
285 if let Some(item
) = tail_li
.meta_item() {
287 ast
::MetaItemKind
::Word
=> {}
// actual lint names handled later
288 ast
::MetaItemKind
::NameValue(ref name_value
) => {
289 if item
.path
== sym
::reason
{
290 if let ast
::LitKind
::Str(rationale
, _
) = name_value
.kind
{
291 if !self.sess
.features_untracked().lint_reasons
{
293 &self.sess
.parse_sess
,
296 "lint reasons are experimental",
300 reason
= Some(rationale
);
302 bad_attr(name_value
.span
)
303 .span_label(name_value
.span
, "reason must be a string literal")
306 // found reason, reslice meta list to exclude it
307 metas
.pop().unwrap();
310 .span_label(item
.span
, "bad attribute argument")
314 ast
::MetaItemKind
::List(_
) => {
315 bad_attr(item
.span
).span_label(item
.span
, "bad attribute argument").emit();
320 for (lint_index
, li
) in metas
.iter_mut().enumerate() {
321 let level
= match level
{
322 Level
::Expect(mut id
) => {
323 id
.set_lint_index(Some(lint_index
as u16));
330 let meta_item
= match li
{
331 ast
::NestedMetaItem
::MetaItem(meta_item
) if meta_item
.is_word() => meta_item
,
333 let mut err
= bad_attr(sp
);
334 let mut add_label
= true;
335 if let Some(item
) = li
.meta_item() {
336 if let ast
::MetaItemKind
::NameValue(_
) = item
.kind
{
337 if item
.path
== sym
::reason
{
338 err
.span_label(sp
, "reason in lint attribute must come last");
344 err
.span_label(sp
, "bad attribute argument");
350 let tool_ident
= if meta_item
.path
.segments
.len() > 1 {
351 Some(meta_item
.path
.segments
.remove(0).ident
)
355 let tool_name
= tool_ident
.map(|ident
| ident
.name
);
356 let name
= pprust
::path_to_string(&meta_item
.path
);
358 self.store
.check_lint_name(&name
, tool_name
, self.registered_tools
);
360 CheckLintNameResult
::Ok(ids
) => {
361 // This checks for instances where the user writes `#[expect(unfulfilled_lint_expectations)]`
362 // in that case we want to avoid overriding the lint level but instead add an expectation that
363 // can't be fulfilled. The lint message will include an explanation, that the
364 // `unfulfilled_lint_expectations` lint can't be expected.
365 if let Level
::Expect(expect_id
) = level
{
366 // The `unfulfilled_lint_expectations` lint is not part of any lint groups. Therefore. we
367 // only need to check the slice if it contains a single lint.
368 let is_unfulfilled_lint_expectations
= match ids
{
369 [lint
] => *lint
== LintId
::of(UNFULFILLED_LINT_EXPECTATIONS
),
372 self.lint_expectations
.push((
374 LintExpectation
::new(
377 is_unfulfilled_lint_expectations
,
382 let src
= LintLevelSource
::Node(
383 meta_item
.path
.segments
.last().expect("empty lint name").ident
.name
,
388 if self.check_gated_lint(id
, attr
.span
) {
389 self.insert_spec(id
, (level
, src
));
394 CheckLintNameResult
::Tool(result
) => {
398 &format
!("{}::{}", tool_ident
.unwrap().name
, name
);
399 let src
= LintLevelSource
::Node(
400 Symbol
::intern(complete_name
),
405 self.insert_spec(*id
, (level
, src
));
407 if let Level
::Expect(expect_id
) = level
{
408 self.lint_expectations
.push((
410 LintExpectation
::new(reason
, sp
, false, tool_name
),
414 Err((Some(ids
), ref new_lint_name
)) => {
415 let lint
= builtin
::RENAMED_AND_REMOVED_LINTS
;
416 let (lvl
, src
) = self.sets
.get_lint_level(
419 Some(self.current_specs()),
430 "lint name `{}` is deprecated \
431 and may not have an effect in the future.",
438 new_lint_name
.to_string(),
439 Applicability
::MachineApplicable
,
445 let src
= LintLevelSource
::Node(
446 Symbol
::intern(&new_lint_name
),
451 self.insert_spec(*id
, (level
, src
));
453 if let Level
::Expect(expect_id
) = level
{
454 self.lint_expectations
.push((
456 LintExpectation
::new(reason
, sp
, false, tool_name
),
461 // If Tool(Err(None, _)) is returned, then either the lint does not
462 // exist in the tool or the code was not compiled with the tool and
463 // therefore the lint was never added to the `LintStore`. To detect
464 // this is the responsibility of the lint tool.
469 &CheckLintNameResult
::NoTool
=> {
470 let mut err
= struct_span_err
!(
472 tool_ident
.map_or(DUMMY_SP
, |ident
| ident
.span
),
474 "unknown tool name `{}` found in scoped lint: `{}::{}`",
477 pprust
::path_to_string(&meta_item
.path
),
479 if sess
.is_nightly_build() {
481 "add `#![register_tool({})]` to the crate root",
489 _
if !self.warn_about_weird_lints
=> {}
491 CheckLintNameResult
::Warning(msg
, renamed
) => {
492 let lint
= builtin
::RENAMED_AND_REMOVED_LINTS
;
493 let (renamed_lint_level
, src
) = self.sets
.get_lint_level(
496 Some(self.current_specs()),
506 let mut err
= lint
.build(&msg
);
507 if let Some(new_name
) = &renamed
{
511 new_name
.to_string(),
512 Applicability
::MachineApplicable
,
519 CheckLintNameResult
::NoLint(suggestion
) => {
520 let lint
= builtin
::UNKNOWN_LINTS
;
521 let (level
, src
) = self.sets
.get_lint_level(
524 Some(self.current_specs()),
527 struct_lint_level(self.sess
, lint
, level
, src
, Some(sp
.into()), |lint
| {
528 let name
= if let Some(tool_ident
) = tool_ident
{
529 format
!("{}::{}", tool_ident
.name
, name
)
533 let mut db
= lint
.build(&format
!("unknown lint: `{}`", name
));
534 if let Some(suggestion
) = suggestion
{
538 suggestion
.to_string(),
539 Applicability
::MachineApplicable
,
546 // If this lint was renamed, apply the new lint instead of ignoring the attribute.
547 // This happens outside of the match because the new lint should be applied even if
548 // we don't warn about the name change.
549 if let CheckLintNameResult
::Warning(_
, Some(new_name
)) = lint_result
{
550 // Ignore any errors or warnings that happen because the new name is inaccurate
551 // NOTE: `new_name` already includes the tool name, so we don't have to add it again.
552 if let CheckLintNameResult
::Ok(ids
) =
553 self.store
.check_lint_name(&new_name
, None
, self.registered_tools
)
555 let src
= LintLevelSource
::Node(Symbol
::intern(&new_name
), sp
, reason
);
557 if self.check_gated_lint(id
, attr
.span
) {
558 self.insert_spec(id
, (level
, src
));
561 if let Level
::Expect(expect_id
) = level
{
562 self.lint_expectations
.push((
564 LintExpectation
::new(reason
, sp
, false, tool_name
),
568 panic
!("renamed lint does not exist: {}", new_name
);
575 for (id
, &(level
, ref src
)) in self.current_specs().iter() {
576 if !id
.lint
.crate_level_only
{
580 let LintLevelSource
::Node(lint_attr_name
, lint_attr_span
, _
) = *src
else {
584 let lint
= builtin
::UNUSED_ATTRIBUTES
;
585 let (lint_level
, lint_src
) =
586 self.sets
.get_lint_level(lint
, self.cur
, Some(self.current_specs()), self.sess
);
592 Some(lint_attr_span
.into()),
594 let mut db
= lint
.build(&format
!(
595 "{}({}) is ignored unless specified at crate level",
602 // don't set a separate error for every lint in the group
607 if self.current_specs().is_empty() {
608 self.sets
.list
.pop();
612 BuilderPush { prev, changed: prev != self.cur }
617 unstable_id
: LintExpectationId
,
620 ) -> LintExpectationId
{
622 LintExpectationId
::Stable { hir_id, attr_index: attr_index as u16, lint_index: None }
;
624 self.expectation_id_map
.insert(unstable_id
, stable_id
);
629 /// Checks if the lint is gated on a feature that is not enabled.
631 /// Returns `true` if the lint's feature is enabled.
632 fn check_gated_lint(&self, lint_id
: LintId
, span
: Span
) -> bool
{
633 if let Some(feature
) = lint_id
.lint
.feature_gate
{
634 if !self.sess
.features_untracked().enabled(feature
) {
635 let lint
= builtin
::UNKNOWN_LINTS
;
636 let (level
, src
) = self.lint_level(builtin
::UNKNOWN_LINTS
);
637 struct_lint_level(self.sess
, lint
, level
, src
, Some(span
.into()), |lint_db
| {
639 lint_db
.build(&format
!("unknown lint: `{}`", lint_id
.lint
.name_lower()));
640 db
.note(&format
!("the `{}` lint is unstable", lint_id
.lint
.name_lower(),));
641 add_feature_diagnostics(&mut db
, &self.sess
.parse_sess
, feature
);
650 /// Called after `push` when the scope of a set of attributes are exited.
651 pub fn pop(&mut self, push
: BuilderPush
) {
652 self.cur
= push
.prev
;
655 /// Find the lint level for a lint.
656 pub fn lint_level(&self, lint
: &'
static Lint
) -> (Level
, LintLevelSource
) {
657 self.sets
.get_lint_level(lint
, self.cur
, None
, self.sess
)
660 /// Used to emit a lint-related diagnostic based on the current state of
661 /// this lint context.
665 span
: Option
<MultiSpan
>,
666 decorate
: impl for<'a
> FnOnce(LintDiagnosticBuilder
<'a
, ()>),
668 let (level
, src
) = self.lint_level(lint
);
669 struct_lint_level(self.sess
, lint
, level
, src
, span
, decorate
)
672 /// Registers the ID provided with the current set of lints stored in
674 pub fn register_id(&mut self, id
: HirId
) {
675 self.id_to_set
.insert(id
, self.cur
);
678 fn update_unstable_expectation_ids(&self) {
679 self.sess
.diagnostic().update_unstable_expectation_id(&self.expectation_id_map
);
682 pub fn build_map(self) -> LintLevelMap
{
685 id_to_set
: self.id_to_set
,
686 lint_expectations
: self.lint_expectations
,
691 struct LintLevelMapBuilder
<'tcx
> {
692 levels
: LintLevelsBuilder
<'tcx
>,
696 impl LintLevelMapBuilder
<'_
> {
697 fn with_lint_attrs
<F
>(&mut self, id
: hir
::HirId
, f
: F
)
699 F
: FnOnce(&mut Self),
701 let is_crate_hir
= id
== hir
::CRATE_HIR_ID
;
702 let attrs
= self.tcx
.hir().attrs(id
);
703 let push
= self.levels
.push(attrs
, is_crate_hir
, Some(id
));
706 self.levels
.register_id(id
);
709 self.levels
.pop(push
);
713 impl<'tcx
> intravisit
::Visitor
<'tcx
> for LintLevelMapBuilder
<'tcx
> {
714 type NestedFilter
= nested_filter
::All
;
716 fn nested_visit_map(&mut self) -> Self::Map
{
720 fn visit_param(&mut self, param
: &'tcx hir
::Param
<'tcx
>) {
721 self.with_lint_attrs(param
.hir_id
, |builder
| {
722 intravisit
::walk_param(builder
, param
);
726 fn visit_item(&mut self, it
: &'tcx hir
::Item
<'tcx
>) {
727 self.with_lint_attrs(it
.hir_id(), |builder
| {
728 intravisit
::walk_item(builder
, it
);
732 fn visit_foreign_item(&mut self, it
: &'tcx hir
::ForeignItem
<'tcx
>) {
733 self.with_lint_attrs(it
.hir_id(), |builder
| {
734 intravisit
::walk_foreign_item(builder
, it
);
738 fn visit_stmt(&mut self, e
: &'tcx hir
::Stmt
<'tcx
>) {
739 // We will call `with_lint_attrs` when we walk
740 // the `StmtKind`. The outer statement itself doesn't
741 // define the lint levels.
742 intravisit
::walk_stmt(self, e
);
745 fn visit_expr(&mut self, e
: &'tcx hir
::Expr
<'tcx
>) {
746 self.with_lint_attrs(e
.hir_id
, |builder
| {
747 intravisit
::walk_expr(builder
, e
);
751 fn visit_field_def(&mut self, s
: &'tcx hir
::FieldDef
<'tcx
>) {
752 self.with_lint_attrs(s
.hir_id
, |builder
| {
753 intravisit
::walk_field_def(builder
, s
);
759 v
: &'tcx hir
::Variant
<'tcx
>,
760 g
: &'tcx hir
::Generics
<'tcx
>,
763 self.with_lint_attrs(v
.id
, |builder
| {
764 intravisit
::walk_variant(builder
, v
, g
, item_id
);
768 fn visit_local(&mut self, l
: &'tcx hir
::Local
<'tcx
>) {
769 self.with_lint_attrs(l
.hir_id
, |builder
| {
770 intravisit
::walk_local(builder
, l
);
774 fn visit_arm(&mut self, a
: &'tcx hir
::Arm
<'tcx
>) {
775 self.with_lint_attrs(a
.hir_id
, |builder
| {
776 intravisit
::walk_arm(builder
, a
);
780 fn visit_trait_item(&mut self, trait_item
: &'tcx hir
::TraitItem
<'tcx
>) {
781 self.with_lint_attrs(trait_item
.hir_id(), |builder
| {
782 intravisit
::walk_trait_item(builder
, trait_item
);
786 fn visit_impl_item(&mut self, impl_item
: &'tcx hir
::ImplItem
<'tcx
>) {
787 self.with_lint_attrs(impl_item
.hir_id(), |builder
| {
788 intravisit
::walk_impl_item(builder
, impl_item
);
793 pub fn provide(providers
: &mut Providers
) {
794 providers
.lint_levels
= lint_levels
;