/* 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, IndexSet}; use anyhow::{bail, Result}; use askama::Template; use uniffi_bindgen::to_askama_error; use uniffi_pipeline::Node; use crate::{ConcurrencyMode, 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, pub namespaces: IndexMap, pub cpp_scaffolding: CppScaffolding, pub module_docs: Vec, } #[derive(Debug, Clone, Node, Template)] #[template(path = "cpp/UniFFIScaffolding.cpp", escape = "none")] pub struct CppScaffolding { pub ffi_definitions: CombinedItems, pub scaffolding_calls: CombinedItems, pub pointer_types: CombinedItems, pub callback_return_handler_classes: CombinedItems, pub callback_interfaces: CombinedItems, } // 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, pub return_ty: Option, } /// 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, /// C++ function variable name pub var_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, } #[derive(Debug, Clone, Node)] pub struct PointerTypeTraitInterfaceInfo { pub free_fn: String, pub clone_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, /// 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, /// Name of the function generated by uniffi-bindgen-gecko-js to clone a callback interface /// handle. pub clone_fn: String, pub vtable_struct_type: FfiTypeNode, pub methods: Vec, } // 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 kind: CallbackMethodKind, pub return_handler_class_name: String, /// Name of the subclass pub async_handler_class_name: String, pub ffi_func: FfiFunctionType, pub arguments: Vec, pub return_ty: Option, pub out_pointer_ty: FfiTypeNode, } /// Callback method kind. /// /// There's currently only 2 options: /// - Methods that are async in both Rust and JS /// - Sync Rust methods wrapped to be fire-and-forget JS methods #[derive(Debug, Clone, Node)] pub enum CallbackMethodKind { Sync, FireAndForget, Async(CppCallbackInterfaceMethodAsyncData), } #[derive(Debug, Clone, Node)] pub struct CppCallbackInterfaceMethodAsyncData { pub complete_callback_type_name: String, pub result_type_name: String, } #[derive(Debug, Clone, Node)] pub struct CallbackReturnHandlerClass { pub name: String, pub return_ty: Option, pub return_type_name: String, } /// Base class for an async callback method handler /// /// This derives from `AsyncCallbackMethodHandlerBase` 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, } #[derive(Debug, Clone, Node, Template)] #[template(path = "js/Module.sys.mjs", escape = "none")] pub struct Namespace { pub name: String, pub config: Config, pub js_name: String, pub js_filename: String, pub fixture: bool, pub crate_name: String, pub docstring: Option, pub js_docstring: String, pub functions: Vec, pub type_definitions: Vec, pub ffi_definitions: IndexSet, pub checksums: Vec, 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, } #[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, pub js_docstring: String, } #[derive(Debug, Clone, Node)] pub struct Constructor { pub name: String, pub self_name: String, pub callable: Callable, pub docstring: Option, pub js_docstring: String, } #[derive(Debug, Clone, Node)] pub struct Method { pub name: String, pub self_name: String, pub callable: Callable, pub docstring: Option, pub js_docstring: String, } /// Common data from Function/Method/Constructor #[derive(Debug, Clone, Node)] pub struct Callable { pub name: String, pub async_data: Option, pub is_js_async: bool, pub concurrency_mode: ConcurrencyMode, // UniFFIScaffolding method used to invoke this callable pub uniffi_scaffolding_method: String, pub kind: CallableKind, pub arguments: Vec, pub return_type: ReturnType, pub throws_type: ThrowsType, pub checksum: Option, pub ffi_func: RustFfiFunctionName, pub id: u64, } #[derive(Debug, Clone, Node)] pub enum CallableKind { Function, Method { self_type: TypeNode, ffi_converter: String, }, Constructor { self_type: TypeNode, primary: bool, }, VTableMethod { self_type: TypeNode, for_callback_interface: bool, }, } #[derive(Debug, Clone, Node)] pub struct ReturnType { pub ty: Option, } #[derive(Debug, Clone, Node)] pub struct ThrowsType { pub ty: Option, } #[derive(Debug, Clone, Node, PartialEq, Eq, Hash)] 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)] pub struct Argument { pub name: String, pub ty: TypeNode, pub by_ref: bool, pub optional: bool, pub default: Option, } #[derive(Debug, Clone, Node, Eq, PartialEq, Hash)] pub enum DefaultValue { Default(TypeNode), Literal(LiteralNode), } #[derive(Debug, Clone, Node, Eq, PartialEq, Hash)] pub struct DefaultValueNode { #[node(wraps)] pub default: DefaultValue, /// The default value rendered as a string pub js_lit: String, } #[derive(Debug, Clone, Node, Eq, PartialEq, Hash)] pub struct LiteralNode { pub js_lit: String, pub lit: Literal, } #[derive(Debug, Clone, Node, Eq, PartialEq, Hash)] 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 }, } // Represent the radix of integer literal values. // We preserve the radix into the generated bindings for readability reasons. #[derive(Debug, Clone, Node, Eq, PartialEq, Hash)] pub enum Radix { Decimal = 10, Octal = 8, Hexadecimal = 16, } #[derive(Debug, Clone, Node)] pub struct Record { pub name: String, pub remote: bool, // only used when generating scaffolding from UDL pub fields: Vec, pub docstring: Option, pub js_docstring: String, pub self_type: TypeNode, } #[derive(Debug, Clone, Node)] pub struct Field { pub name: String, pub ty: TypeNode, pub default: Option, pub docstring: Option, pub js_docstring: String, } #[derive(Debug, Clone, Node)] pub enum EnumShape { Enum, Error { flat: bool }, } #[derive(Debug, Clone, Node)] pub struct Enum { pub name: String, pub is_flat: bool, pub shape: EnumShape, pub remote: bool, pub variants: Vec, pub discr_type: TypeNode, pub non_exhaustive: bool, pub js_docstring: String, pub docstring: Option, pub self_type: TypeNode, } #[derive(Debug, Clone, Node)] pub struct Variant { pub name: String, pub discr: LiteralNode, pub fields: Vec, pub docstring: Option, pub js_docstring: String, } #[derive(Debug, Clone, Node)] pub struct Interface { pub name: String, pub js_class_name: String, pub object_id: u64, pub interface_base_class: InterfaceBaseClass, pub constructors: Vec, pub methods: Vec, pub uniffi_traits: Vec, pub trait_impls: Vec, pub remote: bool, // only used when generating scaffolding from UDL pub imp: ObjectImpl, pub docstring: Option, pub js_docstring: String, pub self_type: TypeNode, pub vtable: Option, pub ffi_func_clone: RustFfiFunctionName, pub ffi_func_free: RustFfiFunctionName, } #[derive(Debug, Clone, Node)] pub struct CallbackInterface { pub name: String, pub interface_base_class: InterfaceBaseClass, pub vtable: VTable, pub docstring: Option, pub js_docstring: String, pub self_type: TypeNode, } /// Javascript interface class. /// /// This is an abstract base class that the interface implements. /// For trait/callback interfaces this is what the JS code should extend. #[derive(Debug, Clone, Node)] pub struct InterfaceBaseClass { pub name: String, pub methods: Vec, pub docstring: Option, pub js_docstring: String, } #[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, } /// 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: Box }, Hash { hash: Method }, } #[derive(Debug, Clone, Node)] pub struct ObjectTraitImpl { pub ty: TypeNode, pub trait_name: String, pub tr_module_name: Option, } #[derive(Debug, Clone, Node)] pub struct CustomType { pub name: String, pub builtin: TypeNode, pub docstring: Option, pub js_docstring: String, pub self_type: TypeNode, pub type_name: Option, pub lift_expr: Option, pub lower_expr: Option, } #[derive(Debug, Clone, Node)] pub struct OptionalType { pub inner: TypeNode, pub self_type: TypeNode, } #[derive(Debug, Clone, Node)] pub struct SequenceType { pub inner: TypeNode, pub self_type: TypeNode, } #[derive(Debug, Clone, Node)] pub struct MapType { pub key: TypeNode, pub value: TypeNode, pub self_type: TypeNode, } #[derive(Debug, Clone, Node)] pub struct ExternalType { pub namespace: String, pub name: String, pub self_type: TypeNode, } #[derive(Debug, Clone, Node, Eq, PartialEq, Hash)] 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, 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, }, Sequence { inner_type: Box, }, Map { key_type: Box, value_type: Box, }, // Custom type on the scaffolding side Custom { module_name: String, name: String, builtin: Box, }, } impl Type { pub fn name(&self) -> Result<&str> { match &self { Type::Record { name, .. } | Type::Enum { name, .. } | Type::Interface { name, .. } | Type::CallbackInterface { name, .. } | Type::Custom { name, .. } => Ok(name.as_str()), _ => bail!("This type has no name"), } } } #[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, PartialEq, Eq, Hash)] 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, Hash)] pub struct RustFfiFunctionName(pub String); #[derive(Debug, Clone, Node, PartialEq, Eq, Hash)] pub struct FfiStructName(pub String); #[derive(Debug, Clone, Node, PartialEq, Eq, Hash)] pub struct FfiFunctionTypeName(pub String); #[derive(Debug, Clone, Node, PartialEq, Eq, Hash)] pub struct FfiFunction { pub name: RustFfiFunctionName, pub async_data: Option, pub arguments: Vec, pub return_type: FfiReturnType, pub has_rust_call_status_arg: bool, pub kind: FfiFunctionKind, } #[derive(Debug, Clone, PartialEq, Eq, Node, Hash)] pub enum FfiFunctionKind { Scaffolding, ObjectClone, ObjectFree, RustFuturePoll, RustFutureComplete, RustFutureCancel, RustFutureFree, RustBufferFromBytes, RustBufferFree, RustBufferAlloc, RustBufferReserve, RustVtableInit, UniffiContractVersion, Checksum, } #[derive(Debug, Clone, Node, PartialEq, Eq, Hash)] pub struct FfiFunctionType { pub name: FfiFunctionTypeName, pub arguments: Vec, pub return_type: FfiReturnType, pub has_rust_call_status_arg: bool, } #[derive(Debug, Clone, Node, PartialEq, Eq, Hash)] pub struct FfiReturnType { pub ty: Option, pub type_name: String, } #[derive(Debug, Clone, Node, PartialEq, Eq, Hash)] pub struct FfiStruct { pub name: FfiStructName, pub fields: Vec, } #[derive(Debug, Clone, Node, PartialEq, Eq, Hash)] pub struct FfiField { pub name: String, pub ty: FfiTypeNode, } #[derive(Debug, Clone, Node, PartialEq, Eq, Hash)] pub struct FfiArgument { pub name: String, pub ty: FfiTypeNode, } #[derive(Debug, Clone, Node, PartialEq, Eq, Hash)] pub struct FfiTypeNode { pub ty: FfiType, pub type_name: String, } #[derive(Debug, Clone, Node, PartialEq, Eq, Hash)] pub enum FfiType { UInt8, Int8, UInt16, Int16, UInt32, Int32, UInt64, Int64, Float32, Float64, RustBuffer(Option), ForeignBytes, Function(FfiFunctionTypeName), Struct(FfiStructName), Handle(HandleKind), RustCallStatus, Reference(Box), MutReference(Box), VoidPointer, } #[derive(Debug, Clone, Node, PartialEq, Eq, Hash)] pub enum HandleKind { RustFuture, ForeignFuture, ForeignFutureCallbackData, StructInterface { namespace: String, interface_name: String, }, TraitInterface { namespace: 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, pub functions: Vec, } /// Combines fixture and non-fixture template items #[derive(Debug, Clone, Node)] pub struct CombinedItems { pub items: Vec, pub fixture_items: Vec, } impl CombinedItems { /// Create a new CombinedItems value /// /// F is a function that finds items in module and pushes them to a vec. pub fn new(root: &mut Root, mut f: F) -> Self where F: FnMut(&mut Namespace, &mut CombinedItemsIdGenerator, &mut Vec), { Self::try_new(root, |namespace, id_generator, items| { f(namespace, id_generator, items); Ok(()) }) .unwrap() } pub fn try_new(root: &mut Root, mut f: F) -> Result where F: FnMut(&mut Namespace, &mut CombinedItemsIdGenerator, &mut Vec) -> 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(|namespace: &mut Namespace| { if !namespace.fixture { f(namespace, &mut id_generator, &mut combined_items.items) } else { Ok(()) } })?; root.try_visit_mut(|namespace: &mut Namespace| { if namespace.fixture { f( namespace, &mut id_generator, &mut combined_items.fixture_items, ) } else { Ok(()) } })?; Ok(combined_items) } pub fn sort_by_key(&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, , preprocssor_condition_end), where /// `preprocssor_condition` is the preprocessor preprocssor_condition that should control if /// the items are included. fn iter(&self) -> impl Iterator { 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(&self, mut f: F) -> CombinedItems where F: FnMut(&Vec) -> Vec, { CombinedItems { items: f(&self.items), fixture_items: f(&self.fixture_items), } } pub fn try_map(&self, mut f: F) -> Result> where F: FnMut(&Vec) -> Result>, { 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() } } pub mod filters { use super::*; use askama::Result; pub fn class_name(ty: &TypeNode, _: &dyn askama::Values) -> Result { match &ty.class_name { Some(class_name) => Ok(class_name.clone()), None => Err(to_askama_error(&format!( "Trying to get class name for {:?}", ty ))), } } // Render an expression to check if two instances of this type are equal pub fn field_equals(field: &Field, _: &dyn askama::Values, first_obj: &str, second_obj: &str) -> Result { 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(text: T, _: &dyn askama::Values) -> Result { 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()) } }