diff options
Diffstat (limited to 'compiler/rustc_lint/src/impl_trait_overcaptures.rs')
| -rw-r--r-- | compiler/rustc_lint/src/impl_trait_overcaptures.rs | 425 |
1 files changed, 425 insertions, 0 deletions
diff --git a/compiler/rustc_lint/src/impl_trait_overcaptures.rs b/compiler/rustc_lint/src/impl_trait_overcaptures.rs new file mode 100644 index 00000000000..30bf80b915b --- /dev/null +++ b/compiler/rustc_lint/src/impl_trait_overcaptures.rs @@ -0,0 +1,425 @@ +use rustc_data_structures::fx::FxIndexSet; +use rustc_data_structures::unord::UnordSet; +use rustc_errors::{Applicability, LintDiagnostic}; +use rustc_hir as hir; +use rustc_hir::def::DefKind; +use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_macros::LintDiagnostic; +use rustc_middle::middle::resolve_bound_vars::ResolvedArg; +use rustc_middle::ty::{ + self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, +}; +use rustc_middle::{bug, span_bug}; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::{sym, BytePos, Span}; + +use crate::fluent_generated as fluent; +use crate::{LateContext, LateLintPass}; + +declare_lint! { + /// The `impl_trait_overcaptures` lint warns against cases where lifetime + /// capture behavior will differ in edition 2024. + /// + /// In the 2024 edition, `impl Trait`s will capture all lifetimes in scope, + /// rather than just the lifetimes that are mentioned in the bounds of the type. + /// Often these sets are equal, but if not, it means that the `impl Trait` may + /// cause erroneous borrow-checker errors. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// # #![feature(precise_capturing)] + /// # #![allow(incomplete_features)] + /// # #![deny(impl_trait_overcaptures)] + /// # use std::fmt::Display; + /// let mut x = vec![]; + /// x.push(1); + /// + /// fn test(x: &Vec<i32>) -> impl Display { + /// x[0] + /// } + /// + /// let element = test(&x); + /// x.push(2); + /// println!("{element}"); + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// In edition < 2024, the returned `impl Display` doesn't capture the + /// lifetime from the `&Vec<i32>`, so the vector can be mutably borrowed + /// while the `impl Display` is live. + /// + /// To fix this, we can explicitly state that the `impl Display` doesn't + /// capture any lifetimes, using `impl use<> Display`. + pub IMPL_TRAIT_OVERCAPTURES, + Allow, + "`impl Trait` will capture more lifetimes than possibly intended in edition 2024", + @feature_gate = sym::precise_capturing; + //@future_incompatible = FutureIncompatibleInfo { + // reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024), + // reference: "<FIXME>", + //}; +} + +declare_lint! { + /// The `impl_trait_redundant_captures` lint warns against cases where use of the + /// precise capturing `use<...>` syntax is not needed. + /// + /// In the 2024 edition, `impl Trait`s will capture all lifetimes in scope. + /// If precise-capturing `use<...>` syntax is used, and the set of parameters + /// that are captures are *equal* to the set of parameters in scope, then + /// the syntax is redundant, and can be removed. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// # #![feature(precise_capturing, lifetime_capture_rules_2024)] + /// # #![allow(incomplete_features)] + /// # #![deny(impl_trait_redundant_captures)] + /// fn test<'a>(x: &'a i32) -> impl use<'a> Sized { x } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// To fix this, remove the `use<'a>`, since the lifetime is already captured + /// since it is in scope. + pub IMPL_TRAIT_REDUNDANT_CAPTURES, + Warn, + "redundant precise-capturing `use<...>` syntax on an `impl Trait`", + @feature_gate = sym::precise_capturing; +} + +declare_lint_pass!( + /// Lint for opaque types that will begin capturing in-scope but unmentioned lifetimes + /// in edition 2024. + ImplTraitOvercaptures => [IMPL_TRAIT_OVERCAPTURES, IMPL_TRAIT_REDUNDANT_CAPTURES] +); + +impl<'tcx> LateLintPass<'tcx> for ImplTraitOvercaptures { + fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'tcx>) { + match &it.kind { + hir::ItemKind::Fn(..) => check_fn(cx.tcx, it.owner_id.def_id), + _ => {} + } + } + + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::ImplItem<'tcx>) { + match &it.kind { + hir::ImplItemKind::Fn(_, _) => check_fn(cx.tcx, it.owner_id.def_id), + _ => {} + } + } + + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::TraitItem<'tcx>) { + match &it.kind { + hir::TraitItemKind::Fn(_, _) => check_fn(cx.tcx, it.owner_id.def_id), + _ => {} + } + } +} + +fn check_fn(tcx: TyCtxt<'_>, parent_def_id: LocalDefId) { + let sig = tcx.fn_sig(parent_def_id).instantiate_identity(); + + let mut in_scope_parameters = FxIndexSet::default(); + // Populate the in_scope_parameters list first with all of the generics in scope + let mut current_def_id = Some(parent_def_id.to_def_id()); + while let Some(def_id) = current_def_id { + let generics = tcx.generics_of(def_id); + for param in &generics.own_params { + in_scope_parameters.insert(param.def_id); + } + current_def_id = generics.parent; + } + + // Then visit the signature to walk through all the binders (incl. the late-bound + // vars on the function itself, which we need to count too). + sig.visit_with(&mut VisitOpaqueTypes { + tcx, + parent_def_id, + in_scope_parameters, + seen: Default::default(), + }); +} + +struct VisitOpaqueTypes<'tcx> { + tcx: TyCtxt<'tcx>, + parent_def_id: LocalDefId, + in_scope_parameters: FxIndexSet<DefId>, + seen: FxIndexSet<LocalDefId>, +} + +impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for VisitOpaqueTypes<'tcx> { + fn visit_binder<T: TypeVisitable<TyCtxt<'tcx>>>( + &mut self, + t: &ty::Binder<'tcx, T>, + ) -> Self::Result { + // When we get into a binder, we need to add its own bound vars to the scope. + let mut added = vec![]; + for arg in t.bound_vars() { + let arg: ty::BoundVariableKind = arg; + match arg { + ty::BoundVariableKind::Region(ty::BoundRegionKind::BrNamed(def_id, ..)) + | ty::BoundVariableKind::Ty(ty::BoundTyKind::Param(def_id, _)) => { + added.push(def_id); + let unique = self.in_scope_parameters.insert(def_id); + assert!(unique); + } + _ => { + self.tcx.dcx().span_delayed_bug( + self.tcx.def_span(self.parent_def_id), + format!("unsupported bound variable kind: {arg:?}"), + ); + } + } + } + + t.super_visit_with(self); + + // And remove them. The `shift_remove` should be `O(1)` since we're popping + // them off from the end. + for arg in added.into_iter().rev() { + self.in_scope_parameters.shift_remove(&arg); + } + } + + fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result { + if !t.has_aliases() { + return; + } + + if let ty::Alias(ty::Projection, opaque_ty) = *t.kind() + && self.tcx.is_impl_trait_in_trait(opaque_ty.def_id) + { + // visit the opaque of the RPITIT + self.tcx + .type_of(opaque_ty.def_id) + .instantiate(self.tcx, opaque_ty.args) + .visit_with(self) + } else if let ty::Alias(ty::Opaque, opaque_ty) = *t.kind() + && let Some(opaque_def_id) = opaque_ty.def_id.as_local() + // Don't recurse infinitely on an opaque + && self.seen.insert(opaque_def_id) + // If it's owned by this function + && let opaque = + self.tcx.hir_node_by_def_id(opaque_def_id).expect_item().expect_opaque_ty() + && let hir::OpaqueTyOrigin::FnReturn(parent_def_id) = opaque.origin + && parent_def_id == self.parent_def_id + { + // Compute the set of args that are captured by the opaque... + let mut captured = FxIndexSet::default(); + let variances = self.tcx.variances_of(opaque_def_id); + let mut current_def_id = Some(opaque_def_id.to_def_id()); + while let Some(def_id) = current_def_id { + let generics = self.tcx.generics_of(def_id); + for param in &generics.own_params { + // A param is captured if it's invariant. + if variances[param.index as usize] != ty::Invariant { + continue; + } + // We need to turn all `ty::Param`/`ConstKind::Param` and + // `ReEarlyParam`/`ReBound` into def ids. + captured.insert(extract_def_id_from_arg( + self.tcx, + generics, + opaque_ty.args[param.index as usize], + )); + } + current_def_id = generics.parent; + } + + // Compute the set of in scope params that are not captured. Get their spans, + // since that's all we really care about them for emitting the diagnostic. + let uncaptured_spans: Vec<_> = self + .in_scope_parameters + .iter() + .filter(|def_id| !captured.contains(*def_id)) + .map(|def_id| self.tcx.def_span(def_id)) + .collect(); + + let opaque_span = self.tcx.def_span(opaque_def_id); + let new_capture_rules = + opaque_span.at_least_rust_2024() || self.tcx.features().lifetime_capture_rules_2024; + + // If we have uncaptured args, and if the opaque doesn't already have + // `use<>` syntax on it, and we're < edition 2024, then warn the user. + if !new_capture_rules + && opaque.precise_capturing_args.is_none() + && !uncaptured_spans.is_empty() + { + let suggestion = if let Ok(snippet) = + self.tcx.sess.source_map().span_to_snippet(opaque_span) + && snippet.starts_with("impl ") + { + let (lifetimes, others): (Vec<_>, Vec<_>) = captured + .into_iter() + .partition(|def_id| self.tcx.def_kind(*def_id) == DefKind::LifetimeParam); + // Take all lifetime params first, then all others (ty/ct). + let generics: Vec<_> = lifetimes + .into_iter() + .chain(others) + .map(|def_id| self.tcx.item_name(def_id).to_string()) + .collect(); + // Make sure that we're not trying to name any APITs + if generics.iter().all(|name| !name.starts_with("impl ")) { + Some(( + format!(" use<{}>", generics.join(", ")), + opaque_span.with_lo(opaque_span.lo() + BytePos(4)).shrink_to_lo(), + )) + } else { + None + } + } else { + None + }; + + self.tcx.emit_node_span_lint( + IMPL_TRAIT_OVERCAPTURES, + self.tcx.local_def_id_to_hir_id(opaque_def_id), + opaque_span, + ImplTraitOvercapturesLint { + self_ty: t, + num_captured: uncaptured_spans.len(), + uncaptured_spans, + suggestion, + }, + ); + } + // Otherwise, if we are edition 2024, have `use<>` syntax, and + // have no uncaptured args, then we should warn to the user that + // it's redundant to capture all args explicitly. + else if new_capture_rules + && let Some((captured_args, capturing_span)) = opaque.precise_capturing_args + { + let mut explicitly_captured = UnordSet::default(); + for arg in captured_args { + match self.tcx.named_bound_var(arg.hir_id()) { + Some( + ResolvedArg::EarlyBound(def_id) | ResolvedArg::LateBound(_, _, def_id), + ) => { + if self.tcx.def_kind(self.tcx.parent(def_id)) == DefKind::OpaqueTy { + let (ty::ReEarlyParam(ty::EarlyParamRegion { def_id, .. }) + | ty::ReLateParam(ty::LateParamRegion { + bound_region: ty::BoundRegionKind::BrNamed(def_id, _), + .. + })) = self + .tcx + .map_opaque_lifetime_to_parent_lifetime(def_id.expect_local()) + .kind() + else { + span_bug!( + self.tcx.def_span(def_id), + "variable should have been duplicated from a parent" + ); + }; + explicitly_captured.insert(def_id); + } else { + explicitly_captured.insert(def_id); + } + } + _ => { + self.tcx.dcx().span_delayed_bug( + self.tcx.hir().span(arg.hir_id()), + "no valid for captured arg", + ); + } + } + } + + if self + .in_scope_parameters + .iter() + .all(|def_id| explicitly_captured.contains(def_id)) + { + self.tcx.emit_node_span_lint( + IMPL_TRAIT_REDUNDANT_CAPTURES, + self.tcx.local_def_id_to_hir_id(opaque_def_id), + opaque_span, + ImplTraitRedundantCapturesLint { capturing_span }, + ); + } + } + + // Walk into the bounds of the opaque, too, since we want to get nested opaques + // in this lint as well. Interestingly, one place that I expect this lint to fire + // is for `impl for<'a> Bound<Out = impl Other>`, since `impl Other` will begin + // to capture `'a` in e2024 (even though late-bound vars in opaques are not allowed). + for clause in + self.tcx.item_bounds(opaque_ty.def_id).iter_instantiated(self.tcx, opaque_ty.args) + { + clause.visit_with(self) + } + } + + t.super_visit_with(self); + } +} + +struct ImplTraitOvercapturesLint<'tcx> { + uncaptured_spans: Vec<Span>, + self_ty: Ty<'tcx>, + num_captured: usize, + suggestion: Option<(String, Span)>, +} + +impl<'a> LintDiagnostic<'a, ()> for ImplTraitOvercapturesLint<'_> { + fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::Diag<'a, ()>) { + diag.arg("self_ty", self.self_ty.to_string()) + .arg("num_captured", self.num_captured) + .span_note(self.uncaptured_spans, fluent::lint_note) + .note(fluent::lint_note2); + if let Some((suggestion, span)) = self.suggestion { + diag.span_suggestion( + span, + fluent::lint_suggestion, + suggestion, + Applicability::MachineApplicable, + ); + } + } + + fn msg(&self) -> rustc_errors::DiagMessage { + fluent::lint_impl_trait_overcaptures + } +} + +#[derive(LintDiagnostic)] +#[diag(lint_impl_trait_redundant_captures)] +struct ImplTraitRedundantCapturesLint { + #[suggestion(lint_suggestion, code = "", applicability = "machine-applicable")] + capturing_span: Span, +} + +fn extract_def_id_from_arg<'tcx>( + tcx: TyCtxt<'tcx>, + generics: &'tcx ty::Generics, + arg: ty::GenericArg<'tcx>, +) -> DefId { + match arg.unpack() { + ty::GenericArgKind::Lifetime(re) => match *re { + ty::ReEarlyParam(ebr) => generics.region_param(ebr, tcx).def_id, + ty::ReBound( + _, + ty::BoundRegion { kind: ty::BoundRegionKind::BrNamed(def_id, ..), .. }, + ) => def_id, + _ => unreachable!(), + }, + ty::GenericArgKind::Type(ty) => { + let ty::Param(param_ty) = *ty.kind() else { + bug!(); + }; + generics.type_param(param_ty, tcx).def_id + } + ty::GenericArgKind::Const(ct) => { + let ty::ConstKind::Param(param_ct) = ct.kind() else { + bug!(); + }; + generics.const_param(param_ct, tcx).def_id + } + } +} |
