Skip to main content

lisette_semantics/
facts.rs

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    // LSP-consumed; reshaping these affects crates/lsp/.
33    pub bindings: HashMap<BindingId, BindingFact>,
34    pub usages: Vec<Usage>,
35    usage_set: HashSet<(Span, Span)>,
36
37    // Lint-support facts: read by reference by passes::lints (mostly
38    // from_facts; interface_satisfied_methods by ref_graph).
39    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    // Drained by passes::deferred via mem::take.
51    pub generic_call_checks: Vec<GenericCallCheck>,
52    pub empty_collection_checks: Vec<EmptyCollectionCheck>,
53    pub statement_tail_checks: Vec<StatementTailCheck>,
54
55    /// Suppresses contradictory lints from or-patterns whose binding sets disagree.
56    pub or_pattern_error_spans: HashSet<Span>,
57
58    /// Spans of binary expressions the checker rejected, so lints can skip them.
59    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    /// If true, this binding is a shorthand in a struct pattern (e.g., `Point { x }`)
336    pub is_struct_field: bool,
337    /// If true, this binding was introduced by an `as` alias (e.g., `Point { .. } as p`)
338    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/// Records a usage of a symbol, linking the usage location to its definition.
380/// Used by LSP for find-references.
381#[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}