use wasm_encoder::ValType;
use crate::ast::{Expr, FnBody, FnDef, Literal, Pattern, Stmt};
use super::super::WasmGcError;
use super::super::types::{TypeRegistry, aver_to_wasm};
use super::FnMap;
use super::infer::{arm_is_option_pattern, arm_is_result_pattern};
pub(super) struct SlotTable {
pub(super) by_slot: Vec<ValType>,
pub(super) subject_scratch: Option<u32>,
pub(super) args_get_scratch: Option<[u32; 4]>,
}
impl SlotTable {
pub(super) fn build_for_fn(
fd: &FnDef,
registry: &TypeRegistry,
_fn_map: &FnMap,
) -> Result<Self, WasmGcError> {
let mut by_slot: Vec<ValType> = Vec::new();
if let Some(resolution) = fd.resolution.as_ref() {
for ty in resolution.local_slot_types.iter() {
let aver_str = ty.display();
let v = aver_to_wasm(&aver_str, Some(registry))
.unwrap_or(None)
.unwrap_or(ValType::I32);
by_slot.push(v);
}
} else {
for (_, ty) in &fd.params {
let v = aver_to_wasm(ty, Some(registry))
.unwrap_or(None)
.unwrap_or(ValType::I32);
by_slot.push(v);
}
}
let needs_scratch = fn_needs_subject_scratch(fd, registry);
let subject_scratch = if needs_scratch {
let scratch_ty = ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Abstract {
shared: false,
ty: wasm_encoder::AbstractHeapType::Eq,
},
});
let idx = by_slot.len() as u32;
by_slot.push(scratch_ty);
Some(idx)
} else {
None
};
let args_get_scratch = if fn_needs_args_get_scratch(fd) {
let i64_ty = ValType::I64;
let list_ref = registry.list_type_idx("List<String>").map(|idx| {
ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(idx),
})
});
let str_ref = registry.string_array_type_idx.map(|idx| {
ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(idx),
})
});
match (list_ref, str_ref) {
(Some(list_ty), Some(s_ty)) => {
let i_idx = by_slot.len() as u32;
by_slot.push(i64_ty);
let len_idx = by_slot.len() as u32;
by_slot.push(i64_ty);
let acc_idx = by_slot.len() as u32;
by_slot.push(list_ty);
let s_idx = by_slot.len() as u32;
by_slot.push(s_ty);
Some([i_idx, len_idx, acc_idx, s_idx])
}
_ => {
return Err(WasmGcError::Validation(
"Args.get() requires List<String> and String slots in registry — \
pre-register them by ensuring the program reaches a List<String> \
literal or String value first"
.into(),
));
}
}
} else {
None
};
Ok(Self {
by_slot,
subject_scratch,
args_get_scratch,
})
}
pub(super) fn extra_locals(&self, params_count: usize) -> Vec<ValType> {
self.by_slot.iter().skip(params_count).copied().collect()
}
}
pub(super) fn fn_needs_args_get_scratch(fd: &FnDef) -> bool {
let FnBody::Block(stmts) = fd.body.as_ref();
stmts.iter().any(stmt_reaches_args_get_no_args)
}
fn stmt_reaches_args_get_no_args(stmt: &Stmt) -> bool {
match stmt {
Stmt::Binding(_, _, e) | Stmt::Expr(e) => expr_reaches_args_get_no_args(&e.node),
}
}
fn expr_reaches_args_get_no_args(expr: &Expr) -> bool {
match expr {
Expr::FnCall(callee, args) => {
if args.is_empty()
&& let Expr::Attr(parent, member) = &callee.node
&& let Expr::Ident(p) = &parent.node
&& p == "Args"
&& member == "get"
{
return true;
}
expr_reaches_args_get_no_args(&callee.node)
|| args.iter().any(|a| expr_reaches_args_get_no_args(&a.node))
}
Expr::BinOp(_, l, r) => {
expr_reaches_args_get_no_args(&l.node) || expr_reaches_args_get_no_args(&r.node)
}
Expr::Match { subject, arms } => {
expr_reaches_args_get_no_args(&subject.node)
|| arms
.iter()
.any(|a| expr_reaches_args_get_no_args(&a.body.node))
}
Expr::TailCall(boxed) => boxed
.args
.iter()
.any(|a| expr_reaches_args_get_no_args(&a.node)),
Expr::Attr(obj, _) => expr_reaches_args_get_no_args(&obj.node),
Expr::ErrorProp(inner) => expr_reaches_args_get_no_args(&inner.node),
Expr::Constructor(_, payload) => payload
.as_deref()
.is_some_and(|p| expr_reaches_args_get_no_args(&p.node)),
Expr::RecordCreate { fields, .. } => fields
.iter()
.any(|(_, e)| expr_reaches_args_get_no_args(&e.node)),
Expr::RecordUpdate { base, updates, .. } => {
expr_reaches_args_get_no_args(&base.node)
|| updates
.iter()
.any(|(_, e)| expr_reaches_args_get_no_args(&e.node))
}
Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
items.iter().any(|e| expr_reaches_args_get_no_args(&e.node))
}
Expr::MapLiteral(entries) => entries.iter().any(|(k, v)| {
expr_reaches_args_get_no_args(&k.node) || expr_reaches_args_get_no_args(&v.node)
}),
Expr::InterpolatedStr(parts) => parts.iter().any(|p| {
if let crate::ast::StrPart::Parsed(inner) = p {
expr_reaches_args_get_no_args(&inner.node)
} else {
false
}
}),
_ => false,
}
}
pub(super) fn fn_needs_subject_scratch(fd: &FnDef, registry: &TypeRegistry) -> bool {
let FnBody::Block(stmts) = fd.body.as_ref();
stmts.iter().any(|s| stmt_needs_scratch(s, registry))
}
pub(super) fn stmt_needs_scratch(stmt: &Stmt, registry: &TypeRegistry) -> bool {
match stmt {
Stmt::Binding(_, _, e) | Stmt::Expr(e) => expr_needs_scratch(&e.node, registry),
}
}
#[allow(clippy::only_used_in_recursion)]
pub(super) fn expr_needs_scratch(expr: &Expr, registry: &TypeRegistry) -> bool {
match expr {
Expr::Match { subject, arms } => {
if expr_needs_scratch(&subject.node, registry) {
return true;
}
if arms.iter().any(arm_is_option_pattern) {
return true;
}
if arms.iter().any(arm_is_result_pattern) {
return true;
}
if arms
.iter()
.any(|a| matches!(&a.pattern, Pattern::EmptyList | Pattern::Cons(_, _)))
{
return true;
}
if arms.iter().any(|a| matches!(&a.pattern, Pattern::Tuple(_))) {
return true;
}
if arms
.iter()
.any(|a| matches!(&a.pattern, Pattern::Literal(Literal::Str(_))))
{
return true;
}
if arms
.iter()
.any(|a| matches!(a.pattern, Pattern::Constructor(_, _)))
{
return true;
}
arms.iter()
.any(|a| expr_needs_scratch(&a.body.node, registry))
}
Expr::BinOp(_, l, r) => {
expr_needs_scratch(&l.node, registry) || expr_needs_scratch(&r.node, registry)
}
Expr::FnCall(callee, args) => {
if let Expr::Attr(parent, member) = &callee.node
&& let Expr::Ident(p) = &parent.node
&& ((p == "Option" || p == "Result") && member == "withDefault")
{
return true;
}
if let Expr::Attr(parent, member) = &callee.node
&& let Expr::Ident(p) = &parent.node
&& p == "Option"
&& member == "toResult"
{
return true;
}
expr_needs_scratch(&callee.node, registry)
|| args.iter().any(|a| expr_needs_scratch(&a.node, registry))
}
Expr::TailCall(boxed) => boxed
.args
.iter()
.any(|a| expr_needs_scratch(&a.node, registry)),
Expr::Attr(obj, _) => expr_needs_scratch(&obj.node, registry),
Expr::ErrorProp(_) => true,
Expr::Constructor(_, payload) => payload
.as_deref()
.is_some_and(|p| expr_needs_scratch(&p.node, registry)),
Expr::RecordCreate { fields, .. } => fields
.iter()
.any(|(_, e)| expr_needs_scratch(&e.node, registry)),
Expr::List(items) if !items.is_empty() => true,
Expr::List(items) => items.iter().any(|e| expr_needs_scratch(&e.node, registry)),
Expr::MapLiteral(entries) => entries.iter().any(|(k, v)| {
expr_needs_scratch(&k.node, registry) || expr_needs_scratch(&v.node, registry)
}),
Expr::IndependentProduct(items, unwrap) => {
*unwrap || items.iter().any(|e| expr_needs_scratch(&e.node, registry))
}
Expr::Tuple(items) => items.iter().any(|e| expr_needs_scratch(&e.node, registry)),
_ => false,
}
}
pub(super) fn count_value_params(params: &[(String, String)]) -> usize {
params.iter().filter(|(_, ty)| ty.trim() != "Unit").count()
}