| use std::assert_matches::debug_assert_matches; |
| use std::cell::LazyCell; |
| |
| use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet}; |
| use rustc_data_structures::unord::UnordSet; |
| use rustc_errors::{LintDiagnostic, Subdiagnostic}; |
| use rustc_hir as hir; |
| use rustc_hir::def::DefKind; |
| use rustc_hir::def_id::{DefId, LocalDefId}; |
| use rustc_infer::infer::TyCtxtInferExt; |
| use rustc_infer::infer::outlives::env::OutlivesEnvironment; |
| use rustc_macros::LintDiagnostic; |
| use rustc_middle::middle::resolve_bound_vars::ResolvedArg; |
| use rustc_middle::ty::relate::{ |
| Relate, RelateResult, TypeRelation, structurally_relate_consts, structurally_relate_tys, |
| }; |
| use rustc_middle::ty::{ |
| self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, |
| }; |
| use rustc_middle::{bug, span_bug}; |
| use rustc_session::lint::FutureIncompatibilityReason; |
| use rustc_session::{declare_lint, declare_lint_pass}; |
| use rustc_span::edition::Edition; |
| use rustc_span::{Span, Symbol}; |
| use rustc_trait_selection::errors::{ |
| AddPreciseCapturingForOvercapture, impl_trait_overcapture_suggestion, |
| }; |
| use rustc_trait_selection::regions::OutlivesEnvironmentBuildExt; |
| use rustc_trait_selection::traits::ObligationCtxt; |
| |
| use crate::{LateContext, LateLintPass, fluent_generated as fluent}; |
| |
| 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,edition2021 |
| /// # #![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 Display + use<>`. |
| pub IMPL_TRAIT_OVERCAPTURES, |
| Allow, |
| "`impl Trait` will capture more lifetimes than possibly intended in edition 2024", |
| @future_incompatible = FutureIncompatibleInfo { |
| reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024), |
| reference: "<https://doc.rust-lang.org/edition-guide/rust-2024/rpit-lifetime-capture.html>", |
| }; |
| } |
| |
| 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,edition2024,compile_fail |
| /// # #![deny(impl_trait_redundant_captures)] |
| /// fn test<'a>(x: &'a i32) -> impl Sized + use<'a> { 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, |
| Allow, |
| "redundant precise-capturing `use<...>` syntax on an `impl Trait`", |
| } |
| |
| 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), |
| _ => {} |
| } |
| } |
| } |
| |
| #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)] |
| enum ParamKind { |
| // Early-bound var. |
| Early(Symbol, u32), |
| // Late-bound var on function, not within a binder. We can capture these. |
| Free(DefId), |
| // Late-bound var in a binder. We can't capture these yet. |
| Late, |
| } |
| |
| fn check_fn(tcx: TyCtxt<'_>, parent_def_id: LocalDefId) { |
| let sig = tcx.fn_sig(parent_def_id).instantiate_identity(); |
| |
| let mut in_scope_parameters = FxIndexMap::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, ParamKind::Early(param.name, param.index)); |
| } |
| current_def_id = generics.parent; |
| } |
| |
| for bound_var in sig.bound_vars() { |
| let ty::BoundVariableKind::Region(ty::BoundRegionKind::Named(def_id)) = bound_var else { |
| span_bug!(tcx.def_span(parent_def_id), "unexpected non-lifetime binder on fn sig"); |
| }; |
| |
| in_scope_parameters.insert(def_id, ParamKind::Free(def_id)); |
| } |
| |
| let sig = tcx.liberate_late_bound_regions(parent_def_id.to_def_id(), sig); |
| |
| // 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(), |
| // Lazily compute these two, since they're likely a bit expensive. |
| variances: LazyCell::new(|| { |
| let mut functional_variances = FunctionalVariances { |
| tcx, |
| variances: FxHashMap::default(), |
| ambient_variance: ty::Covariant, |
| generics: tcx.generics_of(parent_def_id), |
| }; |
| functional_variances.relate(sig, sig).unwrap(); |
| functional_variances.variances |
| }), |
| outlives_env: LazyCell::new(|| { |
| let typing_env = ty::TypingEnv::non_body_analysis(tcx, parent_def_id); |
| let (infcx, param_env) = tcx.infer_ctxt().build_with_typing_env(typing_env); |
| let ocx = ObligationCtxt::new(&infcx); |
| let assumed_wf_tys = ocx.assumed_wf_types(param_env, parent_def_id).unwrap_or_default(); |
| OutlivesEnvironment::new(&infcx, parent_def_id, param_env, assumed_wf_tys) |
| }), |
| }); |
| } |
| |
| struct VisitOpaqueTypes<'tcx, VarFn, OutlivesFn> { |
| tcx: TyCtxt<'tcx>, |
| parent_def_id: LocalDefId, |
| in_scope_parameters: FxIndexMap<DefId, ParamKind>, |
| variances: LazyCell<FxHashMap<DefId, ty::Variance>, VarFn>, |
| outlives_env: LazyCell<OutlivesEnvironment<'tcx>, OutlivesFn>, |
| seen: FxIndexSet<LocalDefId>, |
| } |
| |
| impl<'tcx, VarFn, OutlivesFn> TypeVisitor<TyCtxt<'tcx>> |
| for VisitOpaqueTypes<'tcx, VarFn, OutlivesFn> |
| where |
| VarFn: FnOnce() -> FxHashMap<DefId, ty::Variance>, |
| OutlivesFn: FnOnce() -> OutlivesEnvironment<'tcx>, |
| { |
| fn visit_binder<T: TypeVisitable<TyCtxt<'tcx>>>(&mut self, t: &ty::Binder<'tcx, T>) { |
| // 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::Named(def_id)) |
| | ty::BoundVariableKind::Ty(ty::BoundTyKind::Param(def_id)) => { |
| added.push(def_id); |
| let unique = self.in_scope_parameters.insert(def_id, ParamKind::Late); |
| assert_eq!(unique, None); |
| } |
| _ => { |
| 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>) { |
| 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_opaque_ty() |
| // We want to recurse into RPITs and async fns, even though the latter |
| // doesn't overcapture on its own, it may mention additional RPITs |
| // in its bounds. |
| && let hir::OpaqueTyOrigin::FnReturn { parent, .. } |
| | hir::OpaqueTyOrigin::AsyncFn { parent, .. } = opaque.origin |
| && parent == self.parent_def_id |
| { |
| let opaque_span = self.tcx.def_span(opaque_def_id); |
| let new_capture_rules = opaque_span.at_least_rust_2024(); |
| if !new_capture_rules |
| && !opaque.bounds.iter().any(|bound| matches!(bound, hir::GenericBound::Use(..))) |
| { |
| // Compute the set of args that are captured by the opaque... |
| let mut captured = FxIndexSet::default(); |
| let mut captured_regions = 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; |
| } |
| |
| let arg = opaque_ty.args[param.index as usize]; |
| // 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, arg)); |
| |
| captured_regions.extend(arg.as_region()); |
| } |
| current_def_id = generics.parent; |
| } |
| |
| // Compute the set of in scope params that are not captured. |
| let mut uncaptured_args: FxIndexSet<_> = self |
| .in_scope_parameters |
| .iter() |
| .filter(|&(def_id, _)| !captured.contains(def_id)) |
| .collect(); |
| // Remove the set of lifetimes that are in-scope that outlive some other captured |
| // lifetime and are contravariant (i.e. covariant in argument position). |
| uncaptured_args.retain(|&(def_id, kind)| { |
| let Some(ty::Bivariant | ty::Contravariant) = self.variances.get(def_id) else { |
| // Keep all covariant/invariant args. Also if variance is `None`, |
| // then that means it's either not a lifetime, or it didn't show up |
| // anywhere in the signature. |
| return true; |
| }; |
| // We only computed variance of lifetimes... |
| debug_assert_matches!(self.tcx.def_kind(def_id), DefKind::LifetimeParam); |
| let uncaptured = match *kind { |
| ParamKind::Early(name, index) => ty::Region::new_early_param( |
| self.tcx, |
| ty::EarlyParamRegion { name, index }, |
| ), |
| ParamKind::Free(def_id) => ty::Region::new_late_param( |
| self.tcx, |
| self.parent_def_id.to_def_id(), |
| ty::LateParamRegionKind::Named(def_id), |
| ), |
| // Totally ignore late bound args from binders. |
| ParamKind::Late => return true, |
| }; |
| // Does this region outlive any captured region? |
| !captured_regions.iter().any(|r| { |
| self.outlives_env |
| .free_region_map() |
| .sub_free_regions(self.tcx, *r, uncaptured) |
| }) |
| }); |
| |
| // 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 !uncaptured_args.is_empty() { |
| let suggestion = impl_trait_overcapture_suggestion( |
| self.tcx, |
| opaque_def_id, |
| self.parent_def_id, |
| captured, |
| ); |
| |
| let uncaptured_spans: Vec<_> = uncaptured_args |
| .into_iter() |
| .map(|(def_id, _)| self.tcx.def_span(def_id)) |
| .collect(); |
| |
| 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. |
| if new_capture_rules |
| && let Some((captured_args, capturing_span)) = |
| opaque.bounds.iter().find_map(|bound| match *bound { |
| hir::GenericBound::Use(a, s) => Some((a, s)), |
| _ => None, |
| }) |
| { |
| 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.local_parent(def_id)) == DefKind::OpaqueTy |
| { |
| let def_id = self |
| .tcx |
| .map_opaque_lifetime_to_parent_lifetime(def_id) |
| .opt_param_def_id(self.tcx, self.parent_def_id.to_def_id()) |
| .expect("variable should have been duplicated from parent"); |
| |
| explicitly_captured.insert(def_id); |
| } else { |
| explicitly_captured.insert(def_id.to_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<AddPreciseCapturingForOvercapture>, |
| } |
| |
| impl<'a> LintDiagnostic<'a, ()> for ImplTraitOvercapturesLint<'_> { |
| fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::Diag<'a, ()>) { |
| diag.primary_message(fluent::lint_impl_trait_overcaptures); |
| 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) = self.suggestion { |
| suggestion.add_to_diag(diag); |
| } |
| } |
| } |
| |
| #[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.kind() { |
| ty::GenericArgKind::Lifetime(re) => match re.kind() { |
| ty::ReEarlyParam(ebr) => generics.region_param(ebr, tcx).def_id, |
| ty::ReBound(_, ty::BoundRegion { kind: ty::BoundRegionKind::Named(def_id), .. }) |
| | ty::ReLateParam(ty::LateParamRegion { |
| scope: _, |
| kind: ty::LateParamRegionKind::Named(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 |
| } |
| } |
| } |
| |
| /// Computes the variances of regions that appear in the type, but considering |
| /// late-bound regions too, which don't have their variance computed usually. |
| /// |
| /// Like generalization, this is a unary operation implemented on top of the binary |
| /// relation infrastructure, mostly because it's much easier to have the relation |
| /// track the variance for you, rather than having to do it yourself. |
| struct FunctionalVariances<'tcx> { |
| tcx: TyCtxt<'tcx>, |
| variances: FxHashMap<DefId, ty::Variance>, |
| ambient_variance: ty::Variance, |
| generics: &'tcx ty::Generics, |
| } |
| |
| impl<'tcx> TypeRelation<TyCtxt<'tcx>> for FunctionalVariances<'tcx> { |
| fn cx(&self) -> TyCtxt<'tcx> { |
| self.tcx |
| } |
| |
| fn relate_with_variance<T: Relate<TyCtxt<'tcx>>>( |
| &mut self, |
| variance: ty::Variance, |
| _: ty::VarianceDiagInfo<TyCtxt<'tcx>>, |
| a: T, |
| b: T, |
| ) -> RelateResult<'tcx, T> { |
| let old_variance = self.ambient_variance; |
| self.ambient_variance = self.ambient_variance.xform(variance); |
| self.relate(a, b).unwrap(); |
| self.ambient_variance = old_variance; |
| Ok(a) |
| } |
| |
| fn tys(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> { |
| structurally_relate_tys(self, a, b).unwrap(); |
| Ok(a) |
| } |
| |
| fn regions( |
| &mut self, |
| a: ty::Region<'tcx>, |
| _: ty::Region<'tcx>, |
| ) -> RelateResult<'tcx, ty::Region<'tcx>> { |
| let def_id = match a.kind() { |
| ty::ReEarlyParam(ebr) => self.generics.region_param(ebr, self.tcx).def_id, |
| ty::ReBound(_, ty::BoundRegion { kind: ty::BoundRegionKind::Named(def_id), .. }) |
| | ty::ReLateParam(ty::LateParamRegion { |
| scope: _, |
| kind: ty::LateParamRegionKind::Named(def_id), |
| }) => def_id, |
| _ => { |
| return Ok(a); |
| } |
| }; |
| |
| if let Some(variance) = self.variances.get_mut(&def_id) { |
| *variance = unify(*variance, self.ambient_variance); |
| } else { |
| self.variances.insert(def_id, self.ambient_variance); |
| } |
| |
| Ok(a) |
| } |
| |
| fn consts( |
| &mut self, |
| a: ty::Const<'tcx>, |
| b: ty::Const<'tcx>, |
| ) -> RelateResult<'tcx, ty::Const<'tcx>> { |
| structurally_relate_consts(self, a, b).unwrap(); |
| Ok(a) |
| } |
| |
| fn binders<T>( |
| &mut self, |
| a: ty::Binder<'tcx, T>, |
| b: ty::Binder<'tcx, T>, |
| ) -> RelateResult<'tcx, ty::Binder<'tcx, T>> |
| where |
| T: Relate<TyCtxt<'tcx>>, |
| { |
| self.relate(a.skip_binder(), b.skip_binder()).unwrap(); |
| Ok(a) |
| } |
| } |
| |
| /// What is the variance that satisfies the two variances? |
| fn unify(a: ty::Variance, b: ty::Variance) -> ty::Variance { |
| match (a, b) { |
| // Bivariance is lattice bottom. |
| (ty::Bivariant, other) | (other, ty::Bivariant) => other, |
| // Invariant is lattice top. |
| (ty::Invariant, _) | (_, ty::Invariant) => ty::Invariant, |
| // If type is required to be covariant and contravariant, then it's invariant. |
| (ty::Contravariant, ty::Covariant) | (ty::Covariant, ty::Contravariant) => ty::Invariant, |
| // Otherwise, co + co = co, contra + contra = contra. |
| (ty::Contravariant, ty::Contravariant) => ty::Contravariant, |
| (ty::Covariant, ty::Covariant) => ty::Covariant, |
| } |
| } |