1use 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)]
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) -> &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 && mod_path.segments().len() <= 1
149 {
150 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 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 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 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}