Skip to main content

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;