Skip to main content

hir_ty/
upvars.rs

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