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