use std::collections::HashMap;
use tree_sitter::Node;
use super::hash_key::{is_commutative, normalize_binop, HashKey};
use super::types::{ExpressionRef, GVNEquivalence, GVNReport, Redundancy};
use crate::ast::parser::parse;
use crate::types::Language;
const MAX_DEPTH: usize = 10;
pub struct GVNEngine {
next_vn: usize,
hash_to_vn: HashMap<HashKey, usize>,
var_vn: HashMap<String, usize>,
expressions: Vec<ExpressionRef>,
vn_first: HashMap<usize, ExpressionRef>,
vn_is_commutative: HashMap<usize, bool>,
depth: usize,
source: String,
}
impl GVNEngine {
pub fn new(source: &str) -> Self {
Self {
next_vn: 0,
hash_to_vn: HashMap::new(),
var_vn: HashMap::new(),
expressions: Vec::new(),
vn_first: HashMap::new(),
vn_is_commutative: HashMap::new(),
depth: 0,
source: source.to_string(),
}
}
pub fn fresh_vn(&mut self) -> usize {
let vn = self.next_vn;
self.next_vn += 1;
vn
}
pub fn get_node_text(&self, node: &Node) -> String {
node.utf8_text(self.source.as_bytes())
.unwrap_or("")
.to_string()
}
pub fn hash_node(&mut self, node: &Node) -> (HashKey, bool) {
self.depth += 1;
if self.depth > MAX_DEPTH {
self.depth -= 1;
let id = self.fresh_vn();
return (HashKey::Unique { id }, false);
}
let result = self.hash_node_inner(node);
self.depth -= 1;
result
}
fn hash_node_inner(&mut self, node: &Node) -> (HashKey, bool) {
let kind = node.kind();
match kind {
"integer" | "float" | "string" | "true" | "false" | "none" => {
let text = self.get_node_text(node);
let type_name = match kind {
"integer" => "int",
"float" => "float",
"string" => "str",
"true" | "false" => "bool",
"none" => "NoneType",
_ => "unknown",
};
(
HashKey::Const {
type_name: type_name.to_string(),
repr: text,
},
false,
)
}
"identifier" => {
let name = self.get_node_text(node);
if let Some(&vn) = self.var_vn.get(&name) {
(HashKey::VarVN { vn }, false)
} else {
(HashKey::Name { name }, false)
}
}
"binary_operator" => self.hash_binary_operator(node),
"unary_operator" => self.hash_unary_operator(node),
"boolean_operator" => self.hash_boolean_operator(node),
"comparison_operator" => self.hash_comparison(node),
"call" => {
let id = self.fresh_vn();
(HashKey::Call { unique_id: id }, false)
}
"attribute" => self.hash_attribute(node),
"subscript" => self.hash_subscript(node),
"parenthesized_expression" => {
if let Some(inner) = node
.child_by_field_name("expression")
.or_else(|| node.named_child(0))
{
self.hash_node_inner(&inner)
} else {
let id = self.fresh_vn();
(HashKey::Unique { id }, false)
}
}
_ => {
let id = self.fresh_vn();
(HashKey::Unique { id }, false)
}
}
}
fn hash_binary_operator(&mut self, node: &Node) -> (HashKey, bool) {
let left_node = match node.child_by_field_name("left") {
Some(n) => n,
None => {
let id = self.fresh_vn();
return (HashKey::Unique { id }, false);
}
};
let op_node = match node.child_by_field_name("operator") {
Some(n) => n,
None => {
let mut op = None;
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
let k = child.kind();
if matches!(
k,
"+" | "-"
| "*"
| "/"
| "//"
| "%"
| "**"
| "|"
| "&"
| "^"
| "<<"
| ">>"
| "@"
) {
op = Some(k.to_string());
break;
}
}
}
match op {
Some(o) => {
let op_name = match o.as_str() {
"+" => "Add",
"-" => "Sub",
"*" => "Mult",
"/" => "Div",
"//" => "FloorDiv",
"%" => "Mod",
"**" => "Pow",
"|" => "BitOr",
"&" => "BitAnd",
"^" => "BitXor",
"<<" => "LShift",
">>" => "RShift",
"@" => "MatMult",
_ => &o,
};
return self.build_binop_hash(&left_node, op_name, node);
}
None => {
let id = self.fresh_vn();
return (HashKey::Unique { id }, false);
}
}
}
};
let op_text = self.get_node_text(&op_node);
let op_name = match op_text.as_str() {
"+" => "Add",
"-" => "Sub",
"*" => "Mult",
"/" => "Div",
"//" => "FloorDiv",
"%" => "Mod",
"**" => "Pow",
"|" => "BitOr",
"&" => "BitAnd",
"^" => "BitXor",
"<<" => "LShift",
">>" => "RShift",
"@" => "MatMult",
_ => &op_text,
};
self.build_binop_hash(&left_node, op_name, node)
}
fn build_binop_hash(
&mut self,
left_node: &Node,
op_name: &str,
parent: &Node,
) -> (HashKey, bool) {
let right_node = match parent.child_by_field_name("right") {
Some(n) => n,
None => {
let id = self.fresh_vn();
return (HashKey::Unique { id }, false);
}
};
let (left_key, _) = self.hash_node(left_node);
let (right_key, _) = self.hash_node(&right_node);
let commutative = is_commutative(op_name);
let hash_key = normalize_binop(op_name, left_key, right_key);
(hash_key, commutative)
}
fn hash_unary_operator(&mut self, node: &Node) -> (HashKey, bool) {
let operand_node = match node
.child_by_field_name("argument")
.or_else(|| node.child_by_field_name("operand"))
.or_else(|| {
for i in 0..node.named_child_count() {
if let Some(child) = node.named_child(i) {
if !matches!(child.kind(), "-" | "+" | "~" | "not") {
return Some(child);
}
}
}
None
}) {
Some(n) => n,
None => {
let id = self.fresh_vn();
return (HashKey::Unique { id }, false);
}
};
let op = {
let mut found_op = None;
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
let k = child.kind();
if matches!(k, "-" | "+" | "~" | "not") {
found_op = Some(match k {
"-" => "USub",
"+" => "UAdd",
"~" => "Invert",
"not" => "Not",
_ => k,
});
break;
}
}
}
match found_op {
Some(o) => o.to_string(),
None => {
let id = self.fresh_vn();
return (HashKey::Unique { id }, false);
}
}
};
let (operand_key, _) = self.hash_node(&operand_node);
(
HashKey::UnaryOp {
op,
operand: Box::new(operand_key),
},
false,
)
}
fn hash_boolean_operator(&mut self, node: &Node) -> (HashKey, bool) {
let mut operands = Vec::new();
let mut op_name = String::new();
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
let kind = child.kind();
if kind == "and" || kind == "or" {
op_name = kind.to_string();
} else if child.is_named() {
let (key, _) = self.hash_node(&child);
operands.push(key);
}
}
}
if operands.is_empty() || op_name.is_empty() {
let id = self.fresh_vn();
return (HashKey::Unique { id }, false);
}
(
HashKey::BoolOp {
op: op_name,
operands,
},
false,
)
}
fn hash_comparison(&mut self, node: &Node) -> (HashKey, bool) {
let mut parts = Vec::new();
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
let text = self.get_node_text(&child);
if !text.is_empty() {
parts.push(text);
}
}
}
if parts.is_empty() {
let id = self.fresh_vn();
return (HashKey::Unique { id }, false);
}
(HashKey::Compare { parts }, false)
}
fn hash_attribute(&mut self, node: &Node) -> (HashKey, bool) {
let value_node = match node
.child_by_field_name("object")
.or_else(|| node.child_by_field_name("value"))
{
Some(n) => n,
None => {
let id = self.fresh_vn();
return (HashKey::Unique { id }, false);
}
};
let attr_node = match node.child_by_field_name("attribute") {
Some(n) => n,
None => {
let id = self.fresh_vn();
return (HashKey::Unique { id }, false);
}
};
let (value_key, _) = self.hash_node(&value_node);
let attr = self.get_node_text(&attr_node);
(
HashKey::Attribute {
value: Box::new(value_key),
attr,
},
false,
)
}
fn hash_subscript(&mut self, node: &Node) -> (HashKey, bool) {
let value_node = match node.child_by_field_name("value") {
Some(n) => n,
None => {
let id = self.fresh_vn();
return (HashKey::Unique { id }, false);
}
};
let slice_node = match node.child_by_field_name("subscript") {
Some(n) => n,
None => {
let id = self.fresh_vn();
return (HashKey::Unique { id }, false);
}
};
let (value_key, _) = self.hash_node(&value_node);
let (slice_key, _) = self.hash_node(&slice_node);
(
HashKey::Subscript {
value: Box::new(value_key),
slice: Box::new(slice_key),
},
false,
)
}
pub fn number_expression(&mut self, node: &Node, line: usize) -> usize {
let text = self.get_node_text(node);
if node.kind() == "identifier" {
if let Some(&vn) = self.var_vn.get(&text) {
let expr_ref = ExpressionRef::new(&text, line, vn);
self.expressions.push(expr_ref);
return vn;
}
}
let (hash_key, is_comm) = self.hash_node(node);
let vn = if let Some(&existing_vn) = self.hash_to_vn.get(&hash_key) {
existing_vn
} else {
let new_vn = self.fresh_vn();
self.hash_to_vn.insert(hash_key, new_vn);
new_vn
};
let expr_ref = ExpressionRef::new(&text, line, vn);
self.vn_first
.entry(vn)
.or_insert_with(|| expr_ref.clone());
if is_comm {
self.vn_is_commutative.insert(vn, true);
}
self.expressions.push(expr_ref);
vn
}
pub fn assign_variable(&mut self, name: &str, vn: usize) {
self.var_vn.insert(name.to_string(), vn);
}
pub fn kill_variable(&mut self, name: &str) {
self.var_vn.remove(name);
}
pub fn expressions(&self) -> &[ExpressionRef] {
&self.expressions
}
pub fn get_first(&self, vn: usize) -> Option<&ExpressionRef> {
self.vn_first.get(&vn)
}
pub fn is_commutative_vn(&self, vn: usize) -> bool {
self.vn_is_commutative.get(&vn).copied().unwrap_or(false)
}
pub fn unique_count(&self) -> usize {
self.next_vn
}
pub fn var_vn_map(&self) -> &HashMap<String, usize> {
&self.var_vn
}
pub fn walk_stmt(&mut self, stmt: &Node) {
let kind = stmt.kind();
let line = stmt.start_position().row + 1;
match kind {
"expression_statement" => {
if let Some(inner) = stmt.named_child(0) {
if inner.kind() == "assignment" {
self.handle_assignment(&inner, line);
} else if inner.kind() == "augmented_assignment" {
self.handle_aug_assignment(&inner, line);
} else {
let inner_line = inner.start_position().row + 1;
self.number_expression(&inner, inner_line);
}
}
}
"assignment" => {
self.handle_assignment(stmt, line);
}
"augmented_assignment" => {
self.handle_aug_assignment(stmt, line);
}
"return_statement" => {
for i in 0..stmt.named_child_count() {
if let Some(child) = stmt.named_child(i) {
if child.kind() != "return" {
let child_line = child.start_position().row + 1;
self.number_expression(&child, child_line);
}
}
}
}
"if_statement" => {
if let Some(cond) = stmt.child_by_field_name("condition") {
let cond_line = cond.start_position().row + 1;
self.number_expression(&cond, cond_line);
}
if let Some(body) = stmt.child_by_field_name("consequence") {
self.walk_block(&body);
}
if let Some(alt) = stmt.child_by_field_name("alternative") {
self.walk_block_or_stmt(&alt);
}
}
"for_statement" => {
if let Some(right) = stmt.child_by_field_name("right") {
let right_line = right.start_position().row + 1;
self.number_expression(&right, right_line);
}
if let Some(body) = stmt.child_by_field_name("body") {
self.walk_block(&body);
}
if let Some(alt) = stmt.child_by_field_name("alternative") {
self.walk_block(&alt);
}
}
"while_statement" => {
if let Some(cond) = stmt.child_by_field_name("condition") {
let cond_line = cond.start_position().row + 1;
self.number_expression(&cond, cond_line);
}
if let Some(body) = stmt.child_by_field_name("body") {
self.walk_block(&body);
}
if let Some(alt) = stmt.child_by_field_name("alternative") {
self.walk_block(&alt);
}
}
"with_statement" => {
for i in 0..stmt.named_child_count() {
if let Some(child) = stmt.named_child(i) {
if child.kind() == "with_clause" {
self.walk_with_clause(&child);
} else if child.kind() == "block" {
self.walk_block(&child);
}
}
}
}
"try_statement" => {
if let Some(body) = stmt.child_by_field_name("body") {
self.walk_block(&body);
}
for i in 0..stmt.named_child_count() {
if let Some(child) = stmt.named_child(i) {
match child.kind() {
"except_clause" | "except_group_clause" => {
self.walk_except_clause(&child);
}
"else_clause" => {
if let Some(body) = child.child_by_field_name("body") {
self.walk_block(&body);
}
}
"finally_clause" => {
if let Some(body) = child.child_by_field_name("body") {
self.walk_block(&body);
}
}
_ => {}
}
}
}
}
"function_definition" | "async_function_definition" => {
}
"class_definition" => {
}
"pass_statement" | "break_statement" | "continue_statement" => {
}
"assert_statement" => {
for i in 0..stmt.named_child_count() {
if let Some(child) = stmt.named_child(i) {
let child_line = child.start_position().row + 1;
self.number_expression(&child, child_line);
}
}
}
"raise_statement" => {
for i in 0..stmt.named_child_count() {
if let Some(child) = stmt.named_child(i) {
if child.kind() != "from" {
let child_line = child.start_position().row + 1;
self.number_expression(&child, child_line);
}
}
}
}
"import_statement" | "import_from_statement" => {
}
"global_statement" | "nonlocal_statement" => {
}
"delete_statement" => {
}
"match_statement" => {
if let Some(subject) = stmt.child_by_field_name("subject") {
let subj_line = subject.start_position().row + 1;
self.number_expression(&subject, subj_line);
}
for i in 0..stmt.named_child_count() {
if let Some(child) = stmt.named_child(i) {
if child.kind() == "case_clause" {
if let Some(body) = child.child_by_field_name("consequence") {
self.walk_block(&body);
}
}
}
}
}
_ => {
for i in 0..stmt.named_child_count() {
if let Some(child) = stmt.named_child(i) {
if self.is_statement_kind(child.kind()) {
self.walk_stmt(&child);
}
}
}
}
}
}
fn handle_assignment(&mut self, stmt: &Node, _line: usize) {
if let Some(right) = stmt.child_by_field_name("right") {
let right_line = right.start_position().row + 1;
let vn = self.number_expression(&right, right_line);
if let Some(left) = stmt.child_by_field_name("left") {
self.assign_targets(&left, vn);
}
}
}
fn handle_aug_assignment(&mut self, stmt: &Node, _line: usize) {
if let Some(right) = stmt.child_by_field_name("right") {
let right_line = right.start_position().row + 1;
self.number_expression(&right, right_line);
}
if let Some(left) = stmt.child_by_field_name("left") {
let target_name = self.get_node_text(&left);
self.kill_variable(&target_name);
let new_vn = self.fresh_vn();
self.assign_variable(&target_name, new_vn);
}
}
fn assign_targets(&mut self, target: &Node, vn: usize) {
let kind = target.kind();
match kind {
"identifier" => {
let name = self.get_node_text(target);
self.assign_variable(&name, vn);
}
"pattern_list" | "tuple_pattern" | "list_pattern" => {
for i in 0..target.named_child_count() {
if let Some(child) = target.named_child(i) {
if child.kind() == "identifier" {
let name = self.get_node_text(&child);
self.kill_variable(&name);
}
}
}
}
"tuple" | "list" => {
for i in 0..target.named_child_count() {
if let Some(child) = target.named_child(i) {
if child.kind() == "identifier" {
let name = self.get_node_text(&child);
self.kill_variable(&name);
}
}
}
}
"attribute" | "subscript" => {
}
_ => {
let name = self.get_node_text(target);
if !name.is_empty()
&& name
.chars()
.next()
.map(|c| c.is_alphabetic() || c == '_')
.unwrap_or(false)
{
self.assign_variable(&name, vn);
}
}
}
}
fn walk_block(&mut self, block: &Node) {
for i in 0..block.named_child_count() {
if let Some(child) = block.named_child(i) {
self.walk_stmt(&child);
}
}
}
fn walk_block_or_stmt(&mut self, node: &Node) {
if node.kind() == "block" {
self.walk_block(node);
} else if node.kind() == "elif_clause" || node.kind() == "else_clause" {
if let Some(cond) = node.child_by_field_name("condition") {
let cond_line = cond.start_position().row + 1;
self.number_expression(&cond, cond_line);
}
if let Some(body) = node.child_by_field_name("consequence") {
self.walk_block(&body);
}
if let Some(alt) = node.child_by_field_name("alternative") {
self.walk_block_or_stmt(&alt);
}
} else {
self.walk_stmt(node);
}
}
fn walk_with_clause(&mut self, clause: &Node) {
for i in 0..clause.named_child_count() {
if let Some(item) = clause.named_child(i) {
if item.kind() == "with_item" {
if let Some(value) = item.child_by_field_name("value") {
let value_line = value.start_position().row + 1;
self.number_expression(&value, value_line);
}
}
}
}
}
fn walk_except_clause(&mut self, clause: &Node) {
for i in 0..clause.named_child_count() {
if let Some(child) = clause.named_child(i) {
if child.kind() == "block" {
self.walk_block(&child);
}
}
}
}
fn is_statement_kind(&self, kind: &str) -> bool {
matches!(
kind,
"expression_statement"
| "assignment"
| "augmented_assignment"
| "return_statement"
| "if_statement"
| "for_statement"
| "while_statement"
| "with_statement"
| "try_statement"
| "function_definition"
| "async_function_definition"
| "class_definition"
| "pass_statement"
| "break_statement"
| "continue_statement"
| "assert_statement"
| "raise_statement"
| "import_statement"
| "import_from_statement"
| "global_statement"
| "nonlocal_statement"
| "delete_statement"
| "match_statement"
)
}
pub fn walk_stmts(&mut self, body: &Node) {
for i in 0..body.named_child_count() {
if let Some(child) = body.named_child(i) {
self.walk_stmt(&child);
}
}
}
pub fn build_report(&self, func_name: &str) -> GVNReport {
let mut report = GVNReport::new(func_name);
let mut vn_to_exprs: HashMap<usize, Vec<ExpressionRef>> = HashMap::new();
for expr in &self.expressions {
vn_to_exprs
.entry(expr.value_number)
.or_default()
.push(expr.clone());
}
let unique_vns: std::collections::HashSet<usize> =
self.expressions.iter().map(|e| e.value_number).collect();
report.total_expressions = self.expressions.len();
report.unique_values = unique_vns.len();
for (vn, exprs) in vn_to_exprs {
if exprs.len() >= 2 {
let reason = if self.is_commutative_vn(vn) {
"commutativity".to_string()
} else {
"identical expression".to_string()
};
let equiv = GVNEquivalence::new(vn, exprs.clone(), &reason);
report.equivalences.push(equiv);
let mut sorted_exprs = exprs;
sorted_exprs.sort_by_key(|e| e.line);
if let Some(original) = sorted_exprs.first() {
for redundant in sorted_exprs.iter().skip(1) {
let redund = Redundancy::new(original.clone(), redundant.clone(), &reason);
report.redundancies.push(redund);
}
}
}
}
report
}
}
pub fn compute_gvn(source: &str, function_name: Option<&str>) -> Vec<GVNReport> {
if source.trim().is_empty() {
return vec![];
}
let tree = match parse(source, Language::Python) {
Ok(t) => t,
Err(_) => return vec![],
};
let root = tree.root_node();
let source_bytes = source.as_bytes();
let mut reports = Vec::new();
let mut functions = Vec::new();
find_functions(root, source_bytes, &mut functions);
let filtered: Vec<_> = if let Some(name) = function_name {
functions
.into_iter()
.filter(|(fn_name, _)| fn_name == name)
.collect()
} else {
functions
};
for (fn_name, fn_node) in filtered {
let mut engine = GVNEngine::new(source);
if let Some(body) = fn_node.child_by_field_name("body") {
engine.walk_stmts(&body);
}
let report = engine.build_report(&fn_name);
reports.push(report);
}
reports
}
fn find_functions<'a>(node: Node<'a>, source: &[u8], results: &mut Vec<(String, Node<'a>)>) {
let kind = node.kind();
if kind == "function_definition" || kind == "async_function_definition" {
if let Some(name_node) = node.child_by_field_name("name") {
let name = name_node.utf8_text(source).unwrap_or("");
results.push((name.to_string(), node));
}
}
if kind == "module" {
for i in 0..node.named_child_count() {
if let Some(child) = node.named_child(i) {
find_functions(child, source, results);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::parser::parse;
use crate::types::Language;
fn find_node_by_kind<'a>(
node: tree_sitter::Node<'a>,
kind: &str,
) -> Option<tree_sitter::Node<'a>> {
if node.kind() == kind {
return Some(node);
}
for i in 0..node.named_child_count() {
if let Some(child) = node.named_child(i) {
if let Some(found) = find_node_by_kind(child, kind) {
return Some(found);
}
}
}
None
}
fn find_all_nodes_by_kind<'a>(
node: tree_sitter::Node<'a>,
kind: &str,
results: &mut Vec<tree_sitter::Node<'a>>,
) {
if node.kind() == kind {
results.push(node);
}
for i in 0..node.named_child_count() {
if let Some(child) = node.named_child(i) {
find_all_nodes_by_kind(child, kind, results);
}
}
}
#[test]
fn test_hash_node_constant_integer() {
let source = "42";
let tree = parse(source, Language::Python).unwrap();
let mut engine = GVNEngine::new(source);
let root = tree.root_node();
let int_node = find_node_by_kind(root, "integer").expect("Should find integer node");
let (key, is_comm) = engine.hash_node(&int_node);
assert!(!is_comm, "Constants are not commutative");
match key {
HashKey::Const { type_name, repr } => {
assert_eq!(type_name, "int");
assert_eq!(repr, "42");
}
_ => panic!("Expected Const hash key, got {:?}", key),
}
}
#[test]
fn test_hash_node_constant_string() {
let source = r#""hello""#;
let tree = parse(source, Language::Python).unwrap();
let mut engine = GVNEngine::new(source);
let root = tree.root_node();
let str_node = find_node_by_kind(root, "string").expect("Should find string node");
let (key, is_comm) = engine.hash_node(&str_node);
assert!(!is_comm);
match key {
HashKey::Const { type_name, repr } => {
assert_eq!(type_name, "str");
assert_eq!(repr, r#""hello""#);
}
_ => panic!("Expected Const hash key, got {:?}", key),
}
}
#[test]
fn test_hash_node_binop_commutative_add() {
let source = "x + y";
let tree = parse(source, Language::Python).unwrap();
let mut engine = GVNEngine::new(source);
let root = tree.root_node();
let binop =
find_node_by_kind(root, "binary_operator").expect("Should find binary_operator");
let (key, is_comm) = engine.hash_node(&binop);
assert!(is_comm, "Add is commutative");
match &key {
HashKey::BinOp {
op, commutative, ..
} => {
assert_eq!(op, "Add");
assert!(*commutative);
}
_ => panic!("Expected BinOp hash key, got {:?}", key),
}
}
#[test]
fn test_hash_node_binop_commutative_normalization() {
let source1 = "x + y";
let source2 = "y + x";
let tree1 = parse(source1, Language::Python).unwrap();
let tree2 = parse(source2, Language::Python).unwrap();
let mut engine1 = GVNEngine::new(source1);
let mut engine2 = GVNEngine::new(source2);
let binop1 = find_node_by_kind(tree1.root_node(), "binary_operator").unwrap();
let binop2 = find_node_by_kind(tree2.root_node(), "binary_operator").unwrap();
let (key1, _) = engine1.hash_node(&binop1);
let (key2, _) = engine2.hash_node(&binop2);
assert_eq!(key1, key2, "x + y and y + x should hash to the same key");
}
#[test]
fn test_hash_node_binop_non_commutative() {
let source1 = "x - y";
let source2 = "y - x";
let tree1 = parse(source1, Language::Python).unwrap();
let tree2 = parse(source2, Language::Python).unwrap();
let mut engine1 = GVNEngine::new(source1);
let mut engine2 = GVNEngine::new(source2);
let binop1 = find_node_by_kind(tree1.root_node(), "binary_operator").unwrap();
let binop2 = find_node_by_kind(tree2.root_node(), "binary_operator").unwrap();
let (key1, is_comm1) = engine1.hash_node(&binop1);
let (key2, is_comm2) = engine2.hash_node(&binop2);
assert!(!is_comm1, "Sub is not commutative");
assert!(!is_comm2, "Sub is not commutative");
assert_ne!(key1, key2, "x - y and y - x should hash to different keys");
}
#[test]
fn test_hash_node_call_unique() {
let source = "foo()\nfoo()";
let tree = parse(source, Language::Python).unwrap();
let mut engine = GVNEngine::new(source);
let root = tree.root_node();
let mut calls = Vec::new();
find_all_nodes_by_kind(root, "call", &mut calls);
assert_eq!(calls.len(), 2, "Should find 2 call nodes");
let (key1, _) = engine.hash_node(&calls[0]);
let (key2, _) = engine.hash_node(&calls[1]);
assert_ne!(
key1, key2,
"Each call should get a unique hash key (BC-GVN-4)"
);
match (&key1, &key2) {
(HashKey::Call { unique_id: id1 }, HashKey::Call { unique_id: id2 }) => {
assert_ne!(id1, id2);
}
_ => panic!("Expected Call hash keys"),
}
}
#[test]
fn test_number_expression_basic() {
let source = "x + y";
let tree = parse(source, Language::Python).unwrap();
let mut engine = GVNEngine::new(source);
let binop = find_node_by_kind(tree.root_node(), "binary_operator").unwrap();
let vn = engine.number_expression(&binop, 1);
assert!(vn < 100, "Should get a reasonable VN");
assert_eq!(engine.expressions().len(), 1);
assert_eq!(engine.expressions()[0].text, "x + y");
assert_eq!(engine.expressions()[0].line, 1);
assert_eq!(engine.expressions()[0].value_number, vn);
}
#[test]
fn test_number_expression_same_expression_same_vn() {
let source = "x + y\nx + y";
let tree = parse(source, Language::Python).unwrap();
let mut engine = GVNEngine::new(source);
let mut binops = Vec::new();
find_all_nodes_by_kind(tree.root_node(), "binary_operator", &mut binops);
assert_eq!(binops.len(), 2, "Should find 2 binary operators");
let vn1 = engine.number_expression(&binops[0], 1);
let vn2 = engine.number_expression(&binops[1], 2);
assert_eq!(vn1, vn2, "Identical expressions should get same VN");
}
#[test]
fn test_alias_propagation() {
let source = "x";
let tree = parse(source, Language::Python).unwrap();
let mut engine = GVNEngine::new(source);
let id_node = find_node_by_kind(tree.root_node(), "identifier").unwrap();
let (key_before, _) = engine.hash_node(&id_node);
match &key_before {
HashKey::Name { name } => assert_eq!(name, "x"),
_ => panic!("Expected Name hash key before assignment"),
}
engine.assign_variable("x", 42);
let (key_after, _) = engine.hash_node(&id_node);
match &key_after {
HashKey::VarVN { vn } => assert_eq!(*vn, 42),
_ => panic!(
"Expected VarVN hash key after assignment, got {:?}",
key_after
),
}
}
#[test]
fn test_alias_propagation_number_expression() {
let source = "x";
let tree = parse(source, Language::Python).unwrap();
let mut engine = GVNEngine::new(source);
let id_node = find_node_by_kind(tree.root_node(), "identifier").unwrap();
engine.assign_variable("x", 42);
let vn = engine.number_expression(&id_node, 1);
assert_eq!(vn, 42, "Aliased variable should return assigned VN");
}
#[test]
fn test_kill_variable() {
let source = "x";
let tree = parse(source, Language::Python).unwrap();
let mut engine = GVNEngine::new(source);
let id_node = find_node_by_kind(tree.root_node(), "identifier").unwrap();
engine.assign_variable("x", 42);
engine.kill_variable("x");
let (key, _) = engine.hash_node(&id_node);
match &key {
HashKey::Name { name } => assert_eq!(name, "x"),
_ => panic!("Expected Name hash key after kill"),
}
}
}