diff --git a/compiler/rustc_errors/src/diagnostic_impls.rs b/compiler/rustc_errors/src/diagnostic_impls.rs index f90190797ae..6c0551848d6 100644 --- a/compiler/rustc_errors/src/diagnostic_impls.rs +++ b/compiler/rustc_errors/src/diagnostic_impls.rs @@ -46,6 +46,7 @@ impl<'a, T: Clone + IntoDiagArg> IntoDiagArg for &'a T { } } +#[macro_export] macro_rules! into_diag_arg_using_display { ($( $ty:ty ),+ $(,)?) => { $( diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 82b90e1660a..ad0a5a2b624 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -534,6 +534,8 @@ lint_reason_must_be_string_literal = reason must be a string literal lint_reason_must_come_last = reason in lint attribute must come last +lint_redundant_lifetime_args = lifetime `{$victim}` is required to be equal to `{$candidate}`, and is redundant and can be removed + lint_redundant_semicolons = unnecessary trailing {$multiple -> [true] semicolons diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 31c80c4d75b..d13c9549476 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -78,6 +78,7 @@ mod opaque_hidden_inferred_bound; mod pass_by_value; mod passes; mod ptr_nulls; +mod redundant_lifetime_args; mod redundant_semicolon; mod reference_casting; mod traits; @@ -113,6 +114,7 @@ use noop_method_call::*; use opaque_hidden_inferred_bound::*; use pass_by_value::*; use ptr_nulls::*; +use redundant_lifetime_args::RedundantLifetimeArgs; use redundant_semicolon::*; use reference_casting::*; use traits::*; @@ -233,6 +235,7 @@ late_lint_methods!( MissingDoc: MissingDoc, AsyncFnInTrait: AsyncFnInTrait, NonLocalDefinitions: NonLocalDefinitions::default(), + RedundantLifetimeArgs: RedundantLifetimeArgs, ] ] ); diff --git a/compiler/rustc_lint/src/redundant_lifetime_args.rs b/compiler/rustc_lint/src/redundant_lifetime_args.rs new file mode 100644 index 00000000000..7a7c5387369 --- /dev/null +++ b/compiler/rustc_lint/src/redundant_lifetime_args.rs @@ -0,0 +1,160 @@ +#![allow(rustc::diagnostic_outside_of_impl)] +#![allow(rustc::untranslatable_diagnostic)] + +use rustc_data_structures::fx::FxHashSet; +use rustc_hir as hir; +use rustc_hir::def::DefKind; +use rustc_infer::infer::outlives::env::OutlivesEnvironment; +use rustc_infer::infer::{SubregionOrigin, TyCtxtInferExt}; +use rustc_macros::LintDiagnostic; +use rustc_middle::ty::{self, TyCtxt}; +use rustc_span::DUMMY_SP; +use rustc_trait_selection::traits::{outlives_bounds::InferCtxtExt, ObligationCtxt}; + +use crate::{LateContext, LateLintPass}; + +declare_lint! { + /// Docs + pub REDUNDANT_LIFETIME_ARGS, + Allow, + "do something" +} + +declare_lint_pass!(RedundantLifetimeArgs => [REDUNDANT_LIFETIME_ARGS]); + +impl<'tcx> LateLintPass<'tcx> for RedundantLifetimeArgs { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { + check(cx.tcx, cx.param_env, item.owner_id); + } + + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'tcx>) { + check(cx.tcx, cx.param_env, item.owner_id); + } + + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'tcx>) { + if cx + .tcx + .hir() + .expect_item(cx.tcx.local_parent(item.owner_id.def_id)) + .expect_impl() + .of_trait + .is_some() + { + // Don't check for redundant lifetimes for trait implementations, + // since the signature is required to be compatible with the trait. + return; + } + + check(cx.tcx, cx.param_env, item.owner_id); + } +} + +fn check<'tcx>(tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>, owner_id: hir::OwnerId) { + let def_kind = tcx.def_kind(owner_id); + match def_kind { + DefKind::Struct + | DefKind::Union + | DefKind::Enum + | DefKind::Trait + | DefKind::TraitAlias + | DefKind::AssocTy + | DefKind::Fn + | DefKind::Const + | DefKind::AssocFn + | DefKind::AssocConst + | DefKind::Impl { of_trait: _ } => { + // Proceed + } + DefKind::Mod + | DefKind::Variant + | DefKind::TyAlias + | DefKind::ForeignTy + | DefKind::TyParam + | DefKind::ConstParam + | DefKind::Static(_) + | DefKind::Ctor(_, _) + | DefKind::Macro(_) + | DefKind::ExternCrate + | DefKind::Use + | DefKind::ForeignMod + | DefKind::AnonConst + | DefKind::InlineConst + | DefKind::OpaqueTy + | DefKind::Field + | DefKind::LifetimeParam + | DefKind::GlobalAsm + | DefKind::Closure => return, + } + + let infcx = &tcx.infer_ctxt().build(); + let ocx = ObligationCtxt::new(infcx); + let Ok(assumed_wf_types) = ocx.assumed_wf_types(param_env, owner_id.def_id) else { + return; + }; + + let implied_bounds = infcx.implied_bounds_tys(param_env, owner_id.def_id, assumed_wf_types); + let outlives_env = &OutlivesEnvironment::with_bounds(param_env, implied_bounds); + + let mut lifetimes = vec![tcx.lifetimes.re_static]; + lifetimes.extend( + ty::GenericArgs::identity_for_item(tcx, owner_id).iter().filter_map(|arg| arg.as_region()), + ); + if matches!(def_kind, DefKind::Fn | DefKind::AssocFn) { + for var in tcx.fn_sig(owner_id).instantiate_identity().bound_vars() { + let ty::BoundVariableKind::Region(kind) = var else { continue }; + lifetimes.push(ty::Region::new_late_param(tcx, owner_id.to_def_id(), kind)); + } + } + + // Keep track of lifetimes which have already been replaced with other lifetimes. + let mut shadowed = FxHashSet::default(); + + for (idx, &candidate) in lifetimes.iter().enumerate() { + if shadowed.contains(&candidate) { + // Don't suggest removing a lifetime twice. + continue; + } + + if !candidate.has_name() { + // Can't rename a named lifetime with `'_` without ambiguity. + continue; + } + + for &victim in &lifetimes[(idx + 1)..] { + let (ty::ReEarlyParam(ty::EarlyParamRegion { def_id, .. }) + | ty::ReLateParam(ty::LateParamRegion { + bound_region: ty::BoundRegionKind::BrNamed(def_id, _), + .. + })) = victim.kind() + else { + continue; + }; + + if tcx.parent(def_id) != owner_id.to_def_id() { + // Do not rename generics not local to this item since + // they'll overlap with this lint running on the parent. + continue; + } + + let infcx = infcx.fork(); + infcx.sub_regions(SubregionOrigin::RelateRegionParamBound(DUMMY_SP), candidate, victim); + infcx.sub_regions(SubregionOrigin::RelateRegionParamBound(DUMMY_SP), victim, candidate); + if infcx.resolve_regions(outlives_env).is_empty() { + shadowed.insert(victim); + tcx.emit_spanned_lint( + REDUNDANT_LIFETIME_ARGS, + tcx.local_def_id_to_hir_id(def_id.expect_local()), + tcx.def_span(def_id), + RedundantLifetimeArgsList { candidate, victim }, + ); + } + } + } +} + +#[derive(LintDiagnostic)] +#[diag(lint_redundant_lifetime_args)] +struct RedundantLifetimeArgsList<'tcx> { + candidate: ty::Region<'tcx>, + victim: ty::Region<'tcx>, +} diff --git a/compiler/rustc_middle/src/ty/diagnostics.rs b/compiler/rustc_middle/src/ty/diagnostics.rs index ee18647cdd8..cc1d6e50f6d 100644 --- a/compiler/rustc_middle/src/ty/diagnostics.rs +++ b/compiler/rustc_middle/src/ty/diagnostics.rs @@ -5,13 +5,13 @@ use std::fmt::Write; use std::ops::ControlFlow; use crate::ty::{ - AliasTy, Const, ConstKind, FallibleTypeFolder, InferConst, InferTy, Opaque, PolyTraitPredicate, - Projection, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable, TypeSuperVisitable, TypeVisitable, - TypeVisitor, + self, AliasTy, Const, ConstKind, FallibleTypeFolder, InferConst, InferTy, Opaque, + PolyTraitPredicate, Projection, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable, + TypeSuperVisitable, TypeVisitable, TypeVisitor, }; use rustc_data_structures::fx::FxHashMap; -use rustc_errors::{Applicability, Diag, DiagArgValue, IntoDiagArg}; +use rustc_errors::{into_diag_arg_using_display, Applicability, Diag, DiagArgValue, IntoDiagArg}; use rustc_hir as hir; use rustc_hir::def::DefKind; use rustc_hir::def_id::DefId; @@ -19,10 +19,9 @@ use rustc_hir::{PredicateOrigin, WherePredicate}; use rustc_span::{BytePos, Span}; use rustc_type_ir::TyKind::*; -impl<'tcx> IntoDiagArg for Ty<'tcx> { - fn into_diag_arg(self) -> DiagArgValue { - self.to_string().into_diag_arg() - } +into_diag_arg_using_display! { + Ty<'_>, + ty::Region<'_>, } impl<'tcx> Ty<'tcx> {