Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add #[must_bind] attribute and lint #78715

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
ungated!(allow, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#)),
ungated!(forbid, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#)),
ungated!(deny, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#)),
ungated!(must_bind, AssumedUsed, template!(Word, NameValueStr: "reason")),
ungated!(must_use, AssumedUsed, template!(Word, NameValueStr: "reason")),
// FIXME(#14407)
ungated!(
Expand Down
227 changes: 177 additions & 50 deletions compiler/rustc_lint/src/unused.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,64 @@ declare_lint! {
"unused result of an expression in a statement"
}

declare_lint_pass!(UnusedResults => [UNUSED_MUST_USE, UNUSED_RESULTS]);
declare_lint! {
/// The `unused_must_bind` lint detects attempts to "bind" to `_` value of a type marked with
/// `#[must_bind]`.
///
/// ### Example
///
/// ```rust,compile_fail
/// #![deny(unused_must_bind)]
///
/// #[must_use]
/// #[must_bind]
/// struct SemaphoreGuard;
///
/// fn acquire() -> SemaphoreGuard {
/// SemaphoreGuard
/// }
///
/// fn main() {
/// let _ = acquire();
/// }
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// The `#[must_bind]` attribute is an indicator that it is a mistake to
/// let the value be dropped imediately after acquiring it. See [the reference] for more details.
///
/// [the reference]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_bind-attribute
pub UNUSED_MUST_BIND,
Warn,
"Bogus `_` binding of a type flagged as `#[must_bind]`. Use something like `_guard`.",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Bogus `_` binding of a type flagged as `#[must_bind]`. Use something like `_guard`.",
"types marked as `#[must_bind]` should be assigned to a variable, not dropped immediately",

report_in_external_macro
}

declare_lint_pass!(UnusedResults => [UNUSED_MUST_USE, UNUSED_RESULTS, UNUSED_MUST_BIND]);

impl<'tcx> LateLintPass<'tcx> for UnusedResults {
fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) {
let expr = match s.kind {
hir::StmtKind::Semi(ref expr) => &**expr,
#[derive(Clone, Copy)]
enum WhatAttrToCheck {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rename this to something like UnusedResultsAttr (bikeshedding welcome) and I prefer it declared outside of the function and impl.

MustUse,
MustBind,
}
let (expr, attr_to_check) = match s.kind {
hir::StmtKind::Semi(ref expr) => (&**expr, WhatAttrToCheck::MustUse),
hir::StmtKind::Local(ref local) => {
match local.pat.kind {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer a guard in the match arm: if !matches!(local.pat.kind, hir::PatKind::Wild)

hir::PatKind::Wild => (),
_ => return,
}
if let Some(ref init) = local.init {
(&**init, WhatAttrToCheck::MustBind)
} else {
return;
}
}
_ => return,
};

Expand All @@ -102,7 +154,8 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
}

let ty = cx.typeck_results().expr_ty(&expr);
let type_permits_lack_of_use = check_must_use_ty(cx, ty, &expr, s.span, "", "", 1);
let type_permits_lack_of_use =
check_must_use_ty(cx, ty, &expr, s.span, "", "", 1, attr_to_check);

let mut fn_warned = false;
let mut op_warned = false;
Expand All @@ -124,7 +177,8 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
_ => None,
};
if let Some(def_id) = maybe_def_id {
fn_warned = check_must_use_def(cx, def_id, s.span, "return value of ", "");
fn_warned =
check_must_use_def(cx, def_id, s.span, "return value of ", "", attr_to_check);
} else if type_permits_lack_of_use {
// We don't warn about unused unit or uninhabited types.
// (See https://github.com/rust-lang/rust/issues/43806 for details.)
Expand Down Expand Up @@ -158,15 +212,22 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
_ => None,
};

if let Some(must_use_op) = must_use_op {
cx.struct_span_lint(UNUSED_MUST_USE, expr.span, |lint| {
lint.build(&format!("unused {} that must be used", must_use_op)).emit()
});
op_warned = true;
}
match attr_to_check {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if matches!(attr_to_check, WhatAttrToCheck::MustUse) would be preferable

WhatAttrToCheck::MustUse => {
if let Some(must_use_op) = must_use_op {
cx.struct_span_lint(UNUSED_MUST_USE, expr.span, |lint| {
lint.build(&format!("unused {} that must be used", must_use_op)).emit()
});
op_warned = true;
}

if !(type_permits_lack_of_use || fn_warned || op_warned) {
cx.struct_span_lint(UNUSED_RESULTS, s.span, |lint| lint.build("unused result").emit());
if !(type_permits_lack_of_use || fn_warned || op_warned) {
cx.struct_span_lint(UNUSED_RESULTS, s.span, |lint| {
lint.build("unused result").emit()
});
}
}
_ => (),
}

// Returns whether an error has been emitted (and thus another does not need to be later).
Expand All @@ -178,6 +239,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
descr_pre: &str,
descr_post: &str,
plural_len: usize,
attr_to_check: WhatAttrToCheck,
) -> bool {
if ty.is_unit()
|| cx.tcx.is_ty_uninhabited_from(
Expand All @@ -195,9 +257,20 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
ty::Adt(..) if ty.is_box() => {
let boxed_ty = ty.boxed_ty();
let descr_pre = &format!("{}boxed ", descr_pre);
check_must_use_ty(cx, boxed_ty, expr, span, descr_pre, descr_post, plural_len)
check_must_use_ty(
cx,
boxed_ty,
expr,
span,
descr_pre,
descr_post,
plural_len,
attr_to_check,
)
}
ty::Adt(def, _) => {
check_must_use_def(cx, def.did, span, descr_pre, descr_post, attr_to_check)
}
ty::Adt(def, _) => check_must_use_def(cx, def.did, span, descr_pre, descr_post),
ty::Opaque(def, _) => {
let mut has_emitted = false;
for &(predicate, _) in cx.tcx.explicit_item_bounds(def) {
Expand All @@ -208,7 +281,14 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
let def_id = poly_trait_predicate.trait_ref.def_id;
let descr_pre =
&format!("{}implementer{} of ", descr_pre, plural_suffix,);
if check_must_use_def(cx, def_id, span, descr_pre, descr_post) {
if check_must_use_def(
cx,
def_id,
span,
descr_pre,
descr_post,
attr_to_check,
) {
has_emitted = true;
break;
}
Expand All @@ -223,7 +303,14 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
let def_id = trait_ref.def_id;
let descr_post =
&format!(" trait object{}{}", plural_suffix, descr_post,);
if check_must_use_def(cx, def_id, span, descr_pre, descr_post) {
if check_must_use_def(
cx,
def_id,
span,
descr_pre,
descr_post,
attr_to_check,
) {
has_emitted = true;
break;
}
Expand All @@ -242,8 +329,16 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
for (i, ty) in tys.iter().map(|k| k.expect_ty()).enumerate() {
let descr_post = &format!(" in tuple element {}", i);
let span = *spans.get(i).unwrap_or(&span);
if check_must_use_ty(cx, ty, expr, span, descr_pre, descr_post, plural_len)
{
if check_must_use_ty(
cx,
ty,
expr,
span,
descr_pre,
descr_post,
plural_len,
attr_to_check,
) {
has_emitted = true;
}
}
Expand All @@ -255,31 +350,46 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
// If the array is definitely non-empty, we can do `#[must_use]` checking.
Some(n) => {
let descr_pre = &format!("{}array{} of ", descr_pre, plural_suffix,);
check_must_use_ty(cx, ty, expr, span, descr_pre, descr_post, n as usize + 1)
check_must_use_ty(
cx,
ty,
expr,
span,
descr_pre,
descr_post,
n as usize + 1,
attr_to_check,
)
}
},
ty::Closure(..) => {
cx.struct_span_lint(UNUSED_MUST_USE, span, |lint| {
let mut err = lint.build(&format!(
"unused {}closure{}{} that must be used",
descr_pre, plural_suffix, descr_post,
));
err.note("closures are lazy and do nothing unless called");
err.emit();
});
true
}
ty::Generator(..) => {
cx.struct_span_lint(UNUSED_MUST_USE, span, |lint| {
let mut err = lint.build(&format!(
"unused {}generator{}{} that must be used",
descr_pre, plural_suffix, descr_post,
));
err.note("generators are lazy and do nothing unless resumed");
err.emit();
});
true
}
ty::Closure(..) => match attr_to_check {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer a guard in the match arm: if matches!(attr_to_check, WhatAttrToCheck::MustUse)

WhatAttrToCheck::MustUse => {
cx.struct_span_lint(UNUSED_MUST_USE, span, |lint| {
let mut err = lint.build(&format!(
"unused {}closure{}{} that must be used",
descr_pre, plural_suffix, descr_post,
));
err.note("closures are lazy and do nothing unless called");
err.emit();
});
true
}
_ => false,
},
ty::Generator(..) => match attr_to_check {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise, guard in match arm.

WhatAttrToCheck::MustUse => {
cx.struct_span_lint(UNUSED_MUST_USE, span, |lint| {
let mut err = lint.build(&format!(
"unused {}generator{}{} that must be used",
descr_pre, plural_suffix, descr_post,
));
err.note("generators are lazy and do nothing unless resumed");
err.emit();
});
true
}
_ => false,
},
_ => false,
}
}
Expand All @@ -295,16 +405,33 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
span: Span,
descr_pre_path: &str,
descr_post_path: &str,
attr_to_check: WhatAttrToCheck,
) -> bool {
let attr_name = match attr_to_check {
WhatAttrToCheck::MustUse => sym::must_use,
WhatAttrToCheck::MustBind => sym::must_bind,
};
for attr in cx.tcx.get_attrs(def_id).iter() {
if cx.sess().check_name(attr, sym::must_use) {
cx.struct_span_lint(UNUSED_MUST_USE, span, |lint| {
let msg = format!(
"unused {}`{}`{} that must be used",
descr_pre_path,
cx.tcx.def_path_str(def_id),
descr_post_path
);
if cx.sess().check_name(attr, attr_name) {
let lint_type = match attr_to_check {
WhatAttrToCheck::MustUse => UNUSED_MUST_USE,
WhatAttrToCheck::MustBind => UNUSED_MUST_BIND,
};
cx.struct_span_lint(lint_type, span, |lint| {
let msg = match attr_to_check {
WhatAttrToCheck::MustUse => format!(
"unused {}`{}`{} that must be used",
descr_pre_path,
cx.tcx.def_path_str(def_id),
descr_post_path
),
WhatAttrToCheck::MustBind => format!(
"Bogus `let _ =` binding of {}`{}`{}. Use something like `let _guard =`.",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be changed to the same message suggested by this comment.

descr_pre_path,
cx.tcx.def_path_str(def_id),
descr_post_path
),
};
let mut err = lint.build(&msg);
// check for #[must_use = "..."]
if let Some(note) = attr.value_str() {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,7 @@ symbols! {
mul,
mul_assign,
mul_with_overflow,
must_bind,
must_use,
mut_ptr,
mut_slice_ptr,
Expand Down