diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs index 71a47dcfcba2b..c92164fb36364 100644 --- a/compiler/rustc_ast/src/mut_visit.rs +++ b/compiler/rustc_ast/src/mut_visit.rs @@ -37,6 +37,7 @@ mod sealed { /// This is for compatibility with the regular `Visitor`. pub trait MutVisitorResult { + #[cfg_attr(not(bootstrap), must_use)] type Result: VisitorResult; } diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index 1449a4a5fb30a..d287031a0032d 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -130,6 +130,7 @@ pub enum LifetimeCtxt { pub trait Visitor<'ast>: Sized { /// The result type of the `visit_*` methods. Can be either `()`, /// or `ControlFlow`. + #[cfg_attr(not(bootstrap), must_use)] type Result: VisitorResult = (); fn visit_ident(&mut self, _ident: &'ast Ident) -> Self::Result { @@ -884,7 +885,7 @@ macro_rules! common_visitor_and_walkers { TyKind::BareFn(function_declaration) => { let BareFnTy { safety, ext: _, generic_params, decl, decl_span } = &$($mut)? **function_declaration; - visit_safety(vis, safety); + try_visit!(visit_safety(vis, safety)); try_visit!(visit_generic_params(vis, generic_params)); try_visit!(vis.visit_fn_decl(decl)); try_visit!(visit_span(vis, decl_span)); @@ -1235,7 +1236,7 @@ macro_rules! common_visitor_and_walkers { bounds, bound_generic_params, }) => { - visit_generic_params(vis, bound_generic_params); + try_visit!(visit_generic_params(vis, bound_generic_params)); try_visit!(vis.visit_ty(bounded_ty)); walk_list!(vis, visit_param_bound, bounds, BoundKind::Bound); } @@ -1420,7 +1421,7 @@ macro_rules! common_visitor_and_walkers { let StructExpr { qself, path, fields, rest } = &$($mut)?**se; try_visit!(vis.visit_qself(qself)); try_visit!(vis.visit_path(path)); - visit_expr_fields(vis, fields); + try_visit!(visit_expr_fields(vis, fields)); match rest { StructRest::Base(expr) => try_visit!(vis.visit_expr(expr)), StructRest::Rest(_span) => {} diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs index 57e49625148c2..fe5de33a4c460 100644 --- a/compiler/rustc_hir/src/intravisit.rs +++ b/compiler/rustc_hir/src/intravisit.rs @@ -224,6 +224,7 @@ pub trait Visitor<'v>: Sized { /// The result type of the `visit_*` methods. Can be either `()`, /// or `ControlFlow`. + #[cfg_attr(not(bootstrap), must_use)] type Result: VisitorResult = (); /// If `type NestedFilter` is set to visit nested items, this method diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs index 1620f425794f2..34fd19da41ada 100644 --- a/compiler/rustc_lint/src/unused.rs +++ b/compiler/rustc_lint/src/unused.rs @@ -204,332 +204,324 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { if !(type_lint_emitted_or_suppressed || fn_warned || op_warned) { cx.emit_span_lint(UNUSED_RESULTS, s.span, UnusedResult { ty }); } + } +} - fn check_fn_must_use( - cx: &LateContext<'_>, - expr: &hir::Expr<'_>, - expr_is_from_block: bool, - ) -> bool { - let maybe_def_id = match expr.kind { - hir::ExprKind::Call(callee, _) => { - match callee.kind { - hir::ExprKind::Path(ref qpath) => { - match cx.qpath_res(qpath, callee.hir_id) { - Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) => Some(def_id), - // `Res::Local` if it was a closure, for which we - // do not currently support must-use linting - _ => None, - } - } +fn check_fn_must_use(cx: &LateContext<'_>, expr: &hir::Expr<'_>, expr_is_from_block: bool) -> bool { + let maybe_def_id = match expr.kind { + hir::ExprKind::Call(callee, _) => { + match callee.kind { + hir::ExprKind::Path(ref qpath) => { + match cx.qpath_res(qpath, callee.hir_id) { + Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) => Some(def_id), + // `Res::Local` if it was a closure, for which we + // do not currently support must-use linting _ => None, } } - hir::ExprKind::MethodCall(..) => { - cx.typeck_results().type_dependent_def_id(expr.hir_id) - } _ => None, - }; - if let Some(def_id) = maybe_def_id { - check_must_use_def( - cx, - def_id, - expr.span, - "return value of ", - "", - expr_is_from_block, - ) - } else { - false } } + hir::ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id), + _ => None, + }; + if let Some(def_id) = maybe_def_id { + check_must_use_def(cx, def_id, expr.span, "return value of ", "", expr_is_from_block) + } else { + false + } +} - /// A path through a type to a must_use source. Contains useful info for the lint. - #[derive(Debug)] - enum MustUsePath { - /// Suppress must_use checking. - Suppressed, - /// The root of the normal must_use lint with an optional message. - Def(Span, DefId, Option), - Boxed(Box), - Pinned(Box), - Opaque(Box), - TraitObject(Box), - TupleElement(Vec<(usize, Self)>), - Array(Box, u64), - /// The root of the unused_closures lint. - Closure(Span), - /// The root of the unused_coroutines lint. - Coroutine(Span), - } +/// A path through a type to a must_use source. Contains useful info for the lint. +#[derive(Debug)] +enum MustUsePath { + /// Suppress must_use checking. + Suppressed, + /// The root of the normal must_use lint with an optional message. + Def(Span, DefId, Option), + Boxed(Box), + Pinned(Box), + Opaque(Box), + TraitObject(Box), + TupleElement(Vec<(usize, Self)>), + Array(Box, u64), + /// The root of the unused_closures lint. + Closure(Span), + /// The root of the unused_coroutines lint. + Coroutine(Span), +} - #[instrument(skip(cx, expr), level = "debug", ret)] - fn is_ty_must_use<'tcx>( - cx: &LateContext<'tcx>, - ty: Ty<'tcx>, - expr: &hir::Expr<'_>, - span: Span, - ) -> Option { - if ty.is_unit() - || !ty.is_inhabited_from( - cx.tcx, - cx.tcx.parent_module(expr.hir_id).to_def_id(), - cx.typing_env(), - ) - { - return Some(MustUsePath::Suppressed); - } +#[instrument(skip(cx, expr), level = "debug", ret)] +fn is_ty_must_use<'tcx>( + cx: &LateContext<'tcx>, + ty: Ty<'tcx>, + expr: &hir::Expr<'_>, + span: Span, +) -> Option { + if ty.is_unit() + || !ty.is_inhabited_from( + cx.tcx, + cx.tcx.parent_module(expr.hir_id).to_def_id(), + cx.typing_env(), + ) + { + return Some(MustUsePath::Suppressed); + } - match *ty.kind() { - ty::Adt(..) if let Some(boxed) = ty.boxed_ty() => { - is_ty_must_use(cx, boxed, expr, span) - .map(|inner| MustUsePath::Boxed(Box::new(inner))) - } - ty::Adt(def, args) if cx.tcx.is_lang_item(def.did(), LangItem::Pin) => { - let pinned_ty = args.type_at(0); - is_ty_must_use(cx, pinned_ty, expr, span) - .map(|inner| MustUsePath::Pinned(Box::new(inner))) - } - ty::Adt(def, _) => is_def_must_use(cx, def.did(), span), - ty::Alias(ty::Opaque | ty::Projection, ty::AliasTy { def_id: def, .. }) => { - elaborate(cx.tcx, cx.tcx.explicit_item_self_bounds(def).iter_identity_copied()) - // We only care about self bounds for the impl-trait - .filter_only_self() - .find_map(|(pred, _span)| { - // We only look at the `DefId`, so it is safe to skip the binder here. - if let ty::ClauseKind::Trait(ref poly_trait_predicate) = - pred.kind().skip_binder() - { - let def_id = poly_trait_predicate.trait_ref.def_id; - - is_def_must_use(cx, def_id, span) - } else { - None - } - }) - .map(|inner| MustUsePath::Opaque(Box::new(inner))) - } - ty::Dynamic(binders, _, _) => binders.iter().find_map(|predicate| { - if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() - { - let def_id = trait_ref.def_id; - is_def_must_use(cx, def_id, span) - .map(|inner| MustUsePath::TraitObject(Box::new(inner))) - } else { - None - } - }), - ty::Tuple(tys) => { - let elem_exprs = if let hir::ExprKind::Tup(elem_exprs) = expr.kind { - debug_assert_eq!(elem_exprs.len(), tys.len()); - elem_exprs - } else { - &[] - }; + match *ty.kind() { + ty::Adt(..) if let Some(boxed) = ty.boxed_ty() => { + is_ty_must_use(cx, boxed, expr, span).map(|inner| MustUsePath::Boxed(Box::new(inner))) + } + ty::Adt(def, args) if cx.tcx.is_lang_item(def.did(), LangItem::Pin) => { + let pinned_ty = args.type_at(0); + is_ty_must_use(cx, pinned_ty, expr, span) + .map(|inner| MustUsePath::Pinned(Box::new(inner))) + } + ty::Adt(def, _) => is_def_must_use(cx, def.did(), span), + ty::Alias(ty::Projection, ty::AliasTy { def_id, .. }) => { + if let Some(def) = is_def_must_use(cx, def_id, span) { + Some(def) + } else { + is_alias_must_use(cx, def_id, span) + } + } + ty::Alias(ty::Opaque, ty::AliasTy { def_id, .. }) => is_alias_must_use(cx, def_id, span), + ty::Dynamic(binders, _, _) => binders.iter().find_map(|predicate| { + if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() { + let def_id = trait_ref.def_id; + is_def_must_use(cx, def_id, span) + .map(|inner| MustUsePath::TraitObject(Box::new(inner))) + } else { + None + } + }), + ty::Tuple(tys) => { + let elem_exprs = if let hir::ExprKind::Tup(elem_exprs) = expr.kind { + debug_assert_eq!(elem_exprs.len(), tys.len()); + elem_exprs + } else { + &[] + }; - // Default to `expr`. - let elem_exprs = elem_exprs.iter().chain(iter::repeat(expr)); + // Default to `expr`. + let elem_exprs = elem_exprs.iter().chain(iter::repeat(expr)); - let nested_must_use = tys - .iter() - .zip(elem_exprs) - .enumerate() - .filter_map(|(i, (ty, expr))| { - is_ty_must_use(cx, ty, expr, expr.span).map(|path| (i, path)) - }) - .collect::>(); + let nested_must_use = tys + .iter() + .zip(elem_exprs) + .enumerate() + .filter_map(|(i, (ty, expr))| { + is_ty_must_use(cx, ty, expr, expr.span).map(|path| (i, path)) + }) + .collect::>(); - if !nested_must_use.is_empty() { - Some(MustUsePath::TupleElement(nested_must_use)) - } else { - None - } - } - ty::Array(ty, len) => match len.try_to_target_usize(cx.tcx) { - // If the array is empty we don't lint, to avoid false positives - Some(0) | None => None, - // If the array is definitely non-empty, we can do `#[must_use]` checking. - Some(len) => is_ty_must_use(cx, ty, expr, span) - .map(|inner| MustUsePath::Array(Box::new(inner), len)), - }, - ty::Closure(..) | ty::CoroutineClosure(..) => Some(MustUsePath::Closure(span)), - ty::Coroutine(def_id, ..) => { - // async fn should be treated as "implementor of `Future`" - let must_use = if cx.tcx.coroutine_is_async(def_id) { - let def_id = cx.tcx.lang_items().future_trait()?; - is_def_must_use(cx, def_id, span) - .map(|inner| MustUsePath::Opaque(Box::new(inner))) - } else { - None - }; - must_use.or(Some(MustUsePath::Coroutine(span))) - } - _ => None, + if !nested_must_use.is_empty() { + Some(MustUsePath::TupleElement(nested_must_use)) + } else { + None } } - - fn is_def_must_use(cx: &LateContext<'_>, def_id: DefId, span: Span) -> Option { - if let Some(attr) = cx.tcx.get_attr(def_id, sym::must_use) { - // check for #[must_use = "..."] - let reason = attr.value_str(); - Some(MustUsePath::Def(span, def_id, reason)) + ty::Array(ty, len) => match len.try_to_target_usize(cx.tcx) { + // If the array is empty we don't lint, to avoid false positives + Some(0) | None => None, + // If the array is definitely non-empty, we can do `#[must_use]` checking. + Some(len) => is_ty_must_use(cx, ty, expr, span) + .map(|inner| MustUsePath::Array(Box::new(inner), len)), + }, + ty::Closure(..) | ty::CoroutineClosure(..) => Some(MustUsePath::Closure(span)), + ty::Coroutine(def_id, ..) => { + // async fn should be treated as "implementor of `Future`" + let must_use = if cx.tcx.coroutine_is_async(def_id) { + let def_id = cx.tcx.lang_items().future_trait()?; + is_def_must_use(cx, def_id, span).map(|inner| MustUsePath::Opaque(Box::new(inner))) } else { None - } + }; + must_use.or(Some(MustUsePath::Coroutine(span))) } + _ => None, + } +} - // Returns whether further errors should be suppressed because either a lint has been - // emitted or the type should be ignored. - fn check_must_use_def( - cx: &LateContext<'_>, - def_id: DefId, - span: Span, - descr_pre_path: &str, - descr_post_path: &str, - expr_is_from_block: bool, - ) -> bool { - is_def_must_use(cx, def_id, span) - .map(|must_use_path| { - emit_must_use_untranslated( - cx, - &must_use_path, - descr_pre_path, - descr_post_path, - 1, - false, - expr_is_from_block, - ) - }) - .is_some() - } +fn is_def_must_use(cx: &LateContext<'_>, def_id: DefId, span: Span) -> Option { + if let Some(attr) = cx.tcx.get_attr(def_id, sym::must_use) { + // check for #[must_use = "..."] + let reason = attr.value_str(); + Some(MustUsePath::Def(span, def_id, reason)) + } else { + None + } +} - #[instrument(skip(cx), level = "debug")] - fn emit_must_use_untranslated( - cx: &LateContext<'_>, - path: &MustUsePath, - descr_pre: &str, - descr_post: &str, - plural_len: usize, - is_inner: bool, - expr_is_from_block: bool, - ) { - let plural_suffix = pluralize!(plural_len); - - match path { - MustUsePath::Suppressed => {} - MustUsePath::Boxed(path) => { - let descr_pre = &format!("{descr_pre}boxed "); - emit_must_use_untranslated( - cx, - path, - descr_pre, - descr_post, - plural_len, - true, - expr_is_from_block, - ); - } - MustUsePath::Pinned(path) => { - let descr_pre = &format!("{descr_pre}pinned "); - emit_must_use_untranslated( - cx, - path, - descr_pre, - descr_post, - plural_len, - true, - expr_is_from_block, - ); - } - MustUsePath::Opaque(path) => { - let descr_pre = &format!("{descr_pre}implementer{plural_suffix} of "); - emit_must_use_untranslated( - cx, - path, - descr_pre, - descr_post, - plural_len, - true, - expr_is_from_block, - ); - } - MustUsePath::TraitObject(path) => { - let descr_post = &format!(" trait object{plural_suffix}{descr_post}"); - emit_must_use_untranslated( - cx, - path, - descr_pre, - descr_post, - plural_len, - true, - expr_is_from_block, - ); - } - MustUsePath::TupleElement(elems) => { - for (index, path) in elems { - let descr_post = &format!(" in tuple element {index}"); - emit_must_use_untranslated( - cx, - path, - descr_pre, - descr_post, - plural_len, - true, - expr_is_from_block, - ); - } - } - MustUsePath::Array(path, len) => { - let descr_pre = &format!("{descr_pre}array{plural_suffix} of "); - emit_must_use_untranslated( - cx, - path, - descr_pre, - descr_post, - plural_len.saturating_add(usize::try_from(*len).unwrap_or(usize::MAX)), - true, - expr_is_from_block, - ); - } - MustUsePath::Closure(span) => { - cx.emit_span_lint( - UNUSED_MUST_USE, - *span, - UnusedClosure { count: plural_len, pre: descr_pre, post: descr_post }, - ); - } - MustUsePath::Coroutine(span) => { - cx.emit_span_lint( - UNUSED_MUST_USE, - *span, - UnusedCoroutine { count: plural_len, pre: descr_pre, post: descr_post }, - ); - } - MustUsePath::Def(span, def_id, reason) => { - cx.emit_span_lint( - UNUSED_MUST_USE, - *span, - UnusedDef { - pre: descr_pre, - post: descr_post, - cx, - def_id: *def_id, - note: *reason, - suggestion: (!is_inner).then_some(if expr_is_from_block { - UnusedDefSuggestion::BlockTailExpr { - before_span: span.shrink_to_lo(), - after_span: span.shrink_to_hi(), - } - } else { - UnusedDefSuggestion::NormalExpr { span: span.shrink_to_lo() } - }), - }, - ); - } +// Returns whether further errors should be suppressed because either a lint has been +// emitted or the type should be ignored. +fn check_must_use_def( + cx: &LateContext<'_>, + def_id: DefId, + span: Span, + descr_pre_path: &str, + descr_post_path: &str, + expr_is_from_block: bool, +) -> bool { + is_def_must_use(cx, def_id, span) + .map(|must_use_path| { + emit_must_use_untranslated( + cx, + &must_use_path, + descr_pre_path, + descr_post_path, + 1, + false, + expr_is_from_block, + ) + }) + .is_some() +} + +#[instrument(skip(cx), level = "debug")] +fn emit_must_use_untranslated( + cx: &LateContext<'_>, + path: &MustUsePath, + descr_pre: &str, + descr_post: &str, + plural_len: usize, + is_inner: bool, + expr_is_from_block: bool, +) { + let plural_suffix = pluralize!(plural_len); + + match path { + MustUsePath::Suppressed => {} + MustUsePath::Boxed(path) => { + let descr_pre = &format!("{descr_pre}boxed "); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, + ); + } + MustUsePath::Pinned(path) => { + let descr_pre = &format!("{descr_pre}pinned "); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, + ); + } + MustUsePath::Opaque(path) => { + let descr_pre = &format!("{descr_pre}implementer{plural_suffix} of "); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, + ); + } + MustUsePath::TraitObject(path) => { + let descr_post = &format!(" trait object{plural_suffix}{descr_post}"); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, + ); + } + MustUsePath::TupleElement(elems) => { + for (index, path) in elems { + let descr_post = &format!(" in tuple element {index}"); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, + ); } } + MustUsePath::Array(path, len) => { + let descr_pre = &format!("{descr_pre}array{plural_suffix} of "); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len.saturating_add(usize::try_from(*len).unwrap_or(usize::MAX)), + true, + expr_is_from_block, + ); + } + MustUsePath::Closure(span) => { + cx.emit_span_lint( + UNUSED_MUST_USE, + *span, + UnusedClosure { count: plural_len, pre: descr_pre, post: descr_post }, + ); + } + MustUsePath::Coroutine(span) => { + cx.emit_span_lint( + UNUSED_MUST_USE, + *span, + UnusedCoroutine { count: plural_len, pre: descr_pre, post: descr_post }, + ); + } + MustUsePath::Def(span, def_id, reason) => { + cx.emit_span_lint( + UNUSED_MUST_USE, + *span, + UnusedDef { + pre: descr_pre, + post: descr_post, + cx, + def_id: *def_id, + note: *reason, + suggestion: (!is_inner).then_some(if expr_is_from_block { + UnusedDefSuggestion::BlockTailExpr { + before_span: span.shrink_to_lo(), + after_span: span.shrink_to_hi(), + } + } else { + UnusedDefSuggestion::NormalExpr { span: span.shrink_to_lo() } + }), + }, + ); + } } } +/// Look at the bounds of an alias for a `#[must_use]` trait. +fn is_alias_must_use(cx: &LateContext<'_>, def_id: DefId, span: Span) -> Option { + elaborate(cx.tcx, cx.tcx.explicit_item_self_bounds(def_id).iter_identity_copied()) + // We only care about self bounds for the impl-trait + .filter_only_self() + .find_map(|(pred, _span)| { + // We only look at the `DefId`, so it is safe to skip the binder here. + if let ty::ClauseKind::Trait(ref poly_trait_predicate) = pred.kind().skip_binder() { + let def_id = poly_trait_predicate.trait_ref.def_id; + + is_def_must_use(cx, def_id, span) + } else { + None + } + }) + .map(|inner| MustUsePath::Opaque(Box::new(inner))) +} + declare_lint! { /// The `path_statements` lint detects path statements with no effect. /// diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 4e2be8ff0b817..21e35f98f125d 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -1560,6 +1560,16 @@ impl<'tcx> CheckAttrVisitor<'tcx> { return; } + // `#[must_use]` can be applied to an associated type in a trait, so that it will + // warn on unused associated types in generic contexts. + if let Target::AssocTy = target + && let parent_def_id = self.tcx.hir_get_parent_item(hir_id).def_id + && let containing_item = self.tcx.hir_expect_item(parent_def_id) + && let hir::ItemKind::Trait(..) = containing_item.kind + { + return; + } + let article = match target { Target::ExternCrate | Target::Enum diff --git a/compiler/rustc_privacy/src/lib.rs b/compiler/rustc_privacy/src/lib.rs index 963f4c77d809d..1ed59099f9a1d 100644 --- a/compiler/rustc_privacy/src/lib.rs +++ b/compiler/rustc_privacy/src/lib.rs @@ -67,8 +67,11 @@ impl<'tcx> fmt::Display for LazyDefPathStr<'tcx> { /// manually. Second, it doesn't visit some type components like signatures of fn types, or traits /// in `impl Trait`, see individual comments in `DefIdVisitorSkeleton::visit_ty`. pub trait DefIdVisitor<'tcx> { + #[cfg_attr(not(bootstrap), must_use)] type Result: VisitorResult = (); + const SHALLOW: bool = false; + fn skip_assoc_tys(&self) -> bool { false } diff --git a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs index b0c8fa1f217d2..acc049f70892e 100644 --- a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs +++ b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs @@ -461,6 +461,7 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> { /// The public API to interact with proof trees. pub trait ProofTreeVisitor<'tcx> { + #[cfg_attr(not(bootstrap), must_use)] type Result: VisitorResult = (); fn span(&self) -> Span; diff --git a/compiler/rustc_ty_utils/src/sig_types.rs b/compiler/rustc_ty_utils/src/sig_types.rs index dc6009116ac57..ecc171302dd55 100644 --- a/compiler/rustc_ty_utils/src/sig_types.rs +++ b/compiler/rustc_ty_utils/src/sig_types.rs @@ -9,6 +9,7 @@ use rustc_span::Span; use tracing::{instrument, trace}; pub trait SpannedTypeVisitor<'tcx> { + #[cfg_attr(not(bootstrap), must_use)] type Result: VisitorResult = (); fn visit(&mut self, span: Span, value: impl TypeVisitable>) -> Self::Result; } diff --git a/compiler/rustc_type_ir/src/visit.rs b/compiler/rustc_type_ir/src/visit.rs index a96ac97f785d4..e556a26dea6fa 100644 --- a/compiler/rustc_type_ir/src/visit.rs +++ b/compiler/rustc_type_ir/src/visit.rs @@ -89,6 +89,7 @@ pub trait TypeSuperVisitable: TypeVisitable { /// that recurses into the type's fields in a non-custom fashion. pub trait TypeVisitor: Sized { #[cfg(feature = "nightly")] + #[cfg_attr(not(bootstrap), must_use)] type Result: VisitorResult = (); #[cfg(not(feature = "nightly"))] diff --git a/tests/ui/lint/unused/unused-assoc-ty.rs b/tests/ui/lint/unused/unused-assoc-ty.rs new file mode 100644 index 0000000000000..33c070d2866e3 --- /dev/null +++ b/tests/ui/lint/unused/unused-assoc-ty.rs @@ -0,0 +1,27 @@ +#![deny(unused_must_use)] + +trait Foo { + #[must_use] + type Result; + + fn process(arg: i32) -> Self::Result; +} + +fn generic() { + T::process(10); + //~^ ERROR unused `Foo::Result` that must be used +} + +struct NoOp; +impl Foo for NoOp { + type Result = (); + + fn process(_arg: i32) { println!("did nothing"); } +} + +fn noop() { + // Should not lint. + ::process(10); +} + +fn main() {} diff --git a/tests/ui/lint/unused/unused-assoc-ty.stderr b/tests/ui/lint/unused/unused-assoc-ty.stderr new file mode 100644 index 0000000000000..094e5e2596c9b --- /dev/null +++ b/tests/ui/lint/unused/unused-assoc-ty.stderr @@ -0,0 +1,18 @@ +error: unused `Foo::Result` that must be used + --> $DIR/unused-assoc-ty.rs:11:5 + | +LL | T::process(10); + | ^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/unused-assoc-ty.rs:1:9 + | +LL | #![deny(unused_must_use)] + | ^^^^^^^^^^^^^^^ +help: use `let _ = ...` to ignore the resulting value + | +LL | let _ = T::process(10); + | +++++++ + +error: aborting due to 1 previous error + diff --git a/tests/ui/lint/unused/unused_attributes-must_use.rs b/tests/ui/lint/unused/unused_attributes-must_use.rs index 860fc5046d103..89910f700d78b 100644 --- a/tests/ui/lint/unused/unused_attributes-must_use.rs +++ b/tests/ui/lint/unused/unused_attributes-must_use.rs @@ -67,7 +67,7 @@ fn qux<#[must_use] T>(_: T) {} //~ ERROR `#[must_use]` has no effect trait Use { #[must_use] //~ ERROR `#[must_use]` has no effect const ASSOC_CONST: usize = 4; - #[must_use] //~ ERROR `#[must_use]` has no effect + #[must_use] type AssocTy; #[must_use] diff --git a/tests/ui/lint/unused/unused_attributes-must_use.stderr b/tests/ui/lint/unused/unused_attributes-must_use.stderr index 28fd8eeb8cbdc..b365a580e6057 100644 --- a/tests/ui/lint/unused/unused_attributes-must_use.stderr +++ b/tests/ui/lint/unused/unused_attributes-must_use.stderr @@ -123,12 +123,6 @@ error: `#[must_use]` has no effect when applied to an associated const LL | #[must_use] | ^^^^^^^^^^^ -error: `#[must_use]` has no effect when applied to an associated type - --> $DIR/unused_attributes-must_use.rs:70:5 - | -LL | #[must_use] - | ^^^^^^^^^^^ - error: `#[must_use]` has no effect when applied to a provided trait method --> $DIR/unused_attributes-must_use.rs:83:5 | @@ -223,5 +217,5 @@ help: use `let _ = ...` to ignore the resulting value LL | let _ = ().get_four(); | +++++++ -error: aborting due to 29 previous errors +error: aborting due to 28 previous errors