1use base_db::SourceDatabase;
4use hir_expand::mod_path::PathKind;
5use rustc_hash::{FxHashMap, FxHashSet};
6
7use crate::{
8 DefWithBodyId, ExpressionStoreOwnerId, GenericDefId, 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)]
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 SourceDatabase,
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 SourceDatabase,
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 SourceDatabase,
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 SourceDatabase,
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 SourceDatabase,
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 SourceDatabase,
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 itertools::Itertools;
230 use span::Edition;
231 use test_fixture::WithFixture;
232
233 use crate::{
234 AssocItemId, DefWithBodyId, ModuleDefId, expr_store::Body, nameres::crate_def_map,
235 test_db::TestDB, upvars::upvars_mentioned,
236 };
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 let def_map = crate_def_map(&db, db.test_crate());
242 let func = def_map
243 .modules()
244 .flat_map(|(_, module)| module.scope.declarations())
245 .filter_map(|decl| match decl {
246 ModuleDefId::FunctionId(func) => Some(func),
247 _ => None,
248 })
249 .chain(def_map.modules().flat_map(|(_, module)| {
250 module.scope.impls().flat_map(|impl_| &*impl_.impl_items(&db).items).filter_map(
251 |&(_, item)| match item {
252 AssocItemId::FunctionId(it) => Some(it),
253 _ => None,
254 },
255 )
256 }))
257 .exactly_one()
258 .unwrap_or_else(|_| panic!("expected one function"));
259 let (body, source_map) = Body::with_source_map(&db, func.into());
260 let Some(upvars) = upvars_mentioned(&db, DefWithBodyId::from(func).into()) else {
261 expectation.assert_eq("");
262 return;
263 };
264 let mut closures = Vec::new();
265 for (&closure, upvars) in upvars {
266 let closure_range = source_map.expr_syntax(closure).unwrap().value.text_range();
267 let upvars = upvars
268 .iter()
269 .map(|local| body[local].name.display(&db, Edition::CURRENT))
270 .join(", ");
271 closures.push((closure_range, upvars));
272 }
273 closures.sort_unstable_by_key(|(range, _)| (range.start(), range.end()));
274 let closures =
275 closures.into_iter().map(|(range, upvars)| format!("{range:?}: {upvars}")).join("\n");
276 expectation.assert_eq(&closures);
277 }
278
279 #[test]
280 fn simple() {
281 check(
282 r#"
283struct foo;
284fn foo(param: i32) {
285 let local = "boo";
286 || { param; foo };
287 || local;
288 || { param; local; param; local; };
289 || 0xDEAFBEAF;
290}
291 "#,
292 expect![[r#"
293 60..77: param
294 83..91: local
295 97..131: param, local"#]],
296 );
297 }
298
299 #[test]
300 fn nested() {
301 check(
302 r#"
303fn foo() {
304 let (a, b);
305 || {
306 || a;
307 || b;
308 };
309}
310 "#,
311 expect![[r#"
312 31..69: a, b
313 44..48: a
314 58..62: b"#]],
315 );
316 }
317
318 #[test]
319 fn closure_var() {
320 check(
321 r#"
322fn foo() {
323 let upvar = 1;
324 |closure_param: i32| {
325 let closure_local = closure_param;
326 closure_local + upvar
327 };
328}
329 "#,
330 expect!["34..135: upvar"],
331 );
332 }
333
334 #[test]
335 fn closure_var_nested() {
336 check(
337 r#"
338fn foo() {
339 let a = 1;
340 |b: i32| {
341 || {
342 let c = 123;
343 a + b + c
344 }
345 };
346}
347 "#,
348 expect![[r#"
349 30..116: a
350 49..110: a, b"#]],
351 );
352 }
353
354 #[test]
355 fn self_upvar() {
356 check(
357 r#"
358struct Foo(i32);
359impl Foo {
360 fn foo(&self) {
361 || self.0;
362 }
363}
364 "#,
365 expect!["56..65: self"],
366 );
367 }
368
369 #[test]
370 fn const_block() {
371 check(
372 r#"
373fn main() {
374 || {
375 const {
376 let v = ();
377 v
378 }
379 };
380}
381 "#,
382 expect![""],
383 );
384 }
385
386 #[test]
387 fn async_fn_local() {
388 check(
389 r#"
390async fn foo() {
391 let v = ();
392 v
393}
394 "#,
395 expect![[r#""#]],
396 );
397 }
398}