use uniffi_bindgen::interface::Type;
use super::config::JsBindingsConfig;
use super::naming::{camel_case, safe_js_identifier};
fn resolve_custom(t: &Type) -> &Type {
match t {
Type::Custom { builtin, .. } => resolve_custom(builtin),
_ => t,
}
}
pub(super) fn element_count(t: &Type) -> usize {
let t = resolve_custom(t);
match t {
Type::Int8
| Type::UInt8
| Type::Int16
| Type::UInt16
| Type::Int32
| Type::UInt32
| Type::Int64
| Type::UInt64
| Type::Float32
| Type::Float64
| Type::Boolean => 1,
Type::Object { .. } => 1,
Type::CallbackInterface { .. } => 1,
Type::String
| Type::Bytes
| Type::Duration
| Type::Timestamp
| Type::Optional { .. }
| Type::Sequence { .. }
| Type::Map { .. }
| Type::Record { .. }
| Type::Enum { .. } => 3,
Type::Custom { .. } => unreachable!("resolve_custom strips Custom"),
}
}
pub(super) const CALL_STATUS_ELEMENTS: usize = 4;
pub(super) fn is_rust_buffer_type(t: &Type) -> bool {
element_count(t) == 3
}
pub(super) fn ffibuf_fn_func(namespace: &str, fn_name: &str) -> String {
format!("uniffi_ffibuffer_{namespace}_fn_func_{fn_name}")
}
pub(super) fn ffibuf_fn_constructor(namespace: &str, obj_name: &str, ctor_name: &str) -> String {
let obj_lower = obj_name.to_ascii_lowercase();
format!("uniffi_ffibuffer_{namespace}_fn_constructor_{obj_lower}_{ctor_name}")
}
pub(super) fn ffibuf_fn_method(namespace: &str, obj_name: &str, method_name: &str) -> String {
let obj_lower = obj_name.to_ascii_lowercase();
format!("uniffi_ffibuffer_{namespace}_fn_method_{obj_lower}_{method_name}")
}
pub(super) fn fn_free(namespace: &str, obj_name: &str) -> String {
let obj_lower = obj_name.to_ascii_lowercase();
format!("uniffi_{namespace}_fn_free_{obj_lower}")
}
pub(super) fn fn_clone(namespace: &str, obj_name: &str) -> String {
let obj_lower = obj_name.to_ascii_lowercase();
format!("uniffi_{namespace}_fn_clone_{obj_lower}")
}
pub(super) fn fn_init_callback_vtable(namespace: &str, cb_name: &str) -> String {
let cb_lower = cb_name.to_ascii_lowercase();
format!("uniffi_{namespace}_fn_init_callback_vtable_{cb_lower}")
}
pub(super) fn rust_future_type_suffix(return_type: Option<&Type>) -> &'static str {
match return_type {
None => "void",
Some(t) => {
let t = resolve_custom(t);
match t {
Type::Int8 => "i8",
Type::UInt8 => "u8",
Type::Int16 => "i16",
Type::UInt16 => "u16",
Type::Int32 => "i32",
Type::UInt32 => "u32",
Type::Int64 => "i64",
Type::UInt64 => "u64",
Type::Float32 => "f32",
Type::Float64 => "f64",
Type::Boolean => "i8",
Type::Object { .. } | Type::CallbackInterface { .. } => "u64",
Type::String
| Type::Bytes
| Type::Duration
| Type::Timestamp
| Type::Optional { .. }
| Type::Sequence { .. }
| Type::Map { .. }
| Type::Record { .. }
| Type::Enum { .. } => "rust_buffer",
Type::Custom { .. } => unreachable!("resolve_custom strips Custom"),
}
}
}
}
pub(super) fn rust_future_poll(namespace: &str, suffix: &str) -> String {
format!("ffi_{namespace}_rust_future_poll_{suffix}")
}
pub(super) fn rust_future_complete(namespace: &str, suffix: &str) -> String {
format!("ffi_{namespace}_rust_future_complete_{suffix}")
}
pub(super) fn rust_future_free(namespace: &str, suffix: &str) -> String {
format!("ffi_{namespace}_rust_future_free_{suffix}")
}
pub(super) fn rust_future_complete_uses_retptr(suffix: &str) -> bool {
suffix == "rust_buffer"
}
pub(super) fn gen_read_return(t: &Type, offset_expr: &str, cfg: &JsBindingsConfig) -> String {
if is_rust_buffer_type(t) {
gen_top_level_lift(t, offset_expr, cfg)
} else {
let read_fn = element_read_fn(t);
let raw = format!("_rt.{read_fn}({offset_expr})");
gen_from_ffi(&raw, t)
}
}
fn gen_top_level_lower(var: &str, t: &Type, namespace: &str, cfg: &JsBindingsConfig) -> String {
match t {
Type::String => format!("_rt.lowerString({var})"),
Type::Custom { builtin, .. } => gen_top_level_lower(var, builtin, namespace, cfg),
_ => {
let lower_body = gen_lower_expr(var, t, namespace, "w", cfg);
format!("_rt.lowerIntoBuffer((w) => {{ {lower_body}; }})")
}
}
}
fn gen_top_level_lift(t: &Type, offset_expr: &str, cfg: &JsBindingsConfig) -> String {
match t {
Type::String => {
format!("_rt.liftString(_rt.readRustBufferElements({offset_expr}))")
}
Type::Custom { builtin, .. } => gen_top_level_lift(builtin, offset_expr, cfg),
_ => {
let lift_body = gen_lift_expr("r", t, cfg);
format!(
"_rt.liftFromBuffer(_rt.readRustBufferElements({offset_expr}), (r) => {{ return {lift_body}; }})"
)
}
}
}
fn gen_lower_expr(var: &str, t: &Type, namespace: &str, w: &str, cfg: &JsBindingsConfig) -> String {
match t {
Type::String => format!("{w}.writeString({var})"),
Type::Bytes => format!("{w}.writeBytes({var})"),
Type::Boolean => format!("{w}.writeBool({var})"),
Type::Int8 => format!("{w}.writeI8({var})"),
Type::UInt8 => format!("{w}.writeU8({var})"),
Type::Int16 => format!("{w}.writeI16({var})"),
Type::UInt16 => format!("{w}.writeU16({var})"),
Type::Int32 => format!("{w}.writeI32({var})"),
Type::UInt32 => format!("{w}.writeU32({var})"),
Type::Int64 => format!("{w}.writeI64({var})"),
Type::UInt64 => format!("{w}.writeU64({var})"),
Type::Float32 => format!("{w}.writeF32({var})"),
Type::Float64 => format!("{w}.writeF64({var})"),
Type::Duration => format!("{w}.writeDuration({var})"),
Type::Timestamp => format!("{w}.writeTimestamp({var})"),
Type::Optional { inner_type } => {
let inner_lower = gen_lower_expr("_v", inner_type, namespace, "_w", cfg);
format!("{w}.writeOptional({var}, (_w, _v) => {{ {inner_lower}; }})")
}
Type::Sequence { inner_type } => {
let inner_lower = gen_lower_expr("_v", inner_type, namespace, "_w", cfg);
format!("{w}.writeSequence({var}, (_w, _v) => {{ {inner_lower}; }})")
}
Type::Map {
key_type,
value_type,
} => {
let key_lower = gen_lower_expr("_k", key_type, namespace, "_w", cfg);
let val_lower = gen_lower_expr("_v", value_type, namespace, "_w", cfg);
format!(
"{w}.writeMap({var}, (_w, _k) => {{ {key_lower}; }}, (_w, _v) => {{ {val_lower}; }})"
)
}
Type::Record { name, .. } => {
format!("_lower{name}({w}, {var})")
}
Type::Enum { name, .. } => {
format!("_lower{name}({w}, {var})")
}
Type::Custom { name, builtin, .. } => {
let effective_var = if let Some(ct_cfg) = cfg.custom_types.get(name) {
ct_cfg.lower_expr(var)
} else {
var.to_string()
};
gen_lower_expr(&effective_var, builtin, namespace, w, cfg)
}
Type::Object { name, .. } => {
let clone_fn = fn_clone(namespace, name);
format!("{w}.writeU64(_rt.cloneObjectHandle('{clone_fn}', {var}._handle))")
}
Type::CallbackInterface { .. } => {
format!("{w}.writeU64(_rt.insertCallbackHandle({var}))")
}
}
}
fn gen_lift_expr(reader_var: &str, t: &Type, cfg: &JsBindingsConfig) -> String {
match t {
Type::String => format!("{reader_var}.readString()"),
Type::Bytes => format!("{reader_var}.readBytes()"),
Type::Boolean => format!("{reader_var}.readBool()"),
Type::Int8 => format!("{reader_var}.readI8()"),
Type::UInt8 => format!("{reader_var}.readU8()"),
Type::Int16 => format!("{reader_var}.readI16()"),
Type::UInt16 => format!("{reader_var}.readU16()"),
Type::Int32 => format!("{reader_var}.readI32()"),
Type::UInt32 => format!("{reader_var}.readU32()"),
Type::Int64 => format!("{reader_var}.readI64()"),
Type::UInt64 => format!("{reader_var}.readU64()"),
Type::Float32 => format!("{reader_var}.readF32()"),
Type::Float64 => format!("{reader_var}.readF64()"),
Type::Duration => format!("{reader_var}.readDuration()"),
Type::Timestamp => format!("{reader_var}.readTimestamp()"),
Type::Optional { inner_type } => {
let inner_lift = gen_lift_expr("_r", inner_type, cfg);
format!("{reader_var}.readOptional((_r) => {inner_lift})")
}
Type::Sequence { inner_type } => {
let inner_lift = gen_lift_expr("_r", inner_type, cfg);
format!("{reader_var}.readSequence((_r) => {inner_lift})")
}
Type::Map {
key_type,
value_type,
} => {
let key_lift = gen_lift_expr("_r", key_type, cfg);
let val_lift = gen_lift_expr("_r", value_type, cfg);
format!("{reader_var}.readMap((_r) => {key_lift}, (_r) => {val_lift})")
}
Type::Record { name, .. } => {
format!("_lift{name}({reader_var})")
}
Type::Enum { name, .. } => {
format!("_lift{name}({reader_var})")
}
Type::Custom { name, builtin, .. } => {
let inner = gen_lift_expr(reader_var, builtin, cfg);
if let Some(ct_cfg) = cfg.custom_types.get(name) {
ct_cfg.lift_expr(&inner)
} else {
inner
}
}
Type::Object { name, .. } => {
format!("{name}._fromHandle({reader_var}.readU64())")
}
Type::CallbackInterface { .. } => {
format!("_rt.getCallbackHandle({reader_var}.readU64())")
}
}
}
fn gen_to_ffi(var: &str, t: &Type) -> String {
let t = resolve_custom(t);
match t {
Type::Int64 | Type::UInt64 => format!("BigInt({var})"),
Type::CallbackInterface { .. } => format!("_rt.insertCallbackHandle({var})"),
_ => var.to_string(),
}
}
fn gen_from_ffi(raw: &str, _t: &Type) -> String {
raw.to_string()
}
fn gen_from_ffi_raw(raw: &str, t: &Type) -> String {
let t = resolve_custom(t);
match t {
Type::Boolean => format!("{raw} !== 0"),
_ => raw.to_string(),
}
}
fn element_write_fn(t: &Type) -> &'static str {
let t = resolve_custom(t);
match t {
Type::Int8 => "writeI8Element",
Type::UInt8 => "writeU8Element",
Type::Int16 => "writeI16Element",
Type::UInt16 => "writeU16Element",
Type::Int32 => "writeI32Element",
Type::UInt32 => "writeU32Element",
Type::Int64 => "writeI64Element",
Type::UInt64 => "writeU64Element",
Type::Float32 => "writeF32Element",
Type::Float64 => "writeF64Element",
Type::Boolean => "writeBoolElement",
Type::Object { .. } | Type::CallbackInterface { .. } => "writeHandleElement",
_ => unreachable!("compound types don't use direct element writes"),
}
}
fn element_read_fn(t: &Type) -> &'static str {
let t = resolve_custom(t);
match t {
Type::Int8 => "readI8Element",
Type::UInt8 => "readU8Element",
Type::Int16 => "readI16Element",
Type::UInt16 => "readU16Element",
Type::Int32 => "readI32Element",
Type::UInt32 => "readU32Element",
Type::Int64 => "readI64Element",
Type::UInt64 => "readU64Element",
Type::Float32 => "readF32Element",
Type::Float64 => "readF64Element",
Type::Boolean => "readBoolElement",
Type::Object { .. } | Type::CallbackInterface { .. } => "readHandleElement",
_ => unreachable!("compound types don't use direct element reads"),
}
}
use super::render_helpers::render_literal;
use super::types::{EnumDef, ErrorDef, RecordDef};
pub(super) fn gen_record_lower_fn(
r: &RecordDef,
namespace: &str,
cfg: &JsBindingsConfig,
) -> String {
let name = &r.name;
let mut out = format!("function _lower{name}(w: UniFFIWriter, value: {name}): void {{\n");
for f in &r.fields {
let ts_field = safe_js_identifier(&camel_case(&f.name));
let value_expr = if let Some(ref dv) = f.default {
let lit = match dv {
uniffi_bindgen::interface::DefaultValue::Literal(l) => render_literal(l),
uniffi_bindgen::interface::DefaultValue::Default => "undefined".to_string(),
};
format!("(value.{ts_field} ?? {lit})")
} else {
format!("value.{ts_field}")
};
let lower = gen_lower_expr(&value_expr, &f.type_, namespace, "w", cfg);
out.push_str(&format!(" {lower};\n"));
}
out.push_str("}\n");
out
}
pub(super) fn gen_record_lift_fn(r: &RecordDef, cfg: &JsBindingsConfig) -> String {
let name = &r.name;
let mut out = format!("function _lift{name}(r: UniFFIReader): {name} {{\n return {{\n");
for f in &r.fields {
let ts_field = safe_js_identifier(&camel_case(&f.name));
let lift = gen_lift_expr("r", &f.type_, cfg);
out.push_str(&format!(" {ts_field}: {lift},\n"));
}
out.push_str(" };\n}\n");
out
}
pub(super) fn gen_flat_enum_lower_fn(e: &EnumDef, _namespace: &str) -> String {
let name = &e.name;
let mut out = format!("function _lower{name}(w: UniFFIWriter, value: {name}): void {{\n");
for (i, v) in e.variants.iter().enumerate() {
out.push_str(&format!(
" if (value === '{}') {{ w.writeI32({}); return; }}\n",
v.name,
i + 1
));
}
out.push_str(&format!(
" throw new Error(`Unknown {name} variant: ${{value}}`);\n"
));
out.push_str("}\n");
out
}
pub(super) fn gen_flat_enum_lift_fn(e: &EnumDef) -> String {
let name = &e.name;
let mut out = format!("function _lift{name}(r: UniFFIReader): {name} {{\n");
out.push_str(" const ordinal = r.readI32();\n");
for (i, v) in e.variants.iter().enumerate() {
out.push_str(&format!(
" if (ordinal === {}) return '{}';\n",
i + 1,
v.name
));
}
if e.is_non_exhaustive {
out.push_str(&format!(" return `variant_${{ordinal}}` as {name};\n"));
} else {
out.push_str(&format!(
" throw new Error(`Unknown {name} ordinal: ${{ordinal}}`);\n"
));
}
out.push_str("}\n");
out
}
pub(super) fn gen_data_enum_lower_fn(
e: &EnumDef,
namespace: &str,
cfg: &JsBindingsConfig,
) -> String {
let name = &e.name;
let mut out = format!("function _lower{name}(w: UniFFIWriter, value: {name}): void {{\n");
for (i, v) in e.variants.iter().enumerate() {
let tag = &v.name;
out.push_str(&format!(" if (value.tag === '{tag}') {{\n"));
out.push_str(&format!(" w.writeI32({});\n", i + 1));
for f in &v.fields {
let ts_field = safe_js_identifier(&camel_case(&f.name));
let lower = gen_lower_expr(&format!("value.{ts_field}"), &f.type_, namespace, "w", cfg);
out.push_str(&format!(" {lower};\n"));
}
out.push_str(" return;\n }\n");
}
out.push_str(&format!(
" throw new Error(`Unknown {name} variant: ${{(value as any).tag}}`);\n"
));
out.push_str("}\n");
out
}
pub(super) fn gen_data_enum_lift_fn(e: &EnumDef, cfg: &JsBindingsConfig) -> String {
let name = &e.name;
let mut out = format!("function _lift{name}(r: UniFFIReader): {name} {{\n");
out.push_str(" const ordinal = r.readI32();\n");
for (i, v) in e.variants.iter().enumerate() {
let tag = &v.name;
out.push_str(&format!(" if (ordinal === {}) {{\n", i + 1));
if v.fields.is_empty() {
out.push_str(&format!(" return {{ tag: '{tag}' }};\n"));
} else {
let fields: Vec<String> = v
.fields
.iter()
.map(|f| {
let ts_field = safe_js_identifier(&camel_case(&f.name));
let lift = gen_lift_expr("r", &f.type_, cfg);
format!("{ts_field}: {lift}")
})
.collect();
out.push_str(&format!(
" return {{ tag: '{tag}', {} }};\n",
fields.join(", ")
));
}
out.push_str(" }\n");
}
if e.is_non_exhaustive {
out.push_str(&format!(
" return {{ tag: `variant_${{ordinal}}` }} as {name};\n"
));
} else {
out.push_str(&format!(
" throw new Error(`Unknown {name} ordinal: ${{ordinal}}`);\n"
));
}
out.push_str("}\n");
out
}
pub(super) fn gen_flat_error_lift_fn(e: &ErrorDef) -> String {
let name = &e.name;
let mut out = format!("function _liftError{name}(rb: any): {name} {{\n");
out.push_str(" return _rt.liftFromBuffer(rb, (r) => {\n");
out.push_str(" const ordinal = r.readI32();\n");
for (i, v) in e.variants.iter().enumerate() {
out.push_str(&format!(
" if (ordinal === {}) return new {name}('{}');\n",
i + 1,
v.name
));
}
if e.is_non_exhaustive {
out.push_str(&format!(
" return new {name}(`variant_${{ordinal}}` as any);\n"
));
} else {
out.push_str(&format!(
" throw new Error(`Unknown {name} ordinal: ${{ordinal}}`);\n"
));
}
out.push_str(" });\n");
out.push_str("}\n");
out
}
pub(super) fn gen_rich_error_lift_fn(e: &ErrorDef, cfg: &JsBindingsConfig) -> String {
let name = &e.name;
let variant_type = format!("{name}Variant");
let mut out = format!("function _liftError{name}(rb: any): {name} {{\n");
out.push_str(" return _rt.liftFromBuffer(rb, (r) => {\n");
out.push_str(" const ordinal = r.readI32();\n");
for (i, v) in e.variants.iter().enumerate() {
let tag = &v.name;
out.push_str(&format!(" if (ordinal === {}) {{\n", i + 1));
if v.fields.is_empty() {
out.push_str(&format!(" return new {name}({{ tag: '{tag}' }});\n"));
} else {
let fields: Vec<String> = v
.fields
.iter()
.map(|f| {
let ts_field = safe_js_identifier(&camel_case(&f.name));
let lift = gen_lift_expr("r", &f.type_, cfg);
format!("{ts_field}: {lift}")
})
.collect();
out.push_str(&format!(
" return new {name}({{ tag: '{tag}', {} }});\n",
fields.join(", ")
));
}
out.push_str(" }\n");
}
if e.is_non_exhaustive {
out.push_str(&format!(
" return new {name}({{ tag: `variant_${{ordinal}}` }} as {variant_type});\n"
));
} else {
out.push_str(&format!(
" throw new Error(`Unknown {name} ordinal: ${{ordinal}}`);\n"
));
}
out.push_str(" });\n");
out.push_str("}\n");
out
}
pub(super) fn gen_flat_error_value_lower_fn(e: &ErrorDef, _namespace: &str) -> String {
let name = &e.name;
let mut out = format!("function _lower{name}(w: UniFFIWriter, value: {name}): void {{\n");
for (i, v) in e.variants.iter().enumerate() {
out.push_str(&format!(
" if (value.tag === '{}') {{ w.writeI32({}); return; }}\n",
v.name,
i + 1
));
}
out.push_str(&format!(
" throw new Error(`Unknown {name} variant: ${{value.tag}}`);\n"
));
out.push_str("}\n");
out
}
pub(super) fn gen_flat_error_value_lift_fn(e: &ErrorDef) -> String {
let name = &e.name;
let mut out = format!("function _lift{name}(r: UniFFIReader): {name} {{\n");
out.push_str(" const ordinal = r.readI32();\n");
for (i, v) in e.variants.iter().enumerate() {
out.push_str(&format!(
" if (ordinal === {}) return new {name}('{}');\n",
i + 1,
v.name
));
}
if e.is_non_exhaustive {
out.push_str(&format!(
" return new {name}(`variant_${{ordinal}}` as any);\n"
));
} else {
out.push_str(&format!(
" throw new Error(`Unknown {name} ordinal: ${{ordinal}}`);\n"
));
}
out.push_str("}\n");
out
}
pub(super) fn gen_rich_error_value_lower_fn(
e: &ErrorDef,
namespace: &str,
cfg: &JsBindingsConfig,
) -> String {
let name = &e.name;
let mut out = format!("function _lower{name}(w: UniFFIWriter, value: {name}): void {{\n");
for (i, v) in e.variants.iter().enumerate() {
let tag = &v.name;
out.push_str(&format!(" if (value.variant.tag === '{tag}') {{\n"));
out.push_str(&format!(" w.writeI32({});\n", i + 1));
for f in &v.fields {
let ts_field = safe_js_identifier(&camel_case(&f.name));
let lower = gen_lower_expr(
&format!("value.variant.{ts_field}"),
&f.type_,
namespace,
"w",
cfg,
);
out.push_str(&format!(" {lower};\n"));
}
out.push_str(" return;\n }\n");
}
out.push_str(&format!(
" throw new Error(`Unknown {name} variant: ${{(value.variant as any).tag}}`);\n"
));
out.push_str("}\n");
out
}
pub(super) fn gen_rich_error_value_lift_fn(e: &ErrorDef, cfg: &JsBindingsConfig) -> String {
let name = &e.name;
let variant_type = format!("{name}Variant");
let mut out = format!("function _lift{name}(r: UniFFIReader): {name} {{\n");
out.push_str(" const ordinal = r.readI32();\n");
for (i, v) in e.variants.iter().enumerate() {
let tag = &v.name;
out.push_str(&format!(" if (ordinal === {}) {{\n", i + 1));
if v.fields.is_empty() {
out.push_str(&format!(" return new {name}({{ tag: '{tag}' }});\n"));
} else {
let fields: Vec<String> = v
.fields
.iter()
.map(|f| {
let ts_field = safe_js_identifier(&camel_case(&f.name));
let lift = gen_lift_expr("r", &f.type_, cfg);
format!("{ts_field}: {lift}")
})
.collect();
out.push_str(&format!(
" return new {name}({{ tag: '{tag}', {} }});\n",
fields.join(", ")
));
}
out.push_str(" }\n");
}
if e.is_non_exhaustive {
out.push_str(&format!(
" return new {name}({{ tag: `variant_${{ordinal}}` }} as {variant_type});\n"
));
} else {
out.push_str(&format!(
" throw new Error(`Unknown {name} ordinal: ${{ordinal}}`);\n"
));
}
out.push_str("}\n");
out
}
pub(super) fn gen_object_error_lift_fn(name: &str) -> String {
let mut out = format!("function _liftError{name}(rb: any): {name} {{\n");
out.push_str(" return _rt.liftFromBuffer(rb, (r) => {\n");
out.push_str(&format!(" return {name}._fromHandle(r.readU64());\n"));
out.push_str(" });\n");
out.push_str("}\n");
out
}
pub(super) fn gen_ffi_call(
ffi_name: &str,
namespace: &str,
args: &[(&str, &Type)], return_type: Option<&Type>,
throws_name: Option<&str>,
indent: &str,
cfg: &JsBindingsConfig,
) -> String {
let mut lines = Vec::new();
let arg_elements: usize = args.iter().map(|(_, t)| element_count(t)).sum();
let ret_elements = return_type.map_or(0, element_count);
let total_ret_elements = ret_elements + CALL_STATUS_ELEMENTS;
let mut rb_setups = Vec::new();
for (var, t) in args {
if is_rust_buffer_type(t) {
let rb_var = format!("_rb_{}", var.replace('.', "_"));
let lower = gen_top_level_lower(var, t, namespace, cfg);
rb_setups.push(format!("{indent}const {rb_var} = {lower};"));
}
}
lines.extend(rb_setups);
if arg_elements > 0 {
lines.push(format!(
"{indent}const _argPtr = _rt.scratchAlloc({} * 8);",
arg_elements
));
} else {
lines.push(format!("{indent}const _argPtr = 0;"));
}
let mut offset = 0;
for (var, t) in args {
let offset_expr = if offset == 0 {
"_argPtr".to_string()
} else {
format!("_argPtr + {}", offset * 8)
};
if is_rust_buffer_type(t) {
let rb_var = format!("_rb_{}", var.replace('.', "_"));
lines.push(format!(
"{indent}_rt.writeRustBufferElements({offset_expr}, {rb_var});"
));
} else {
let write_fn = element_write_fn(t);
let value = gen_to_ffi(var, t);
lines.push(format!("{indent}_rt.{write_fn}({offset_expr}, {value});"));
}
offset += element_count(t);
}
lines.push(format!(
"{indent}const _retPtr = _rt.scratchAlloc({} * 8);",
total_ret_elements
));
lines.push(format!("{indent}try {{"));
lines.push(format!(
"{indent} _rt.call('{ffi_name}', _argPtr, _retPtr);"
));
let status_offset = if ret_elements > 0 {
format!("_retPtr + {}", ret_elements * 8)
} else {
"_retPtr".to_string()
};
if let Some(err_name) = throws_name {
lines.push(format!(
"{indent} _rt.checkCallStatus({status_offset}, (rb) => _liftError{err_name}(rb));"
));
} else {
lines.push(format!("{indent} _rt.checkCallStatus({status_offset});"));
}
if let Some(ret_type) = return_type {
let result = gen_read_return(ret_type, "_retPtr", cfg);
lines.push(format!("{indent} const _result = {result};"));
lines.push(format!("{indent} return _result;"));
} else {
}
lines.push(format!("{indent}}} finally {{"));
lines.push(format!("{indent} _rt.scratchReset();"));
lines.push(format!("{indent}}}"));
lines.join("\n")
}
pub(super) fn gen_async_ffi_call(
ffi_name: &str,
namespace: &str,
args: &[(&str, &Type)],
return_type: Option<&Type>,
throws_name: Option<&str>,
indent: &str,
cfg: &JsBindingsConfig,
) -> String {
let mut lines = Vec::new();
let suffix = rust_future_type_suffix(return_type);
let poll_fn = rust_future_poll(namespace, suffix);
let complete_fn = rust_future_complete(namespace, suffix);
let free_fn = rust_future_free(namespace, suffix);
let uses_retptr = rust_future_complete_uses_retptr(suffix);
let arg_elements: usize = args.iter().map(|(_, t)| element_count(t)).sum();
for (var, t) in args {
if is_rust_buffer_type(t) {
let rb_var = format!("_rb_{}", var.replace('.', "_"));
let lower = gen_top_level_lower(var, t, namespace, cfg);
lines.push(format!("{indent}const {rb_var} = {lower};"));
}
}
if arg_elements > 0 {
lines.push(format!(
"{indent}const _argPtr = _rt.scratchAlloc({} * 8);",
arg_elements
));
} else {
lines.push(format!("{indent}const _argPtr = 0;"));
}
let mut offset = 0;
for (var, t) in args {
let offset_expr = if offset == 0 {
"_argPtr".to_string()
} else {
format!("_argPtr + {}", offset * 8)
};
if is_rust_buffer_type(t) {
let rb_var = format!("_rb_{}", var.replace('.', "_"));
lines.push(format!(
"{indent}_rt.writeRustBufferElements({offset_expr}, {rb_var});"
));
} else {
let write_fn = element_write_fn(t);
let value = gen_to_ffi(var, t);
lines.push(format!("{indent}_rt.{write_fn}({offset_expr}, {value});"));
}
offset += element_count(t);
}
lines.push(format!("{indent}const _retPtr = _rt.scratchAlloc(1 * 8);"));
lines.push(format!("{indent}_rt.call('{ffi_name}', _argPtr, _retPtr);"));
lines.push(format!(
"{indent}const _futureHandle = _rt.readHandleElement(_retPtr);"
));
lines.push(format!("{indent}try {{"));
lines.push(format!(
"{indent} await _rt.pollToReady(_futureHandle, '{poll_fn}');"
));
lines.push(format!("{indent} _rt.scratchReset();"));
if uses_retptr {
lines.push(format!(
"{indent} const _rbRetPtr = _rt.scratchAlloc({RUST_BUFFER_STRUCT_SIZE});"
));
lines.push(format!(
"{indent} const _statusPtr = _rt.scratchAlloc({RUST_CALL_STATUS_STRUCT_SIZE});"
));
lines.push(format!(
"{indent} _rt._writeRustCallStatusStruct(_statusPtr);"
));
lines.push(format!(
"{indent} (_rt.getExport('{complete_fn}') as any)(_rbRetPtr, _futureHandle, _statusPtr);"
));
} else if suffix == "void" {
lines.push(format!(
"{indent} const _statusPtr = _rt.scratchAlloc({RUST_CALL_STATUS_STRUCT_SIZE});"
));
lines.push(format!(
"{indent} _rt._writeRustCallStatusStruct(_statusPtr);"
));
lines.push(format!(
"{indent} (_rt.getExport('{complete_fn}') as any)(_futureHandle, _statusPtr);"
));
} else {
lines.push(format!(
"{indent} const _statusPtr = _rt.scratchAlloc({RUST_CALL_STATUS_STRUCT_SIZE});"
));
lines.push(format!(
"{indent} _rt._writeRustCallStatusStruct(_statusPtr);"
));
lines.push(format!(
"{indent} const _result = (_rt.getExport('{complete_fn}') as any)(_futureHandle, _statusPtr);"
));
}
let status_check = if let Some(err_name) = throws_name {
format!("{indent} _rt.checkCallStatus(_statusPtr, (rb) => _liftError{err_name}(rb));",)
} else {
format!("{indent} _rt.checkCallStatus(_statusPtr);")
};
lines.push(status_check);
if uses_retptr {
let rb = "_rt._readRustBufferStruct(_rbRetPtr)";
if let Some(ret_type) = return_type {
let lift = gen_top_level_lift_from_rb(ret_type, rb, cfg);
lines.push(format!("{indent} const _result = {lift};"));
lines.push(format!("{indent} return _result;"));
}
} else if suffix != "void" {
if let Some(ret_type) = return_type {
let raw = gen_from_ffi_raw("_result", ret_type);
lines.push(format!("{indent} return {raw};"));
}
}
lines.push(format!("{indent}}} finally {{"));
lines.push(format!("{indent} _rt.scratchReset();"));
lines.push(format!(
"{indent} (_rt.getExport('{free_fn}') as any)(_futureHandle);"
));
lines.push(format!("{indent}}}"));
lines.join("\n")
}
const RUST_BUFFER_STRUCT_SIZE: usize = 24;
const RUST_CALL_STATUS_STRUCT_SIZE: usize = 32;
fn gen_top_level_lift_from_rb(t: &Type, rb_expr: &str, cfg: &JsBindingsConfig) -> String {
match t {
Type::String => format!("_rt.liftString({rb_expr})"),
Type::Custom { builtin, .. } => gen_top_level_lift_from_rb(builtin, rb_expr, cfg),
_ => {
let lift_body = gen_lift_expr("r", t, cfg);
format!("_rt.liftFromBuffer({rb_expr}, (r) => {{ return {lift_body}; }})")
}
}
}
use super::types::CallbackInterfaceDef;
fn wasm_type_str(t: &Type) -> &'static str {
let t = resolve_custom(t);
match t {
Type::Int8
| Type::UInt8
| Type::Int16
| Type::UInt16
| Type::Int32
| Type::UInt32
| Type::Boolean => "i32",
Type::Int64 | Type::UInt64 => "i64",
Type::Float32 => "f32",
Type::Float64 => "f64",
Type::Object { .. } | Type::CallbackInterface { .. } => "i64",
Type::String
| Type::Bytes
| Type::Duration
| Type::Timestamp
| Type::Optional { .. }
| Type::Sequence { .. }
| Type::Map { .. }
| Type::Record { .. }
| Type::Enum { .. } => "i32",
Type::Custom { .. } => unreachable!("resolve_custom strips Custom"),
}
}
fn is_callback_ptr_type(t: &Type) -> bool {
let t = resolve_custom(t);
matches!(
t,
Type::String
| Type::Bytes
| Type::Duration
| Type::Timestamp
| Type::Optional { .. }
| Type::Sequence { .. }
| Type::Map { .. }
| Type::Record { .. }
| Type::Enum { .. }
)
}
fn gen_callback_arg_lift(var: &str, t: &Type, cfg: &JsBindingsConfig) -> String {
let t = resolve_custom(t);
match t {
Type::String => {
format!(
"(() => {{ const _rb = _rt._readRustBufferStruct({var}); return _rt._readUtf8(_rb.dataPtr, _rb.len); }})()"
)
}
_ if is_callback_ptr_type(t) => {
let lift_body = gen_lift_expr("_r", t, cfg);
format!(
"_rt.liftFromBuffer(_rt._readRustBufferStruct({var}), (_r) => {{ return {lift_body}; }})"
)
}
Type::Boolean => format!("{var} !== 0"),
_ => var.to_string(),
}
}
fn gen_callback_ret_lower(
result_var: &str,
out_ptr: &str,
t: &Type,
namespace: &str,
cfg: &JsBindingsConfig,
) -> String {
let t = resolve_custom(t);
match t {
Type::String => {
format!(
"const _retRb = _rt.lowerString({result_var}); _rt._writeRustBufferStruct({out_ptr}, _retRb);"
)
}
_ if is_callback_ptr_type(t) => {
let lower_body = gen_lower_expr(result_var, t, namespace, "w", cfg);
format!(
"const _retRb = _rt.lowerIntoBuffer((w) => {{ {lower_body}; }}); _rt._writeRustBufferStruct({out_ptr}, _retRb);"
)
}
Type::Boolean => {
format!("_rt._dv().setInt8({out_ptr}, {result_var} ? 1 : 0);")
}
Type::Int8 => format!("_rt._dv().setInt8({out_ptr}, {result_var});"),
Type::UInt8 => format!("_rt._dv().setUint8({out_ptr}, {result_var});"),
Type::Int16 => format!("_rt._dv().setInt16({out_ptr}, {result_var}, true);"),
Type::UInt16 => format!("_rt._dv().setUint16({out_ptr}, {result_var}, true);"),
Type::Int32 => format!("_rt._dv().setInt32({out_ptr}, {result_var}, true);"),
Type::UInt32 => format!("_rt._dv().setUint32({out_ptr}, {result_var}, true);"),
Type::Int64 => format!("_rt._dv().setBigInt64({out_ptr}, {result_var}, true);"),
Type::UInt64 => format!("_rt._dv().setBigUint64({out_ptr}, {result_var}, true);"),
Type::Float32 => format!("_rt._dv().setFloat32({out_ptr}, {result_var}, true);"),
Type::Float64 => format!("_rt._dv().setFloat64({out_ptr}, {result_var}, true);"),
Type::Object { .. } | Type::CallbackInterface { .. } => {
format!("_rt._dv().setBigUint64({out_ptr}, {result_var}, true);")
}
Type::Custom { .. } => unreachable!("resolve_custom strips Custom"),
_ => unreachable!("unsupported callback return type: {t:?}"),
}
}
pub(super) fn gen_callback_vtable_registration(
cb: &CallbackInterfaceDef,
namespace: &str,
cfg: &JsBindingsConfig,
) -> String {
let mut out = String::new();
let cb_name = &cb.name;
out.push_str(&format!(
"// --- VTable for callback interface {cb_name} ---\n"
));
out.push_str(&format!(
"_rt.registerCallbackVTable('{cb_name}', '{init_fn}', [\n",
init_fn = fn_init_callback_vtable(namespace, cb_name),
));
out.push_str(" {\n");
out.push_str(" params: ['i64'], results: [],\n");
out.push_str(" fn: (handle: bigint) => { _rt.removeCallbackHandle(handle); },\n");
out.push_str(" },\n");
out.push_str(" {\n");
out.push_str(" params: ['i64'], results: ['i64'],\n");
out.push_str(" fn: (handle: bigint) => { return _rt.cloneCallbackHandle(handle); },\n");
out.push_str(" },\n");
for m in &cb.methods {
let method_name = &m.name;
let ts_method = safe_js_identifier(&camel_case(method_name));
let mut wasm_params = vec!["'i64'".to_string()]; for arg in &m.args {
wasm_params.push(format!("'{}'", wasm_type_str(&arg.type_)));
}
let has_return = m.return_type.is_some();
if has_return {
wasm_params.push("'i32'".to_string()); }
wasm_params.push("'i32'".to_string());
out.push_str(" {\n");
out.push_str(&format!(
" params: [{}], results: [],\n",
wasm_params.join(", ")
));
let mut param_names = vec!["_handle: bigint".to_string()];
for (i, arg) in m.args.iter().enumerate() {
let wt = wasm_type_str(&arg.type_);
let ts_type = if wt == "i64" { "bigint" } else { "number" };
param_names.push(format!("_arg{i}: {ts_type}"));
}
if has_return {
param_names.push("_outPtr: number".to_string());
}
param_names.push("_statusPtr: number".to_string());
out.push_str(&format!(" fn: ({}) => {{\n", param_names.join(", ")));
out.push_str(" const _savedScratch = _rt.scratchSave();\n");
out.push_str(" try {\n");
out.push_str(" const _obj = _rt.getCallbackHandle(_handle) as any;\n");
let mut call_args = Vec::new();
for (i, arg) in m.args.iter().enumerate() {
let arg_var = format!("_arg{i}");
let lifted = gen_callback_arg_lift(&arg_var, &arg.type_, cfg);
let lifted_var = format!("_lifted{i}");
out.push_str(&format!(" const {lifted_var} = {lifted};\n"));
call_args.push(lifted_var);
}
let call_expr = format!("_obj.{ts_method}({})", call_args.join(", "));
if has_return {
out.push_str(&format!(" const _result = {call_expr};\n"));
let ret_type = m.return_type.as_ref().unwrap();
let lower_code = gen_callback_ret_lower("_result", "_outPtr", ret_type, namespace, cfg);
out.push_str(&format!(" {lower_code}\n"));
} else {
out.push_str(&format!(" {call_expr};\n"));
}
out.push_str(" _rt._writeCallStatusSuccess(_statusPtr);\n");
out.push_str(" } catch (_e) {\n");
out.push_str(" _rt._writeCallStatusPanic(_statusPtr, _e);\n");
out.push_str(" } finally {\n");
out.push_str(" _rt.scratchRestore(_savedScratch);\n");
out.push_str(" }\n");
out.push_str(" },\n");
out.push_str(" },\n");
}
out.push_str("]);\n");
out
}