about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMichael Goulet <michael@errs.io>2023-11-27 21:29:29 +0000
committerMichael Goulet <michael@errs.io>2024-04-09 12:15:27 -0400
commit03c901fd35b67ee0e67c13c350bc4e1a60e1d91d (patch)
treed41d82702e74cfacc630ed70600adccf644e62e0
parentff24ef91fc848832a8dab483d3b17097cbead5a8 (diff)
downloadrust-03c901fd35b67ee0e67c13c350bc4e1a60e1d91d.tar.gz
rust-03c901fd35b67ee0e67c13c350bc4e1a60e1d91d.zip
Add redundant_lifetime_args lint
-rw-r--r--compiler/rustc_errors/src/diagnostic_impls.rs1
-rw-r--r--compiler/rustc_lint/messages.ftl2
-rw-r--r--compiler/rustc_lint/src/lib.rs3
-rw-r--r--compiler/rustc_lint/src/redundant_lifetime_args.rs160
-rw-r--r--compiler/rustc_middle/src/ty/diagnostics.rs15
5 files changed, 173 insertions, 8 deletions
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> {