tsz_solver/flow_analysis.rs
1//! Flow Analysis Integration with Solver
2//!
3//! This module integrates flow analysis with the solver's type system to provide:
4//! - Type narrowing based on control flow
5//! - Definite assignment checking
6//! - TDZ (Temporal Dead Zone) validation
7//!
8//! The key insight is that flow analysis needs to track:
9//! 1. **Type Narrowings**: How types change based on control flow guards
10//! 2. **Definite Assignments**: Which variables are definitely assigned at a point
11//! 3. **TDZ Violations**: Variables used before their declaration
12
13use crate::narrowing::NarrowingContext;
14use crate::{QueryDatabase, TypeId};
15use rustc_hash::{FxHashMap, FxHashSet};
16use tsz_common::interner::Atom;
17
18/// Flow facts that represent the state of variables at a specific program point.
19///
20/// This structure bridges the checker's flow analysis and the solver's type system
21/// by tracking what we know about variables at a given control flow point.
22#[derive(Clone, Debug, Default)]
23pub struct FlowFacts {
24 /// Type narrowings: maps variable name to its narrowed type
25 pub type_narrowings: FxHashMap<String, TypeId>,
26
27 /// Variables that are definitely assigned at this point
28 pub definite_assignments: FxHashSet<String>,
29
30 /// Variables that violate TDZ (used before declaration)
31 pub tdz_violations: FxHashSet<String>,
32}
33
34impl FlowFacts {
35 /// Create empty flow facts
36 pub fn new() -> Self {
37 Self::default()
38 }
39
40 /// Add a type narrowing for a variable
41 pub fn add_narrowing(&mut self, variable: String, narrowed_type: TypeId) {
42 self.type_narrowings.insert(variable, narrowed_type);
43 }
44
45 /// Mark a variable as definitely assigned
46 pub fn mark_definitely_assigned(&mut self, variable: String) {
47 self.definite_assignments.insert(variable);
48 }
49
50 /// Mark a variable as having a TDZ violation
51 pub fn mark_tdz_violation(&mut self, variable: String) {
52 self.tdz_violations.insert(variable);
53 }
54
55 /// Check if a variable is definitely assigned
56 pub fn is_definitely_assigned(&self, variable: &str) -> bool {
57 self.definite_assignments.contains(variable)
58 }
59
60 /// Check if a variable has a TDZ violation
61 pub fn has_tdz_violation(&self, variable: &str) -> bool {
62 self.tdz_violations.contains(variable)
63 }
64
65 /// Get the narrowed type for a variable (if any)
66 pub fn get_narrowed_type(&self, variable: &str) -> Option<TypeId> {
67 self.type_narrowings.get(variable).copied()
68 }
69
70 /// Merge two flow fact sets (for join points in control flow)
71 ///
72 /// At control flow join points (e.g., after if/else), we:
73 /// - Keep only narrowings that are present in both branches (intersection)
74 /// - Keep only definite assignments that are present in both branches
75 /// - Keep only TDZ violations that are present in both branches (intersection)
76 pub fn merge(&self, other: &Self) -> Self {
77 let mut result = Self::new();
78
79 // Intersection for type narrowings (must be narrowed in all paths)
80 for (var, ty) in &self.type_narrowings {
81 if let Some(other_ty) = other.type_narrowings.get(var)
82 && ty == other_ty
83 {
84 result.type_narrowings.insert(var.clone(), *ty);
85 }
86 }
87
88 // Intersection for definite assignments (must be assigned in all paths)
89 for var in &self.definite_assignments {
90 if other.definite_assignments.contains(var) {
91 result.definite_assignments.insert(var.clone());
92 }
93 }
94
95 // Intersection for TDZ violations (must be a violation in all paths)
96 for var in &self.tdz_violations {
97 if other.tdz_violations.contains(var) {
98 result.tdz_violations.insert(var.clone());
99 }
100 }
101
102 result
103 }
104}
105
106/// Flow type evaluator that integrates flow analysis with the solver.
107///
108/// This evaluator uses the solver's type operations to compute narrowed types
109/// based on flow facts gathered during control flow analysis.
110pub struct FlowTypeEvaluator<'a> {
111 narrowing_context: NarrowingContext<'a>,
112}
113
114impl<'a> FlowTypeEvaluator<'a> {
115 /// Create a new flow type evaluator
116 pub fn new(db: &'a dyn QueryDatabase) -> Self {
117 let narrowing_context = NarrowingContext::new(db);
118 Self { narrowing_context }
119 }
120
121 /// Compute the narrowed type for a variable based on flow facts.
122 ///
123 /// This integrates with the solver's narrowing logic to apply type guards
124 /// (typeof checks, discriminant checks, null checks) to produce a refined type.
125 ///
126 /// # Arguments
127 /// - `original_type`: The declared type of the variable
128 /// - `flow_facts`: Flow facts gathered from control flow analysis
129 /// - `variable_name`: The name of the variable being checked
130 ///
131 /// # Returns
132 /// The narrowed type, or the original type if no narrowing applies
133 pub fn compute_narrowed_type(
134 &self,
135 original_type: TypeId,
136 flow_facts: &FlowFacts,
137 variable_name: &str,
138 ) -> TypeId {
139 // First check if we have a narrowed type from flow facts
140 if let Some(narrowed) = flow_facts.get_narrowed_type(variable_name) {
141 return narrowed;
142 }
143
144 // No narrowing information - return original type
145 original_type
146 }
147
148 /// Narrow a type based on a typeof guard.
149 ///
150 /// This is used when flow analysis encounters a typeof check:
151 /// ```typescript
152 /// if (typeof x === "string") {
153 /// // x is narrowed to string
154 /// }
155 /// ```
156 pub fn narrow_by_typeof(&self, source_type: TypeId, typeof_result: &str) -> TypeId {
157 self.narrowing_context
158 .narrow_by_typeof(source_type, typeof_result)
159 }
160
161 /// Narrow a type based on a discriminant check.
162 ///
163 /// This is used for discriminated unions:
164 /// ```typescript
165 /// if (action.type === "add") {
166 /// // action is narrowed to the "add" variant
167 /// }
168 /// ```
169 pub fn narrow_by_discriminant(
170 &self,
171 union_type: TypeId,
172 property_path: &[Atom],
173 literal_value: TypeId,
174 ) -> TypeId {
175 self.narrowing_context
176 .narrow_by_discriminant(union_type, property_path, literal_value)
177 }
178
179 /// Narrow a type by excluding a specific type.
180 ///
181 /// This is used for negative type guards:
182 /// ```typescript
183 /// if (x !== null) {
184 /// // x is narrowed to non-null
185 /// }
186 /// ```
187 pub fn narrow_excluding_type(&self, source_type: TypeId, excluded_type: TypeId) -> TypeId {
188 self.narrowing_context
189 .narrow_excluding_type(source_type, excluded_type)
190 }
191
192 /// Check if a variable is definitely assigned at this point.
193 ///
194 /// This integrates with the flow analysis to determine if a variable
195 /// has been assigned on all control flow paths leading to this point.
196 ///
197 /// # Arguments
198 /// - `variable`: The name of the variable to check
199 /// - `flow_facts`: Flow facts gathered from control flow analysis
200 ///
201 /// # Returns
202 /// true if the variable is definitely assigned, false otherwise
203 pub fn is_definitely_assigned(&self, variable: &str, flow_facts: &FlowFacts) -> bool {
204 flow_facts.is_definitely_assigned(variable)
205 }
206
207 /// Check if a variable has a TDZ (Temporal Dead Zone) violation.
208 ///
209 /// TDZ violations occur when a variable is used before its declaration:
210 /// ```typescript
211 /// console.log(x); // TDZ violation!
212 /// let x;
213 /// ```
214 ///
215 /// # Arguments
216 /// - `variable`: The name of the variable to check
217 /// - `flow_facts`: Flow facts gathered from control flow analysis
218 ///
219 /// # Returns
220 /// true if the variable has a TDZ violation, false otherwise
221 pub fn has_tdz_violation(&self, variable: &str, flow_facts: &FlowFacts) -> bool {
222 flow_facts.has_tdz_violation(variable)
223 }
224
225 /// Create flow facts from a set of definite assignments.
226 ///
227 /// This is a convenience method for creating `FlowFacts` when you only
228 /// have definite assignment information.
229 pub fn facts_from_assignments(&self, assignments: FxHashSet<String>) -> FlowFacts {
230 FlowFacts {
231 definite_assignments: assignments,
232 ..Default::default()
233 }
234 }
235
236 /// Create flow facts from a set of type narrowings.
237 ///
238 /// This is a convenience method for creating `FlowFacts` when you only
239 /// have type narrowing information.
240 pub fn facts_from_narrowings(&self, narrowings: FxHashMap<String, TypeId>) -> FlowFacts {
241 FlowFacts {
242 type_narrowings: narrowings,
243 ..Default::default()
244 }
245 }
246}
247
248#[cfg(test)]
249#[path = "../tests/flow_analysis_tests.rs"]
250mod tests;