Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use indexmap::IndexMap;
use anyhow::Result;
use askama::Template;
use uniffi_bindgen::backend::filters::to_askama_error;
use uniffi_pipeline::{AsRef, Node};
use crate::Config;
/// Initial IR, this stores the metadata and other data
#[derive(Debug, Clone, Node)]
pub struct Root {
/// In library mode, we get the name of the library file for free.
pub cdylib: Option<String>,
pub modules: IndexMap<String, Module>,
pub cpp_scaffolding: CppScaffolding,
pub module_docs: Vec<ApiModuleDocs>,
}
#[derive(Debug, Clone, Node, Template)]
#[template(path = "cpp/UniFFIScaffolding.cpp", escape = "none")]
pub struct CppScaffolding {
pub ffi_definitions: CombinedItems<FfiDefinition>,
pub scaffolding_calls: CombinedItems<ScaffoldingCall>,
pub pointer_types: CombinedItems<PointerType>,
pub callback_interfaces: CombinedItems<CppCallbackInterface>,
pub async_callback_method_handler_bases: CombinedItems<AsyncCallbackMethodHandlerBase>,
}
// A Scaffolding call implemented in the C++ code
#[derive(Debug, Clone, Node)]
pub struct ScaffoldingCall {
pub id: u64,
pub ffi_func: FfiFunction,
pub handler_class_name: String,
pub arguments: Vec<FfiValueArgument>,
pub return_ty: Option<FfiValueReturnType>,
}
/// FFI argument that's handled by one of the `FfiValue*` classes in the C++ code
#[derive(Debug, Clone, Node)]
pub struct FfiValueArgument {
pub name: String,
/// C++ class field name
pub field_name: String,
pub ffi_value_class: String,
/// Is this argument for a method receiver?
pub receiver: bool,
pub ty: FfiTypeNode,
}
/// FFI return value that's handled by one of the `FfiValue*` classes in the C++ code
#[derive(Debug, Clone, Node)]
pub struct FfiValueReturnType {
pub ffi_value_class: String,
pub ty: FfiTypeNode,
}
// A `PointerType` const to define in the C++ code
#[derive(Debug, Clone, Node)]
pub struct PointerType {
pub id: u64,
pub name: String,
pub label: String,
pub ffi_value_class: String,
pub ffi_func_clone: RustFfiFunctionName,
pub ffi_func_free: RustFfiFunctionName,
pub trait_interface_info: Option<PointerTypeTraitInterfaceInfo>,
}
#[derive(Debug, Clone, Node)]
pub struct PointerTypeTraitInterfaceInfo {
pub free_fn: String,
}
// Used to generate the C++ callback interface code
#[derive(Debug, Clone, Node)]
pub struct CppCallbackInterface {
pub id: u64,
pub name: String,
/// C++ class that handles:
/// - Lowering the JS value, storing it, then passing the value to Rust
/// - Storing values from Rust, then lifting them to JS
/// - Cleaning up the stored value when we fail to lower/lift other values.
///
/// This is only generated for regular callback interfaces. For trait interfaces, the FfiValue
/// class is defined in `PointerType.cpp`
pub ffi_value_class: Option<String>,
/// Name of the C++ variable that stores the UniFFICallbackHandler instance
pub handler_var: String,
// Name of the C++ static variable for the VTable
pub vtable_var: String,
/// Rust scaffolding function to initialize the VTable
pub init_fn: RustFfiFunctionName,
/// Name of the function generated by uniffi-bindgen-gecko-js to free a callback interface
/// handle.
pub free_fn: String,
pub vtable_struct_type: FfiTypeNode,
pub methods: Vec<CppCallbackInterfaceMethod>,
}
// Used to generate the C++ code to handle a callback method
#[derive(Debug, Clone, Node)]
pub struct CppCallbackInterfaceMethod {
/// Name of the handler function
pub fn_name: String,
pub async_data: Option<CppCallbackInterfaceMethodAsyncData>,
pub base_class_name: String,
/// Name of the subclass
pub handler_class_name: String,
pub ffi_func: FfiFunctionType,
pub arguments: Vec<FfiValueArgument>,
pub return_ty: Option<FfiValueReturnType>,
pub out_pointer_ty: FfiTypeNode,
}
#[derive(Debug, Clone, Node)]
pub struct CppCallbackInterfaceMethodAsyncData {
pub callback_handler_base_class: String,
pub complete_callback_type_name: String,
pub result_type_name: String,
}
/// Base class for an async callback method handler
///
/// This derives from `UniffiCallbackMethodHandlerBase` and adds support for returning data to
/// Rust. The final callback method handler derives from this and adds support for argument
/// handling.
///
/// Splitting the classes this way reduces memory usage. We only need to create one of these base
/// classes for each return type rather than one per method.
#[derive(Debug, Clone, Node)]
pub struct AsyncCallbackMethodHandlerBase {
pub class_name: String,
pub complete_callback_type_name: String,
pub result_type_name: String,
pub return_type: Option<FfiValueReturnType>,
}
#[derive(Debug, Clone, Node, Template)]
#[template(path = "js/Module.sys.mjs", escape = "none")]
pub struct Module {
pub name: String,
pub config: Config,
pub js_name: String,
pub js_filename: String,
pub fixture: bool,
pub crate_name: String,
pub docstring: Option<String>,
pub js_docstring: String,
pub functions: Vec<Function>,
pub type_definitions: Vec<TypeDefinition>,
pub ffi_definitions: Vec<FfiDefinition>,
pub checksums: Vec<Checksum>,
pub ffi_rustbuffer_alloc: RustFfiFunctionName,
pub ffi_rustbuffer_from_bytes: RustFfiFunctionName,
pub ffi_rustbuffer_free: RustFfiFunctionName,
pub ffi_rustbuffer_reserve: RustFfiFunctionName,
pub ffi_uniffi_contract_version: RustFfiFunctionName,
pub string_type_node: TypeNode,
pub has_callback_interface: bool,
pub imports: Vec<String>,
}
#[derive(Debug, Clone, Node)]
pub enum TypeDefinition {
Interface(Interface),
CallbackInterface(CallbackInterface),
Record(Record),
Enum(Enum),
Custom(CustomType),
Simple(TypeNode),
Optional(OptionalType),
Sequence(SequenceType),
Map(MapType),
External(ExternalType),
}
#[derive(Debug, Clone, Node)]
pub struct NamespaceMetadata {
pub crate_name: String,
pub name: String,
}
#[derive(Debug, Clone, Node)]
pub struct Function {
pub name: String,
pub callable: Callable,
pub docstring: Option<String>,
pub js_docstring: String,
}
#[derive(Debug, Clone, Node)]
pub struct Constructor {
pub name: String,
pub self_name: String,
pub callable: Callable,
pub docstring: Option<String>,
pub js_docstring: String,
}
#[derive(Debug, Clone, Node)]
pub struct Method {
pub name: String,
pub self_name: String,
pub callable: Callable,
pub docstring: Option<String>,
pub js_docstring: String,
}
/// Common data from Function/Method/Constructor
#[derive(Debug, Clone, Node)]
pub struct Callable {
pub name: String,
pub async_data: Option<AsyncData>,
pub is_js_async: bool,
// UniFFIScaffolding method used to invoke this callable
pub uniffi_scaffolding_method: String,
pub kind: CallableKind,
pub arguments: Vec<Argument>,
pub return_type: ReturnType,
pub throws_type: ThrowsType,
pub checksum: Option<u16>,
pub ffi_func: RustFfiFunctionName,
pub id: u64,
}
#[derive(Debug, Clone, Node)]
pub enum CallableKind {
Function,
Method {
interface_name: String,
ffi_converter: String,
},
Constructor {
interface_name: String,
primary: bool,
},
VTableMethod {
trait_name: String,
},
}
#[derive(Debug, Clone, Node)]
pub struct ReturnType {
pub ty: Option<TypeNode>,
}
#[derive(Debug, Clone, Node)]
pub struct ThrowsType {
pub ty: Option<TypeNode>,
}
#[derive(Debug, Clone, Node)]
pub struct AsyncData {
pub ffi_rust_future_poll: RustFfiFunctionName,
pub ffi_rust_future_cancel: RustFfiFunctionName,
pub ffi_rust_future_free: RustFfiFunctionName,
pub ffi_rust_future_complete: RustFfiFunctionName,
pub ffi_foreign_future_complete: FfiFunctionTypeName,
pub ffi_foreign_future_result: FfiStructName,
}
#[derive(Debug, Clone, Node, AsRef)]
pub struct Argument {
pub name: String,
#[as_ref]
pub ty: TypeNode,
pub by_ref: bool,
pub optional: bool,
pub default: Option<LiteralNode>,
}
#[derive(Debug, Clone, Node)]
pub struct LiteralNode {
pub js_lit: String,
pub lit: Literal,
}
#[derive(Debug, Clone, Node)]
pub enum Literal {
Boolean(bool),
String(String),
// Integers are represented as the widest representation we can.
// Number formatting vary with language and radix, so we avoid a lot of parsing and
// formatting duplication by using only signed and unsigned variants.
UInt(u64, Radix, TypeNode),
Int(i64, Radix, TypeNode),
// Pass the string representation through as typed in the UDL.
// This avoids a lot of uncertainty around precision and accuracy,
// though bindings for languages less sophisticated number parsing than WebIDL
// will have to do extra work.
Float(String, TypeNode),
Enum(String, TypeNode),
EmptySequence,
EmptyMap,
None,
Some { inner: Box<Literal> },
}
// Represent the radix of integer literal values.
// We preserve the radix into the generated bindings for readability reasons.
#[derive(Debug, Clone, Node)]
pub enum Radix {
Decimal = 10,
Octal = 8,
Hexadecimal = 16,
}
#[derive(Debug, Clone, Node, AsRef)]
pub struct Record {
pub name: String,
pub remote: bool, // only used when generating scaffolding from UDL
pub fields: Vec<Field>,
pub docstring: Option<String>,
pub js_docstring: String,
#[as_ref]
pub self_type: TypeNode,
}
#[derive(Debug, Clone, Node, AsRef)]
pub struct Field {
pub name: String,
#[as_ref]
pub ty: TypeNode,
pub default: Option<LiteralNode>,
pub docstring: Option<String>,
pub js_docstring: String,
}
#[derive(Debug, Clone, Node)]
pub enum EnumShape {
Enum,
Error { flat: bool },
}
#[derive(Debug, Clone, Node, AsRef)]
pub struct Enum {
pub name: String,
pub is_flat: bool,
pub shape: EnumShape,
pub remote: bool,
pub variants: Vec<Variant>,
pub discr_type: Option<TypeNode>,
pub resolved_discr_type: TypeNode,
pub non_exhaustive: bool,
pub js_docstring: String,
pub docstring: Option<String>,
#[as_ref]
pub self_type: TypeNode,
}
#[derive(Debug, Clone, Node)]
pub struct Variant {
pub name: String,
pub discr: Option<LiteralNode>,
pub resolved_discr: LiteralNode,
pub fields: Vec<Field>,
pub docstring: Option<String>,
pub js_docstring: String,
}
#[derive(Debug, Clone, Node, AsRef)]
pub struct Interface {
pub name: String,
pub object_id: u64,
pub constructors: Vec<Constructor>,
pub methods: Vec<Method>,
pub uniffi_traits: Vec<UniffiTrait>,
pub trait_impls: Vec<ObjectTraitImpl>,
pub remote: bool, // only used when generating scaffolding from UDL
pub imp: ObjectImpl,
pub docstring: Option<String>,
pub js_docstring: String,
#[as_ref]
pub self_type: TypeNode,
pub vtable: Option<VTable>,
pub ffi_func_clone: RustFfiFunctionName,
pub ffi_func_free: RustFfiFunctionName,
}
#[derive(Debug, Clone, Node, AsRef)]
pub struct CallbackInterface {
pub name: String,
pub vtable: VTable,
pub docstring: Option<String>,
pub js_docstring: String,
#[as_ref]
pub self_type: TypeNode,
}
#[derive(Debug, Clone, Node)]
pub struct VTable {
/// Name of the interface this VTable is for
pub interface_name: String,
/// Was this generated for a CallbackInterface?
pub callback_interface: bool,
pub callback_interface_id: u64,
/// Name of the JS variable that stores the UniFFICallbackHandler instance
pub js_handler_var: String,
pub struct_type: FfiTypeNode,
pub init_fn: RustFfiFunctionName,
pub methods: Vec<VTableMethod>,
}
/// Single method in a vtable
#[derive(Debug, Clone, Node)]
pub struct VTableMethod {
pub callable: Callable,
pub ffi_type: FfiTypeNode,
}
#[derive(Debug, Clone, Node)]
pub enum UniffiTrait {
Debug { fmt: Method },
Display { fmt: Method },
Eq { eq: Method, ne: Method },
Hash { hash: Method },
}
#[derive(Debug, Clone, Node)]
pub struct ObjectTraitImpl {
pub ty: TypeNode,
pub trait_name: String,
pub tr_module_name: Option<String>,
}
#[derive(Debug, Clone, Node, AsRef)]
pub struct CustomType {
pub name: String,
pub builtin: TypeNode,
pub docstring: Option<String>,
pub js_docstring: String,
#[as_ref]
pub self_type: TypeNode,
pub type_name: Option<String>,
pub lift_expr: Option<String>,
pub lower_expr: Option<String>,
}
#[derive(Debug, Clone, Node, AsRef)]
pub struct OptionalType {
pub inner: TypeNode,
#[as_ref]
pub self_type: TypeNode,
}
#[derive(Debug, Clone, Node, AsRef)]
pub struct SequenceType {
pub inner: TypeNode,
#[as_ref]
pub self_type: TypeNode,
}
#[derive(Debug, Clone, Node, AsRef)]
pub struct MapType {
pub key: TypeNode,
pub value: TypeNode,
#[as_ref]
pub self_type: TypeNode,
}
#[derive(Debug, Clone, Node, AsRef)]
pub struct ExternalType {
pub module_name: String,
pub name: String,
#[as_ref]
pub self_type: TypeNode,
}
#[derive(Debug, Clone, Node, AsRef)]
#[as_ref]
pub struct TypeNode {
pub ty: Type,
/// Name of the JS class for this type (only set for user-defined types like
/// enums/records/interfaces).
pub class_name: Option<String>,
pub jsdoc_name: String,
pub canonical_name: String,
pub ffi_converter: String,
pub is_used_as_error: bool,
pub ffi_type: FfiTypeNode,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Node)]
pub enum Type {
// Primitive types.
UInt8,
Int8,
UInt16,
Int16,
UInt32,
Int32,
UInt64,
Int64,
Float32,
Float64,
Boolean,
String,
Bytes,
Timestamp,
Duration,
Interface {
// The module path to the object
module_name: String,
// The name in the "type universe"
name: String,
// How the object is implemented.
imp: ObjectImpl,
},
// Types defined in the component API, each of which has a string name.
Record {
module_name: String,
name: String,
},
Enum {
module_name: String,
name: String,
},
CallbackInterface {
module_name: String,
name: String,
},
// Structurally recursive types.
Optional {
inner_type: Box<Type>,
},
Sequence {
inner_type: Box<Type>,
},
Map {
key_type: Box<Type>,
value_type: Box<Type>,
},
// Custom type on the scaffolding side
Custom {
module_name: String,
name: String,
builtin: Box<Type>,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Node)]
pub enum ObjectImpl {
// A single Rust type
Struct,
// A trait that's can be implemented by Rust types
Trait,
// A trait + a callback interface -- can be implemented by both Rust and foreign types.
CallbackTrait,
}
#[derive(Debug, Clone, Node)]
pub enum FfiDefinition {
/// FFI Function exported in the Rust library
RustFunction(FfiFunction),
/// FFI Function definition used in the interface, language, for example a callback interface method.
FunctionType(FfiFunctionType),
/// Struct definition used in the interface, for example a callback interface Vtable.
Struct(FfiStruct),
}
#[derive(Debug, Clone, Node, PartialEq, Eq)]
pub struct RustFfiFunctionName(pub String);
#[derive(Debug, Clone, Node, PartialEq, Eq)]
pub struct FfiStructName(pub String);
#[derive(Debug, Clone, Node, PartialEq, Eq)]
pub struct FfiFunctionTypeName(pub String);
#[derive(Debug, Clone, Node)]
pub struct FfiFunction {
pub name: RustFfiFunctionName,
pub async_data: Option<AsyncData>,
pub arguments: Vec<FfiArgument>,
pub return_type: FfiReturnType,
pub has_rust_call_status_arg: bool,
pub kind: FfiFunctionKind,
}
#[derive(Debug, Clone, PartialEq, Eq, Node)]
pub enum FfiFunctionKind {
Scaffolding,
ObjectClone,
ObjectFree,
RustFuturePoll,
RustFutureComplete,
RustFutureCancel,
RustFutureFree,
RustBufferFromBytes,
RustBufferFree,
RustBufferAlloc,
RustBufferReserve,
RustVtableInit,
UniffiContractVersion,
Checksum,
}
#[derive(Debug, Clone, Node)]
pub struct FfiFunctionType {
pub name: FfiFunctionTypeName,
pub arguments: Vec<FfiArgument>,
pub return_type: FfiReturnType,
pub has_rust_call_status_arg: bool,
}
#[derive(Debug, Clone, Node)]
pub struct FfiReturnType {
pub ty: Option<FfiTypeNode>,
pub type_name: String,
}
#[derive(Debug, Clone, Node)]
pub struct FfiStruct {
pub name: FfiStructName,
pub fields: Vec<FfiField>,
}
#[derive(Debug, Clone, Node)]
pub struct FfiField {
pub name: String,
pub ty: FfiTypeNode,
}
#[derive(Debug, Clone, Node)]
pub struct FfiArgument {
pub name: String,
pub ty: FfiTypeNode,
}
#[derive(Debug, Clone, Node)]
pub struct FfiTypeNode {
#[node(wraps)]
pub ty: FfiType,
pub type_name: String,
}
#[derive(Debug, Clone, Node)]
pub enum FfiType {
UInt8,
Int8,
UInt16,
Int16,
UInt32,
Int32,
UInt64,
Int64,
Float32,
Float64,
RustArcPtr {
module_name: String,
object_name: String,
},
RustBuffer(Option<String>),
ForeignBytes,
Function(FfiFunctionTypeName),
Struct(FfiStructName),
Handle(HandleKind),
RustCallStatus,
Reference(Box<FfiType>),
MutReference(Box<FfiType>),
VoidPointer,
}
#[derive(Debug, Clone, Node, PartialEq, Eq, Hash)]
pub enum HandleKind {
RustFuture,
ForeignFuture,
ForeignFutureCallbackData,
CallbackInterface {
module_name: String,
interface_name: String,
},
}
#[derive(Debug, Clone, Node)]
pub struct Checksum {
pub fn_name: RustFfiFunctionName,
pub checksum: u16,
}
#[derive(Debug, Clone, Node, Template)]
#[template(path = "api-doc.md", escape = "none")]
pub struct ApiModuleDocs {
pub filename: String,
pub jsdoc_module_name: String,
pub module_name: String,
pub classes: Vec<String>,
pub functions: Vec<String>,
}
/// Combines fixture and non-fixture template items
#[derive(Debug, Clone, Node)]
pub struct CombinedItems<T> {
pub items: Vec<T>,
pub fixture_items: Vec<T>,
}
impl<T> CombinedItems<T> {
/// Create a new CombinedItems value
///
/// F is a function that finds items in module and pushes them to a vec.
pub fn new<F>(root: &mut Root, mut f: F) -> Self
where
F: FnMut(&mut Module, &mut CombinedItemsIdGenerator, &mut Vec<T>),
{
Self::try_new(root, |module, id_generator, items| {
f(module, id_generator, items);
Ok(())
})
.unwrap()
}
pub fn try_new<F>(root: &mut Root, mut f: F) -> Result<Self>
where
F: FnMut(&mut Module, &mut CombinedItemsIdGenerator, &mut Vec<T>) -> Result<()>,
{
// Use 2 separate counters for "real" Rust components vs fixtures.
let mut combined_items = Self {
items: vec![],
fixture_items: vec![],
};
let mut id_generator = CombinedItemsIdGenerator::default();
// Process non-fixture items first for a couple reasons:
// * It works better when a pass wants to de-dupe items, like we do for FFI definitions,
// and an item appears in both `items` and `fixture_items`. This way the de-duped item
// won't be disabled by the if guard and also it will appear on top of the item list,
// which avoids issues with dependent definitions.
// * It means the IDs get grouped together, which can make for a more efficient `switch`
// statement.
root.try_visit_mut(|module: &mut Module| {
if !module.fixture {
f(module, &mut id_generator, &mut combined_items.items)
} else {
Ok(())
}
})?;
root.try_visit_mut(|module: &mut Module| {
if module.fixture {
f(module, &mut id_generator, &mut combined_items.fixture_items)
} else {
Ok(())
}
})?;
Ok(combined_items)
}
pub fn sort_by_key<F, K>(&mut self, f: F)
where
F: Fn(&T) -> K,
K: Ord,
{
self.items.sort_by_key(&f);
self.fixture_items.sort_by_key(&f);
}
/// Iterate over child items
/// Each item is the tuple (preprocssor_condition, <T>, preprocssor_condition_end), where
/// `preprocssor_condition` is the preprocessor preprocssor_condition that should control if
/// the items are included.
fn iter(&self) -> impl Iterator<Item = (String, &[T], String)> {
vec![
("".to_string(), &*self.items, "".to_string()),
(
"#ifdef MOZ_UNIFFI_FIXTURES".to_string(),
&*self.fixture_items,
"#endif /* MOZ_UNIFFI_FIXTURES */".to_string(),
),
]
.into_iter()
}
/// Create a new CombinedItems value by mapping the items and fixture_items lists to new lists.
pub fn map<F, U>(&self, mut f: F) -> CombinedItems<U>
where
F: FnMut(&Vec<T>) -> Vec<U>,
{
CombinedItems {
items: f(&self.items),
fixture_items: f(&self.fixture_items),
}
}
pub fn try_map<F, U>(&self, mut f: F) -> Result<CombinedItems<U>>
where
F: FnMut(&Vec<T>) -> Result<Vec<U>>,
{
Ok(CombinedItems {
items: f(&self.items)?,
fixture_items: f(&self.fixture_items)?,
})
}
}
#[derive(Default)]
pub struct CombinedItemsIdGenerator {
counter: u64,
}
impl CombinedItemsIdGenerator {
pub fn new_id(&mut self) -> u64 {
self.counter += 1;
self.counter
}
}
impl ScaffoldingCall {
pub fn is_async(&self) -> bool {
self.ffi_func.async_data.is_some()
}
}
impl FfiFunction {
pub fn arg_types(&self) -> Vec<&str> {
self.arguments
.iter()
.map(|a| a.ty.type_name.as_str())
.chain(self.has_rust_call_status_arg.then_some("RustCallStatus*"))
.collect()
}
}
impl FfiFunctionType {
pub fn arg_types(&self) -> Vec<&str> {
self.arguments
.iter()
.map(|a| a.ty.type_name.as_str())
.chain(self.has_rust_call_status_arg.then_some("RustCallStatus*"))
.collect()
}
}
impl CppCallbackInterfaceMethod {
fn is_async(&self) -> bool {
self.async_data.is_some()
}
}
pub mod filters {
use super::*;
use askama::Result;
pub fn ffi_converter(ty: impl AsRef<TypeNode>) -> Result<String> {
Ok(ty.as_ref().ffi_converter.to_string())
}
pub fn class_name(ty: impl AsRef<TypeNode>) -> Result<String> {
let ty = ty.as_ref();
match &ty.class_name {
Some(class_name) => Ok(class_name.clone()),
None => Err(to_askama_error(&format!(
"Trying to get class name for {:?}",
ty
))),
}
}
pub fn lift_fn(ty: impl AsRef<TypeNode>) -> Result<String> {
Ok(format!("{}.lift", ty.as_ref().ffi_converter))
}
pub fn lower_fn(ty: impl AsRef<TypeNode>) -> Result<String> {
Ok(format!("{}.lower", ty.as_ref().ffi_converter))
}
pub fn read_fn(ty: impl AsRef<TypeNode>) -> Result<String> {
Ok(format!("{}.read", ty.as_ref().ffi_converter))
}
pub fn write_fn(ty: impl AsRef<TypeNode>) -> Result<String> {
Ok(format!("{}.write", ty.as_ref().ffi_converter))
}
pub fn compute_size_fn(ty: impl AsRef<TypeNode>) -> Result<String> {
Ok(format!("{}.computeSize", ty.as_ref().ffi_converter))
}
pub fn check_type_fn(ty: impl AsRef<TypeNode>) -> Result<String> {
Ok(format!("{}.checkType", ty.as_ref().ffi_converter))
}
// Render an expression to check if two instances of this type are equal
pub fn field_equals(field: &Field, first_obj: &str, second_obj: &str) -> Result<String> {
let name = &field.name;
Ok(match &field.ty.ty {
Type::Record { .. } => format!("{first_obj}.{name}.equals({second_obj}.{name})"),
_ => format!("{first_obj}.{name} == {second_obj}.{name}"),
})
}
// Remove the trailing comma from a block of text.
//
// This can make generating argument lists more convenient.
pub fn remove_trailing_comma<T: std::fmt::Display>(text: T) -> Result<String> {
let text = text.to_string();
let Some(last_comma) = text.rfind(',') else {
return Ok(text.to_string());
};
if !text[last_comma + 1..].chars().all(char::is_whitespace) {
return Ok(text.to_string());
}
Ok(text[..last_comma].to_string())
}
}