#![allow(rustc::diagnostic_outside_of_impl)] #![allow(rustc::untranslatable_diagnostic)] use std::ops::ControlFlow; use either::Either; use itertools::Itertools as _; use rustc_data_structures::fx::FxIndexSet; use rustc_errors::{Diag, Subdiagnostic}; use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_middle::mir::{self, ConstraintCategory, Location}; use rustc_middle::ty::{ self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, }; use rustc_trait_selection::errors::impl_trait_overcapture_suggestion; use crate::MirBorrowckCtxt; use crate::borrow_set::BorrowData; use crate::consumers::RegionInferenceContext; use crate::type_check::Locations; impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { /// Try to note when an opaque is involved in a borrowck error and that /// opaque captures lifetimes due to edition 2024. // FIXME: This code is otherwise somewhat general, and could easily be adapted // to explain why other things overcapture... like async fn and RPITITs. pub(crate) fn note_due_to_edition_2024_opaque_capture_rules( &self, borrow: &BorrowData<'tcx>, diag: &mut Diag<'_>, ) { // We look at all the locals. Why locals? Because it's the best thing // I could think of that's correlated with the *instantiated* higher-ranked // binder for calls, since we don't really store those anywhere else. for ty in self.body.local_decls.iter().map(|local| local.ty) { if !ty.has_opaque_types() { continue; } let tcx = self.infcx.tcx; let ControlFlow::Break((opaque_def_id, offending_region_idx, location)) = ty .visit_with(&mut FindOpaqueRegion { regioncx: &self.regioncx, tcx, borrow_region: borrow.region, }) else { continue; }; // If an opaque explicitly captures a lifetime, then no need to point it out. // FIXME: We should be using a better heuristic for `use<>`. if tcx.rendered_precise_capturing_args(opaque_def_id).is_some() { continue; } // If one of the opaque's bounds mentions the region, then no need to // point it out, since it would've been captured on edition 2021 as well. // // Also, while we're at it, collect all the lifetimes that the opaque // *does* mention. We'll use that for the `+ use<'a>` suggestion below. let mut visitor = CheckExplicitRegionMentionAndCollectGenerics { tcx, generics: tcx.generics_of(opaque_def_id), offending_region_idx, seen_opaques: [opaque_def_id].into_iter().collect(), seen_lifetimes: Default::default(), }; if tcx .explicit_item_bounds(opaque_def_id) .skip_binder() .visit_with(&mut visitor) .is_break() { continue; } // If we successfully located a terminator, then point it out // and provide a suggestion if it's local. match self.body.stmt_at(location) { Either::Right(mir::Terminator { source_info, .. }) => { diag.span_note( source_info.span, "this call may capture more lifetimes than intended, \ because Rust 2024 has adjusted the `impl Trait` lifetime capture rules", ); let mut captured_args = visitor.seen_lifetimes; // Add in all of the type and const params, too. // Ordering here is kinda strange b/c we're walking backwards, // but we're trying to provide *a* suggestion, not a nice one. let mut next_generics = Some(visitor.generics); let mut any_synthetic = false; while let Some(generics) = next_generics { for param in &generics.own_params { if param.kind.is_ty_or_const() { captured_args.insert(param.def_id); } if param.kind.is_synthetic() { any_synthetic = true; } } next_generics = generics.parent.map(|def_id| tcx.generics_of(def_id)); } if let Some(opaque_def_id) = opaque_def_id.as_local() && let hir::OpaqueTyOrigin::FnReturn { parent, .. } = tcx.hir_expect_opaque_ty(opaque_def_id).origin { if let Some(sugg) = impl_trait_overcapture_suggestion( tcx, opaque_def_id, parent, captured_args, ) { sugg.add_to_diag(diag); } } else { diag.span_help( tcx.def_span(opaque_def_id), format!( "if you can modify this crate, add a precise \ capturing bound to avoid overcapturing: `+ use<{}>`", if any_synthetic { "/* Args */".to_string() } else { captured_args .into_iter() .map(|def_id| tcx.item_name(def_id)) .join(", ") } ), ); } return; } Either::Left(_) => {} } } } } /// This visitor contains the bulk of the logic for this lint. struct FindOpaqueRegion<'a, 'tcx> { tcx: TyCtxt<'tcx>, regioncx: &'a RegionInferenceContext<'tcx>, borrow_region: ty::RegionVid, } impl<'tcx> TypeVisitor> for FindOpaqueRegion<'_, 'tcx> { type Result = ControlFlow<(DefId, usize, Location), ()>; fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result { // If we find an opaque in a local ty, then for each of its captured regions, // try to find a path between that captured regions and our borrow region... if let ty::Alias(ty::Opaque, opaque) = *ty.kind() && let hir::OpaqueTyOrigin::FnReturn { parent, in_trait_or_impl: None } = self.tcx.opaque_ty_origin(opaque.def_id) { let variances = self.tcx.variances_of(opaque.def_id); for (idx, (arg, variance)) in std::iter::zip(opaque.args, variances).enumerate() { // Skip uncaptured args. if *variance == ty::Bivariant { continue; } // We only care about regions. let Some(opaque_region) = arg.as_region() else { continue; }; // Don't try to convert a late-bound region, which shouldn't exist anyways (yet). if opaque_region.is_bound() { continue; } let opaque_region_vid = self.regioncx.to_region_vid(opaque_region); // Find a path between the borrow region and our opaque capture. if let Some((path, _)) = self.regioncx.find_constraint_paths_between_regions(self.borrow_region, |r| { r == opaque_region_vid }) { for constraint in path { // If we find a call in this path, then check if it defines the opaque. if let ConstraintCategory::CallArgument(Some(call_ty)) = constraint.category && let ty::FnDef(call_def_id, _) = *call_ty.kind() // This function defines the opaque :D && call_def_id == parent && let Locations::Single(location) = constraint.locations { return ControlFlow::Break((opaque.def_id, idx, location)); } } } } } ty.super_visit_with(self) } } struct CheckExplicitRegionMentionAndCollectGenerics<'tcx> { tcx: TyCtxt<'tcx>, generics: &'tcx ty::Generics, offending_region_idx: usize, seen_opaques: FxIndexSet, seen_lifetimes: FxIndexSet, } impl<'tcx> TypeVisitor> for CheckExplicitRegionMentionAndCollectGenerics<'tcx> { type Result = ControlFlow<(), ()>; fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result { match *ty.kind() { ty::Alias(ty::Opaque, opaque) => { if self.seen_opaques.insert(opaque.def_id) { for (bound, _) in self .tcx .explicit_item_bounds(opaque.def_id) .iter_instantiated_copied(self.tcx, opaque.args) { bound.visit_with(self)?; } } ControlFlow::Continue(()) } _ => ty.super_visit_with(self), } } fn visit_region(&mut self, r: ty::Region<'tcx>) -> Self::Result { match r.kind() { ty::ReEarlyParam(param) => { if param.index as usize == self.offending_region_idx { ControlFlow::Break(()) } else { self.seen_lifetimes.insert(self.generics.region_param(param, self.tcx).def_id); ControlFlow::Continue(()) } } _ => ControlFlow::Continue(()), } } }