Skip to main content

rustc_borrowck/diagnostics/
opaque_types.rs

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