rustc_lint/
impl_trait_overcaptures.rs

1use std::assert_matches::debug_assert_matches;
2use std::cell::LazyCell;
3
4use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet};
5use rustc_data_structures::unord::UnordSet;
6use rustc_errors::{LintDiagnostic, Subdiagnostic};
7use rustc_hir as hir;
8use rustc_hir::def::DefKind;
9use rustc_hir::def_id::{DefId, LocalDefId};
10use rustc_infer::infer::TyCtxtInferExt;
11use rustc_infer::infer::outlives::env::OutlivesEnvironment;
12use rustc_macros::LintDiagnostic;
13use rustc_middle::middle::resolve_bound_vars::ResolvedArg;
14use rustc_middle::ty::relate::{
15    Relate, RelateResult, TypeRelation, structurally_relate_consts, structurally_relate_tys,
16};
17use rustc_middle::ty::{
18    self, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitableExt,
19    TypeVisitor,
20};
21use rustc_middle::{bug, span_bug};
22use rustc_session::lint::FutureIncompatibilityReason;
23use rustc_session::{declare_lint, declare_lint_pass};
24use rustc_span::edition::Edition;
25use rustc_span::{Span, Symbol};
26use rustc_trait_selection::errors::{
27    AddPreciseCapturingForOvercapture, impl_trait_overcapture_suggestion,
28};
29use rustc_trait_selection::regions::OutlivesEnvironmentBuildExt;
30use rustc_trait_selection::traits::ObligationCtxt;
31
32use crate::{LateContext, LateLintPass, fluent_generated as fluent};
33
34declare_lint! {
35    /// The `impl_trait_overcaptures` lint warns against cases where lifetime
36    /// capture behavior will differ in edition 2024.
37    ///
38    /// In the 2024 edition, `impl Trait`s will capture all lifetimes in scope,
39    /// rather than just the lifetimes that are mentioned in the bounds of the type.
40    /// Often these sets are equal, but if not, it means that the `impl Trait` may
41    /// cause erroneous borrow-checker errors.
42    ///
43    /// ### Example
44    ///
45    /// ```rust,compile_fail,edition2021
46    /// # #![deny(impl_trait_overcaptures)]
47    /// # use std::fmt::Display;
48    /// let mut x = vec![];
49    /// x.push(1);
50    ///
51    /// fn test(x: &Vec<i32>) -> impl Display {
52    ///     x[0]
53    /// }
54    ///
55    /// let element = test(&x);
56    /// x.push(2);
57    /// println!("{element}");
58    /// ```
59    ///
60    /// {{produces}}
61    ///
62    /// ### Explanation
63    ///
64    /// In edition < 2024, the returned `impl Display` doesn't capture the
65    /// lifetime from the `&Vec<i32>`, so the vector can be mutably borrowed
66    /// while the `impl Display` is live.
67    ///
68    /// To fix this, we can explicitly state that the `impl Display` doesn't
69    /// capture any lifetimes, using `impl Display + use<>`.
70    pub IMPL_TRAIT_OVERCAPTURES,
71    Allow,
72    "`impl Trait` will capture more lifetimes than possibly intended in edition 2024",
73    @future_incompatible = FutureIncompatibleInfo {
74        reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024),
75        reference: "<https://doc.rust-lang.org/nightly/edition-guide/rust-2024/rpit-lifetime-capture.html>",
76    };
77}
78
79declare_lint! {
80    /// The `impl_trait_redundant_captures` lint warns against cases where use of the
81    /// precise capturing `use<...>` syntax is not needed.
82    ///
83    /// In the 2024 edition, `impl Trait`s will capture all lifetimes in scope.
84    /// If precise-capturing `use<...>` syntax is used, and the set of parameters
85    /// that are captures are *equal* to the set of parameters in scope, then
86    /// the syntax is redundant, and can be removed.
87    ///
88    /// ### Example
89    ///
90    /// ```rust,edition2024,compile_fail
91    /// # #![deny(impl_trait_redundant_captures)]
92    /// fn test<'a>(x: &'a i32) -> impl Sized + use<'a> { x }
93    /// ```
94    ///
95    /// {{produces}}
96    ///
97    /// ### Explanation
98    ///
99    /// To fix this, remove the `use<'a>`, since the lifetime is already captured
100    /// since it is in scope.
101    pub IMPL_TRAIT_REDUNDANT_CAPTURES,
102    Allow,
103    "redundant precise-capturing `use<...>` syntax on an `impl Trait`",
104}
105
106declare_lint_pass!(
107    /// Lint for opaque types that will begin capturing in-scope but unmentioned lifetimes
108    /// in edition 2024.
109    ImplTraitOvercaptures => [IMPL_TRAIT_OVERCAPTURES, IMPL_TRAIT_REDUNDANT_CAPTURES]
110);
111
112impl<'tcx> LateLintPass<'tcx> for ImplTraitOvercaptures {
113    fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'tcx>) {
114        match &it.kind {
115            hir::ItemKind::Fn { .. } => check_fn(cx.tcx, it.owner_id.def_id),
116            _ => {}
117        }
118    }
119
120    fn check_impl_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::ImplItem<'tcx>) {
121        match &it.kind {
122            hir::ImplItemKind::Fn(_, _) => check_fn(cx.tcx, it.owner_id.def_id),
123            _ => {}
124        }
125    }
126
127    fn check_trait_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::TraitItem<'tcx>) {
128        match &it.kind {
129            hir::TraitItemKind::Fn(_, _) => check_fn(cx.tcx, it.owner_id.def_id),
130            _ => {}
131        }
132    }
133}
134
135#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
136enum ParamKind {
137    // Early-bound var.
138    Early(Symbol, u32),
139    // Late-bound var on function, not within a binder. We can capture these.
140    Free(DefId, Symbol),
141    // Late-bound var in a binder. We can't capture these yet.
142    Late,
143}
144
145fn check_fn(tcx: TyCtxt<'_>, parent_def_id: LocalDefId) {
146    let sig = tcx.fn_sig(parent_def_id).instantiate_identity();
147
148    let mut in_scope_parameters = FxIndexMap::default();
149    // Populate the in_scope_parameters list first with all of the generics in scope
150    let mut current_def_id = Some(parent_def_id.to_def_id());
151    while let Some(def_id) = current_def_id {
152        let generics = tcx.generics_of(def_id);
153        for param in &generics.own_params {
154            in_scope_parameters.insert(param.def_id, ParamKind::Early(param.name, param.index));
155        }
156        current_def_id = generics.parent;
157    }
158
159    for bound_var in sig.bound_vars() {
160        let ty::BoundVariableKind::Region(ty::BoundRegionKind::Named(def_id, name)) = bound_var
161        else {
162            span_bug!(tcx.def_span(parent_def_id), "unexpected non-lifetime binder on fn sig");
163        };
164
165        in_scope_parameters.insert(def_id, ParamKind::Free(def_id, name));
166    }
167
168    let sig = tcx.liberate_late_bound_regions(parent_def_id.to_def_id(), sig);
169
170    // Then visit the signature to walk through all the binders (incl. the late-bound
171    // vars on the function itself, which we need to count too).
172    sig.visit_with(&mut VisitOpaqueTypes {
173        tcx,
174        parent_def_id,
175        in_scope_parameters,
176        seen: Default::default(),
177        // Lazily compute these two, since they're likely a bit expensive.
178        variances: LazyCell::new(|| {
179            let mut functional_variances = FunctionalVariances {
180                tcx,
181                variances: FxHashMap::default(),
182                ambient_variance: ty::Covariant,
183                generics: tcx.generics_of(parent_def_id),
184            };
185            functional_variances.relate(sig, sig).unwrap();
186            functional_variances.variances
187        }),
188        outlives_env: LazyCell::new(|| {
189            let typing_env = ty::TypingEnv::non_body_analysis(tcx, parent_def_id);
190            let (infcx, param_env) = tcx.infer_ctxt().build_with_typing_env(typing_env);
191            let ocx = ObligationCtxt::new(&infcx);
192            let assumed_wf_tys = ocx.assumed_wf_types(param_env, parent_def_id).unwrap_or_default();
193            OutlivesEnvironment::new(&infcx, parent_def_id, param_env, assumed_wf_tys)
194        }),
195    });
196}
197
198struct VisitOpaqueTypes<'tcx, VarFn, OutlivesFn> {
199    tcx: TyCtxt<'tcx>,
200    parent_def_id: LocalDefId,
201    in_scope_parameters: FxIndexMap<DefId, ParamKind>,
202    variances: LazyCell<FxHashMap<DefId, ty::Variance>, VarFn>,
203    outlives_env: LazyCell<OutlivesEnvironment<'tcx>, OutlivesFn>,
204    seen: FxIndexSet<LocalDefId>,
205}
206
207impl<'tcx, VarFn, OutlivesFn> TypeVisitor<TyCtxt<'tcx>>
208    for VisitOpaqueTypes<'tcx, VarFn, OutlivesFn>
209where
210    VarFn: FnOnce() -> FxHashMap<DefId, ty::Variance>,
211    OutlivesFn: FnOnce() -> OutlivesEnvironment<'tcx>,
212{
213    fn visit_binder<T: TypeFoldable<TyCtxt<'tcx>>>(&mut self, t: &ty::Binder<'tcx, T>) {
214        // When we get into a binder, we need to add its own bound vars to the scope.
215        let mut added = vec![];
216        for arg in t.bound_vars() {
217            let arg: ty::BoundVariableKind = arg;
218            match arg {
219                ty::BoundVariableKind::Region(ty::BoundRegionKind::Named(def_id, ..))
220                | ty::BoundVariableKind::Ty(ty::BoundTyKind::Param(def_id, _)) => {
221                    added.push(def_id);
222                    let unique = self.in_scope_parameters.insert(def_id, ParamKind::Late);
223                    assert_eq!(unique, None);
224                }
225                _ => {
226                    self.tcx.dcx().span_delayed_bug(
227                        self.tcx.def_span(self.parent_def_id),
228                        format!("unsupported bound variable kind: {arg:?}"),
229                    );
230                }
231            }
232        }
233
234        t.super_visit_with(self);
235
236        // And remove them. The `shift_remove` should be `O(1)` since we're popping
237        // them off from the end.
238        for arg in added.into_iter().rev() {
239            self.in_scope_parameters.shift_remove(&arg);
240        }
241    }
242
243    fn visit_ty(&mut self, t: Ty<'tcx>) {
244        if !t.has_aliases() {
245            return;
246        }
247
248        if let ty::Alias(ty::Projection, opaque_ty) = *t.kind()
249            && self.tcx.is_impl_trait_in_trait(opaque_ty.def_id)
250        {
251            // visit the opaque of the RPITIT
252            self.tcx
253                .type_of(opaque_ty.def_id)
254                .instantiate(self.tcx, opaque_ty.args)
255                .visit_with(self)
256        } else if let ty::Alias(ty::Opaque, opaque_ty) = *t.kind()
257            && let Some(opaque_def_id) = opaque_ty.def_id.as_local()
258            // Don't recurse infinitely on an opaque
259            && self.seen.insert(opaque_def_id)
260            // If it's owned by this function
261            && let opaque =
262                self.tcx.hir_node_by_def_id(opaque_def_id).expect_opaque_ty()
263            // We want to recurse into RPITs and async fns, even though the latter
264            // doesn't overcapture on its own, it may mention additional RPITs
265            // in its bounds.
266            && let hir::OpaqueTyOrigin::FnReturn { parent, .. }
267                | hir::OpaqueTyOrigin::AsyncFn { parent, .. } = opaque.origin
268            && parent == self.parent_def_id
269        {
270            let opaque_span = self.tcx.def_span(opaque_def_id);
271            let new_capture_rules = opaque_span.at_least_rust_2024();
272            if !new_capture_rules
273                && !opaque.bounds.iter().any(|bound| matches!(bound, hir::GenericBound::Use(..)))
274            {
275                // Compute the set of args that are captured by the opaque...
276                let mut captured = FxIndexSet::default();
277                let mut captured_regions = FxIndexSet::default();
278                let variances = self.tcx.variances_of(opaque_def_id);
279                let mut current_def_id = Some(opaque_def_id.to_def_id());
280                while let Some(def_id) = current_def_id {
281                    let generics = self.tcx.generics_of(def_id);
282                    for param in &generics.own_params {
283                        // A param is captured if it's invariant.
284                        if variances[param.index as usize] != ty::Invariant {
285                            continue;
286                        }
287
288                        let arg = opaque_ty.args[param.index as usize];
289                        // We need to turn all `ty::Param`/`ConstKind::Param` and
290                        // `ReEarlyParam`/`ReBound` into def ids.
291                        captured.insert(extract_def_id_from_arg(self.tcx, generics, arg));
292
293                        captured_regions.extend(arg.as_region());
294                    }
295                    current_def_id = generics.parent;
296                }
297
298                // Compute the set of in scope params that are not captured.
299                let mut uncaptured_args: FxIndexSet<_> = self
300                    .in_scope_parameters
301                    .iter()
302                    .filter(|&(def_id, _)| !captured.contains(def_id))
303                    .collect();
304                // Remove the set of lifetimes that are in-scope that outlive some other captured
305                // lifetime and are contravariant (i.e. covariant in argument position).
306                uncaptured_args.retain(|&(def_id, kind)| {
307                    let Some(ty::Bivariant | ty::Contravariant) = self.variances.get(def_id) else {
308                        // Keep all covariant/invariant args. Also if variance is `None`,
309                        // then that means it's either not a lifetime, or it didn't show up
310                        // anywhere in the signature.
311                        return true;
312                    };
313                    // We only computed variance of lifetimes...
314                    debug_assert_matches!(self.tcx.def_kind(def_id), DefKind::LifetimeParam);
315                    let uncaptured = match *kind {
316                        ParamKind::Early(name, index) => ty::Region::new_early_param(
317                            self.tcx,
318                            ty::EarlyParamRegion { name, index },
319                        ),
320                        ParamKind::Free(def_id, name) => ty::Region::new_late_param(
321                            self.tcx,
322                            self.parent_def_id.to_def_id(),
323                            ty::LateParamRegionKind::Named(def_id, name),
324                        ),
325                        // Totally ignore late bound args from binders.
326                        ParamKind::Late => return true,
327                    };
328                    // Does this region outlive any captured region?
329                    !captured_regions.iter().any(|r| {
330                        self.outlives_env
331                            .free_region_map()
332                            .sub_free_regions(self.tcx, *r, uncaptured)
333                    })
334                });
335
336                // If we have uncaptured args, and if the opaque doesn't already have
337                // `use<>` syntax on it, and we're < edition 2024, then warn the user.
338                if !uncaptured_args.is_empty() {
339                    let suggestion = impl_trait_overcapture_suggestion(
340                        self.tcx,
341                        opaque_def_id,
342                        self.parent_def_id,
343                        captured,
344                    );
345
346                    let uncaptured_spans: Vec<_> = uncaptured_args
347                        .into_iter()
348                        .map(|(def_id, _)| self.tcx.def_span(def_id))
349                        .collect();
350
351                    self.tcx.emit_node_span_lint(
352                        IMPL_TRAIT_OVERCAPTURES,
353                        self.tcx.local_def_id_to_hir_id(opaque_def_id),
354                        opaque_span,
355                        ImplTraitOvercapturesLint {
356                            self_ty: t,
357                            num_captured: uncaptured_spans.len(),
358                            uncaptured_spans,
359                            suggestion,
360                        },
361                    );
362                }
363            }
364
365            // Otherwise, if we are edition 2024, have `use<>` syntax, and
366            // have no uncaptured args, then we should warn to the user that
367            // it's redundant to capture all args explicitly.
368            if new_capture_rules
369                && let Some((captured_args, capturing_span)) =
370                    opaque.bounds.iter().find_map(|bound| match *bound {
371                        hir::GenericBound::Use(a, s) => Some((a, s)),
372                        _ => None,
373                    })
374            {
375                let mut explicitly_captured = UnordSet::default();
376                for arg in captured_args {
377                    match self.tcx.named_bound_var(arg.hir_id()) {
378                        Some(
379                            ResolvedArg::EarlyBound(def_id) | ResolvedArg::LateBound(_, _, def_id),
380                        ) => {
381                            if self.tcx.def_kind(self.tcx.local_parent(def_id)) == DefKind::OpaqueTy
382                            {
383                                let def_id = self
384                                    .tcx
385                                    .map_opaque_lifetime_to_parent_lifetime(def_id)
386                                    .opt_param_def_id(self.tcx, self.parent_def_id.to_def_id())
387                                    .expect("variable should have been duplicated from parent");
388
389                                explicitly_captured.insert(def_id);
390                            } else {
391                                explicitly_captured.insert(def_id.to_def_id());
392                            }
393                        }
394                        _ => {
395                            self.tcx.dcx().span_delayed_bug(
396                                self.tcx.hir_span(arg.hir_id()),
397                                "no valid for captured arg",
398                            );
399                        }
400                    }
401                }
402
403                if self
404                    .in_scope_parameters
405                    .iter()
406                    .all(|(def_id, _)| explicitly_captured.contains(def_id))
407                {
408                    self.tcx.emit_node_span_lint(
409                        IMPL_TRAIT_REDUNDANT_CAPTURES,
410                        self.tcx.local_def_id_to_hir_id(opaque_def_id),
411                        opaque_span,
412                        ImplTraitRedundantCapturesLint { capturing_span },
413                    );
414                }
415            }
416
417            // Walk into the bounds of the opaque, too, since we want to get nested opaques
418            // in this lint as well. Interestingly, one place that I expect this lint to fire
419            // is for `impl for<'a> Bound<Out = impl Other>`, since `impl Other` will begin
420            // to capture `'a` in e2024 (even though late-bound vars in opaques are not allowed).
421            for clause in
422                self.tcx.item_bounds(opaque_ty.def_id).iter_instantiated(self.tcx, opaque_ty.args)
423            {
424                clause.visit_with(self)
425            }
426        }
427
428        t.super_visit_with(self);
429    }
430}
431
432struct ImplTraitOvercapturesLint<'tcx> {
433    uncaptured_spans: Vec<Span>,
434    self_ty: Ty<'tcx>,
435    num_captured: usize,
436    suggestion: Option<AddPreciseCapturingForOvercapture>,
437}
438
439impl<'a> LintDiagnostic<'a, ()> for ImplTraitOvercapturesLint<'_> {
440    fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::Diag<'a, ()>) {
441        diag.primary_message(fluent::lint_impl_trait_overcaptures);
442        diag.arg("self_ty", self.self_ty.to_string())
443            .arg("num_captured", self.num_captured)
444            .span_note(self.uncaptured_spans, fluent::lint_note)
445            .note(fluent::lint_note2);
446        if let Some(suggestion) = self.suggestion {
447            suggestion.add_to_diag(diag);
448        }
449    }
450}
451
452#[derive(LintDiagnostic)]
453#[diag(lint_impl_trait_redundant_captures)]
454struct ImplTraitRedundantCapturesLint {
455    #[suggestion(lint_suggestion, code = "", applicability = "machine-applicable")]
456    capturing_span: Span,
457}
458
459fn extract_def_id_from_arg<'tcx>(
460    tcx: TyCtxt<'tcx>,
461    generics: &'tcx ty::Generics,
462    arg: ty::GenericArg<'tcx>,
463) -> DefId {
464    match arg.unpack() {
465        ty::GenericArgKind::Lifetime(re) => match re.kind() {
466            ty::ReEarlyParam(ebr) => generics.region_param(ebr, tcx).def_id,
467            ty::ReBound(
468                _,
469                ty::BoundRegion { kind: ty::BoundRegionKind::Named(def_id, ..), .. },
470            )
471            | ty::ReLateParam(ty::LateParamRegion {
472                scope: _,
473                kind: ty::LateParamRegionKind::Named(def_id, ..),
474            }) => def_id,
475            _ => unreachable!(),
476        },
477        ty::GenericArgKind::Type(ty) => {
478            let ty::Param(param_ty) = *ty.kind() else {
479                bug!();
480            };
481            generics.type_param(param_ty, tcx).def_id
482        }
483        ty::GenericArgKind::Const(ct) => {
484            let ty::ConstKind::Param(param_ct) = ct.kind() else {
485                bug!();
486            };
487            generics.const_param(param_ct, tcx).def_id
488        }
489    }
490}
491
492/// Computes the variances of regions that appear in the type, but considering
493/// late-bound regions too, which don't have their variance computed usually.
494///
495/// Like generalization, this is a unary operation implemented on top of the binary
496/// relation infrastructure, mostly because it's much easier to have the relation
497/// track the variance for you, rather than having to do it yourself.
498struct FunctionalVariances<'tcx> {
499    tcx: TyCtxt<'tcx>,
500    variances: FxHashMap<DefId, ty::Variance>,
501    ambient_variance: ty::Variance,
502    generics: &'tcx ty::Generics,
503}
504
505impl<'tcx> TypeRelation<TyCtxt<'tcx>> for FunctionalVariances<'tcx> {
506    fn cx(&self) -> TyCtxt<'tcx> {
507        self.tcx
508    }
509
510    fn relate_with_variance<T: Relate<TyCtxt<'tcx>>>(
511        &mut self,
512        variance: ty::Variance,
513        _: ty::VarianceDiagInfo<TyCtxt<'tcx>>,
514        a: T,
515        b: T,
516    ) -> RelateResult<'tcx, T> {
517        let old_variance = self.ambient_variance;
518        self.ambient_variance = self.ambient_variance.xform(variance);
519        self.relate(a, b).unwrap();
520        self.ambient_variance = old_variance;
521        Ok(a)
522    }
523
524    fn tys(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> {
525        structurally_relate_tys(self, a, b).unwrap();
526        Ok(a)
527    }
528
529    fn regions(
530        &mut self,
531        a: ty::Region<'tcx>,
532        _: ty::Region<'tcx>,
533    ) -> RelateResult<'tcx, ty::Region<'tcx>> {
534        let def_id = match a.kind() {
535            ty::ReEarlyParam(ebr) => self.generics.region_param(ebr, self.tcx).def_id,
536            ty::ReBound(
537                _,
538                ty::BoundRegion { kind: ty::BoundRegionKind::Named(def_id, ..), .. },
539            )
540            | ty::ReLateParam(ty::LateParamRegion {
541                scope: _,
542                kind: ty::LateParamRegionKind::Named(def_id, ..),
543            }) => def_id,
544            _ => {
545                return Ok(a);
546            }
547        };
548
549        if let Some(variance) = self.variances.get_mut(&def_id) {
550            *variance = unify(*variance, self.ambient_variance);
551        } else {
552            self.variances.insert(def_id, self.ambient_variance);
553        }
554
555        Ok(a)
556    }
557
558    fn consts(
559        &mut self,
560        a: ty::Const<'tcx>,
561        b: ty::Const<'tcx>,
562    ) -> RelateResult<'tcx, ty::Const<'tcx>> {
563        structurally_relate_consts(self, a, b).unwrap();
564        Ok(a)
565    }
566
567    fn binders<T>(
568        &mut self,
569        a: ty::Binder<'tcx, T>,
570        b: ty::Binder<'tcx, T>,
571    ) -> RelateResult<'tcx, ty::Binder<'tcx, T>>
572    where
573        T: Relate<TyCtxt<'tcx>>,
574    {
575        self.relate(a.skip_binder(), b.skip_binder()).unwrap();
576        Ok(a)
577    }
578}
579
580/// What is the variance that satisfies the two variances?
581fn unify(a: ty::Variance, b: ty::Variance) -> ty::Variance {
582    match (a, b) {
583        // Bivariance is lattice bottom.
584        (ty::Bivariant, other) | (other, ty::Bivariant) => other,
585        // Invariant is lattice top.
586        (ty::Invariant, _) | (_, ty::Invariant) => ty::Invariant,
587        // If type is required to be covariant and contravariant, then it's invariant.
588        (ty::Contravariant, ty::Covariant) | (ty::Covariant, ty::Contravariant) => ty::Invariant,
589        // Otherwise, co + co = co, contra + contra = contra.
590        (ty::Contravariant, ty::Contravariant) => ty::Contravariant,
591        (ty::Covariant, ty::Covariant) => ty::Covariant,
592    }
593}