Skip to content

Commit 279ee03

Browse files
committed
Port #[allow], #[deny], #[expect], #[forbid], #[warn] to attr parser
1 parent 1c02c88 commit 279ee03

20 files changed

Lines changed: 805 additions & 177 deletions

File tree

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

compiler/rustc_attr_parsing/src/attributes/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ pub(crate) mod dummy;
4545
pub(crate) mod inline;
4646
pub(crate) mod instruction_set;
4747
pub(crate) mod link_attrs;
48+
pub(crate) mod lint;
4849
pub(crate) mod lint_helpers;
4950
pub(crate) mod loop_match;
5051
pub(crate) mod macro_attrs;

compiler/rustc_attr_parsing/src/context.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ use crate::attributes::dummy::*;
3232
use crate::attributes::inline::*;
3333
use crate::attributes::instruction_set::*;
3434
use crate::attributes::link_attrs::*;
35+
use crate::attributes::lint::*;
3536
use crate::attributes::lint_helpers::*;
3637
use crate::attributes::loop_match::*;
3738
use crate::attributes::macro_attrs::*;
@@ -141,15 +142,20 @@ attribute_parsers!(
141142
// tidy-alphabetical-start
142143
AlignParser,
143144
AlignStaticParser,
145+
AllowParser,
144146
BodyStabilityParser,
145147
ConfusablesParser,
146148
ConstStabilityParser,
149+
DenyParser,
147150
DocParser,
151+
ExpectParser,
152+
ForbidParser,
148153
MacroUseParser,
149154
NakedParser,
150155
RustcCguTestAttributeParser,
151156
StabilityParser,
152157
UsedParser,
158+
WarnParser,
153159
// tidy-alphabetical-end
154160

155161
// tidy-alphabetical-start

0 commit comments

Comments
 (0)