rustc_borrowck/diagnostics/
opaque_suggestions.rs

1#![allow(rustc::diagnostic_outside_of_impl)]
2#![allow(rustc::untranslatable_diagnostic)]
3
4use std::ops::ControlFlow;
5
6use either::Either;
7use itertools::Itertools as _;
8use rustc_data_structures::fx::FxIndexSet;
9use rustc_errors::{Diag, Subdiagnostic};
10use rustc_hir as hir;
11use rustc_hir::def_id::DefId;
12use rustc_middle::mir::{self, ConstraintCategory, Location};
13use rustc_middle::ty::{
14    self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor,
15};
16use rustc_trait_selection::errors::impl_trait_overcapture_suggestion;
17
18use crate::MirBorrowckCtxt;
19use crate::borrow_set::BorrowData;
20use crate::consumers::RegionInferenceContext;
21use crate::type_check::Locations;
22
23impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
24    /// Try to note when an opaque is involved in a borrowck error and that
25    /// opaque captures lifetimes due to edition 2024.
26    // FIXME: This code is otherwise somewhat general, and could easily be adapted
27    // to explain why other things overcapture... like async fn and RPITITs.
28    pub(crate) fn note_due_to_edition_2024_opaque_capture_rules(
29        &self,
30        borrow: &BorrowData<'tcx>,
31        diag: &mut Diag<'_>,
32    ) {
33        // We look at all the locals. Why locals? Because it's the best thing
34        // I could think of that's correlated with the *instantiated* higher-ranked
35        // binder for calls, since we don't really store those anywhere else.
36        for ty in self.body.local_decls.iter().map(|local| local.ty) {
37            if !ty.has_opaque_types() {
38                continue;
39            }
40
41            let tcx = self.infcx.tcx;
42            let ControlFlow::Break((opaque_def_id, offending_region_idx, location)) = ty
43                .visit_with(&mut FindOpaqueRegion {
44                    regioncx: &self.regioncx,
45                    tcx,
46                    borrow_region: borrow.region,
47                })
48            else {
49                continue;
50            };
51
52            // If an opaque explicitly captures a lifetime, then no need to point it out.
53            // FIXME: We should be using a better heuristic for `use<>`.
54            if tcx.rendered_precise_capturing_args(opaque_def_id).is_some() {
55                continue;
56            }
57
58            // If one of the opaque's bounds mentions the region, then no need to
59            // point it out, since it would've been captured on edition 2021 as well.
60            //
61            // Also, while we're at it, collect all the lifetimes that the opaque
62            // *does* mention. We'll use that for the `+ use<'a>` suggestion below.
63            let mut visitor = CheckExplicitRegionMentionAndCollectGenerics {
64                tcx,
65                generics: tcx.generics_of(opaque_def_id),
66                offending_region_idx,
67                seen_opaques: [opaque_def_id].into_iter().collect(),
68                seen_lifetimes: Default::default(),
69            };
70            if tcx
71                .explicit_item_bounds(opaque_def_id)
72                .skip_binder()
73                .visit_with(&mut visitor)
74                .is_break()
75            {
76                continue;
77            }
78
79            // If we successfully located a terminator, then point it out
80            // and provide a suggestion if it's local.
81            match self.body.stmt_at(location) {
82                Either::Right(mir::Terminator { source_info, .. }) => {
83                    diag.span_note(
84                        source_info.span,
85                        "this call may capture more lifetimes than intended, \
86                        because Rust 2024 has adjusted the `impl Trait` lifetime capture rules",
87                    );
88                    let mut captured_args = visitor.seen_lifetimes;
89                    // Add in all of the type and const params, too.
90                    // Ordering here is kinda strange b/c we're walking backwards,
91                    // but we're trying to provide *a* suggestion, not a nice one.
92                    let mut next_generics = Some(visitor.generics);
93                    let mut any_synthetic = false;
94                    while let Some(generics) = next_generics {
95                        for param in &generics.own_params {
96                            if param.kind.is_ty_or_const() {
97                                captured_args.insert(param.def_id);
98                            }
99                            if param.kind.is_synthetic() {
100                                any_synthetic = true;
101                            }
102                        }
103                        next_generics = generics.parent.map(|def_id| tcx.generics_of(def_id));
104                    }
105
106                    if let Some(opaque_def_id) = opaque_def_id.as_local()
107                        && let hir::OpaqueTyOrigin::FnReturn { parent, .. } =
108                            tcx.hir().expect_opaque_ty(opaque_def_id).origin
109                    {
110                        if let Some(sugg) = impl_trait_overcapture_suggestion(
111                            tcx,
112                            opaque_def_id,
113                            parent,
114                            captured_args,
115                        ) {
116                            sugg.add_to_diag(diag);
117                        }
118                    } else {
119                        diag.span_help(
120                            tcx.def_span(opaque_def_id),
121                            format!(
122                                "if you can modify this crate, add a precise \
123                                capturing bound to avoid overcapturing: `+ use<{}>`",
124                                if any_synthetic {
125                                    "/* Args */".to_string()
126                                } else {
127                                    captured_args
128                                        .into_iter()
129                                        .map(|def_id| tcx.item_name(def_id))
130                                        .join(", ")
131                                }
132                            ),
133                        );
134                    }
135                    return;
136                }
137                Either::Left(_) => {}
138            }
139        }
140    }
141}
142
143/// This visitor contains the bulk of the logic for this lint.
144struct FindOpaqueRegion<'a, 'tcx> {
145    tcx: TyCtxt<'tcx>,
146    regioncx: &'a RegionInferenceContext<'tcx>,
147    borrow_region: ty::RegionVid,
148}
149
150impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for FindOpaqueRegion<'_, 'tcx> {
151    type Result = ControlFlow<(DefId, usize, Location), ()>;
152
153    fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result {
154        // If we find an opaque in a local ty, then for each of its captured regions,
155        // try to find a path between that captured regions and our borrow region...
156        if let ty::Alias(ty::Opaque, opaque) = *ty.kind()
157            && let hir::OpaqueTyOrigin::FnReturn { parent, in_trait_or_impl: None } =
158                self.tcx.opaque_ty_origin(opaque.def_id)
159        {
160            let variances = self.tcx.variances_of(opaque.def_id);
161            for (idx, (arg, variance)) in std::iter::zip(opaque.args, variances).enumerate() {
162                // Skip uncaptured args.
163                if *variance == ty::Bivariant {
164                    continue;
165                }
166                // We only care about regions.
167                let Some(opaque_region) = arg.as_region() else {
168                    continue;
169                };
170                // Don't try to convert a late-bound region, which shouldn't exist anyways (yet).
171                if opaque_region.is_bound() {
172                    continue;
173                }
174                let opaque_region_vid = self.regioncx.to_region_vid(opaque_region);
175
176                // Find a path between the borrow region and our opaque capture.
177                if let Some((path, _)) =
178                    self.regioncx.find_constraint_paths_between_regions(self.borrow_region, |r| {
179                        r == opaque_region_vid
180                    })
181                {
182                    for constraint in path {
183                        // If we find a call in this path, then check if it defines the opaque.
184                        if let ConstraintCategory::CallArgument(Some(call_ty)) = constraint.category
185                            && let ty::FnDef(call_def_id, _) = *call_ty.kind()
186                            // This function defines the opaque :D
187                            && call_def_id == parent
188                            && let Locations::Single(location) = constraint.locations
189                        {
190                            return ControlFlow::Break((opaque.def_id, idx, location));
191                        }
192                    }
193                }
194            }
195        }
196
197        ty.super_visit_with(self)
198    }
199}
200
201struct CheckExplicitRegionMentionAndCollectGenerics<'tcx> {
202    tcx: TyCtxt<'tcx>,
203    generics: &'tcx ty::Generics,
204    offending_region_idx: usize,
205    seen_opaques: FxIndexSet<DefId>,
206    seen_lifetimes: FxIndexSet<DefId>,
207}
208
209impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for CheckExplicitRegionMentionAndCollectGenerics<'tcx> {
210    type Result = ControlFlow<(), ()>;
211
212    fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result {
213        match *ty.kind() {
214            ty::Alias(ty::Opaque, opaque) => {
215                if self.seen_opaques.insert(opaque.def_id) {
216                    for (bound, _) in self
217                        .tcx
218                        .explicit_item_bounds(opaque.def_id)
219                        .iter_instantiated_copied(self.tcx, opaque.args)
220                    {
221                        bound.visit_with(self)?;
222                    }
223                }
224                ControlFlow::Continue(())
225            }
226            _ => ty.super_visit_with(self),
227        }
228    }
229
230    fn visit_region(&mut self, r: ty::Region<'tcx>) -> Self::Result {
231        match r.kind() {
232            ty::ReEarlyParam(param) => {
233                if param.index as usize == self.offending_region_idx {
234                    ControlFlow::Break(())
235                } else {
236                    self.seen_lifetimes.insert(self.generics.region_param(param, self.tcx).def_id);
237                    ControlFlow::Continue(())
238                }
239            }
240            _ => ControlFlow::Continue(()),
241        }
242    }
243}