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
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    /// If true, this binding is a shorthand in a struct pattern (e.g., `Point { x }`)
315    pub is_struct_field: bool,
316    /// If true, this binding was introduced by an `as` alias (e.g., `Point { .. } as p`)
317    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/// Records a usage of a symbol, linking the usage location to its definition.
359/// Used by LSP for find-references.
360#[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}