thru-abi-gen 0.2.30

ABI code generation utilities for the Thru blockchain
Documentation
use super::helpers::{escape_ts_keyword, struct_field_const_offset};
use super::ir_helpers::sanitize_param_name;
use crate::abi::expr::ExprKind;
use crate::abi::resolved::{ResolvedEnumVariant, ResolvedField, ResolvedType, ResolvedTypeKind};

#[derive(Clone)]
pub struct EnumFieldInfo<'a> {
    pub enum_field: &'a ResolvedField,
    pub tag_field: Option<&'a ResolvedField>,
    pub variants: &'a [ResolvedEnumVariant],
    pub enum_ts_name: String,
    pub tag_ts_name: String,
    pub descriptor_prop: String,
    pub payload_offset: Option<u64>,
    pub tag_offset: Option<u64>,
    pub is_tail: bool,
    pub tag_expression: Option<ExprKind>,
    pub tag_parameter: Option<String>,
    pub tag_param_ts_name: Option<String>,
}

pub fn enum_field_info<'a>(resolved_type: &'a ResolvedType) -> Option<EnumFieldInfo<'a>> {
    let fields = match &resolved_type.kind {
        ResolvedTypeKind::Struct { fields, .. } => fields,
        _ => return None,
    };
    let enum_field = fields
        .iter()
        .find(|field| matches!(field.field_type.kind, ResolvedTypeKind::Enum { .. }))?;
    build_enum_field_info(resolved_type, fields, enum_field)
}

pub fn enum_field_info_by_name<'a>(
    resolved_type: &'a ResolvedType,
    field_name: &str,
) -> Option<EnumFieldInfo<'a>> {
    let fields = match &resolved_type.kind {
        ResolvedTypeKind::Struct { fields, .. } => fields,
        _ => return None,
    };
    let enum_field = fields.iter().find(|field| {
        field.name == field_name && matches!(field.field_type.kind, ResolvedTypeKind::Enum { .. })
    })?;
    build_enum_field_info(resolved_type, fields, enum_field)
}

pub fn enum_field_infos<'a>(resolved_type: &'a ResolvedType) -> Vec<EnumFieldInfo<'a>> {
    let fields = match &resolved_type.kind {
        ResolvedTypeKind::Struct { fields, .. } => fields,
        _ => return Vec::new(),
    };
    fields
        .iter()
        .filter_map(|field| {
            if matches!(field.field_type.kind, ResolvedTypeKind::Enum { .. }) {
                build_enum_field_info(resolved_type, fields, field)
            } else {
                None
            }
        })
        .collect()
}

fn build_enum_field_info<'a>(
    struct_type: &'a ResolvedType,
    fields: &'a [ResolvedField],
    enum_field: &'a ResolvedField,
) -> Option<EnumFieldInfo<'a>> {
    let (variants, tag_data) = match &enum_field.field_type.kind {
        ResolvedTypeKind::Enum {
            variants,
            tag_expression,
            ..
        } => {
            if let Some(tag_name) = extract_field_name(tag_expression) {
                (variants, EnumTagSource::Field(tag_name.to_string()))
            } else {
                (variants, EnumTagSource::Computed(tag_expression.clone()))
            }
        }
        _ => return None,
    };

    let (tag_field_opt, tag_ts_name, tag_offset, tag_expression, tag_parameter) = match tag_data {
        EnumTagSource::Field(name) => {
            let tag_field = fields.iter().find(|field| field.name == name)?;
            let offset = tag_field
                .offset
                .or_else(|| struct_field_const_offset(struct_type, &tag_field.name));
            (
                Some(tag_field),
                escape_ts_keyword(&tag_field.name),
                offset,
                None,
                None,
            )
        }
        EnumTagSource::Computed(expr) => (
            None,
            escape_ts_keyword(&format!("{}_computed_tag", enum_field.name)),
            None,
            Some(expr.clone()),
            Some(format!("{}.computed_tag", enum_field.field_type.name)),
        ),
    };

    let payload_offset = enum_field
        .offset
        .or_else(|| struct_field_const_offset(struct_type, &enum_field.name));
    let is_tail = fields
        .iter()
        .enumerate()
        .find(|(_, field)| std::ptr::eq(*field, enum_field))
        .map(|(idx, _)| idx + 1 == fields.len())
        .unwrap_or(false);
    let tag_param_ts_name = tag_parameter.as_ref().map(|name| sanitize_param_name(name));

    Some(EnumFieldInfo {
        enum_field,
        tag_field: tag_field_opt,
        variants,
        enum_ts_name: escape_ts_keyword(&enum_field.name),
        tag_ts_name,
        descriptor_prop: format!("{}VariantDescriptors", escape_ts_keyword(&enum_field.name)),
        payload_offset,
        tag_offset,
        is_tail,
        tag_expression,
        tag_parameter,
        tag_param_ts_name,
    })
}

enum EnumTagSource {
    Field(String),
    Computed(ExprKind),
}

fn extract_field_name(expr: &ExprKind) -> Option<&str> {
    match expr {
        ExprKind::FieldRef(field_ref) if field_ref.path.len() == 1 => {
            field_ref.path.first().map(|s| s.as_str())
        }
        _ => None,
    }
}