rustc_attr_parsing/attributes/
lint.rs1use rustc_ast::LitKind;
2use rustc_hir::HashIgnoredAttrId;
3use rustc_hir::attrs::{LintAttribute, LintAttributeKind, LintInstance};
4use rustc_hir::lints::AttributeLintKind;
5use rustc_hir::target::GenericParamKind;
6use rustc_session::DynLintStore;
7use rustc_session::lint::builtin::{RENAMED_AND_REMOVED_LINTS, UNKNOWN_LINTS, UNUSED_ATTRIBUTES};
8use rustc_session::lint::{CheckLintNameResult, LintId};
9
10use super::prelude::*;
11use crate::attributes::AcceptFn;
12use crate::session_diagnostics::UnknownToolInScopedLint;
13
14pub(crate) trait Lint {
15 const KIND: LintAttributeKind;
16 const ATTR_SYMBOL: Symbol = Self::KIND.symbol();
17}
18
19pub(crate) struct Allow;
20
21impl Lint for Allow {
22 const KIND: LintAttributeKind = LintAttributeKind::Allow;
23}
24pub(crate) struct Deny;
25
26impl Lint for Deny {
27 const KIND: LintAttributeKind = LintAttributeKind::Deny;
28}
29pub(crate) struct Expect;
30
31impl Lint for Expect {
32 const KIND: LintAttributeKind = LintAttributeKind::Expect;
33}
34pub(crate) struct Forbid;
35
36impl Lint for Forbid {
37 const KIND: LintAttributeKind = LintAttributeKind::Forbid;
38}
39pub(crate) struct Warn;
40
41impl Lint for Warn {
42 const KIND: LintAttributeKind = LintAttributeKind::Warn;
43}
44
45#[derive(#[automatically_derived]
impl ::core::default::Default for LintParser {
#[inline]
fn default() -> LintParser {
LintParser { lint_attrs: ::core::default::Default::default() }
}
}Default)]
46pub(crate) struct LintParser {
47 lint_attrs: ThinVec<LintAttribute>,
48}
49
50trait Mapping<S: Stage> {
51 const MAPPING: (&'static [Symbol], AttributeTemplate, AcceptFn<LintParser, S>);
52}
53impl<S: Stage, T: Lint> Mapping<S> for T {
54 const MAPPING: (&'static [Symbol], AttributeTemplate, AcceptFn<LintParser, S>) = (
55 &[T::ATTR_SYMBOL],
56 ::rustc_feature::AttributeTemplate {
word: false,
list: Some(&["lint1", "lint1, lint2, ...",
r#"lint1, lint2, lint3, reason = "...""#]),
one_of: &[],
name_value_str: None,
docs: Some("https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes"),
}template!(
57 List: &["lint1", "lint1, lint2, ...", r#"lint1, lint2, lint3, reason = "...""#],
58 "https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes"
59 ),
60 |this, cx, args| {
61 if let Some(lint_attr) = validate_lint_attr::<T, S>(cx, args) {
62 this.lint_attrs.push(lint_attr);
63 }
64 },
65 );
66}
67
68impl<S: Stage> AttributeParser<S> for LintParser {
69 const ATTRIBUTES: AcceptMapping<Self, S> =
70 &[Allow::MAPPING, Deny::MAPPING, Expect::MAPPING, Forbid::MAPPING, Warn::MAPPING];
71
72 const ALLOWED_TARGETS: AllowedTargets = {
73 use super::prelude::{Allow, Warn};
74 AllowedTargets::AllowList(&[
75 Allow(Target::ExternCrate),
76 Allow(Target::Use),
77 Allow(Target::Static),
78 Allow(Target::Const),
79 Allow(Target::Fn),
80 Allow(Target::Closure),
81 Allow(Target::Mod),
82 Allow(Target::ForeignMod),
83 Allow(Target::GlobalAsm),
84 Allow(Target::TyAlias),
85 Allow(Target::Enum),
86 Allow(Target::Variant),
87 Allow(Target::Struct),
88 Allow(Target::Field),
89 Allow(Target::Union),
90 Allow(Target::Trait),
91 Allow(Target::TraitAlias),
92 Allow(Target::Impl { of_trait: false }),
93 Allow(Target::Impl { of_trait: true }),
94 Allow(Target::Expression),
95 Allow(Target::Statement),
96 Allow(Target::Arm),
97 Allow(Target::AssocConst),
98 Allow(Target::Method(MethodKind::Inherent)),
99 Allow(Target::Method(MethodKind::Trait { body: false })),
100 Allow(Target::Method(MethodKind::Trait { body: true })),
101 Allow(Target::Method(MethodKind::TraitImpl)),
102 Allow(Target::AssocTy),
103 Allow(Target::ForeignFn),
104 Allow(Target::ForeignStatic),
105 Allow(Target::ForeignTy),
106 Allow(Target::MacroDef),
107 Allow(Target::Param),
108 Allow(Target::PatField),
109 Allow(Target::ExprField),
110 Allow(Target::Crate),
111 Allow(Target::Delegation { mac: false }),
112 Allow(Target::Delegation { mac: true }),
113 Allow(Target::GenericParam { kind: GenericParamKind::Type, has_default: false }),
114 Allow(Target::GenericParam { kind: GenericParamKind::Lifetime, has_default: false }),
115 Allow(Target::GenericParam { kind: GenericParamKind::Const, has_default: false }),
116 Allow(Target::GenericParam { kind: GenericParamKind::Type, has_default: true }),
117 Allow(Target::GenericParam { kind: GenericParamKind::Lifetime, has_default: true }),
118 Allow(Target::GenericParam { kind: GenericParamKind::Const, has_default: true }),
119 Warn(Target::MacroCall),
120 ])
121 };
122
123 fn finalize(mut self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
124 if !self.lint_attrs.is_empty() {
125 self.lint_attrs.sort_by(|a, b| a.attr_span.cmp(&b.attr_span));
127 Some(AttributeKind::LintAttributes(self.lint_attrs))
128 } else {
129 None
130 }
131 }
132}
133
134#[inline(always)]
135fn validate_lint_attr<T: Lint, S: Stage>(
136 cx: &mut AcceptContext<'_, '_, S>,
137 args: &ArgParser,
138) -> Option<LintAttribute> {
139 let Some(lint_store) = cx.sess.lint_store.as_ref().map(|store| store.to_owned()) else {
140 {
::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
format_args!("lint_store required while parsing attributes")));
};unreachable!("lint_store required while parsing attributes");
141 };
142 let lint_store = lint_store.as_ref();
143 let Some(list) = args.list() else {
144 let span = cx.inner_span;
145 cx.adcx().expected_list(span, args);
146 return None;
147 };
148 let mut list = list.mixed().peekable();
149
150 let mut skip_unused_check = false;
151 let mut errored = false;
152 let mut reason = None;
153 let mut lint_instances = ThinVec::new();
154 let mut lint_index = 0;
155 let targeting_crate = #[allow(non_exhaustive_omitted_patterns)] match cx.target {
Target::Crate => true,
_ => false,
}matches!(cx.target, Target::Crate);
156 while let Some(item) = list.next() {
157 let Some(meta_item) = item.meta_item() else {
158 cx.adcx().expected_identifier(item.span());
159 errored = true;
160 continue;
161 };
162
163 match meta_item.args() {
164 ArgParser::NameValue(nv_parser) if meta_item.path().word_is(sym::reason) => {
165 if list.peek().is_some() {
167 cx.adcx().expected_nv_as_last_argument(meta_item.span(), sym::reason);
168 errored = true;
169 continue;
170 }
171
172 let val_lit = nv_parser.value_as_lit();
173 let LitKind::Str(reason_sym, _) = val_lit.kind else {
174 cx.adcx().expected_string_literal(nv_parser.value_span, Some(val_lit));
175 errored = true;
176 continue;
177 };
178 reason = Some(reason_sym);
179 }
180 ArgParser::NameValue(_) => {
181 cx.adcx().expected_specific_argument(meta_item.span(), &[sym::reason]);
182 errored = true;
183 }
184 ArgParser::List(list) => {
185 cx.adcx().expected_no_args(list.span);
186 errored = true;
187 }
188 ArgParser::NoArgs => {
189 skip_unused_check = true;
190 let mut segments = meta_item.path().segments();
191
192 let Some(tool_or_name) = segments.next() else {
193 {
::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
format_args!("first segment should always exist")));
};unreachable!("first segment should always exist");
194 };
195
196 let rest = segments.collect::<Vec<_>>();
197 let (tool_name, tool_span, name): (Option<Symbol>, Option<Span>, _) =
198 if rest.is_empty() {
199 let name = tool_or_name.name;
200 (None, None, name.to_string())
201 } else {
202 let tool = tool_or_name;
203 let name = rest
204 .into_iter()
205 .map(|ident| ident.to_string())
206 .collect::<Vec<_>>()
207 .join("::");
208 (Some(tool.name), Some(tool.span), name)
209 };
210
211 let meta_item_span = meta_item.span();
212 let original_name = Symbol::intern(&name);
213 let mut full_name = tool_name
214 .map(|tool| Symbol::intern(&::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{1}::{0}", original_name, tool))
})format!("{tool}::{}", original_name)))
215 .unwrap_or(original_name);
216
217 if let Some(ids) = check_lint(
218 cx,
219 lint_store,
220 original_name,
221 &mut full_name,
222 tool_name,
223 tool_span,
224 meta_item_span,
225 ) {
226 if !targeting_crate && ids.iter().any(|lint_id| lint_id.lint.crate_level_only) {
227 cx.emit_lint(
228 UNUSED_ATTRIBUTES,
229 AttributeLintKind::IgnoredUnlessCrateSpecified {
230 level: T::ATTR_SYMBOL,
231 name: original_name,
232 },
233 meta_item_span,
234 );
235 }
236 lint_instances.extend(ids.into_iter().map(|id| {
237 LintInstance::new(full_name, id.to_string(), meta_item_span, lint_index)
238 }));
239 }
240 lint_index += 1;
241 }
242 }
243 }
244 if !skip_unused_check && !errored && lint_instances.is_empty() {
245 let span = cx.attr_span;
246 cx.adcx().warn_empty_attribute(span);
247 }
248
249 (!errored).then_some(LintAttribute {
250 reason,
251 lint_instances,
252 attr_span: cx.attr_span,
253 attr_style: cx.attr_style,
254 attr_id: HashIgnoredAttrId { attr_id: cx.attr_id },
255 kind: T::KIND,
256 })
257}
258
259fn check_lint<'a, S: Stage>(
260 cx: &mut AcceptContext<'_, '_, S>,
261 lint_store: &'a dyn DynLintStore,
262 original_name: Symbol,
263 full_name: &mut Symbol,
264 tool_name: Option<Symbol>,
265 tool_span: Option<Span>,
266 span: Span,
267) -> Option<&'a [LintId]> {
268 let Some(tools) = cx.tools else {
269 {
::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
format_args!("tools required while parsing attributes")));
};unreachable!("tools required while parsing attributes");
270 };
271 if tools.is_empty() {
272 {
::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
format_args!("tools should never be empty")));
}unreachable!("tools should never be empty")
273 }
274
275 match lint_store.check_lint_name(original_name.as_str(), tool_name, tools) {
276 CheckLintNameResult::Ok(ids) => Some(ids),
277 CheckLintNameResult::Tool(ids, new_lint_name) => {
278 let _name = match new_lint_name {
279 None => original_name,
280 Some(new_lint_name) => {
281 let new_lint_name = Symbol::intern(&new_lint_name);
282 cx.emit_lint(
283 RENAMED_AND_REMOVED_LINTS,
284 AttributeLintKind::DeprecatedLintName {
285 name: *full_name,
286 suggestion: span,
287 replace: new_lint_name,
288 },
289 span,
290 );
291 new_lint_name
292 }
293 };
294 Some(ids)
295 }
296
297 CheckLintNameResult::MissingTool => {
298 None
303 }
304
305 CheckLintNameResult::NoTool => {
306 cx.emit_err(UnknownToolInScopedLint {
307 span: tool_span,
308 tool_name: tool_name.unwrap(),
309 full_lint_name: *full_name,
310 is_nightly_build: cx.sess.is_nightly_build(),
311 });
312 None
313 }
314
315 CheckLintNameResult::Renamed(replace) => {
316 cx.emit_lint(
317 RENAMED_AND_REMOVED_LINTS,
318 AttributeLintKind::RenamedLint { name: *full_name, replace, suggestion: span },
319 span,
320 );
321
322 *full_name = replace;
328
329 match lint_store.check_lint_name(replace.as_str(), None, tools) {
335 CheckLintNameResult::Ok(ids) => Some(ids),
336 _ => {
::core::panicking::panic_fmt(format_args!("renamed lint does not exist: {0}",
replace));
}panic!("renamed lint does not exist: {replace}"),
337 }
338 }
339
340 CheckLintNameResult::RenamedToolLint(new_name) => {
341 cx.emit_lint(
342 RENAMED_AND_REMOVED_LINTS,
343 AttributeLintKind::RenamedLint {
344 name: *full_name,
345 replace: new_name,
346 suggestion: span,
347 },
348 span,
349 );
350 None
351 }
352
353 CheckLintNameResult::Removed(reason) => {
354 cx.emit_lint(
355 RENAMED_AND_REMOVED_LINTS,
356 AttributeLintKind::RemovedLint { name: *full_name, reason },
357 span,
358 );
359 None
360 }
361
362 CheckLintNameResult::NoLint(suggestion) => {
363 cx.emit_lint(
364 UNKNOWN_LINTS,
365 AttributeLintKind::UnknownLint { name: *full_name, suggestion, span },
366 span,
367 );
368 None
369 }
370 }
371}