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