use std::collections::HashMap;
use stellar_xdr::curr::ScSpecEntry;
use crate::ir::{Expr, Literal, MethodCall, Statement};
pub(super) fn resolve_storage_keys(
stmts: Vec<Statement>,
all_entries: &[ScSpecEntry],
) -> Vec<Statement> {
let key_enum = all_entries.iter().find_map(|e| {
if let ScSpecEntry::UdtUnionV0(u) = e {
let name = u.name.to_utf8_string_lossy();
if name.contains("Key") || name.contains("DataKey") {
let variants: Vec<String> = u.cases.iter().map(|c| match c {
stellar_xdr::curr::ScSpecUdtUnionCaseV0::VoidV0(v) => v.name.to_utf8_string_lossy(),
stellar_xdr::curr::ScSpecUdtUnionCaseV0::TupleV0(t) => t.name.to_utf8_string_lossy(),
}).collect();
return Some((name, variants));
}
}
if let ScSpecEntry::UdtEnumV0(e) = e {
let name = e.name.to_utf8_string_lossy();
if name.contains("Key") || name.contains("DataKey") {
let variants: Vec<String> = e.cases.iter()
.map(|c| c.name.to_utf8_string_lossy())
.collect();
return Some((name, variants));
}
}
None
});
let Some((enum_name, variants)) = key_enum else {
return stmts;
};
let mut resolved_vars: std::collections::HashSet<String> = std::collections::HashSet::new();
for stmt in &stmts {
if let Statement::Let { name, value, .. } = stmt {
if matches!(value, Expr::EnumVariant { enum_name: en, .. } if en == &enum_name) {
resolved_vars.insert(name.clone());
}
}
}
let mut key_to_variant: HashMap<String, usize> = HashMap::new();
let mut next_substantive = variants.iter().position(|v| !v.to_lowercase().contains("init")).unwrap_or(0);
let mut next_marker = variants.iter().position(|v| v.to_lowercase().contains("init")).unwrap_or(0);
stmts.into_iter().map(|stmt| {
resolve_key_in_stmt(stmt, &enum_name, &variants, &mut key_to_variant,
&mut next_substantive, &mut next_marker, &resolved_vars)
}).collect()
}
fn resolve_key_in_stmt(
stmt: Statement,
enum_name: &str,
variants: &[String],
key_map: &mut HashMap<String, usize>,
next_sub: &mut usize,
next_mark: &mut usize,
resolved_vars: &std::collections::HashSet<String>,
) -> Statement {
match stmt {
Statement::Expr(ref expr) | Statement::Let { value: ref expr, .. } => {
if let Some(resolved) = try_resolve_key_in_method_chain(expr, enum_name, variants, key_map, next_sub, next_mark, resolved_vars) {
match stmt {
Statement::Expr(_) => Statement::Expr(resolved),
Statement::Let { name, mutable, .. } => Statement::Let { name, mutable, value: resolved },
_ => unreachable!(),
}
} else {
stmt
}
}
Statement::If { condition, then_body, else_body } => Statement::If {
condition,
then_body: then_body.into_iter()
.map(|s| resolve_key_in_stmt(s, enum_name, variants, key_map, next_sub, next_mark, resolved_vars))
.collect(),
else_body: else_body.into_iter()
.map(|s| resolve_key_in_stmt(s, enum_name, variants, key_map, next_sub, next_mark, resolved_vars))
.collect(),
},
other => other,
}
}
fn try_resolve_key_in_method_chain(
expr: &Expr,
enum_name: &str,
variants: &[String],
key_map: &mut HashMap<String, usize>,
next_sub: &mut usize,
next_mark: &mut usize,
resolved_vars: &std::collections::HashSet<String>,
) -> Option<Expr> {
if let Expr::MethodChain { receiver, calls } = expr {
let storage_op_idx = calls.iter().position(|c| {
(c.name == "set" || c.name == "get" || c.name == "has" || c.name == "remove")
&& !c.args.is_empty()
});
let is_storage_op = calls.first().map_or(false, |c| c.name == "storage")
&& storage_op_idx.is_some();
if is_storage_op {
let op_idx = storage_op_idx.unwrap();
let storage_call = &calls[op_idx];
if let Some(key_arg) = storage_call.args.first() {
let is_already_resolved = match key_arg {
Expr::Ref(inner) => match inner.as_ref() {
Expr::EnumVariant { enum_name: en, .. } => en == enum_name,
Expr::Var(name) => resolved_vars.contains(name),
_ => false,
},
Expr::EnumVariant { enum_name: en, .. } => en == enum_name,
Expr::Var(name) => resolved_vars.contains(name),
_ => false,
};
let is_unresolved = !is_already_resolved;
if !is_unresolved {
return None;
}
let val_arg = if storage_call.name == "set" {
storage_call.args.get(1)
} else {
None
};
let is_void_value = val_arg.map_or(false, |v| matches!(v,
Expr::Ref(inner) if matches!(inner.as_ref(), Expr::Literal(Literal::Unit))
));
let key_sig = format!("{:?}", key_arg);
let reuse_last = if storage_call.name == "remove" && !key_map.is_empty() {
key_map.values().last().copied()
} else {
None
};
let is_peek = storage_call.name == "has";
let idx = if let Some(&existing) = key_map.get(&key_sig) {
existing
} else if let Some(last_idx) = reuse_last {
last_idx
} else if is_void_value {
let idx = *next_mark;
if !is_peek { *next_mark = (*next_mark + 1).min(variants.len()); }
idx
} else {
let idx = *next_sub;
if !is_peek { *next_sub = (*next_sub + 1).min(variants.len()); }
idx
};
key_map.insert(key_sig, idx);
if idx < variants.len() {
let key_expr = Expr::EnumVariant {
enum_name: enum_name.to_string(),
variant_name: variants[idx].clone(),
fields: vec![],
};
let mut new_calls = calls.clone();
let target = &mut new_calls[op_idx];
if !target.args.is_empty() {
target.args[0] = Expr::Ref(Box::new(key_expr));
}
return Some(Expr::MethodChain {
receiver: receiver.clone(),
calls: new_calls,
});
}
}
}
}
None
}
pub(super) fn synthesize_tuple_keys(
stmts: Vec<Statement>,
all_entries: &[ScSpecEntry],
) -> Vec<Statement> {
let has_key_enum = all_entries.iter().any(|e| match e {
ScSpecEntry::UdtUnionV0(u) => {
let name = u.name.to_utf8_string_lossy();
name.contains("Key") || name.contains("DataKey")
}
ScSpecEntry::UdtEnumV0(e) => {
let name = e.name.to_utf8_string_lossy();
name.contains("Key") || name.contains("DataKey")
}
_ => false,
});
if has_key_enum { return stmts; }
let mut sym_map: std::collections::HashMap<String, String> = std::collections::HashMap::new();
for stmt in &stmts {
if let Statement::Let { name, value: Expr::HostCall { module, name: fn_name, args }, .. } = stmt {
if module == "Symbol" && fn_name == "new" {
if let Some(Expr::Literal(Literal::Str(variant))) = args.get(1) {
sym_map.insert(name.clone(), variant.clone());
}
}
}
}
if sym_map.is_empty() { return stmts; }
let mut vec_map: std::collections::HashMap<String, (String, Vec<Expr>)> = std::collections::HashMap::new();
for stmt in &stmts {
if let Statement::Let { name, value: Expr::MacroCall { name: mac, args }, .. } = stmt {
if mac == "vec" && args.len() >= 2 {
if let Some(Expr::Var(sym_var)) = args.get(1) {
if sym_map.contains_key(sym_var) {
let fields: Vec<Expr> = args[2..].to_vec();
vec_map.insert(name.clone(), (sym_var.clone(), fields));
}
}
}
}
}
if vec_map.is_empty() { return stmts; }
let sym_vars: std::collections::HashSet<String> = sym_map.keys().cloned().collect();
let vec_vars: std::collections::HashSet<String> = vec_map.keys().cloned().collect();
stmts.into_iter().filter_map(|stmt| {
if let Statement::Let { name, .. } = &stmt {
if sym_vars.contains(name) { return None; }
if vec_vars.contains(name) { return None; }
}
Some(replace_vec_keys_in_stmt(stmt, &sym_map, &vec_map))
}).collect()
}
fn replace_vec_keys_in_stmt(
stmt: Statement,
sym_map: &std::collections::HashMap<String, String>,
vec_map: &std::collections::HashMap<String, (String, Vec<Expr>)>,
) -> Statement {
match stmt {
Statement::Expr(e) => Statement::Expr(replace_vec_keys_in_expr(e, sym_map, vec_map)),
Statement::Let { name, mutable, value } => Statement::Let {
name, mutable,
value: replace_vec_keys_in_expr(value, sym_map, vec_map),
},
Statement::If { condition, then_body, else_body } => Statement::If {
condition: replace_vec_keys_in_expr(condition, sym_map, vec_map),
then_body: then_body.into_iter().map(|s| replace_vec_keys_in_stmt(s, sym_map, vec_map)).collect(),
else_body: else_body.into_iter().map(|s| replace_vec_keys_in_stmt(s, sym_map, vec_map)).collect(),
},
other => other,
}
}
fn replace_vec_keys_in_expr(
expr: Expr,
sym_map: &std::collections::HashMap<String, String>,
vec_map: &std::collections::HashMap<String, (String, Vec<Expr>)>,
) -> Expr {
match expr {
Expr::Ref(inner) => {
if let Expr::Var(ref var_name) = *inner {
if let Some((sym_var, fields)) = vec_map.get(var_name) {
if let Some(variant_name) = sym_map.get(sym_var) {
return Expr::Ref(Box::new(Expr::EnumVariant {
enum_name: "DataKey".into(),
variant_name: variant_name.clone(),
fields: fields.clone(),
}));
}
}
}
Expr::Ref(Box::new(replace_vec_keys_in_expr(*inner, sym_map, vec_map)))
}
Expr::Var(ref var_name) => {
if let Some((sym_var, fields)) = vec_map.get(var_name) {
if let Some(variant_name) = sym_map.get(sym_var) {
return Expr::EnumVariant {
enum_name: "DataKey".into(),
variant_name: variant_name.clone(),
fields: fields.clone(),
};
}
}
expr
}
Expr::MethodChain { receiver, calls } => Expr::MethodChain {
receiver: Box::new(replace_vec_keys_in_expr(*receiver, sym_map, vec_map)),
calls: calls.into_iter().map(|c| MethodCall {
name: c.name,
args: c.args.into_iter().map(|a| replace_vec_keys_in_expr(a, sym_map, vec_map)).collect(),
}).collect(),
},
Expr::BinOp { op, left, right } => Expr::BinOp {
op,
left: Box::new(replace_vec_keys_in_expr(*left, sym_map, vec_map)),
right: Box::new(replace_vec_keys_in_expr(*right, sym_map, vec_map)),
},
other => other,
}
}