Skip to content

Commit 00e2c28

Browse files
committed
Auto merge of #164918 - JonathanBrouwer:lint-id, r=<try>
Move lint validation to attribute parsing
2 parents dc44e7f + 1a9e5b3 commit 00e2c28

33 files changed

Lines changed: 481 additions & 490 deletions

‎compiler/rustc_ast_lowering/src/lib.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
378378
tcx.sess,
379379
tcx.features(),
380380
tcx.registered_attr_tools(()),
381+
tcx.registered_lint_tools(()),
381382
ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed },
382383
),
383384
}

‎compiler/rustc_attr_ir/src/lint.rs‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,12 @@ impl LintCheckKind {
4545
pub struct LintCheck {
4646
/// The lint's tool name, if present.
4747
pub tool: Option<Ident>,
48-
/// The lint's name, minus the tool name if any.
48+
/// The full normalized lint name of an existing lint, including the tool name.
49+
/// This may not be what we user wrote down, in case of renamed lints the new lint name is here.
50+
/// The above implies that `LintStore::find_lints` is guaranteed to find this lint using this symbol.
4951
///
50-
/// For example:
51-
/// - `clippy::blah` -> `blah`
52-
/// - `unstable_imports` -> `unstable_imports`
53-
/// - `custom_tool::blah::bleh` -> `blah::bleh`
54-
pub lint_name: Symbol,
52+
/// For example, `single_use_lifetime` is changed to `single_use_lifetimes`.
53+
pub lint_name_normalized: Symbol,
5554
/// The span of the lint name.
5655
///
5756
/// For example `#[deny(foo, bar, reason = "reason")]`

‎compiler/rustc_attr_parsing/src/attributes/lint.rs‎

Lines changed: 153 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@ use rustc_ast::join_path_syms;
22
use rustc_attr_ir::AttributeKind;
33
use rustc_attr_ir::lint::{LintCheck, LintCheckKind};
44
use rustc_attr_ir::target::{AssocCtxt, MethodKind, Target};
5-
use rustc_lint_defs::builtin::UNUSED_ATTRIBUTES;
6-
use rustc_span::{Symbol, sym};
5+
use rustc_lint_defs::builtin::{RENAMED_AND_REMOVED_LINTS, UNKNOWN_LINTS, UNUSED_ATTRIBUTES};
6+
use rustc_lint_defs::{CheckLintNameResult, LintId};
7+
use rustc_span::{DUMMY_SP, Ident, Span, Symbol, sym};
78
use thin_vec::ThinVec;
89

910
use crate::attributes::{AcceptMapping, AttributeParser, AttributeStability};
1011
use crate::context::{AcceptContext, ExpectStringLiteral, FinalizeContext};
12+
use crate::diagnostics::{
13+
DeprecatedLintName, RemovedLint, RenamedLint, RenamedLintSuggestion, UnknownLint,
14+
UnknownLintSuggestion, UnknownToolInScopedLint,
15+
};
1116
use crate::parser::{ArgParser, MetaItemOrLitParser};
1217
use crate::target_checking::AllowedTargets;
1318
use crate::target_checking::Policy::{Allow, Warn};
@@ -69,9 +74,18 @@ impl LintParser {
6974
),
7075
};
7176

77+
let lint_name_normalized = if let Some((_lint_ids, new_lint_name)) =
78+
Self::check_lint_name(lint_name, tool, p.span(), cx)
79+
{
80+
new_lint_name
81+
} else {
82+
// Discard lint attributes with unknown lints in them
83+
continue;
84+
};
85+
7286
self.lints.push(LintCheck {
7387
tool,
74-
lint_name,
88+
lint_name_normalized,
7589
lint_span: p.span(),
7690
kind,
7791
attr_id,
@@ -130,6 +144,142 @@ impl LintParser {
130144
}
131145
}
132146
}
147+
148+
fn check_lint_name<'a>(
149+
lint_name: Symbol,
150+
tool: Option<Ident>,
151+
lint_span: Span,
152+
cx: &mut AcceptContext<'_, 'a>,
153+
) -> Option<(&'a [LintId], Symbol)> {
154+
let lint_result = cx.sess.lint_store.as_ref().unwrap().check_lint_name(
155+
lint_name.as_str(),
156+
tool.map(|tool| tool.name),
157+
cx.lint_tools.unwrap(),
158+
);
159+
160+
// FIXME(164898) Lint attributes are parsed twice, once on the AST and once on the HIR
161+
// Both times emit diagnostics, for historic reasons
162+
// We only want to emit these lints once, on the HIR.
163+
// On the AST `target_span` will be `DUMMY_SP`, which we use to detect which instance we are.
164+
// #164898 will fix this, after that PR this check can be removed
165+
let should_lint = cx.target_span != DUMMY_SP;
166+
167+
let (ids, name) = match lint_result {
168+
CheckLintNameResult::Ok(ids) => (ids, lint_name),
169+
170+
CheckLintNameResult::Tool(ids, new_lint_name) => {
171+
let name = match new_lint_name {
172+
None => {
173+
let complete_name = &format!("{}::{lint_name}", tool.unwrap().name);
174+
Symbol::intern(complete_name)
175+
}
176+
Some(new_lint_name) => {
177+
let symbol = Symbol::intern(&new_lint_name);
178+
if should_lint {
179+
cx.emit_lint(
180+
RENAMED_AND_REMOVED_LINTS,
181+
DeprecatedLintName {
182+
name: lint_name.to_string(),
183+
suggestion: lint_span,
184+
replace: new_lint_name,
185+
},
186+
lint_span,
187+
);
188+
}
189+
symbol
190+
}
191+
};
192+
(ids, name)
193+
}
194+
195+
CheckLintNameResult::MissingTool => {
196+
// If `MissingTool` is returned, then either the lint does not
197+
// exist in the tool or the code was not compiled with the tool and
198+
// therefore the lint was never added to the `LintStore`. To detect
199+
// this is the responsibility of the lint tool.
200+
return None;
201+
}
202+
203+
CheckLintNameResult::NoTool => {
204+
if should_lint {
205+
cx.emit_err(UnknownToolInScopedLint {
206+
span: Some(tool.unwrap().span),
207+
tool_name: tool.unwrap().name,
208+
lint_name: lint_name.to_string(),
209+
is_nightly_build: cx.sess.is_nightly_build(),
210+
});
211+
}
212+
return None;
213+
}
214+
215+
CheckLintNameResult::Renamed(ref replace) => {
216+
if should_lint {
217+
let suggestion = RenamedLintSuggestion::WithSpan {
218+
suggestion: lint_span,
219+
replace: replace.clone(),
220+
};
221+
let name = tool
222+
.map(|Ident { name, .. }| format!("{name}::{lint_name}"))
223+
.unwrap_or_else(|| lint_name.to_string());
224+
cx.emit_lint(
225+
RENAMED_AND_REMOVED_LINTS,
226+
RenamedLint { name, replace: replace.clone(), suggestion },
227+
lint_span,
228+
);
229+
}
230+
231+
// If this lint was renamed, apply the new lint instead of ignoring the
232+
// attribute. Ignore any errors or warnings that happen because the new
233+
// name is inaccurate.
234+
// NOTE: `new_name` already includes the tool name, so we don't
235+
// have to add it again.
236+
let CheckLintNameResult::Ok(ids) = cx
237+
.sess
238+
.lint_store
239+
.as_ref()
240+
.unwrap()
241+
.check_lint_name(replace, None, cx.lint_tools.unwrap())
242+
else {
243+
panic!("renamed lint does not exist: {replace}");
244+
};
245+
246+
(ids, Symbol::intern(&replace))
247+
}
248+
249+
CheckLintNameResult::Removed(ref reason) => {
250+
if should_lint {
251+
let name = if let Some(Ident { name, .. }) = tool {
252+
format!("{name}::{lint_name}")
253+
} else {
254+
lint_name.to_string()
255+
};
256+
cx.emit_lint(
257+
RENAMED_AND_REMOVED_LINTS,
258+
RemovedLint { name, reason: reason.clone() },
259+
lint_span,
260+
);
261+
}
262+
return None;
263+
}
264+
265+
CheckLintNameResult::NoLint(suggestion) => {
266+
if should_lint {
267+
let name = tool
268+
.map(|Ident { name, .. }| format!("{name}::{lint_name}"))
269+
.unwrap_or_else(|| lint_name.to_string());
270+
let suggestion =
271+
suggestion.map(|(replace, from_rustc)| UnknownLintSuggestion::WithSpan {
272+
suggestion: lint_span,
273+
replace,
274+
from_rustc,
275+
});
276+
cx.emit_lint(UNKNOWN_LINTS, UnknownLint { name, suggestion }, lint_span);
277+
}
278+
return None;
279+
}
280+
};
281+
Some((ids, name))
282+
}
133283
}
134284

135285
impl AttributeParser for LintParser {

‎compiler/rustc_attr_parsing/src/diagnostics.rs‎

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2184,3 +2184,84 @@ pub(crate) struct Unused {
21842184
#[subdiagnostic]
21852185
pub note: UnusedNote,
21862186
}
2187+
2188+
#[derive(Diagnostic)]
2189+
#[diag("lint name `{$name}` is deprecated and may not have an effect in the future")]
2190+
pub(crate) struct DeprecatedLintName {
2191+
pub name: String,
2192+
#[suggestion("change it to", code = "{replace}", applicability = "machine-applicable")]
2193+
pub suggestion: Span,
2194+
pub replace: String,
2195+
}
2196+
2197+
#[derive(Diagnostic)]
2198+
#[diag("unknown tool name `{$tool_name}` found in scoped lint: `{$tool_name}::{$lint_name}`", code = E0710)]
2199+
pub(crate) struct UnknownToolInScopedLint {
2200+
#[primary_span]
2201+
pub span: Option<Span>,
2202+
pub tool_name: Symbol,
2203+
pub lint_name: String,
2204+
#[help("add `#![register_tool({$tool_name})]` to the crate root")]
2205+
pub is_nightly_build: bool,
2206+
}
2207+
2208+
#[derive(Diagnostic)]
2209+
#[diag("lint `{$name}` has been renamed to `{$replace}`")]
2210+
pub(crate) struct RenamedLint {
2211+
pub name: String,
2212+
pub replace: String,
2213+
#[subdiagnostic]
2214+
pub suggestion: RenamedLintSuggestion,
2215+
}
2216+
2217+
#[derive(Subdiagnostic)]
2218+
pub enum RenamedLintSuggestion {
2219+
#[suggestion("use the new name", code = "{replace}", applicability = "machine-applicable")]
2220+
WithSpan {
2221+
#[primary_span]
2222+
suggestion: Span,
2223+
replace: String,
2224+
},
2225+
#[help("use the new name `{$replace}`")]
2226+
WithoutSpan { replace: String },
2227+
}
2228+
2229+
#[derive(Diagnostic)]
2230+
#[diag("unknown lint: `{$name}`")]
2231+
pub(crate) struct UnknownLint {
2232+
pub name: String,
2233+
#[subdiagnostic]
2234+
pub suggestion: Option<UnknownLintSuggestion>,
2235+
}
2236+
2237+
#[derive(Subdiagnostic)]
2238+
pub enum UnknownLintSuggestion {
2239+
#[suggestion(
2240+
"{$from_rustc ->
2241+
[true] a lint with a similar name exists in `rustc` lints
2242+
*[false] did you mean
2243+
}",
2244+
code = "{replace}",
2245+
applicability = "maybe-incorrect"
2246+
)]
2247+
WithSpan {
2248+
#[primary_span]
2249+
suggestion: Span,
2250+
replace: Symbol,
2251+
from_rustc: bool,
2252+
},
2253+
#[help(
2254+
"{$from_rustc ->
2255+
[true] a lint with a similar name exists in `rustc` lints: `{$replace}`
2256+
*[false] did you mean: `{$replace}`
2257+
}"
2258+
)]
2259+
WithoutSpan { replace: Symbol, from_rustc: bool },
2260+
}
2261+
2262+
#[derive(Diagnostic)]
2263+
#[diag("lint `{$name}` has been removed: {$reason}")]
2264+
pub(crate) struct RemovedLint {
2265+
pub name: String,
2266+
pub reason: String,
2267+
}

‎compiler/rustc_attr_parsing/src/interface.rs‎

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pub struct EmitAttribute(
3838
/// context, through which all attributes can be lowered.
3939
pub struct AttributeParser<'sess> {
4040
pub(crate) attr_tools: Option<&'sess RegisteredTools>,
41+
pub(crate) lint_tools: Option<&'sess RegisteredTools>,
4142
pub(crate) features: Option<&'sess Features>,
4243
pub(crate) sess: &'sess Session,
4344
pub(crate) should_emit: ShouldEmit,
@@ -113,6 +114,7 @@ impl<'sess> AttributeParser<'sess> {
113114
features,
114115
should_emit,
115116
None,
117+
None,
116118
);
117119
assert!(parsed.len() <= 1);
118120
parsed.pop()
@@ -155,8 +157,10 @@ impl<'sess> AttributeParser<'sess> {
155157
features: Option<&'sess Features>,
156158
should_emit: ShouldEmit,
157159
attr_tools: Option<&'sess RegisteredTools>,
160+
lint_tools: Option<&'sess RegisteredTools>,
158161
) -> Vec<Attribute> {
159-
let mut p = AttributeParser { features, attr_tools, parse_filter, sess, should_emit };
162+
let mut p =
163+
AttributeParser { features, attr_tools, lint_tools, parse_filter, sess, should_emit };
160164
p.parse_attribute_list(
161165
attrs,
162166
target_span,
@@ -235,7 +239,14 @@ impl<'sess> AttributeParser<'sess> {
235239
parse_fn: fn(cx: &mut AcceptContext<'_, '_>, item: &I) -> T,
236240
template: &AttributeTemplate,
237241
) -> T {
238-
let mut parser = Self { features, attr_tools: None, parse_filter: None, sess, should_emit };
242+
let mut parser = Self {
243+
features,
244+
attr_tools: None,
245+
lint_tools: None,
246+
parse_filter: None,
247+
sess,
248+
should_emit,
249+
};
239250
let mut emit_lint = |lint_id: LintId, span: MultiSpan, kind: EmitAttribute| {
240251
sess.psess.dyn_buffer_lint_sess(lint_id.lint, span, target_node_id, kind.0)
241252
};
@@ -277,11 +288,13 @@ impl<'sess> AttributeParser<'sess> {
277288
sess: &'sess Session,
278289
features: &'sess Features,
279290
attr_tools: &'sess RegisteredTools,
291+
lint_tools: &'sess RegisteredTools,
280292
should_emit: ShouldEmit,
281293
) -> Self {
282294
Self {
283295
features: Some(features),
284296
attr_tools: Some(attr_tools),
297+
lint_tools: Some(lint_tools),
285298
parse_filter: None,
286299
sess,
287300
should_emit,

‎compiler/rustc_attr_parsing/src/lib.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ pub use attributes::cfg::{
117117
pub use attributes::cfg_select::*;
118118
pub use attributes::util::{is_builtin_attr, parse_version};
119119
pub use context::ShouldEmit;
120-
pub use diagnostics::ParsedDescription;
120+
pub use diagnostics::{ParsedDescription, RenamedLintSuggestion, UnknownLintSuggestion};
121121
pub use interface::{AttributeParser, EmitAttribute};
122122
pub use rustc_parse::parser::Recovery;
123123
pub use template::AttributeTemplate;

‎compiler/rustc_expand/src/expand.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2255,6 +2255,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
22552255
Some(self.cx.ecfg.features),
22562256
ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed },
22572257
Some(self.cx.resolver.registered_attr_tools()),
2258+
Some(self.cx.resolver.registered_lint_tools()),
22582259
);
22592260

22602261
let current_span = if let Some(sp) = span { sp.to(attr.span) } else { attr.span };

‎compiler/rustc_interface/src/limits.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pub fn get_limits(krate_attrs: &[ast::Attribute], sess: &Session, features: &Fea
3939
// So, no lints here to avoid duplicates.
4040
ShouldEmit::EarlyFatal { also_emit_lints: false },
4141
None,
42+
None,
4243
);
4344
let attrs = &attrs;
4445
Limits {

0 commit comments

Comments
 (0)