rustc_ast_lowering/
lifetime_collector.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use rustc_ast::visit::{self, BoundKind, LifetimeCtxt, Visitor};
use rustc_ast::{
    GenericBound, GenericBounds, Lifetime, NodeId, PathSegment, PolyTraitRef, Ty, TyKind,
};
use rustc_data_structures::fx::FxIndexSet;
use rustc_hir::def::{DefKind, LifetimeRes, Res};
use rustc_middle::span_bug;
use rustc_middle::ty::ResolverAstLowering;
use rustc_span::Span;
use rustc_span::symbol::{Ident, kw};

use super::ResolverAstLoweringExt;

struct LifetimeCollectVisitor<'ast> {
    resolver: &'ast mut ResolverAstLowering,
    always_capture_in_scope: bool,
    current_binders: Vec<NodeId>,
    collected_lifetimes: FxIndexSet<Lifetime>,
}

impl<'ast> LifetimeCollectVisitor<'ast> {
    fn new(resolver: &'ast mut ResolverAstLowering, always_capture_in_scope: bool) -> Self {
        Self {
            resolver,
            always_capture_in_scope,
            current_binders: Vec::new(),
            collected_lifetimes: FxIndexSet::default(),
        }
    }

    fn visit_opaque(&mut self, opaque_ty_node_id: NodeId, bounds: &'ast GenericBounds, span: Span) {
        // If we're edition 2024 or within a TAIT or RPITIT, *and* there is no
        // `use<>` statement to override the default capture behavior, then
        // capture all of the in-scope lifetimes.
        if (self.always_capture_in_scope || span.at_least_rust_2024())
            && bounds.iter().all(|bound| !matches!(bound, GenericBound::Use(..)))
        {
            for (ident, id, _) in self.resolver.extra_lifetime_params(opaque_ty_node_id) {
                self.record_lifetime_use(Lifetime { id, ident });
            }
        }

        // We also recurse on the bounds to make sure we capture all the lifetimes
        // mentioned in the bounds. These may disagree with the `use<>` list, in which
        // case we will error on these later. We will also recurse to visit any
        // nested opaques, which may *implicitly* capture lifetimes.
        for bound in bounds {
            self.visit_param_bound(bound, BoundKind::Bound);
        }
    }

    fn record_lifetime_use(&mut self, lifetime: Lifetime) {
        match self.resolver.get_lifetime_res(lifetime.id).unwrap_or(LifetimeRes::Error) {
            LifetimeRes::Param { binder, .. } | LifetimeRes::Fresh { binder, .. } => {
                if !self.current_binders.contains(&binder) {
                    self.collected_lifetimes.insert(lifetime);
                }
            }
            LifetimeRes::Static { .. } | LifetimeRes::Error => {
                self.collected_lifetimes.insert(lifetime);
            }
            LifetimeRes::Infer => {}
            res => {
                let bug_msg = format!(
                    "Unexpected lifetime resolution {:?} for {:?} at {:?}",
                    res, lifetime.ident, lifetime.ident.span
                );
                span_bug!(lifetime.ident.span, "{}", bug_msg);
            }
        }
    }

    /// This collect lifetimes that are elided, for nodes like `Foo<T>` where there are no explicit
    /// lifetime nodes. Is equivalent to having "pseudo" nodes introduced for each of the node ids
    /// in the list start..end.
    fn record_elided_anchor(&mut self, node_id: NodeId, span: Span) {
        if let Some(LifetimeRes::ElidedAnchor { start, end }) =
            self.resolver.get_lifetime_res(node_id)
        {
            for i in start..end {
                let lifetime = Lifetime { id: i, ident: Ident::new(kw::UnderscoreLifetime, span) };
                self.record_lifetime_use(lifetime);
            }
        }
    }
}

impl<'ast> Visitor<'ast> for LifetimeCollectVisitor<'ast> {
    fn visit_lifetime(&mut self, lifetime: &'ast Lifetime, _: LifetimeCtxt) {
        self.record_lifetime_use(*lifetime);
    }

    fn visit_path_segment(&mut self, path_segment: &'ast PathSegment) {
        self.record_elided_anchor(path_segment.id, path_segment.ident.span);
        visit::walk_path_segment(self, path_segment);
    }

    fn visit_poly_trait_ref(&mut self, t: &'ast PolyTraitRef) {
        self.current_binders.push(t.trait_ref.ref_id);

        visit::walk_poly_trait_ref(self, t);

        self.current_binders.pop();
    }

    fn visit_ty(&mut self, t: &'ast Ty) {
        match &t.kind {
            TyKind::Path(None, _) => {
                // We can sometimes encounter bare trait objects
                // which are represented in AST as paths.
                if let Some(partial_res) = self.resolver.get_partial_res(t.id)
                    && let Some(Res::Def(DefKind::Trait | DefKind::TraitAlias, _)) =
                        partial_res.full_res()
                {
                    self.current_binders.push(t.id);
                    visit::walk_ty(self, t);
                    self.current_binders.pop();
                } else {
                    visit::walk_ty(self, t);
                }
            }
            TyKind::BareFn(_) => {
                self.current_binders.push(t.id);
                visit::walk_ty(self, t);
                self.current_binders.pop();
            }
            TyKind::Ref(None, _) | TyKind::PinnedRef(None, _) => {
                self.record_elided_anchor(t.id, t.span);
                visit::walk_ty(self, t);
            }
            TyKind::ImplTrait(opaque_ty_node_id, bounds) => {
                self.visit_opaque(*opaque_ty_node_id, bounds, t.span)
            }
            _ => {
                visit::walk_ty(self, t);
            }
        }
    }
}

pub(crate) fn lifetimes_for_opaque(
    resolver: &mut ResolverAstLowering,
    always_capture_in_scope: bool,
    opaque_ty_node_id: NodeId,
    bounds: &GenericBounds,
    span: Span,
) -> FxIndexSet<Lifetime> {
    let mut visitor = LifetimeCollectVisitor::new(resolver, always_capture_in_scope);
    visitor.visit_opaque(opaque_ty_node_id, bounds, span);
    visitor.collected_lifetimes
}