about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
authorMichael Goulet <michael@errs.io>2024-10-03 00:44:14 -0400
committerMichael Goulet <michael@errs.io>2024-10-31 01:35:14 +0000
commitc1457798db03ffb7f207eaf208d8d4d77ba01563 (patch)
treef971a769db7432ffdc4a0628c5481518e97f18eb /compiler
parente093b82a41906c8c228643314e2f799568b37ee9 (diff)
downloadrust-c1457798db03ffb7f207eaf208d8d4d77ba01563.tar.gz
rust-c1457798db03ffb7f207eaf208d8d4d77ba01563.zip
Try to point out when edition 2024 lifetime capture rules cause borrowck issues
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs8
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/mod.rs1
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/opaque_suggestions.rs224
-rw-r--r--compiler/rustc_middle/src/ty/context.rs2
4 files changed, 233 insertions, 2 deletions
diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs
index c687be69b1a..1bfb54a7bb7 100644
--- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs
+++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs
@@ -1489,6 +1489,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
             &borrow_msg,
             &value_msg,
         );
+        self.note_due_to_edition_2024_opaque_capture_rules(borrow, &mut err);
 
         borrow_spans.var_path_only_subdiag(&mut err, crate::InitializationRequiringAction::Borrow);
 
@@ -1561,6 +1562,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
             borrow_span,
             &self.describe_any_place(borrow.borrowed_place.as_ref()),
         );
+        self.note_due_to_edition_2024_opaque_capture_rules(borrow, &mut err);
+
         borrow_spans.var_subdiag(&mut err, Some(borrow.kind), |kind, var_span| {
             use crate::session_diagnostics::CaptureVarCause::*;
             let place = &borrow.borrowed_place;
@@ -1820,6 +1823,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
                 unreachable!()
             }
         };
+        self.note_due_to_edition_2024_opaque_capture_rules(issued_borrow, &mut err);
 
         if issued_spans == borrow_spans {
             borrow_spans.var_subdiag(&mut err, Some(gen_borrow_kind), |kind, var_span| {
@@ -2860,7 +2864,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
 
         debug!(?place_desc, ?explanation);
 
-        let err = match (place_desc, explanation) {
+        let mut err = match (place_desc, explanation) {
             // If the outlives constraint comes from inside the closure,
             // for example:
             //
@@ -2939,6 +2943,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
                 explanation,
             ),
         };
+        self.note_due_to_edition_2024_opaque_capture_rules(borrow, &mut err);
 
         self.buffer_error(err);
     }
@@ -3777,6 +3782,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
         }
 
         let mut err = self.cannot_assign_to_borrowed(span, loan_span, &descr_place);
+        self.note_due_to_edition_2024_opaque_capture_rules(loan, &mut err);
 
         loan_spans.var_subdiag(&mut err, Some(loan.kind), |kind, var_span| {
             use crate::session_diagnostics::CaptureVarCause::*;
diff --git a/compiler/rustc_borrowck/src/diagnostics/mod.rs b/compiler/rustc_borrowck/src/diagnostics/mod.rs
index 801c7af2de7..3b600716036 100644
--- a/compiler/rustc_borrowck/src/diagnostics/mod.rs
+++ b/compiler/rustc_borrowck/src/diagnostics/mod.rs
@@ -48,6 +48,7 @@ mod conflict_errors;
 mod explain_borrow;
 mod move_errors;
 mod mutability_errors;
+mod opaque_suggestions;
 mod region_errors;
 
 pub(crate) use bound_region_errors::{ToUniverseInfo, UniverseInfo};
diff --git a/compiler/rustc_borrowck/src/diagnostics/opaque_suggestions.rs b/compiler/rustc_borrowck/src/diagnostics/opaque_suggestions.rs
new file mode 100644
index 00000000000..bfd7e83501c
--- /dev/null
+++ b/compiler/rustc_borrowck/src/diagnostics/opaque_suggestions.rs
@@ -0,0 +1,224 @@
+#![allow(rustc::diagnostic_outside_of_impl)]
+#![allow(rustc::untranslatable_diagnostic)]
+
+use std::ops::ControlFlow;
+
+use either::Either;
+use rustc_data_structures::fx::FxIndexSet;
+use rustc_errors::{Applicability, Diag};
+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_span::Symbol;
+
+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* higer-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,
+                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 seen_generics: Vec<_> =
+                        visitor.seen_lifetimes.iter().map(ToString::to_string).collect();
+                    // Capture all in-scope ty/const params.
+                    seen_generics.extend(
+                        ty::GenericArgs::identity_for_item(tcx, opaque_def_id)
+                            .iter()
+                            .filter(|arg| {
+                                matches!(
+                                    arg.unpack(),
+                                    ty::GenericArgKind::Type(_) | ty::GenericArgKind::Const(_)
+                                )
+                            })
+                            .map(|arg| arg.to_string()),
+                    );
+                    if opaque_def_id.is_local() {
+                        diag.span_suggestion_verbose(
+                            tcx.def_span(opaque_def_id).shrink_to_hi(),
+                            "add a precise capturing bound to avoid overcapturing",
+                            format!(" + use<{}>", seen_generics.join(", ")),
+                            Applicability::MaybeIncorrect,
+                        );
+                    } 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<{}>`",
+                                seen_generics.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<TyCtxt<'tcx>> 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>,
+    offending_region_idx: usize,
+    seen_opaques: FxIndexSet<DefId>,
+    seen_lifetimes: FxIndexSet<Symbol>,
+}
+
+impl<'tcx> TypeVisitor<TyCtxt<'tcx>> 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(param.name);
+                    ControlFlow::Continue(())
+                }
+            }
+            _ => ControlFlow::Continue(()),
+        }
+    }
+}
diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs
index 9f1ffabc4f8..9abad6d1a68 100644
--- a/compiler/rustc_middle/src/ty/context.rs
+++ b/compiler/rustc_middle/src/ty/context.rs
@@ -55,7 +55,7 @@ use rustc_type_ir::fold::TypeFoldable;
 use rustc_type_ir::lang_items::TraitSolverLangItem;
 pub use rustc_type_ir::lift::Lift;
 use rustc_type_ir::{CollectAndApply, Interner, TypeFlags, WithCachedTypeInfo, search_graph};
-use tracing::{debug, trace};
+use tracing::{debug, instrument};
 
 use crate::arena::Arena;
 use crate::dep_graph::{DepGraph, DepKindStruct};