Skip to main content

lisette_semantics/
facts.rs

1use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
2
3use diagnostics::{PatternIssue, UnusedExpressionKind};
4use syntax::ast::{BindingId, BindingKind, DeadCodeCause, Span};
5
6#[derive(Debug, Default)]
7pub struct Facts {
8    next_id: BindingId,
9    pub bindings: HashMap<BindingId, BindingFact>,
10    pub dead_code: Vec<DeadCodeFact>,
11    pub pattern_issues: Vec<PatternIssue>,
12    pub unused_expressions: Vec<UnusedExpressionFact>,
13    pub discarded_tail_expressions: Vec<DiscardedTailFact>,
14    pub overused_references: Vec<OverusedReferenceFact>,
15    pub unused_type_params: Vec<UnusedTypeParamFact>,
16    pub always_failing_try_blocks: Vec<Span>,
17    pub expression_only_fstrings: Vec<Span>,
18    /// Spans of or-patterns with binding errors, used to suppress contradictory lints.
19    pub or_pattern_error_spans: HashSet<Span>,
20    /// Tracks usage locations for find-references in LSP
21    pub usages: Vec<Usage>,
22    usage_set: HashSet<(Span, Span)>,
23    /// Tracks methods used via interface satisfaction during type checking.
24    /// Maps (module_id, method_name) -> usage locations
25    pub interface_satisfied_methods: HashMap<(String, String), Vec<Span>>,
26}
27
28impl Facts {
29    pub fn new() -> Self {
30        Self::default()
31    }
32
33    fn new_binding_id(&mut self) -> BindingId {
34        let id = self.next_id;
35        self.next_id += 1;
36        id
37    }
38
39    pub fn add_binding(
40        &mut self,
41        name: String,
42        span: Span,
43        kind: BindingKind,
44        is_typedef: bool,
45        is_struct_field: bool,
46    ) -> BindingId {
47        let id = self.new_binding_id();
48        self.bindings.insert(
49            id,
50            BindingFact {
51                name,
52                span,
53                kind,
54                used: false,
55                mutated: false,
56                is_typedef,
57                is_struct_field,
58            },
59        );
60        id
61    }
62
63    pub fn mark_used(&mut self, id: BindingId) {
64        if let Some(fact) = self.bindings.get_mut(&id) {
65            fact.used = true;
66        }
67    }
68
69    pub fn mark_mutated(&mut self, id: BindingId) {
70        if let Some(fact) = self.bindings.get_mut(&id) {
71            fact.mutated = true;
72        }
73    }
74
75    pub fn binding_checkpoint(&self) -> BindingId {
76        self.next_id
77    }
78
79    pub fn remove_bindings_from(&mut self, checkpoint: BindingId) {
80        self.bindings.retain(|id, _| *id < checkpoint);
81        self.next_id = checkpoint;
82    }
83
84    pub fn add_dead_code(&mut self, span: Span, cause: DeadCodeCause) {
85        self.dead_code.push(DeadCodeFact { span, cause });
86    }
87
88    pub fn add_unused_expression(&mut self, span: Span, kind: UnusedExpressionKind) {
89        self.unused_expressions
90            .push(UnusedExpressionFact { span, kind });
91    }
92
93    pub fn add_discarded_tail(&mut self, span: Span, kind: DiscardedTailKind, return_type: String) {
94        self.discarded_tail_expressions.push(DiscardedTailFact {
95            span,
96            kind,
97            return_type,
98        });
99    }
100
101    pub fn add_overused_reference(&mut self, span: Span, name: Option<String>) {
102        self.overused_references
103            .push(OverusedReferenceFact { span, name });
104    }
105
106    pub fn add_unused_type_param(&mut self, name: String, span: Span, is_typedef: bool) {
107        self.unused_type_params.push(UnusedTypeParamFact {
108            name,
109            span,
110            is_typedef,
111        });
112    }
113
114    pub fn add_always_failing_try_block(&mut self, span: Span) {
115        self.always_failing_try_blocks.push(span);
116    }
117
118    pub fn add_expression_only_fstring(&mut self, span: Span) {
119        self.expression_only_fstrings.push(span);
120    }
121
122    pub fn add_usage(&mut self, usage_span: Span, definition_span: Span) {
123        if self.usage_set.insert((usage_span, definition_span)) {
124            self.usages.push(Usage {
125                usage_span,
126                definition_span,
127            });
128        }
129    }
130
131    pub fn mark_method_used_for_interface(
132        &mut self,
133        module_id: String,
134        method_name: String,
135        usage_span: Span,
136    ) {
137        self.interface_satisfied_methods
138            .entry((module_id, method_name))
139            .or_default()
140            .push(usage_span);
141    }
142}
143
144#[derive(Debug, Clone)]
145pub struct BindingFact {
146    pub name: String,
147    pub span: Span,
148    pub kind: BindingKind,
149    pub used: bool,
150    pub mutated: bool,
151    pub is_typedef: bool,
152    /// If true, this binding is a shorthand in a struct pattern (e.g., `Point { x }`)
153    pub is_struct_field: bool,
154}
155
156#[derive(Debug, Clone)]
157pub struct DeadCodeFact {
158    pub span: Span,
159    pub cause: DeadCodeCause,
160}
161
162#[derive(Debug, Clone)]
163pub struct UnusedExpressionFact {
164    pub span: Span,
165    pub kind: UnusedExpressionKind,
166}
167
168#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169pub enum DiscardedTailKind {
170    Result,
171    Option,
172    Partial,
173}
174
175#[derive(Debug, Clone)]
176pub struct DiscardedTailFact {
177    pub span: Span,
178    pub kind: DiscardedTailKind,
179    pub return_type: String,
180}
181
182#[derive(Debug, Clone)]
183pub struct OverusedReferenceFact {
184    pub span: Span,
185    pub name: Option<String>,
186}
187
188#[derive(Debug, Clone)]
189pub struct UnusedTypeParamFact {
190    pub name: String,
191    pub span: Span,
192    pub is_typedef: bool,
193}
194
195/// Records a usage of a symbol, linking the usage location to its definition.
196/// Used by LSP for find-references.
197#[derive(Debug, Clone)]
198pub struct Usage {
199    pub usage_span: Span,
200    pub definition_span: Span,
201}