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