diff --git a/clippy_lints/src/drop_forget_ref.rs b/clippy_lints/src/drop_forget_ref.rs index 5da76bc3095f..6dc2718702c8 100644 --- a/clippy_lints/src/drop_forget_ref.rs +++ b/clippy_lints/src/drop_forget_ref.rs @@ -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)) diff --git a/clippy_lints/src/functions/must_use.rs b/clippy_lints/src/functions/must_use.rs index 04c657104fe1..2a919bfae854 100644 --- a/clippy_lints/src/functions/must_use.rs +++ b/clippy_lints/src/functions/must_use.rs @@ -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, @@ -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, @@ -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, @@ -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, @@ -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; } @@ -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()) { diff --git a/clippy_lints/src/let_underscore.rs b/clippy_lints/src/let_underscore.rs index 984574c221fb..d42b1944f85b 100644 --- a/clippy_lints/src/let_underscore.rs +++ b/clippy_lints/src/let_underscore.rs @@ -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, diff --git a/clippy_lints/src/return_self_not_must_use.rs b/clippy_lints/src/return_self_not_must_use.rs index 78f5167fa543..fb1d3ef1ae9c 100644 --- a/clippy_lints/src/return_self_not_must_use.rs +++ b/clippy_lints/src/return_self_not_must_use.rs @@ -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, diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index a9e29987bc32..691a26d3ff8b 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -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; @@ -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 . -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 diff --git a/tests/ui/must_use_candidates.fixed b/tests/ui/must_use_candidates.fixed index 17569884658b..b3d4c9f0db58 100644 --- a/tests/ui/must_use_candidates.fixed +++ b/tests/ui/must_use_candidates.fixed @@ -120,3 +120,9 @@ pub fn result_uninhabited() -> Result { pub fn controlflow_uninhabited() -> std::ops::ControlFlow { 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> { + Box::new(Ok(0)) +} diff --git a/tests/ui/must_use_candidates.rs b/tests/ui/must_use_candidates.rs index 8b898533d01b..a69ea7265c47 100644 --- a/tests/ui/must_use_candidates.rs +++ b/tests/ui/must_use_candidates.rs @@ -113,3 +113,9 @@ pub fn result_uninhabited() -> Result { pub fn controlflow_uninhabited() -> std::ops::ControlFlow { 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> { + Box::new(Ok(0)) +}