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