Skip to main content

hir_def/
upvars.rs

1//! A simple query to collect tall locals (upvars) a closure use.
2
3use base_db::SourceDatabase;
4use hir_expand::mod_path::PathKind;
5use rustc_hash::FxHashSet;
6
7use crate::{
8    DefWithBodyId, ExpressionStoreOwnerId, GenericDefId, ThinFxHashMap, VariantId,
9    expr_store::{ExpressionStore, StoreVisitor, StoreVisitorExt, path::Path},
10    hir::{BindingId, Expr, ExprId, PatId},
11    resolver::{HasResolver, Resolver, ValueNs},
12    type_ref::TypeRefId,
13};
14
15#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16// Kept sorted.
17pub struct Upvars(Box<[BindingId]>);
18
19impl Upvars {
20    fn new(upvars: &FxHashSet<BindingId>) -> Upvars {
21        let mut upvars = upvars.iter().copied().collect::<Box<[_]>>();
22        upvars.sort_unstable();
23        Upvars(upvars)
24    }
25
26    #[inline]
27    pub fn contains(&self, local: BindingId) -> bool {
28        self.0.binary_search(&local).is_ok()
29    }
30
31    #[inline]
32    pub fn iter(&self) -> impl ExactSizeIterator<Item = BindingId> {
33        self.0.iter().copied()
34    }
35
36    #[inline]
37    pub fn is_empty(&self) -> bool {
38        self.0.is_empty()
39    }
40
41    #[inline]
42    pub fn as_ref(&self) -> UpvarsRef<'_> {
43        UpvarsRef(&self.0)
44    }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
48// Kept sorted.
49pub struct UpvarsRef<'db>(&'db [BindingId]);
50
51impl UpvarsRef<'_> {
52    #[inline]
53    pub fn contains(self, local: BindingId) -> bool {
54        self.0.binary_search(&local).is_ok()
55    }
56
57    #[inline]
58    pub fn iter(self) -> impl ExactSizeIterator<Item = BindingId> {
59        self.0.iter().copied()
60    }
61
62    #[inline]
63    pub fn is_empty(self) -> bool {
64        self.0.is_empty()
65    }
66
67    #[inline]
68    pub const fn empty() -> Self {
69        UpvarsRef(&[])
70    }
71}
72
73/// Returns a map from `Expr::Closure` to its upvars.
74pub fn upvars_mentioned(
75    db: &dyn SourceDatabase,
76    owner: ExpressionStoreOwnerId,
77) -> &ThinFxHashMap<ExprId, Upvars> {
78    return match owner {
79        ExpressionStoreOwnerId::Signature(owner) => signature_upvars_mentioned(db, owner),
80        ExpressionStoreOwnerId::Body(owner) => body_upvars_mentioned(db, owner),
81        ExpressionStoreOwnerId::VariantFields(owner) => variant_fields_upvars_mentioned(db, owner),
82    };
83
84    #[salsa::tracked]
85    pub fn signature_upvars_mentioned(
86        db: &dyn SourceDatabase,
87        owner: GenericDefId,
88    ) -> ThinFxHashMap<ExprId, Upvars> {
89        upvars_mentioned_impl(db, owner.into())
90    }
91
92    #[salsa::tracked]
93    pub fn body_upvars_mentioned(
94        db: &dyn SourceDatabase,
95        owner: DefWithBodyId,
96    ) -> ThinFxHashMap<ExprId, Upvars> {
97        upvars_mentioned_impl(db, owner.into())
98    }
99
100    #[salsa::tracked]
101    pub fn variant_fields_upvars_mentioned(
102        db: &dyn SourceDatabase,
103        owner: VariantId,
104    ) -> ThinFxHashMap<ExprId, Upvars> {
105        upvars_mentioned_impl(db, owner.into())
106    }
107}
108
109pub fn upvars_mentioned_impl(
110    db: &dyn SourceDatabase,
111    owner: ExpressionStoreOwnerId,
112) -> ThinFxHashMap<ExprId, Upvars> {
113    let store = ExpressionStore::of(db, owner);
114    if store.expr_roots().next().is_none() {
115        return ThinFxHashMap::new();
116    }
117    let resolver = owner.resolver(db);
118    let mut visitor = UpvarsMentionedVisitor {
119        db,
120        resolver,
121        owner,
122        store,
123        closures_map: ThinFxHashMap::default(),
124        current_closure: None,
125    };
126    visitor.on_exprs(store.expr_roots());
127    let mut result = visitor.closures_map;
128    result.shrink_to_fit();
129    result
130}
131
132struct UpvarsMentionedVisitor<'db> {
133    db: &'db dyn SourceDatabase,
134    resolver: Resolver<'db>,
135    owner: ExpressionStoreOwnerId,
136    store: &'db ExpressionStore,
137    closures_map: ThinFxHashMap<ExprId, Upvars>,
138    current_closure: Option<(ExprId, FxHashSet<BindingId>)>,
139}
140
141impl UpvarsMentionedVisitor<'_> {
142    fn resolve_maybe_upvar(&mut self, expr: ExprId, path: &Path) {
143        let Some((current_closure, upvars)) = &mut self.current_closure else { return };
144
145        if let Path::BarePath(mod_path) = path
146        && matches!(mod_path.kind, PathKind::Plain | PathKind::SELF)
147        // `self` is length zero.
148        && mod_path.segments().len() <= 1
149        {
150            // Could be a variable.
151            let guard = self.resolver.update_to_inner_scope(self.db, self.owner, expr);
152            let resolution = self.resolver.resolve_path_in_value_ns_fully(
153                self.db,
154                path,
155                self.store.expr_or_pat_path_hygiene(expr.into()),
156            );
157            if let Some(ValueNs::LocalBinding(local)) = resolution
158                && self.store.binding_owner(local) != Some(*current_closure)
159            {
160                upvars.insert(local);
161            }
162            self.resolver.reset_to_guard(guard);
163        }
164    }
165}
166
167impl StoreVisitor for UpvarsMentionedVisitor<'_> {
168    fn on_expr(&mut self, expr: ExprId) {
169        match &self.store[expr] {
170            Expr::Path(path) => {
171                self.resolve_maybe_upvar(expr, path);
172                self.on_path(path);
173            }
174            Expr::Closure {
175                body: body_expr,
176                args,
177                arg_types,
178                ret_type,
179                capture_by: _,
180                closure_kind: _,
181            } => {
182                self.on_pats(args);
183                arg_types.iter().for_each(|arg_ty| self.on_type_opt(*arg_ty));
184                self.on_type_opt(*ret_type);
185
186                let mut old_current_closure =
187                    self.current_closure.replace((expr, FxHashSet::default()));
188                self.on_expr(*body_expr);
189                let closure_upvars = self.current_closure.take().unwrap().1;
190                if !closure_upvars.is_empty() {
191                    self.closures_map.insert(expr, Upvars::new(&closure_upvars));
192                    // All nested closure's upvars are also upvars of the parent closure.
193                    if let Some((current_closure, upvars)) = &mut old_current_closure {
194                        upvars.extend(closure_upvars.iter().copied().filter(|local| {
195                            self.store.binding_owner(*local) != Some(*current_closure)
196                        }));
197                    }
198                }
199                self.current_closure = old_current_closure;
200            }
201            _ => self.store.visit_expr_children(expr, self),
202        }
203    }
204
205    fn on_anon_const_expr(&mut self, expr: ExprId) {
206        // Anon consts don't contribute upvars.
207        let old_current_closure = self.current_closure.take();
208        self.on_expr(expr);
209        self.current_closure = old_current_closure;
210    }
211
212    fn on_pat(&mut self, pat: PatId) {
213        self.store.visit_pat_children(pat, self);
214    }
215
216    fn on_type(&mut self, ty: TypeRefId) {
217        // Anon consts don't contribute upvars.
218        let old_current_closure = self.current_closure.take();
219        self.store.visit_type_ref_children(ty, &mut *self);
220        self.current_closure = old_current_closure;
221    }
222}
223
224#[cfg(test)]
225mod tests {
226    use expect_test::{Expect, expect};
227    use itertools::Itertools;
228    use span::Edition;
229    use test_fixture::WithFixture;
230
231    use crate::{
232        AssocItemId, DefWithBodyId, ModuleDefId, expr_store::Body, nameres::crate_def_map,
233        test_db::TestDB, upvars::upvars_mentioned,
234    };
235
236    #[track_caller]
237    fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str, expectation: Expect) {
238        let db = TestDB::with_files(ra_fixture);
239        let def_map = crate_def_map(&db, db.test_crate());
240        let func = def_map
241            .modules()
242            .flat_map(|(_, module)| module.scope.declarations())
243            .filter_map(|decl| match decl {
244                ModuleDefId::FunctionId(func) => Some(func),
245                _ => None,
246            })
247            .chain(def_map.modules().flat_map(|(_, module)| {
248                module.scope.impls().flat_map(|impl_| &*impl_.impl_items(&db).items).filter_map(
249                    |&(_, item)| match item {
250                        AssocItemId::FunctionId(it) => Some(it),
251                        _ => None,
252                    },
253                )
254            }))
255            .exactly_one()
256            .unwrap_or_else(|_| panic!("expected one function"));
257        let (body, source_map) = Body::with_source_map(&db, func.into());
258        let upvars = upvars_mentioned(&db, DefWithBodyId::from(func).into());
259        let mut closures = Vec::new();
260        for (&closure, upvars) in upvars {
261            let closure_range = source_map.expr_syntax(closure).unwrap().value.text_range();
262            let upvars = upvars
263                .iter()
264                .map(|local| body[local].name.display(&db, Edition::CURRENT))
265                .join(", ");
266            closures.push((closure_range, upvars));
267        }
268        closures.sort_unstable_by_key(|(range, _)| (range.start(), range.end()));
269        let closures =
270            closures.into_iter().map(|(range, upvars)| format!("{range:?}: {upvars}")).join("\n");
271        expectation.assert_eq(&closures);
272    }
273
274    #[test]
275    fn simple() {
276        check(
277            r#"
278struct foo;
279fn foo(param: i32) {
280    let local = "boo";
281    || { param; foo };
282    || local;
283    || { param; local; param; local; };
284    || 0xDEAFBEAF;
285}
286        "#,
287            expect![[r#"
288                60..77: param
289                83..91: local
290                97..131: param, local"#]],
291        );
292    }
293
294    #[test]
295    fn nested() {
296        check(
297            r#"
298fn foo() {
299    let (a, b);
300    || {
301        || a;
302        || b;
303    };
304}
305        "#,
306            expect![[r#"
307                31..69: a, b
308                44..48: a
309                58..62: b"#]],
310        );
311    }
312
313    #[test]
314    fn closure_var() {
315        check(
316            r#"
317fn foo() {
318    let upvar = 1;
319    |closure_param: i32| {
320        let closure_local = closure_param;
321        closure_local + upvar
322    };
323}
324        "#,
325            expect!["34..135: upvar"],
326        );
327    }
328
329    #[test]
330    fn closure_var_nested() {
331        check(
332            r#"
333fn foo() {
334    let a = 1;
335    |b: i32| {
336        || {
337            let c = 123;
338            a + b + c
339        }
340    };
341}
342        "#,
343            expect![[r#"
344                30..116: a
345                49..110: a, b"#]],
346        );
347    }
348
349    #[test]
350    fn self_upvar() {
351        check(
352            r#"
353struct Foo(i32);
354impl Foo {
355    fn foo(&self) {
356        || self.0;
357    }
358}
359        "#,
360            expect!["56..65: self"],
361        );
362    }
363
364    #[test]
365    fn const_block() {
366        check(
367            r#"
368fn main() {
369    || {
370        const {
371            let v = ();
372            v
373        }
374    };
375}
376        "#,
377            expect![""],
378        );
379    }
380
381    #[test]
382    fn async_fn_local() {
383        check(
384            r#"
385async fn foo() {
386    let v = ();
387    v
388}
389        "#,
390            expect![[r#""#]],
391        );
392    }
393}