1use 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)]
16pub 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)]
48pub 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
73pub 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 && mod_path.segments().len() <= 1
151 {
152 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 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 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 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}