Skip to content
Draft
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
2 changes: 1 addition & 1 deletion clippy_lints/src/drop_forget_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetRef {
sym::mem_drop
if !(arg_ty.needs_drop(cx.tcx, cx.typing_env())
|| is_must_use_func_call(cx, arg)
|| is_must_use_ty(cx, arg_ty)
|| is_must_use_ty(cx, arg_ty, expr.hir_id)
|| drop_is_single_call_in_arm) =>
{
(DROP_NON_DROP, DROP_NON_DROP_SUMMARY.into(), Some(arg.span))
Expand Down
10 changes: 7 additions & 3 deletions clippy_lints/src/functions/must_use.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>
cx,
sig.decl,
item.owner_id,
item.hir_id(),
item.span,
fn_header_span,
*attr_span,
Expand Down Expand Up @@ -71,6 +72,7 @@ pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Imp
cx,
sig.decl,
item.owner_id,
item.hir_id(),
item.span,
fn_header_span,
*attr_span,
Expand Down Expand Up @@ -104,6 +106,7 @@ pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Tr
cx,
sig.decl,
item.owner_id,
item.hir_id(),
item.span,
fn_header_span,
*attr_span,
Expand Down Expand Up @@ -134,6 +137,7 @@ fn check_needless_must_use(
cx: &LateContext<'_>,
decl: &hir::FnDecl<'_>,
item_id: hir::OwnerId,
item_hir_id: hir::HirId,
item_span: Span,
fn_header_span: Span,
attr_span: Span,
Expand Down Expand Up @@ -170,12 +174,12 @@ fn check_needless_must_use(
"remove `must_use`",
);
}
} else if reason.is_none() && is_must_use_ty(cx, return_ty(cx, item_id)) {
} else if reason.is_none() && is_must_use_ty(cx, return_ty(cx, item_id), item_hir_id) {
// Ignore async functions unless Future::Output type is a must_use type
if sig.header.is_async() {
let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode());
if let Some(future_ty) = infcx.err_ctxt().get_impl_future_output_ty(return_ty(cx, item_id))
&& !is_must_use_ty(cx, future_ty)
&& !is_must_use_ty(cx, future_ty, item_hir_id)
{
return;
}
Expand Down Expand Up @@ -206,7 +210,7 @@ fn check_must_use_candidate<'tcx>(
|| item_span.in_external_macro(cx.sess().source_map())
|| returns_unit(decl)
|| !cx.effective_visibilities.is_exported(item_id.def_id)
|| is_must_use_ty(cx, return_ty(cx, item_id))
|| is_must_use_ty(cx, return_ty(cx, item_id), body.value.hir_id)
|| item_span.from_expansion()
|| is_entrypoint_fn(cx, item_id.def_id.to_def_id())
{
Expand Down
2 changes: 1 addition & 1 deletion clippy_lints/src/let_underscore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
diag.help("consider awaiting the future or dropping explicitly with `std::mem::drop`");
},
);
} else if is_must_use_ty(cx, cx.typeck_results().expr_ty(init)) {
} else if is_must_use_ty(cx, cx.typeck_results().expr_ty(init), init.hir_id) {
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx,
Expand Down
2 changes: 1 addition & 1 deletion clippy_lints/src/return_self_not_must_use.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ fn check_method(cx: &LateContext<'_>, decl: &FnDecl<'_>, fn_def: LocalDefId, spa
// there is one, we shouldn't emit a warning!
&& self_arg.peel_refs() == ret_ty
// If `Self` is already marked as `#[must_use]`, no need for the attribute here.
&& !is_must_use_ty(cx, ret_ty)
&& !is_must_use_ty(cx, ret_ty, cx.tcx.local_def_id_to_hir_id(fn_def))
{
span_lint_and_help(
cx,
Expand Down
50 changes: 9 additions & 41 deletions clippy_utils/src/ty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir as hir;
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::{Expr, FnDecl, LangItem, find_attr};
use rustc_hir::{Expr, ExprKind, FnDecl, HirId, LangItem};
use rustc_hir_analysis::lower_ty;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::LateContext;
use rustc_lint::unused::must_use::{IsTyMustUse, is_ty_must_use};
use rustc_middle::mir::ConstValue;
use rustc_middle::mir::interpret::Scalar;
use rustc_middle::traits::EvaluationResult;
Expand Down Expand Up @@ -310,46 +311,13 @@ pub fn has_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
// Returns whether the `ty` has `#[must_use]` attribute. If `ty` is a `Result`/`ControlFlow`
// whose `Err`/`Break` payload is an uninhabited type, the `Ok`/`Continue` payload type
// will be used instead. See <https://github.com/rust-lang/rust/pull/148214>.
pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
match ty.kind() {
ty::Adt(adt, args) => match cx.tcx.get_diagnostic_name(adt.did()) {
Some(sym::Result) if args.type_at(1).is_privately_uninhabited(cx.tcx, cx.typing_env()) => {
is_must_use_ty(cx, args.type_at(0))
},
Some(sym::ControlFlow) if args.type_at(0).is_privately_uninhabited(cx.tcx, cx.typing_env()) => {
is_must_use_ty(cx, args.type_at(1))
},
_ => find_attr!(cx.tcx, adt.did(), MustUse { .. }),
},
ty::Foreign(did) => find_attr!(cx.tcx, *did, MustUse { .. }),
ty::Slice(ty) | ty::Array(ty, _) | ty::RawPtr(ty, _) | ty::Ref(_, ty, _) => {
// for the Array case we don't need to care for the len == 0 case
// because we don't want to lint functions returning empty arrays
is_must_use_ty(cx, *ty)
},
ty::Tuple(args) => args.iter().any(|ty| is_must_use_ty(cx, ty)),
ty::Alias(ty::Opaque, AliasTy { def_id, .. }) => {
for (predicate, _) in cx.tcx.explicit_item_self_bounds(*def_id).skip_binder() {
if let ty::ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder()
&& find_attr!(cx.tcx, trait_predicate.trait_ref.def_id, MustUse { .. })
{
return true;
}
}
false
},
ty::Dynamic(binder, _) => {
for predicate in *binder {
if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder()
&& find_attr!(cx.tcx, trait_ref.def_id, MustUse { .. })
{
return true;
}
}
false
},
_ => false,
}
pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, hir_id: HirId) -> bool {
let dummy_expr = Expr {
hir_id,
span: DUMMY_SP,
kind: ExprKind::Ret(None),
};
matches!(is_ty_must_use(cx, ty, &dummy_expr, true), IsTyMustUse::Yes(_))
}

/// Returns `true` if the given type is a non aggregate primitive (a `bool` or `char`, any
Expand Down
6 changes: 6 additions & 0 deletions tests/ui/must_use_candidates.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,9 @@ pub fn result_uninhabited() -> Result<i32, std::convert::Infallible> {
pub fn controlflow_uninhabited() -> std::ops::ControlFlow<std::convert::Infallible, i32> {
todo!()
}

// Do not lint: even though `Box` itself is not `#[must_use]`, if the content is, the
// compiler will treat the box as-is.
pub fn with_box() -> Box<Result<u32, u32>> {
Box::new(Ok(0))
}
6 changes: 6 additions & 0 deletions tests/ui/must_use_candidates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,9 @@ pub fn result_uninhabited() -> Result<i32, std::convert::Infallible> {
pub fn controlflow_uninhabited() -> std::ops::ControlFlow<std::convert::Infallible, i32> {
todo!()
}

// Do not lint: even though `Box` itself is not `#[must_use]`, if the content is, the
// compiler will treat the box as a `#[must_use]` type already.
pub fn with_box() -> Box<Result<u32, u32>> {
Box::new(Ok(0))
}
Loading