diff --git a/CHANGELOG.md b/CHANGELOG.md index d1d70f4ddfb8..cd9b9f9ef4a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5453,6 +5453,7 @@ Released 2018-09-13 [`explicit_deref_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_deref_methods [`explicit_into_iter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_into_iter_loop [`explicit_iter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_iter_loop +[`explicit_lifetimes_bound`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_lifetimes_bound [`explicit_write`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_write [`extend_from_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#extend_from_slice [`extend_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#extend_with_drain @@ -5514,6 +5515,7 @@ Released 2018-09-13 [`impl_trait_in_params`]: https://rust-lang.github.io/rust-clippy/master/index.html#impl_trait_in_params [`implicit_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_clone [`implicit_hasher`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_hasher +[`implicit_lifetimes_bound`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_lifetimes_bound [`implicit_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_return [`implicit_saturating_add`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_saturating_add [`implicit_saturating_sub`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_saturating_sub diff --git a/README.md b/README.md index ec76a6dfb08e..1690e2beb16f 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code. -[There are over 700 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) +[There are over 750 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category. diff --git a/book/src/README.md b/book/src/README.md index 7bdfb97c3acf..23527ba896af 100644 --- a/book/src/README.md +++ b/book/src/README.md @@ -6,7 +6,7 @@ A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code. -[There are over 700 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) +[There are over 750 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 3c4e75df8abe..8828ea8dc2d1 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -266,6 +266,8 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::let_with_type_underscore::LET_WITH_TYPE_UNDERSCORE_INFO, crate::lifetimes::EXTRA_UNUSED_LIFETIMES_INFO, crate::lifetimes::NEEDLESS_LIFETIMES_INFO, + crate::lifetimes_bound_nested_ref::EXPLICIT_LIFETIMES_BOUND_INFO, + crate::lifetimes_bound_nested_ref::IMPLICIT_LIFETIMES_BOUND_INFO, crate::lines_filter_map_ok::LINES_FILTER_MAP_OK_INFO, crate::literal_representation::DECIMAL_LITERAL_REPRESENTATION_INFO, crate::literal_representation::INCONSISTENT_DIGIT_GROUPING_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 153820140129..78fe2fd82b5d 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -195,6 +195,7 @@ mod let_if_seq; mod let_underscore; mod let_with_type_underscore; mod lifetimes; +mod lifetimes_bound_nested_ref; mod lines_filter_map_ok; mod literal_representation; mod loops; @@ -936,6 +937,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(move |_| Box::new(assigning_clones::AssigningClones::new(conf))); store.register_late_pass(|_| Box::new(zero_repeat_side_effects::ZeroRepeatSideEffects)); store.register_late_pass(|_| Box::new(manual_unwrap_or_default::ManualUnwrapOrDefault)); + store.register_early_pass(|| Box::new(lifetimes_bound_nested_ref::LifetimesBoundNestedRef)); store.register_late_pass(|_| Box::new(integer_division_remainder_used::IntegerDivisionRemainderUsed)); store.register_late_pass(move |_| Box::new(macro_metavars_in_unsafe::ExprMetavarsInUnsafe::new(conf))); store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(conf))); diff --git a/clippy_lints/src/lifetimes_bound_nested_ref.rs b/clippy_lints/src/lifetimes_bound_nested_ref.rs new file mode 100644 index 000000000000..4b0072fca3d8 --- /dev/null +++ b/clippy_lints/src/lifetimes_bound_nested_ref.rs @@ -0,0 +1,528 @@ +use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then}; +use rustc_ast::visit::FnKind; +use rustc_ast::{ + AngleBracketedArg, FnRetTy, GenericArg, GenericArgs, GenericBound, GenericParamKind, Generics, Item, ItemKind, + NodeId, Path, Ty, TyKind, WherePredicate, +}; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass, Lint, LintContext}; +use rustc_session::impl_lint_pass; +use rustc_span::symbol::Ident; +use rustc_span::{Span, Symbol}; +use std::collections::BTreeMap; +use std::collections::btree_map::Entry; +/// Lints to help dealing with unsoundness due to a compiler bug described here: +/// . +/// +/// For the following three cases the current compiler (1.76.0) gives a later error message when +/// declaring a generic lifetime bound that is implied by a nested reference: +/// +/// [Issue 25860](https://github.com/rust-lang/rust/issues/25860): +/// Implied bounds on nested references + variance = soundness hole +/// +/// [Issue 84591](https://github.com/rust-lang/rust/issues/84591): +/// HRTB on subtrait unsoundly provides HTRB on supertrait with weaker implied bounds +/// +/// [Issue 100051](https://github.com/rust-lang/rust/issues/100051): +/// Implied bounds from projections in impl header can be unsound +/// +/// The lint here suggests to declare such lifetime bounds in the hope that +/// the unsoundness is avoided. +/// +/// There is also a reverse lint that suggests to remove lifetime bounds +/// that are implied by nested references. This reverse lint is intended to be used only +/// when the compiler has been fixed to handle these lifetime bounds correctly. +extern crate rustc_hash; +use rustc_hash::FxHashMap; + +declare_clippy_lint! { + /// ### What it does + /// Checks for nested references with declared generic lifetimes + /// in function arguments and return values and in trait implementation declarations. + /// Such a nested reference implies a lifetimes bound because the inner reference must + /// outlive the outer reference. + /// + /// This lint suggests to declare such implicit lifetime bounds in case they are not declared. + /// Adding such a lifetimes bound helps to avoid unsound code because this addition + /// can lead to a compiler error in related source code, as observed in rustc 1.76.0. + /// + /// The unusual way to use this lint is: + /// 1) Set the lint to warn by this clippy command line argument: + /// ```--warn clippy::explicit-lifetimes-bound``` + /// Without clippy errors, stop here. + /// 2) Add the implied lifetime bound manually, or do this automatically with these command line arguments: + /// ```--fix --warn clippy::explicit-lifetimes-bound``` + /// The code now has a declared explicit lifetimes bound that corresponds to the implied bound. + /// 3) Run the compiler on the code with this declared lifetimes bound. + /// In case the compiler now produces a compiler error on related code, + /// the compiler should already have produced this error before declaring the implied bound. + /// Leave the added lifetimes bound in the code and fix the code producing the compiler error. + /// + /// See also the reverse lint clippy::implicit-lifetimes-bound. + /// + /// ### Why is this bad? + /// The unsoundness is described + /// [here](https://github.com/rust-lang/rustc-dev-guide/blob/478a77a902f64e5128e7164e4e8a3980cfe4b133/src/traits/implied-bounds.md). + /// + /// ### Known problems + /// This lint tries to detect implied lifetime bounds for + /// [issue 25860](https://github.com/rust-lang/rust/issues/25860), + /// [issue 84591](https://github.com/rust-lang/rust/issues/84591), and + /// [issue 100051](https://github.com/rust-lang/rust/issues/100051). + /// It is not known whether this covers all cases that lead to unsoundness for implied lifetime bounds. + /// + /// The automatic fix is not extensively tested, so manually adding the implied lifetimes bound may be necessary. + /// + /// ### Example + /// Here the type of the ```val_a``` argument contains ```&'a &'b``` which implies the lifetimes bound ```'b: 'a```: + /// ```no_run + /// pub const fn lifetime_translator<'a, 'b, T>(val_a: &'a &'b (), val_b: &'b T) -> &'a T { + /// val_b + /// } + /// ``` + /// Use instead: + /// ```no_run + /// pub const fn lifetime_translator<'a, 'b: 'a, T>(val_a: &'a &'b (), val_b: &'b T) -> &'a T { + /// val_b + /// } + /// ``` + #[clippy::version = "1.81.0"] + pub EXPLICIT_LIFETIMES_BOUND, + nursery, + "declare lifetime bounds implied by nested references" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for nested references with declared generic lifetimes + /// in function arguments and return values and in trait implementation declarations. + /// Such a nested reference implies a lifetimes bound because the inner reference must + /// outlive the outer reference. + /// + /// This lint shows such implicit lifetime bounds in case they are declared. + /// **WARNING:** Do not remove these lifetime bounds declararations, see "Known problems" below. + /// + /// See also the reverse lint clippy::explicit-lifetimes-bound. + /// + /// ### Why is this bad? + /// The declared lifetime bounds are superfluous. + /// + /// ### Known problems + /// This lint tries to detect implied lifetime bounds for + /// [issue 25860](https://github.com/rust-lang/rust/issues/25860), + /// [issue 84591](https://github.com/rust-lang/rust/issues/84591), and + /// [issue 100051](https://github.com/rust-lang/rust/issues/100051). + /// Removing the corresponding explicitly declared lifetime bounds may lead to the unsoundness described + /// [here](https://github.com/rust-lang/rustc-dev-guide/blob/478a77a902f64e5128e7164e4e8a3980cfe4b133/src/traits/implied-bounds.md). + /// + /// Removing these redundant lifetime bounds should only be done after the compiler + /// has been fixed to deal correctly with implied lifetime bounds. + /// + /// ### Example + /// Here the type of the ```val_a``` argument contains ```&'a &'b``` which implies the lifetimes bound ```'b: 'a```: + /// ```no_run + /// pub const fn lifetime_translator<'a, 'b: 'a, T>(val_a: &'a &'b (), val_b: &'b T) -> &'a T { + /// val_b + /// } + /// ``` + /// Only after the compiler is fixed, use instead: + /// ```no_run + /// pub const fn lifetime_translator<'a, 'b, T>(val_a: &'a &'b (), val_b: &'b T) -> &'a T { + /// val_b + /// } + /// ``` + #[clippy::version = "1.79.0"] + pub IMPLICIT_LIFETIMES_BOUND, + nursery, + "detect declared lifetime bounds implied by nested references" +} + +pub struct LifetimesBoundNestedRef; + +impl_lint_pass!(LifetimesBoundNestedRef => [ + EXPLICIT_LIFETIMES_BOUND, + IMPLICIT_LIFETIMES_BOUND, +]); + +impl EarlyLintPass for LifetimesBoundNestedRef { + /// For issue 25860: from the generic arguments of a function, + /// get the declared lifetimes and the declared lifetime bounds. + /// Compare these with lifetime bounds implied by nested references + /// in the function arguments and return value. + fn check_fn(&mut self, early_context: &EarlyContext<'_>, fn_kind: FnKind<'_>, _fn_span: Span, _node_id: NodeId) { + let FnKind::Fn(_fn_ctxt, _ident, fn_sig, _visibility, generics, _opt_block) = fn_kind else { + return; + }; + let declared_lifetimes_spans = get_declared_lifetimes_spans(generics); + if declared_lifetimes_spans.len() <= 1 { + return; + } + let mut linter = ImpliedBoundsLinter::new(declared_lifetimes_spans, generics); + for param in &fn_sig.decl.inputs { + linter.collect_implied_lifetime_bounds(¶m.ty); + } + if let FnRetTy::Ty(ret_ty) = &fn_sig.decl.output { + linter.collect_implied_lifetime_bounds(ret_ty); + } + linter.report_lints(early_context); + } + + /// For issues 84591 and 100051: + /// From the generic arguments of a trait implementation declaration + /// get the declared lifetimes and the declared lifetime bounds. + /// Compare these with lifetime bounds implied by nested references + /// in the trait implementation declaration. + /// + /// For issue 100051 also check for nested references + /// in the `for` projection: `impl ... for ...` . + fn check_item_post(&mut self, early_context: &EarlyContext<'_>, item: &Item) { + let ItemKind::Impl(box_impl) = &item.kind else { + return; + }; + let Some(of_trait_ref) = &box_impl.of_trait else { + return; + }; + let declared_lifetimes = get_declared_lifetimes_spans(&box_impl.generics); + if declared_lifetimes.len() <= 1 { + return; + } + let mut linter = ImpliedBoundsLinter::new(declared_lifetimes, &box_impl.generics); + linter.collect_implied_lifetime_bounds_path(&of_trait_ref.path); + // issue 10051 for clause: impl ... for for_clause_ty + let for_clause_ty = &box_impl.self_ty; + linter.collect_implied_lifetime_bounds(for_clause_ty); + linter.report_lints(early_context); + } +} + +/// A lifetimes bound between a pair of lifetime symbols, +/// e.g. 'long: 'outlived. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] // use lexicographic ordering +struct BoundLftSymbolPair { + long_lft: Symbol, + outlived_lft: Symbol, +} + +impl BoundLftSymbolPair { + fn new(long_lft: Symbol, outlived_lft: Symbol) -> Self { + BoundLftSymbolPair { long_lft, outlived_lft } + } + + /// A declarion for the lifetimes bound + fn to_declaration(&self) -> String { + format!("{}: {}", self.long_lft, self.outlived_lft) + } +} + +/// From a [`Generics`] provide an [`FxHashMap`] of the declared lifetime symbols to their spans. +fn get_declared_lifetimes_spans(generics: &Generics) -> FxHashMap { + generics + .params + .iter() + .filter_map(|gp| { + if let GenericParamKind::Lifetime = gp.kind { + Some((gp.ident.name, gp.ident.span)) + } else { + None + } + }) + .collect() +} + +/// From a [`Generics`] provide a [`BTreeMap`] of the declared lifetime bounds to the spans of the +/// declarations. +fn get_declared_bounds_spans(generics: &Generics) -> BTreeMap { + let mut declared_bounds = BTreeMap::new(); + generics.params.iter().for_each(|gp| { + let long_lft_sym = gp.ident.name; + gp.bounds.iter().for_each(|bound| { + if let GenericBound::Outlives(outlived_lft) = bound { + let decl_span = if let Some(colon_span) = gp.colon_span { + colon_span.to(outlived_lft.ident.span) + } else { + outlived_lft.ident.span + }; + declared_bounds.insert( + BoundLftSymbolPair::new(long_lft_sym, outlived_lft.ident.name), + decl_span, + ); + } + }); + }); + generics.where_clause.predicates.iter().for_each(|wp| { + if let WherePredicate::RegionPredicate(wrp) = wp { + let long_lft_sym = wrp.lifetime.ident.name; + wrp.bounds.iter().for_each(|bound| { + if let GenericBound::Outlives(outlived_lft) = bound { + // CHECKME: how to make a good span for the lifetimes bound declaration here? + declared_bounds.insert(BoundLftSymbolPair::new(long_lft_sym, outlived_lft.ident.name), wrp.span); + } + }); + } + }); + declared_bounds +} + +#[derive(Debug)] +struct ImpliedBoundsLinter { + declared_lifetimes_spans: FxHashMap, + declared_bounds_spans: BTreeMap, + /// Map each implied lifetime bound to the spans of the earliest lifetime identifier pair + /// that implies the bound by a nested reference. + implied_bounds_span_pairs: BTreeMap, +} + +impl ImpliedBoundsLinter { + fn new(declared_lifetimes_spans: FxHashMap, generics: &Generics) -> Self { + ImpliedBoundsLinter { + declared_lifetimes_spans, + declared_bounds_spans: get_declared_bounds_spans(generics), + implied_bounds_span_pairs: BTreeMap::new(), + } + } + + /// Collect implied lifetime bounds span pairs from a complete [Path] + fn collect_implied_lifetime_bounds_path(&mut self, path: &Path) { + self.collect_nested_ref_bounds_path(path, None); + } + + /// Collect implied lifetime bounds span pairs from a [Path] + /// that is possibly contained in an outlived lifetime + fn collect_nested_ref_bounds_path(&mut self, path: &Path, opt_outlived_lft_ident: Option<&Ident>) { + for path_segment in &path.segments { + if let Some(generic_args) = &path_segment.args { + if let GenericArgs::AngleBracketed(ab_args) = &**generic_args { + for ab_arg in &ab_args.args { + if let AngleBracketedArg::Arg(generic_arg) = ab_arg { + use GenericArg as GA; + match generic_arg { + GA::Lifetime(long_lft) => { + if let Some(outlived_lft_ident) = opt_outlived_lft_ident + && self.is_declared_lifetime_sym(long_lft.ident.name) + { + self.add_implied_bound_span_pair(&long_lft.ident, outlived_lft_ident); + } + }, + GA::Type(p_ty) => { + self.collect_nested_ref_bounds(p_ty, opt_outlived_lft_ident); + }, + GA::Const(_anon_const) => {}, + } + } + } + } + } + } + } + + fn is_declared_lifetime_sym(&self, lft_sym: Symbol) -> bool { + self.declared_lifetimes_spans.contains_key(&lft_sym) + } + + /// Collect implied lifetime bounds span pairs from a complete [Ty] + fn collect_implied_lifetime_bounds(&mut self, ty: &Ty) { + self.collect_nested_ref_bounds(ty, None); + } + + /// Collect implied lifetime bounds span pairs from a [Ty] + /// that is possibly contained in an outlived lifetime + fn collect_nested_ref_bounds(&mut self, outliving_ty: &Ty, opt_outlived_lft_ident: Option<&Ident>) { + let mut outliving_tys = vec![outliving_ty]; // stack to avoid recursion + while let Some(ty) = outliving_tys.pop() { + use TyKind as TK; + match &ty.kind { + TK::Ref(opt_lifetime, referred_to_mut_ty) => { + // common to issues 25860, 84591 and 100051 + let referred_to_ty = &referred_to_mut_ty.ty; + if let Some(lifetime) = opt_lifetime + && self.is_declared_lifetime_sym(lifetime.ident.name) + { + if let Some(outlived_lft_ident) = opt_outlived_lft_ident { + self.add_implied_bound_span_pair(&lifetime.ident, outlived_lft_ident); + } + // recursion for nested references outliving this lifetime + self.collect_nested_ref_bounds(referred_to_ty, Some(&lifetime.ident)); + } else { + outliving_tys.push(referred_to_ty); + } + }, + TK::Slice(element_ty) => { + // not needed to detect reported issues + outliving_tys.push(element_ty); + }, + TK::Array(element_ty, _anon_const) => { + // not needed to detect reported issues + outliving_tys.push(element_ty); + }, + TK::Tup(tuple_tys) => { + // not needed to detect reported issues + for tuple_ty in tuple_tys { + outliving_tys.push(tuple_ty); + } + }, + TK::Path(opt_q_self, path) => { + if let Some(q_self) = opt_q_self { + // issue 100051 + outliving_tys.push(&q_self.ty); + } + self.collect_nested_ref_bounds_path(path, opt_outlived_lft_ident); + }, + TK::TraitObject(generic_bounds, _trait_object_syntax) => { + // dyn, not needed to detect reported issues + self.collect_nested_ref_bounds_gbs(generic_bounds, opt_outlived_lft_ident); + }, + TK::ImplTrait(_node_id, generic_bounds) => { + // impl, not needed to detect reported issues + self.collect_nested_ref_bounds_gbs(generic_bounds, opt_outlived_lft_ident); + }, + TK::AnonStruct(_node_id, _field_defs) | TK::AnonUnion(_node_id, _field_defs) => { + // CHECKME: can the field definition types of an anonymous struct/union have + // generic lifetimes? + }, + TK::BareFn(_bare_fn_ty) => { + // CHECKME: can bare functions have generic lifetimes? + }, + TK::CVarArgs + | TK::Dummy + | TK::Err(..) + | TK::ImplicitSelf + | TK::Infer + | TK::MacCall(..) + | TK::Never + | TK::Pat(..) + | TK::Paren(..) + | TK::Ptr(..) + | TK::Typeof(..) => {}, + } + } + } + + /// Collect implied lifetime bounds span pairs from [`GenericBound`]s + /// that are possibly contained in an outlived lifetime + fn collect_nested_ref_bounds_gbs( + &mut self, + generic_bounds: &Vec, + opt_outlived_lft_ident: Option<&Ident>, + ) { + for gb in generic_bounds { + use GenericBound as GB; + match gb { + GB::Trait(poly_trait_ref, _trait_bound_modifiers) => { + for bgp in &poly_trait_ref.bound_generic_params { + use GenericParamKind as GPK; + match &bgp.kind { + GPK::Lifetime => { + if let Some(outlived_lft_ident) = opt_outlived_lft_ident + && self.is_declared_lifetime_sym(bgp.ident.name) + { + self.add_implied_bound_span_pair(&bgp.ident, outlived_lft_ident); + } + }, + GPK::Type { default: opt_p_ty } => { + if let Some(ty) = opt_p_ty { + self.collect_nested_ref_bounds(ty, opt_outlived_lft_ident); + } + }, + GPK::Const { ty, .. } => { + self.collect_nested_ref_bounds(ty, opt_outlived_lft_ident); + }, + } + } + }, + GB::Outlives(_lifetime) => { + // CHECKME: should this coincide with already collected declared bounds? + }, + GB::Use(_precise_capturing_args, _span) => { + // CHECKME: how to treat the lifetime in a PreciseCapturingArg ? + }, + } + } + } + + fn add_implied_bound_span_pair(&mut self, long_lft_ident: &Ident, outlived_lft_ident: &Ident) { + if long_lft_ident.name == outlived_lft_ident.name { + // only unequal symbols form a lifetime bound + return; + } + match self + .implied_bounds_span_pairs + .entry(BoundLftSymbolPair::new(long_lft_ident.name, outlived_lft_ident.name)) + { + Entry::Vacant(new_entry) => { + // in nested references the outlived lifetime occurs first + new_entry.insert((outlived_lft_ident.span, long_lft_ident.span)); + }, + Entry::Occupied(mut prev_entry) => { + // keep the first occurring spans for the nested reference. + // (the insertion order here depends on the recursion order on the input types.) + let prev_spans = prev_entry.get_mut(); + if (outlived_lft_ident.span < prev_spans.0) + || (outlived_lft_ident.span == prev_spans.0 && (long_lft_ident.span < prev_spans.1)) + { + *prev_spans = (outlived_lft_ident.span, long_lft_ident.span); + } + }, + } + } + + fn report_lints(self, cx: &EarlyContext<'_>) { + let bound_implied_here_note = "this lifetimes bound is implied here:"; + + for (implied_bound, (outlived_lft_span, long_lft_span)) in &self.implied_bounds_span_pairs { + if !self.declared_bounds_spans.contains_key(implied_bound) { + let impl_bound_decl = implied_bound.to_declaration(); + let msg_missing = format!("missing lifetimes bound declaration: {impl_bound_decl}"); + if let Some(long_lft_decl_span) = self.declared_lifetimes_spans.get(&implied_bound.long_lft) { + let nested_ref_span = outlived_lft_span.to(*long_lft_span); + span_lint_and_fix_sugg_and_note_cause( + cx, + EXPLICIT_LIFETIMES_BOUND, + *long_lft_decl_span, + &msg_missing, + "try", + impl_bound_decl, + nested_ref_span, + bound_implied_here_note, + ); + } + } + } + + for (declared_bound, decl_span) in self.declared_bounds_spans { + if let Some((outlived_lft_span, long_lft_span)) = self.implied_bounds_span_pairs.get(&declared_bound) { + let nested_ref_span = outlived_lft_span.to(*long_lft_span); + span_lint_and_note( + cx, + IMPLICIT_LIFETIMES_BOUND, + decl_span, + format!( + // only remove these lifetime bounds after the compiler is fixed + "declared lifetimes bound: {} is redundant, but do not remove it", + declared_bound.to_declaration(), + ), + Some(nested_ref_span), + bound_implied_here_note, + ); + } + } + } +} + +/// Combine `span_lint_and_sugg` and `span_lint_and_help`: +/// give a lint error, a suggestion to fix, and a note on the cause of the lint in the code. +#[allow(clippy::too_many_arguments)] +fn span_lint_and_fix_sugg_and_note_cause( + cx: &T, + lint: &'static Lint, + sp: Span, + msg: &str, + fix_help: &str, + sugg: String, + cause_span: Span, + cause_note: &'static str, +) { + span_lint_and_then(cx, lint, sp, msg.to_owned(), |diag| { + diag.span_suggestion(sp, fix_help.to_string(), sugg, Applicability::MachineApplicable); + diag.span_note(cause_span, cause_note); + }); +} diff --git a/tests/ui/lifetimes_bound_nested_ref_expl.fixed b/tests/ui/lifetimes_bound_nested_ref_expl.fixed new file mode 100644 index 000000000000..f57dd692b226 --- /dev/null +++ b/tests/ui/lifetimes_bound_nested_ref_expl.fixed @@ -0,0 +1,69 @@ +#![warn(clippy::explicit_lifetimes_bound)] +use core::mem::MaybeUninit; + +// issue 25860, missing implicit bound +pub fn lifetime_translator_1<'lfta, 'lftb: 'lfta, T>(val_a: &'lfta &'lftb T, _val_b: &'lftb T) -> &'lfta T { + val_a +} + +// helper declarations for issue 84591 +trait Supertrait<'a, 'b> { + fn convert(x: &'a T) -> &'b T; +} + +struct MyStruct; + +impl<'a: 'b, 'b> Supertrait<'a, 'b> for MyStruct { + fn convert(x: &'a T) -> &'b T { + x + } +} + +trait Subtrait<'a, 'b, R>: Supertrait<'a, 'b> {} + +// issue 84591, missing implicit bound: +impl<'a: 'b, 'b> Subtrait<'a, 'b, &'b &'a ()> for MyStruct {} + +// helper declarations for issue 100051 +trait Trait1 { + type Type1; +} + +impl Trait1 for T1 { + type Type1 = (); +} + +trait Extend1<'a, 'b> { + fn extend(self, s: &'a str) -> &'b str; +} + +// issue 100051, missing implicit bound +impl<'a: 'b, 'b> Extend1<'a, 'b> for <&'b &'a () as Trait1>::Type1 { + fn extend(self, s: &'a str) -> &'b str { + s + } +} + +// from the httparse crate, lib.rs: bounds implied by argument and return types are the same +// missing implicit bound: +unsafe fn deinit_slice_mut<'a, 'b: 'a, T>(s: &'a mut &'b mut [T]) -> &'a mut &'b mut [MaybeUninit] { + let s: *mut &mut [T] = s; + let s = s as *mut &mut [MaybeUninit]; + &mut *s +} + +// test case for unnamed references. +// helper declarations: +struct Thing1<'a> { + ref_u8: &'a u8, +} +struct Thing2<'a> { + ref_u16: &'a u16, +} +// missing implicit bound +fn test_unnamed_ref<'a: 'b, 'b>(w1: &'b mut &mut Thing1<'a>, w2: &mut Thing2<'b>) -> &'a u8 { + let _ = w2; + w1.ref_u8 +} + +fn main() {} diff --git a/tests/ui/lifetimes_bound_nested_ref_expl.rs b/tests/ui/lifetimes_bound_nested_ref_expl.rs new file mode 100644 index 000000000000..522fedf4f404 --- /dev/null +++ b/tests/ui/lifetimes_bound_nested_ref_expl.rs @@ -0,0 +1,69 @@ +#![warn(clippy::explicit_lifetimes_bound)] +use core::mem::MaybeUninit; + +// issue 25860, missing implicit bound +pub fn lifetime_translator_1<'lfta, 'lftb, T>(val_a: &'lfta &'lftb T, _val_b: &'lftb T) -> &'lfta T { + val_a +} + +// helper declarations for issue 84591 +trait Supertrait<'a, 'b> { + fn convert(x: &'a T) -> &'b T; +} + +struct MyStruct; + +impl<'a: 'b, 'b> Supertrait<'a, 'b> for MyStruct { + fn convert(x: &'a T) -> &'b T { + x + } +} + +trait Subtrait<'a, 'b, R>: Supertrait<'a, 'b> {} + +// issue 84591, missing implicit bound: +impl<'a, 'b> Subtrait<'a, 'b, &'b &'a ()> for MyStruct {} + +// helper declarations for issue 100051 +trait Trait1 { + type Type1; +} + +impl Trait1 for T1 { + type Type1 = (); +} + +trait Extend1<'a, 'b> { + fn extend(self, s: &'a str) -> &'b str; +} + +// issue 100051, missing implicit bound +impl<'a, 'b> Extend1<'a, 'b> for <&'b &'a () as Trait1>::Type1 { + fn extend(self, s: &'a str) -> &'b str { + s + } +} + +// from the httparse crate, lib.rs: bounds implied by argument and return types are the same +// missing implicit bound: +unsafe fn deinit_slice_mut<'a, 'b, T>(s: &'a mut &'b mut [T]) -> &'a mut &'b mut [MaybeUninit] { + let s: *mut &mut [T] = s; + let s = s as *mut &mut [MaybeUninit]; + &mut *s +} + +// test case for unnamed references. +// helper declarations: +struct Thing1<'a> { + ref_u8: &'a u8, +} +struct Thing2<'a> { + ref_u16: &'a u16, +} +// missing implicit bound +fn test_unnamed_ref<'a, 'b>(w1: &'b mut &mut Thing1<'a>, w2: &mut Thing2<'b>) -> &'a u8 { + let _ = w2; + w1.ref_u8 +} + +fn main() {} diff --git a/tests/ui/lifetimes_bound_nested_ref_expl.stderr b/tests/ui/lifetimes_bound_nested_ref_expl.stderr new file mode 100644 index 000000000000..13ceacd5780f --- /dev/null +++ b/tests/ui/lifetimes_bound_nested_ref_expl.stderr @@ -0,0 +1,64 @@ +error: missing lifetimes bound declaration: 'lftb: 'lfta + --> tests/ui/lifetimes_bound_nested_ref_expl.rs:5:37 + | +LL | pub fn lifetime_translator_1<'lfta, 'lftb, T>(val_a: &'lfta &'lftb T, _val_b: &'lftb T) -> &'lfta T { + | ^^^^^ help: try: `'lftb: 'lfta` + | +note: this lifetimes bound is implied here: + --> tests/ui/lifetimes_bound_nested_ref_expl.rs:5:55 + | +LL | pub fn lifetime_translator_1<'lfta, 'lftb, T>(val_a: &'lfta &'lftb T, _val_b: &'lftb T) -> &'lfta T { + | ^^^^^^^^^^^^ + = note: `-D clippy::explicit-lifetimes-bound` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::explicit_lifetimes_bound)]` + +error: missing lifetimes bound declaration: 'a: 'b + --> tests/ui/lifetimes_bound_nested_ref_expl.rs:25:6 + | +LL | impl<'a, 'b> Subtrait<'a, 'b, &'b &'a ()> for MyStruct {} + | ^^ help: try: `'a: 'b` + | +note: this lifetimes bound is implied here: + --> tests/ui/lifetimes_bound_nested_ref_expl.rs:25:32 + | +LL | impl<'a, 'b> Subtrait<'a, 'b, &'b &'a ()> for MyStruct {} + | ^^^^^^ + +error: missing lifetimes bound declaration: 'a: 'b + --> tests/ui/lifetimes_bound_nested_ref_expl.rs:41:6 + | +LL | impl<'a, 'b> Extend1<'a, 'b> for <&'b &'a () as Trait1>::Type1 { + | ^^ help: try: `'a: 'b` + | +note: this lifetimes bound is implied here: + --> tests/ui/lifetimes_bound_nested_ref_expl.rs:41:36 + | +LL | impl<'a, 'b> Extend1<'a, 'b> for <&'b &'a () as Trait1>::Type1 { + | ^^^^^^ + +error: missing lifetimes bound declaration: 'b: 'a + --> tests/ui/lifetimes_bound_nested_ref_expl.rs:49:32 + | +LL | unsafe fn deinit_slice_mut<'a, 'b, T>(s: &'a mut &'b mut [T]) -> &'a mut &'b mut [MaybeUninit] { + | ^^ help: try: `'b: 'a` + | +note: this lifetimes bound is implied here: + --> tests/ui/lifetimes_bound_nested_ref_expl.rs:49:43 + | +LL | unsafe fn deinit_slice_mut<'a, 'b, T>(s: &'a mut &'b mut [T]) -> &'a mut &'b mut [MaybeUninit] { + | ^^^^^^^^^^ + +error: missing lifetimes bound declaration: 'a: 'b + --> tests/ui/lifetimes_bound_nested_ref_expl.rs:64:21 + | +LL | fn test_unnamed_ref<'a, 'b>(w1: &'b mut &mut Thing1<'a>, w2: &mut Thing2<'b>) -> &'a u8 { + | ^^ help: try: `'a: 'b` + | +note: this lifetimes bound is implied here: + --> tests/ui/lifetimes_bound_nested_ref_expl.rs:64:34 + | +LL | fn test_unnamed_ref<'a, 'b>(w1: &'b mut &mut Thing1<'a>, w2: &mut Thing2<'b>) -> &'a u8 { + | ^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 5 previous errors + diff --git a/tests/ui/lifetimes_bound_nested_ref_impl.rs b/tests/ui/lifetimes_bound_nested_ref_impl.rs new file mode 100644 index 000000000000..3da34736c9ba --- /dev/null +++ b/tests/ui/lifetimes_bound_nested_ref_impl.rs @@ -0,0 +1,71 @@ +// This was started as a copy from the fixed output of lifetimes_bound_nested_ref_expl.fixed +// Adapted: the lint name and the code comments. +#![warn(clippy::implicit_lifetimes_bound)] +use core::mem::MaybeUninit; + +// issue 25860, with declared bound +pub fn lifetime_translator_1<'lfta, 'lftb: 'lfta, T>(val_a: &'lfta &'lftb T, _val_b: &'lftb T) -> &'lfta T { + val_a +} + +// helper declarations for issue 84591 +trait Supertrait<'a, 'b> { + fn convert(x: &'a T) -> &'b T; +} + +struct MyStruct; + +impl<'a: 'b, 'b> Supertrait<'a, 'b> for MyStruct { + fn convert(x: &'a T) -> &'b T { + x + } +} + +trait Subtrait<'a, 'b, R>: Supertrait<'a, 'b> {} + +// issue 84591, with declared bound: +impl<'a: 'b, 'b> Subtrait<'a, 'b, &'b &'a ()> for MyStruct {} + +// helper declarations for issue 100051 +trait Trait1 { + type Type1; +} + +impl Trait1 for T1 { + type Type1 = (); +} + +trait Extend1<'a, 'b> { + fn extend(self, s: &'a str) -> &'b str; +} + +// issue 100051, with declared bound +impl<'a: 'b, 'b> Extend1<'a, 'b> for <&'b &'a () as Trait1>::Type1 { + fn extend(self, s: &'a str) -> &'b str { + s + } +} + +// from the httparse crate, lib.rs: bounds implied by argument and return types are the same +// with declared bound: +unsafe fn deinit_slice_mut<'a, 'b: 'a, T>(s: &'a mut &'b mut [T]) -> &'a mut &'b mut [MaybeUninit] { + let s: *mut &mut [T] = s; + let s = s as *mut &mut [MaybeUninit]; + &mut *s +} + +// test case for unnamed references. +// helper declarations: +struct Thing1<'a> { + ref_u8: &'a u8, +} +struct Thing2<'a> { + ref_u16: &'a u16, +} +// with declared bound +fn test_unnamed_ref<'a: 'b, 'b>(w1: &'b mut &mut Thing1<'a>, w2: &mut Thing2<'b>) -> &'a u8 { + let _ = w2; + w1.ref_u8 +} + +fn main() {} diff --git a/tests/ui/lifetimes_bound_nested_ref_impl.stderr b/tests/ui/lifetimes_bound_nested_ref_impl.stderr new file mode 100644 index 000000000000..8a923701cb78 --- /dev/null +++ b/tests/ui/lifetimes_bound_nested_ref_impl.stderr @@ -0,0 +1,64 @@ +error: declared lifetimes bound: 'lftb: 'lfta is redundant, but do not remove it + --> tests/ui/lifetimes_bound_nested_ref_impl.rs:7:42 + | +LL | pub fn lifetime_translator_1<'lfta, 'lftb: 'lfta, T>(val_a: &'lfta &'lftb T, _val_b: &'lftb T) -> &'lfta T { + | ^^^^^^^ + | +note: this lifetimes bound is implied here: + --> tests/ui/lifetimes_bound_nested_ref_impl.rs:7:62 + | +LL | pub fn lifetime_translator_1<'lfta, 'lftb: 'lfta, T>(val_a: &'lfta &'lftb T, _val_b: &'lftb T) -> &'lfta T { + | ^^^^^^^^^^^^ + = note: `-D clippy::implicit-lifetimes-bound` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::implicit_lifetimes_bound)]` + +error: declared lifetimes bound: 'a: 'b is redundant, but do not remove it + --> tests/ui/lifetimes_bound_nested_ref_impl.rs:27:8 + | +LL | impl<'a: 'b, 'b> Subtrait<'a, 'b, &'b &'a ()> for MyStruct {} + | ^^^^ + | +note: this lifetimes bound is implied here: + --> tests/ui/lifetimes_bound_nested_ref_impl.rs:27:36 + | +LL | impl<'a: 'b, 'b> Subtrait<'a, 'b, &'b &'a ()> for MyStruct {} + | ^^^^^^ + +error: declared lifetimes bound: 'a: 'b is redundant, but do not remove it + --> tests/ui/lifetimes_bound_nested_ref_impl.rs:43:8 + | +LL | impl<'a: 'b, 'b> Extend1<'a, 'b> for <&'b &'a () as Trait1>::Type1 { + | ^^^^ + | +note: this lifetimes bound is implied here: + --> tests/ui/lifetimes_bound_nested_ref_impl.rs:43:40 + | +LL | impl<'a: 'b, 'b> Extend1<'a, 'b> for <&'b &'a () as Trait1>::Type1 { + | ^^^^^^ + +error: declared lifetimes bound: 'b: 'a is redundant, but do not remove it + --> tests/ui/lifetimes_bound_nested_ref_impl.rs:51:34 + | +LL | unsafe fn deinit_slice_mut<'a, 'b: 'a, T>(s: &'a mut &'b mut [T]) -> &'a mut &'b mut [MaybeUninit] { + | ^^^^ + | +note: this lifetimes bound is implied here: + --> tests/ui/lifetimes_bound_nested_ref_impl.rs:51:47 + | +LL | unsafe fn deinit_slice_mut<'a, 'b: 'a, T>(s: &'a mut &'b mut [T]) -> &'a mut &'b mut [MaybeUninit] { + | ^^^^^^^^^^ + +error: declared lifetimes bound: 'a: 'b is redundant, but do not remove it + --> tests/ui/lifetimes_bound_nested_ref_impl.rs:66:23 + | +LL | fn test_unnamed_ref<'a: 'b, 'b>(w1: &'b mut &mut Thing1<'a>, w2: &mut Thing2<'b>) -> &'a u8 { + | ^^^^ + | +note: this lifetimes bound is implied here: + --> tests/ui/lifetimes_bound_nested_ref_impl.rs:66:38 + | +LL | fn test_unnamed_ref<'a: 'b, 'b>(w1: &'b mut &mut Thing1<'a>, w2: &mut Thing2<'b>) -> &'a u8 { + | ^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 5 previous errors +