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 add_unused_expression(&mut self, span: Span, kind: UnusedExpressionKind) {
276 self.unused_expressions
277 .push(UnusedExpressionFact { span, kind });
278 }
279
280 pub fn add_discarded_tail(
281 &mut self,
282 span: Span,
283 return_type: String,
284 expected_span: Span,
285 expected_type: String,
286 ) {
287 self.discarded_tail_expressions.push(DiscardedTailFact {
288 span,
289 return_type,
290 expected_span,
291 expected_type,
292 });
293 }
294
295 pub fn add_unused_type_param(&mut self, name: String, span: Span) {
296 self.unused_type_params
297 .push(UnusedTypeParamFact { name, span });
298 }
299
300 pub fn add_type_param_only_in_bound(&mut self, name: String, span: Span) {
301 self.type_params_only_in_bound
302 .push(TypeParamOnlyInBoundFact { name, span });
303 }
304}
305
306#[derive(Debug, Clone)]
307pub struct BindingFact {
308 pub name: String,
309 pub span: Span,
310 pub kind: BindingKind,
311 pub used: bool,
312 pub mutated: bool,
313 pub is_typedef: bool,
314 pub is_struct_field: bool,
316 pub is_as_alias: bool,
318}
319
320#[derive(Debug, Clone)]
321pub struct DeadCodeFact {
322 pub span: Span,
323 pub cause: DeadCodeCause,
324}
325
326#[derive(Debug, Clone)]
327pub struct UnusedExpressionFact {
328 pub span: Span,
329 pub kind: UnusedExpressionKind,
330}
331
332#[derive(Debug, Clone)]
333pub struct DiscardedTailFact {
334 pub span: Span,
335 pub return_type: String,
336 pub expected_span: Span,
337 pub expected_type: String,
338}
339
340#[derive(Debug, Clone)]
341pub struct OverusedReferenceFact {
342 pub span: Span,
343 pub name: Option<String>,
344}
345
346#[derive(Debug, Clone)]
347pub struct UnusedTypeParamFact {
348 pub name: String,
349 pub span: Span,
350}
351
352#[derive(Debug, Clone)]
353pub struct TypeParamOnlyInBoundFact {
354 pub name: String,
355 pub span: Span,
356}
357
358#[derive(Debug, Clone)]
361pub struct Usage {
362 pub usage_span: Span,
363 pub definition_span: Span,
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369 use syntax::ast::BindingKind;
370
371 fn span(offset: u32) -> Span {
372 Span::new(0, offset, 1)
373 }
374
375 #[test]
376 fn merge_preserves_unique_binding_ids_across_tasks() {
377 let allocator = Arc::new(BindingIdAllocator::new());
378 let mut a = Facts::new(allocator.clone());
379 let mut b = Facts::new(allocator.clone());
380
381 let a_id = a.add_binding(
382 "a".into(),
383 span(0),
384 BindingKind::Let { mutable: false },
385 false,
386 false,
387 false,
388 );
389 let b_id = b.add_binding(
390 "b".into(),
391 span(1),
392 BindingKind::Let { mutable: false },
393 false,
394 false,
395 false,
396 );
397 assert_ne!(a_id, b_id);
398
399 a.merge(b);
400 assert_eq!(a.bindings.len(), 2);
401 assert!(a.bindings.contains_key(&a_id));
402 assert!(a.bindings.contains_key(&b_id));
403 }
404
405 #[test]
406 fn merge_extends_vec_facts() {
407 let allocator = Arc::new(BindingIdAllocator::new());
408 let mut a = Facts::new(allocator.clone());
409 let mut b = Facts::new(allocator);
410
411 a.add_always_failing_try_block(span(0));
412 b.add_always_failing_try_block(span(1));
413 b.add_always_failing_try_block(span(2));
414
415 a.merge(b);
416 assert_eq!(a.always_failing_try_blocks.len(), 3);
417 }
418
419 #[test]
420 fn merge_deduplicates_usages() {
421 let allocator = Arc::new(BindingIdAllocator::new());
422 let mut a = Facts::new(allocator.clone());
423 let mut b = Facts::new(allocator);
424
425 a.add_usage(span(10), span(0));
426 b.add_usage(span(10), span(0));
427 b.add_usage(span(20), span(0));
428
429 a.merge(b);
430 assert_eq!(a.usages.len(), 2);
431 }
432
433 #[test]
434 fn merge_deduplicates_or_pattern_error_spans() {
435 let allocator = Arc::new(BindingIdAllocator::new());
436 let mut a = Facts::new(allocator.clone());
437 let mut b = Facts::new(allocator);
438
439 a.or_pattern_error_spans.insert(span(0));
440 b.or_pattern_error_spans.insert(span(0));
441 b.or_pattern_error_spans.insert(span(1));
442
443 a.merge(b);
444 assert_eq!(a.or_pattern_error_spans.len(), 2);
445 }
446
447 #[test]
448 fn absorb_local_facts_extends_all_four_streams() {
449 let allocator = Arc::new(BindingIdAllocator::new());
450 let mut facts = Facts::new(allocator);
451
452 let mut local = LocalFacts::default();
453 local.add_unused_expression(span(0), UnusedExpressionKind::Value);
454 local.add_discarded_tail(span(1), "Int".into(), span(2), "Unit".into());
455 local.add_unused_type_param("T".into(), span(3));
456 local.add_type_param_only_in_bound("U".into(), span(4));
457
458 facts.absorb_local_facts(local);
459
460 assert_eq!(facts.unused_expressions.len(), 1);
461 assert_eq!(facts.discarded_tail_expressions.len(), 1);
462 assert_eq!(facts.unused_type_params.len(), 1);
463 assert_eq!(facts.type_params_only_in_bound.len(), 1);
464 }
465
466 #[test]
467 fn merge_concatenates_interface_method_spans() {
468 let allocator = Arc::new(BindingIdAllocator::new());
469 let mut a = Facts::new(allocator.clone());
470 let mut b = Facts::new(allocator);
471
472 a.mark_method_used_for_interface("m".into(), "f".into(), span(0));
473 b.mark_method_used_for_interface("m".into(), "f".into(), span(1));
474 b.mark_method_used_for_interface("m".into(), "g".into(), span(2));
475
476 a.merge(b);
477 assert_eq!(a.interface_satisfied_methods.len(), 2);
478 assert_eq!(
479 a.interface_satisfied_methods[&("m".into(), "f".into())].len(),
480 2
481 );
482 assert_eq!(
483 a.interface_satisfied_methods[&("m".into(), "g".into())].len(),
484 1
485 );
486 }
487}