1use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
2use std::sync::Arc;
3use std::sync::atomic::{AtomicU32, Ordering};
4
5use diagnostics::{PatternIssue, UnusedExpressionKind};
6use syntax::ast::{BindingId, BindingKind, DeadCodeCause, Span};
7use syntax::types::Type;
8
9#[derive(Debug, Default)]
10pub struct BindingIdAllocator {
11 next: AtomicU32,
12}
13
14impl BindingIdAllocator {
15 pub fn new() -> Self {
16 Self::default()
17 }
18
19 pub fn reserve(&self) -> BindingId {
20 self.next.fetch_add(1, Ordering::Relaxed)
21 }
22
23 pub fn snapshot(&self) -> BindingId {
24 self.next.load(Ordering::Relaxed)
25 }
26}
27
28#[derive(Debug)]
29pub struct Facts {
30 allocator: Arc<BindingIdAllocator>,
31
32 pub bindings: HashMap<BindingId, BindingFact>,
34 pub usages: Vec<Usage>,
35 usage_set: HashSet<(Span, Span)>,
36
37 pub dead_code: Vec<DeadCodeFact>,
40 pub pattern_issues: Vec<PatternIssue>,
41 pub unused_expressions: Vec<UnusedExpressionFact>,
42 pub discarded_tail_expressions: Vec<DiscardedTailFact>,
43 pub overused_references: Vec<OverusedReferenceFact>,
44 pub unused_type_params: Vec<UnusedTypeParamFact>,
45 pub type_params_only_in_bound: Vec<TypeParamOnlyInBoundFact>,
46 pub always_failing_try_blocks: Vec<Span>,
47 pub expression_only_fstrings: Vec<Span>,
48 pub interface_satisfied_methods: HashMap<(String, String), Vec<Span>>,
49
50 pub generic_call_checks: Vec<GenericCallCheck>,
52 pub empty_collection_checks: Vec<EmptyCollectionCheck>,
53 pub statement_tail_checks: Vec<StatementTailCheck>,
54
55 pub or_pattern_error_spans: HashSet<Span>,
57}
58
59#[derive(Debug, Clone)]
60pub struct GenericCallCheck {
61 pub return_ty: Type,
62 pub span: Span,
63}
64
65#[derive(Debug, Clone)]
66pub struct EmptyCollectionCheck {
67 pub name: String,
68 pub ty: Type,
69 pub span: Span,
70}
71
72#[derive(Debug, Clone)]
73pub struct StatementTailCheck {
74 pub expected_ty: Type,
75 pub span: Span,
76}
77
78impl Facts {
79 pub fn new(allocator: Arc<BindingIdAllocator>) -> Self {
80 Self {
81 allocator,
82 bindings: HashMap::default(),
83 dead_code: Vec::new(),
84 pattern_issues: Vec::new(),
85 unused_expressions: Vec::new(),
86 discarded_tail_expressions: Vec::new(),
87 overused_references: Vec::new(),
88 unused_type_params: Vec::new(),
89 type_params_only_in_bound: Vec::new(),
90 always_failing_try_blocks: Vec::new(),
91 expression_only_fstrings: Vec::new(),
92 generic_call_checks: Vec::new(),
93 empty_collection_checks: Vec::new(),
94 statement_tail_checks: Vec::new(),
95 or_pattern_error_spans: HashSet::default(),
96 usages: Vec::new(),
97 usage_set: HashSet::default(),
98 interface_satisfied_methods: HashMap::default(),
99 }
100 }
101
102 pub fn add_binding(
103 &mut self,
104 name: String,
105 span: Span,
106 kind: BindingKind,
107 is_typedef: bool,
108 is_struct_field: bool,
109 is_as_alias: bool,
110 ) -> BindingId {
111 let id = self.allocator.reserve();
112 self.bindings.insert(
113 id,
114 BindingFact {
115 name,
116 span,
117 kind,
118 used: false,
119 mutated: false,
120 is_typedef,
121 is_struct_field,
122 is_as_alias,
123 },
124 );
125 id
126 }
127
128 pub fn mark_used(&mut self, id: BindingId) {
129 if let Some(fact) = self.bindings.get_mut(&id) {
130 fact.used = true;
131 }
132 }
133
134 pub fn mark_mutated(&mut self, id: BindingId) {
135 if let Some(fact) = self.bindings.get_mut(&id) {
136 fact.mutated = true;
137 }
138 }
139
140 pub fn binding_checkpoint(&self) -> BindingId {
141 self.allocator.snapshot()
142 }
143
144 pub fn remove_bindings_from(&mut self, checkpoint: BindingId) {
145 self.bindings.retain(|id, _| *id < checkpoint);
146 }
147
148 pub fn add_dead_code(&mut self, span: Span, cause: DeadCodeCause) {
149 self.dead_code.push(DeadCodeFact { span, cause });
150 }
151
152 pub fn add_overused_reference(&mut self, span: Span, name: Option<String>) {
153 self.overused_references
154 .push(OverusedReferenceFact { span, name });
155 }
156
157 pub fn add_always_failing_try_block(&mut self, span: Span) {
158 self.always_failing_try_blocks.push(span);
159 }
160
161 pub fn add_expression_only_fstring(&mut self, span: Span) {
162 self.expression_only_fstrings.push(span);
163 }
164
165 pub fn add_usage(&mut self, usage_span: Span, definition_span: Span) {
166 if self.usage_set.insert((usage_span, definition_span)) {
167 self.usages.push(Usage {
168 usage_span,
169 definition_span,
170 });
171 }
172 }
173
174 pub fn mark_method_used_for_interface(
175 &mut self,
176 module_id: String,
177 method_name: String,
178 usage_span: Span,
179 ) {
180 self.interface_satisfied_methods
181 .entry((module_id, method_name))
182 .or_default()
183 .push(usage_span);
184 }
185
186 pub fn absorb_local_facts(&mut self, local: LocalFacts) {
187 let LocalFacts {
188 unused_expressions,
189 discarded_tail_expressions,
190 unused_type_params,
191 type_params_only_in_bound,
192 } = local;
193 self.unused_expressions.extend(unused_expressions);
194 self.discarded_tail_expressions
195 .extend(discarded_tail_expressions);
196 self.unused_type_params.extend(unused_type_params);
197 self.type_params_only_in_bound
198 .extend(type_params_only_in_bound);
199 }
200
201 pub fn merge(&mut self, other: Facts) {
202 debug_assert!(
203 Arc::ptr_eq(&self.allocator, &other.allocator),
204 "Facts::merge requires a shared BindingIdAllocator",
205 );
206
207 let Facts {
208 allocator: _,
209 bindings,
210 dead_code,
211 pattern_issues,
212 unused_expressions,
213 discarded_tail_expressions,
214 overused_references,
215 unused_type_params,
216 type_params_only_in_bound,
217 always_failing_try_blocks,
218 expression_only_fstrings,
219 generic_call_checks,
220 empty_collection_checks,
221 statement_tail_checks,
222 or_pattern_error_spans,
223 usages,
224 usage_set: _,
225 interface_satisfied_methods,
226 } = other;
227
228 self.bindings.extend(bindings);
229 self.dead_code.extend(dead_code);
230 self.pattern_issues.extend(pattern_issues);
231 self.unused_expressions.extend(unused_expressions);
232 self.discarded_tail_expressions
233 .extend(discarded_tail_expressions);
234 self.overused_references.extend(overused_references);
235 self.unused_type_params.extend(unused_type_params);
236 self.type_params_only_in_bound
237 .extend(type_params_only_in_bound);
238 self.always_failing_try_blocks
239 .extend(always_failing_try_blocks);
240 self.expression_only_fstrings
241 .extend(expression_only_fstrings);
242 self.generic_call_checks.extend(generic_call_checks);
243 self.empty_collection_checks.extend(empty_collection_checks);
244 self.statement_tail_checks.extend(statement_tail_checks);
245 self.or_pattern_error_spans.extend(or_pattern_error_spans);
246
247 self.usages.reserve(usages.len());
248 self.usage_set.reserve(usages.len());
249 for Usage {
250 usage_span,
251 definition_span,
252 } in usages
253 {
254 self.add_usage(usage_span, definition_span);
255 }
256
257 for (key, spans) in interface_satisfied_methods {
258 self.interface_satisfied_methods
259 .entry(key)
260 .or_default()
261 .extend(spans);
262 }
263 }
264}
265
266#[derive(Debug, Default)]
267pub struct LocalFacts {
268 pub unused_expressions: Vec<UnusedExpressionFact>,
269 pub discarded_tail_expressions: Vec<DiscardedTailFact>,
270 pub unused_type_params: Vec<UnusedTypeParamFact>,
271 pub type_params_only_in_bound: Vec<TypeParamOnlyInBoundFact>,
272}
273
274impl LocalFacts {
275 pub fn merge(&mut self, other: LocalFacts) {
276 let LocalFacts {
277 unused_expressions,
278 discarded_tail_expressions,
279 unused_type_params,
280 type_params_only_in_bound,
281 } = other;
282 self.unused_expressions.extend(unused_expressions);
283 self.discarded_tail_expressions
284 .extend(discarded_tail_expressions);
285 self.unused_type_params.extend(unused_type_params);
286 self.type_params_only_in_bound
287 .extend(type_params_only_in_bound);
288 }
289
290 pub fn add_unused_expression(&mut self, span: Span, kind: UnusedExpressionKind) {
291 self.unused_expressions
292 .push(UnusedExpressionFact { span, kind });
293 }
294
295 pub fn add_discarded_tail(
296 &mut self,
297 span: Span,
298 return_type: String,
299 expected_span: Span,
300 expected_type: String,
301 ) {
302 self.discarded_tail_expressions.push(DiscardedTailFact {
303 span,
304 return_type,
305 expected_span,
306 expected_type,
307 });
308 }
309
310 pub fn add_unused_type_param(&mut self, name: String, span: Span) {
311 self.unused_type_params
312 .push(UnusedTypeParamFact { name, span });
313 }
314
315 pub fn add_type_param_only_in_bound(&mut self, name: String, span: Span) {
316 self.type_params_only_in_bound
317 .push(TypeParamOnlyInBoundFact { name, span });
318 }
319}
320
321#[derive(Debug, Clone)]
322pub struct BindingFact {
323 pub name: String,
324 pub span: Span,
325 pub kind: BindingKind,
326 pub used: bool,
327 pub mutated: bool,
328 pub is_typedef: bool,
329 pub is_struct_field: bool,
331 pub is_as_alias: bool,
333}
334
335#[derive(Debug, Clone)]
336pub struct DeadCodeFact {
337 pub span: Span,
338 pub cause: DeadCodeCause,
339}
340
341#[derive(Debug, Clone)]
342pub struct UnusedExpressionFact {
343 pub span: Span,
344 pub kind: UnusedExpressionKind,
345}
346
347#[derive(Debug, Clone)]
348pub struct DiscardedTailFact {
349 pub span: Span,
350 pub return_type: String,
351 pub expected_span: Span,
352 pub expected_type: String,
353}
354
355#[derive(Debug, Clone)]
356pub struct OverusedReferenceFact {
357 pub span: Span,
358 pub name: Option<String>,
359}
360
361#[derive(Debug, Clone)]
362pub struct UnusedTypeParamFact {
363 pub name: String,
364 pub span: Span,
365}
366
367#[derive(Debug, Clone)]
368pub struct TypeParamOnlyInBoundFact {
369 pub name: String,
370 pub span: Span,
371}
372
373#[derive(Debug, Clone)]
376pub struct Usage {
377 pub usage_span: Span,
378 pub definition_span: Span,
379}
380
381#[cfg(test)]
382mod tests {
383 use super::*;
384 use syntax::ast::BindingKind;
385
386 fn span(offset: u32) -> Span {
387 Span::new(0, offset, 1)
388 }
389
390 #[test]
391 fn merge_preserves_unique_binding_ids_across_tasks() {
392 let allocator = Arc::new(BindingIdAllocator::new());
393 let mut a = Facts::new(allocator.clone());
394 let mut b = Facts::new(allocator.clone());
395
396 let a_id = a.add_binding(
397 "a".into(),
398 span(0),
399 BindingKind::Let { mutable: false },
400 false,
401 false,
402 false,
403 );
404 let b_id = b.add_binding(
405 "b".into(),
406 span(1),
407 BindingKind::Let { mutable: false },
408 false,
409 false,
410 false,
411 );
412 assert_ne!(a_id, b_id);
413
414 a.merge(b);
415 assert_eq!(a.bindings.len(), 2);
416 assert!(a.bindings.contains_key(&a_id));
417 assert!(a.bindings.contains_key(&b_id));
418 }
419
420 #[test]
421 fn merge_extends_vec_facts() {
422 let allocator = Arc::new(BindingIdAllocator::new());
423 let mut a = Facts::new(allocator.clone());
424 let mut b = Facts::new(allocator);
425
426 a.add_always_failing_try_block(span(0));
427 b.add_always_failing_try_block(span(1));
428 b.add_always_failing_try_block(span(2));
429
430 a.merge(b);
431 assert_eq!(a.always_failing_try_blocks.len(), 3);
432 }
433
434 #[test]
435 fn merge_deduplicates_usages() {
436 let allocator = Arc::new(BindingIdAllocator::new());
437 let mut a = Facts::new(allocator.clone());
438 let mut b = Facts::new(allocator);
439
440 a.add_usage(span(10), span(0));
441 b.add_usage(span(10), span(0));
442 b.add_usage(span(20), span(0));
443
444 a.merge(b);
445 assert_eq!(a.usages.len(), 2);
446 }
447
448 #[test]
449 fn merge_deduplicates_or_pattern_error_spans() {
450 let allocator = Arc::new(BindingIdAllocator::new());
451 let mut a = Facts::new(allocator.clone());
452 let mut b = Facts::new(allocator);
453
454 a.or_pattern_error_spans.insert(span(0));
455 b.or_pattern_error_spans.insert(span(0));
456 b.or_pattern_error_spans.insert(span(1));
457
458 a.merge(b);
459 assert_eq!(a.or_pattern_error_spans.len(), 2);
460 }
461
462 #[test]
463 fn absorb_local_facts_extends_all_four_streams() {
464 let allocator = Arc::new(BindingIdAllocator::new());
465 let mut facts = Facts::new(allocator);
466
467 let mut local = LocalFacts::default();
468 local.add_unused_expression(span(0), UnusedExpressionKind::Value);
469 local.add_discarded_tail(span(1), "Int".into(), span(2), "Unit".into());
470 local.add_unused_type_param("T".into(), span(3));
471 local.add_type_param_only_in_bound("U".into(), span(4));
472
473 facts.absorb_local_facts(local);
474
475 assert_eq!(facts.unused_expressions.len(), 1);
476 assert_eq!(facts.discarded_tail_expressions.len(), 1);
477 assert_eq!(facts.unused_type_params.len(), 1);
478 assert_eq!(facts.type_params_only_in_bound.len(), 1);
479 }
480
481 #[test]
482 fn merge_concatenates_interface_method_spans() {
483 let allocator = Arc::new(BindingIdAllocator::new());
484 let mut a = Facts::new(allocator.clone());
485 let mut b = Facts::new(allocator);
486
487 a.mark_method_used_for_interface("m".into(), "f".into(), span(0));
488 b.mark_method_used_for_interface("m".into(), "f".into(), span(1));
489 b.mark_method_used_for_interface("m".into(), "g".into(), span(2));
490
491 a.merge(b);
492 assert_eq!(a.interface_satisfied_methods.len(), 2);
493 assert_eq!(
494 a.interface_satisfied_methods[&("m".into(), "f".into())].len(),
495 2
496 );
497 assert_eq!(
498 a.interface_satisfied_methods[&("m".into(), "g".into())].len(),
499 1
500 );
501 }
502}