Skip to content

Commit c92de58

Browse files
committed
Auto merge of #11523 - Alexendoo:used-underscore-bindings-lint-levels, r=xFrednet
used_underscore_bindings: respect lint levels on the binding definition Fixes #11520 Fixes #947 Also ignores usages of `PhantomData` changelog: none
2 parents 251a475 + 32d3387 commit c92de58

File tree

4 files changed

+140
-66
lines changed

4 files changed

+140
-66
lines changed

clippy_lints/src/misc.rs

+46-58
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,23 @@
1-
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_hir_and_then};
1+
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then};
22
use clippy_utils::source::{snippet, snippet_opt, snippet_with_context};
3+
use clippy_utils::sugg::Sugg;
4+
use clippy_utils::{
5+
any_parent_is_automatically_derived, fulfill_or_allowed, get_parent_expr, in_constant, is_integer_literal,
6+
is_lint_allowed, is_no_std_crate, iter_input_pats, last_path_segment, SpanlessEq,
7+
};
38
use if_chain::if_chain;
49
use rustc_errors::Applicability;
10+
use rustc_hir::def::Res;
511
use rustc_hir::intravisit::FnKind;
612
use rustc_hir::{
7-
self as hir, def, BinOpKind, BindingAnnotation, Body, ByRef, Expr, ExprKind, FnDecl, Mutability, PatKind, Stmt,
8-
StmtKind, TyKind,
13+
BinOpKind, BindingAnnotation, Body, ByRef, Expr, ExprKind, FnDecl, Mutability, PatKind, QPath, Stmt, StmtKind, Ty,
14+
TyKind,
915
};
10-
use rustc_lint::{LateContext, LateLintPass};
16+
use rustc_lint::{LateContext, LateLintPass, LintContext};
1117
use rustc_middle::lint::in_external_macro;
1218
use rustc_session::{declare_tool_lint, impl_lint_pass};
1319
use rustc_span::def_id::LocalDefId;
14-
use rustc_span::hygiene::DesugaringKind;
15-
use rustc_span::source_map::{ExpnKind, Span};
16-
17-
use clippy_utils::sugg::Sugg;
18-
use clippy_utils::{
19-
get_parent_expr, in_constant, is_integer_literal, is_lint_allowed, is_no_std_crate, iter_input_pats,
20-
last_path_segment, SpanlessEq,
21-
};
20+
use rustc_span::source_map::Span;
2221

2322
use crate::ref_patterns::REF_PATTERNS;
2423

@@ -257,46 +256,56 @@ impl<'tcx> LateLintPass<'tcx> for LintPass {
257256
self.check_cast(cx, expr.span, e, ty);
258257
return;
259258
}
260-
if in_attributes_expansion(expr) || expr.span.is_desugaring(DesugaringKind::Await) {
261-
// Don't lint things expanded by #[derive(...)], etc or `await` desugaring
259+
if in_external_macro(cx.sess(), expr.span)
260+
|| expr.span.desugaring_kind().is_some()
261+
|| any_parent_is_automatically_derived(cx.tcx, expr.hir_id)
262+
{
262263
return;
263264
}
264-
let sym;
265-
let binding = match expr.kind {
266-
ExprKind::Path(ref qpath) if !matches!(qpath, hir::QPath::LangItem(..)) => {
267-
let binding = last_path_segment(qpath).ident.as_str();
268-
if binding.starts_with('_') &&
269-
!binding.starts_with("__") &&
270-
binding != "_result" && // FIXME: #944
271-
is_used(cx, expr) &&
272-
// don't lint if the declaration is in a macro
273-
non_macro_local(cx, cx.qpath_res(qpath, expr.hir_id))
265+
let (definition_hir_id, ident) = match expr.kind {
266+
ExprKind::Path(ref qpath) => {
267+
if let QPath::Resolved(None, path) = qpath
268+
&& let Res::Local(id) = path.res
269+
&& is_used(cx, expr)
274270
{
275-
Some(binding)
271+
(id, last_path_segment(qpath).ident)
276272
} else {
277-
None
273+
return;
278274
}
279275
},
280-
ExprKind::Field(_, ident) => {
281-
sym = ident.name;
282-
let name = sym.as_str();
283-
if name.starts_with('_') && !name.starts_with("__") {
284-
Some(name)
276+
ExprKind::Field(recv, ident) => {
277+
if let Some(adt_def) = cx.typeck_results().expr_ty_adjusted(recv).ty_adt_def()
278+
&& let Some(field) = adt_def.all_fields().find(|field| field.name == ident.name)
279+
&& let Some(local_did) = field.did.as_local()
280+
&& let Some(hir_id) = cx.tcx.opt_local_def_id_to_hir_id(local_did)
281+
&& !cx.tcx.type_of(field.did).skip_binder().is_phantom_data()
282+
{
283+
(hir_id, ident)
285284
} else {
286-
None
285+
return;
287286
}
288287
},
289-
_ => None,
288+
_ => return,
290289
};
291-
if let Some(binding) = binding {
292-
span_lint(
290+
291+
let name = ident.name.as_str();
292+
if name.starts_with('_')
293+
&& !name.starts_with("__")
294+
&& let definition_span = cx.tcx.hir().span(definition_hir_id)
295+
&& !definition_span.from_expansion()
296+
&& !fulfill_or_allowed(cx, USED_UNDERSCORE_BINDING, [expr.hir_id, definition_hir_id])
297+
{
298+
span_lint_and_then(
293299
cx,
294300
USED_UNDERSCORE_BINDING,
295301
expr.span,
296302
&format!(
297-
"used binding `{binding}` which is prefixed with an underscore. A leading \
303+
"used binding `{name}` which is prefixed with an underscore. A leading \
298304
underscore signals that a binding will not be used"
299305
),
306+
|diag| {
307+
diag.span_note(definition_span, format!("`{name}` is defined here"));
308+
}
300309
);
301310
}
302311
}
@@ -312,29 +321,8 @@ fn is_used(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
312321
})
313322
}
314323

315-
/// Tests whether an expression is in a macro expansion (e.g., something
316-
/// generated by `#[derive(...)]` or the like).
317-
fn in_attributes_expansion(expr: &Expr<'_>) -> bool {
318-
use rustc_span::hygiene::MacroKind;
319-
if expr.span.from_expansion() {
320-
let data = expr.span.ctxt().outer_expn_data();
321-
matches!(data.kind, ExpnKind::Macro(MacroKind::Attr | MacroKind::Derive, _))
322-
} else {
323-
false
324-
}
325-
}
326-
327-
/// Tests whether `res` is a variable defined outside a macro.
328-
fn non_macro_local(cx: &LateContext<'_>, res: def::Res) -> bool {
329-
if let def::Res::Local(id) = res {
330-
!cx.tcx.hir().span(id).from_expansion()
331-
} else {
332-
false
333-
}
334-
}
335-
336324
impl LintPass {
337-
fn check_cast(&self, cx: &LateContext<'_>, span: Span, e: &Expr<'_>, ty: &hir::Ty<'_>) {
325+
fn check_cast(&self, cx: &LateContext<'_>, span: Span, e: &Expr<'_>, ty: &Ty<'_>) {
338326
if_chain! {
339327
if let TyKind::Ptr(ref mut_ty) = ty.kind;
340328
if is_integer_literal(e, 0);

clippy_utils/src/lib.rs

+27
Original file line numberDiff line numberDiff line change
@@ -1785,6 +1785,33 @@ pub fn is_try<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tc
17851785
None
17861786
}
17871787

1788+
/// Returns `true` if the lint is `#[allow]`ed or `#[expect]`ed at any of the `ids`, fulfilling all
1789+
/// of the expectations in `ids`
1790+
///
1791+
/// This should only be used when the lint would otherwise be emitted, for a way to check if a lint
1792+
/// is allowed early to skip work see [`is_lint_allowed`]
1793+
///
1794+
/// To emit at a lint at a different context than the one current see
1795+
/// [`span_lint_hir`](diagnostics::span_lint_hir) or
1796+
/// [`span_lint_hir_and_then`](diagnostics::span_lint_hir_and_then)
1797+
pub fn fulfill_or_allowed(cx: &LateContext<'_>, lint: &'static Lint, ids: impl IntoIterator<Item = HirId>) -> bool {
1798+
let mut suppress_lint = false;
1799+
1800+
for id in ids {
1801+
let (level, _) = cx.tcx.lint_level_at_node(lint, id);
1802+
if let Some(expectation) = level.get_expectation_id() {
1803+
cx.fulfill_expectation(expectation);
1804+
}
1805+
1806+
match level {
1807+
Level::Allow | Level::Expect(_) => suppress_lint = true,
1808+
Level::Warn | Level::ForceWarn(_) | Level::Deny | Level::Forbid => {},
1809+
}
1810+
}
1811+
1812+
suppress_lint
1813+
}
1814+
17881815
/// Returns `true` if the lint is allowed in the current context. This is useful for
17891816
/// skipping long running code when it's unnecessary
17901817
///

tests/ui/used_underscore_binding.rs

+26-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
//@aux-build:proc_macro_derive.rs
2-
#![feature(rustc_private)]
3-
#![warn(clippy::all)]
2+
#![feature(rustc_private, lint_reasons)]
43
#![warn(clippy::used_underscore_binding)]
54
#![allow(clippy::disallowed_names, clippy::eq_op, clippy::uninlined_format_args)]
65

@@ -107,6 +106,31 @@ async fn await_desugaring() {
107106
.await
108107
}
109108

109+
struct PhantomField<T> {
110+
_marker: std::marker::PhantomData<T>,
111+
}
112+
113+
impl<T> std::fmt::Debug for PhantomField<T> {
114+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
115+
f.debug_struct("PhantomField").field("_marker", &self._marker).finish()
116+
}
117+
}
118+
119+
struct AllowedField {
120+
#[allow(clippy::used_underscore_binding)]
121+
_allowed: usize,
122+
}
123+
124+
struct ExpectedField {
125+
#[expect(clippy::used_underscore_binding)]
126+
_expected: usize,
127+
}
128+
129+
fn lint_levels(allowed: AllowedField, expected: ExpectedField) {
130+
let _ = allowed._allowed;
131+
let _ = expected._expected;
132+
}
133+
110134
fn main() {
111135
let foo = 0u32;
112136
// tests of unused_underscore lint
+41-6
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,76 @@
11
error: used binding `_foo` which is prefixed with an underscore. A leading underscore signals that a binding will not be used
2-
--> $DIR/used_underscore_binding.rs:24:5
2+
--> $DIR/used_underscore_binding.rs:23:5
33
|
44
LL | _foo + 1
55
| ^^^^
66
|
7+
note: `_foo` is defined here
8+
--> $DIR/used_underscore_binding.rs:22:22
9+
|
10+
LL | fn prefix_underscore(_foo: u32) -> u32 {
11+
| ^^^^
712
= note: `-D clippy::used-underscore-binding` implied by `-D warnings`
813
= help: to override `-D warnings` add `#[allow(clippy::used_underscore_binding)]`
914

1015
error: used binding `_foo` which is prefixed with an underscore. A leading underscore signals that a binding will not be used
11-
--> $DIR/used_underscore_binding.rs:29:20
16+
--> $DIR/used_underscore_binding.rs:28:20
1217
|
1318
LL | println!("{}", _foo);
1419
| ^^^^
20+
|
21+
note: `_foo` is defined here
22+
--> $DIR/used_underscore_binding.rs:27:24
23+
|
24+
LL | fn in_macro_or_desugar(_foo: u32) {
25+
| ^^^^
1526

1627
error: used binding `_foo` which is prefixed with an underscore. A leading underscore signals that a binding will not be used
17-
--> $DIR/used_underscore_binding.rs:30:16
28+
--> $DIR/used_underscore_binding.rs:29:16
1829
|
1930
LL | assert_eq!(_foo, _foo);
2031
| ^^^^
32+
|
33+
note: `_foo` is defined here
34+
--> $DIR/used_underscore_binding.rs:27:24
35+
|
36+
LL | fn in_macro_or_desugar(_foo: u32) {
37+
| ^^^^
2138

2239
error: used binding `_foo` which is prefixed with an underscore. A leading underscore signals that a binding will not be used
23-
--> $DIR/used_underscore_binding.rs:30:22
40+
--> $DIR/used_underscore_binding.rs:29:22
2441
|
2542
LL | assert_eq!(_foo, _foo);
2643
| ^^^^
44+
|
45+
note: `_foo` is defined here
46+
--> $DIR/used_underscore_binding.rs:27:24
47+
|
48+
LL | fn in_macro_or_desugar(_foo: u32) {
49+
| ^^^^
2750

2851
error: used binding `_underscore_field` which is prefixed with an underscore. A leading underscore signals that a binding will not be used
29-
--> $DIR/used_underscore_binding.rs:43:5
52+
--> $DIR/used_underscore_binding.rs:42:5
3053
|
3154
LL | s._underscore_field += 1;
3255
| ^^^^^^^^^^^^^^^^^^^
56+
|
57+
note: `_underscore_field` is defined here
58+
--> $DIR/used_underscore_binding.rs:36:5
59+
|
60+
LL | _underscore_field: u32,
61+
| ^^^^^^^^^^^^^^^^^^^^^^
3362

3463
error: used binding `_i` which is prefixed with an underscore. A leading underscore signals that a binding will not be used
35-
--> $DIR/used_underscore_binding.rs:104:16
64+
--> $DIR/used_underscore_binding.rs:103:16
3665
|
3766
LL | uses_i(_i);
3867
| ^^
68+
|
69+
note: `_i` is defined here
70+
--> $DIR/used_underscore_binding.rs:102:13
71+
|
72+
LL | let _i = 5;
73+
| ^^
3974

4075
error: aborting due to 6 previous errors
4176

0 commit comments

Comments
 (0)