use rustc_hash::FxHashMap;
use rustc_hash::FxHashSet;
use tsz_binder::{FlowNode, FlowNodeArena, FlowNodeId, flow_flags};
use tsz_parser::parser::node::NodeArena;
use tsz_parser::parser::{NodeIndex, syntax_kind_ext};
use tsz_scanner::SyntaxKind;
const MAX_FLOW_ANALYSIS_ITERATIONS: usize = 100_000;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AssignmentState {
Unassigned,
MaybeAssigned,
DefinitelyAssigned,
}
impl AssignmentState {
const fn merge(self, other: Self) -> Self {
match (self, other) {
(Self::DefinitelyAssigned, Self::DefinitelyAssigned) => Self::DefinitelyAssigned,
(Self::Unassigned, Self::Unassigned) => Self::Unassigned,
_ => Self::MaybeAssigned,
}
}
}
#[derive(Clone, Debug)]
pub struct AssignmentStateMap {
states: FxHashMap<u32, AssignmentState>,
}
impl AssignmentStateMap {
pub fn new() -> Self {
Self {
states: FxHashMap::default(),
}
}
pub fn get(&self, var_id: NodeIndex) -> AssignmentState {
self.states
.get(&var_id.0)
.copied()
.unwrap_or(AssignmentState::Unassigned)
}
pub fn set(&mut self, var_id: NodeIndex, state: AssignmentState) {
self.states.insert(var_id.0, state);
}
pub fn mark_assigned(&mut self, var_id: NodeIndex) {
self.set(var_id, AssignmentState::DefinitelyAssigned);
}
pub fn merge(&mut self, other: &Self) {
let mut all_vars: FxHashSet<u32> = self.states.keys().copied().collect();
all_vars.extend(other.states.keys().copied());
for var_id in all_vars {
let self_state = self.get(NodeIndex(var_id));
let other_state = other.get(NodeIndex(var_id));
let merged = self_state.merge(other_state);
self.set(NodeIndex(var_id), merged);
}
}
pub fn is_definite(&self) -> bool {
!self
.states
.values()
.any(|&s| s == AssignmentState::MaybeAssigned)
}
}
impl Default for AssignmentStateMap {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct DefiniteAssignmentResult {
pub states: AssignmentStateMap,
}
impl DefiniteAssignmentResult {
pub fn is_definitely_assigned(&self, var_id: NodeIndex) -> bool {
self.states.get(var_id) == AssignmentState::DefinitelyAssigned
}
pub fn is_maybe_assigned(&self, var_id: NodeIndex) -> bool {
let state = self.states.get(var_id);
state == AssignmentState::DefinitelyAssigned || state == AssignmentState::MaybeAssigned
}
}
pub struct DefiniteAssignmentAnalyzer<'a> {
arena: &'a NodeArena,
flow_arena: &'a FlowNodeArena,
node_states: FxHashMap<FlowNodeId, AssignmentStateMap>,
tracked_vars: FxHashSet<u32>,
}
impl<'a> DefiniteAssignmentAnalyzer<'a> {
pub fn new(arena: &'a NodeArena, flow_arena: &'a FlowNodeArena) -> Self {
Self {
arena,
flow_arena,
node_states: FxHashMap::default(),
tracked_vars: FxHashSet::default(),
}
}
pub fn track_variable(&mut self, var_decl: NodeIndex) {
self.tracked_vars.insert(var_decl.0);
}
pub fn analyze(&mut self, entry: FlowNodeId) -> &FxHashMap<FlowNodeId, AssignmentStateMap> {
let initial_state = AssignmentStateMap::new();
let mut worklist: Vec<FlowNodeId> = vec![entry];
let mut in_worklist: FxHashSet<FlowNodeId> = FxHashSet::default();
in_worklist.insert(entry);
let mut iterations = 0;
while let Some(flow_id) = worklist.pop() {
iterations += 1;
if iterations > MAX_FLOW_ANALYSIS_ITERATIONS {
break;
}
in_worklist.remove(&flow_id);
let Some(flow_node) = self.flow_arena.get(flow_id) else {
continue;
};
let state_before = match flow_node.antecedent.len() {
0 => {
if flow_id == entry {
initial_state.clone()
} else {
AssignmentStateMap::new()
}
}
1 => {
let pred = flow_node.antecedent[0];
if pred.is_none() {
initial_state.clone()
} else if let Some(pred_state) = self.node_states.get(&pred) {
pred_state.clone()
} else if pred == entry {
initial_state.clone()
} else {
AssignmentStateMap::new()
}
}
_ => {
let mut merged_state = AssignmentStateMap::new();
let mut has_predecessor = false;
for &pred in &flow_node.antecedent {
if pred.is_none() {
continue;
}
if let Some(pred_state) = self.node_states.get(&pred) {
if has_predecessor {
merged_state.merge(pred_state);
} else {
merged_state = pred_state.clone();
has_predecessor = true;
}
} else if pred == entry {
if has_predecessor {
merged_state.merge(&initial_state);
} else {
merged_state = initial_state.clone();
has_predecessor = true;
}
}
}
merged_state
}
};
let state_after = self.process_flow_node(flow_node, state_before);
let changed = if let Some(existing) = self.node_states.get(&flow_id) {
if existing.states.is_empty() && !state_after.states.is_empty() {
true
} else {
false
}
} else {
true
};
self.node_states.insert(flow_id, state_after);
if changed {
for &antecedent in &flow_node.antecedent {
if antecedent.is_some() && !in_worklist.contains(&antecedent) {
worklist.push(antecedent);
in_worklist.insert(antecedent);
}
}
}
}
&self.node_states
}
fn process_flow_node(
&self,
flow_node: &FlowNode,
mut state: AssignmentStateMap,
) -> AssignmentStateMap {
if flow_node.has_any_flags(flow_flags::ASSIGNMENT) {
if let Some(target_var) = self.get_assignment_target(flow_node.node)
&& self.tracked_vars.contains(&target_var.0)
{
state.mark_assigned(target_var);
}
} else if flow_node.has_any_flags(flow_flags::BRANCH_LABEL) {
} else if flow_node.has_any_flags(flow_flags::LOOP_LABEL) {
if flow_node.antecedent.len() > 1 {
for &var_id in &self.tracked_vars {
let current_state = state.get(NodeIndex(var_id));
if current_state == AssignmentState::DefinitelyAssigned {
}
}
}
} else if flow_node.has_any_flags(flow_flags::TRUE_CONDITION | flow_flags::FALSE_CONDITION)
{
} else if flow_node.has_any_flags(flow_flags::SWITCH_CLAUSE) {
}
state
}
fn get_assignment_target(&self, node: NodeIndex) -> Option<NodeIndex> {
let node_data = self.arena.get(node)?;
match node_data.kind {
k if k == SyntaxKind::Identifier as u16 => {
Some(node)
}
syntax_kind_ext::BINARY_EXPRESSION => {
if let Some(bin_expr) = self.arena.get_binary_expr(node_data)
&& let Some(left_node) = self.arena.get(bin_expr.left)
&& left_node.kind == SyntaxKind::Identifier as u16
{
return Some(bin_expr.left);
}
None
}
_ => None,
}
}
pub fn get_state_at(&self, flow_id: FlowNodeId) -> Option<&AssignmentStateMap> {
self.node_states.get(&flow_id)
}
pub fn is_definitely_assigned(&self, var_id: NodeIndex, flow_id: FlowNodeId) -> bool {
if let Some(state) = self.get_state_at(flow_id) {
state.get(var_id) == AssignmentState::DefinitelyAssigned
} else {
false
}
}
pub const fn node_states(&self) -> &FxHashMap<FlowNodeId, AssignmentStateMap> {
&self.node_states
}
}
pub fn merge_assignment_states(states: &[AssignmentStateMap]) -> AssignmentStateMap {
if states.is_empty() {
return AssignmentStateMap::new();
}
let mut result = states[0].clone();
for state in &states[1..] {
result.merge(state);
}
result
}
#[cfg(test)]
#[path = "../../tests/flow_analyzer.rs"]
mod tests;