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 crate::{CallbackIds, Config, FunctionIds, ObjectIds};
use askama::Template;
use extend::ext;
use heck::{ToShoutySnakeCase, ToUpperCamelCase};
use std::collections::HashSet;
use std::iter;
use uniffi_bindgen::interface::{
CallbackInterface, ComponentInterface, FfiArgument, FfiFunction, FfiType, Object,
};
#[derive(Template)]
#[template(path = "UniFFIScaffolding.cpp", escape = "none")]
pub struct CPPScaffoldingTemplate<'a> {
// Prefix for each function name in. This is related to how we handle the test fixtures. For
// each function defined in the UniFFI namespace in UniFFI.webidl we:
// - Generate a function in to handle it using the real UDL files
// - Generate a different function in for handle it using the fixture UDL files
// - Have a hand-written stub function that always calls the first function and only calls
// the second function in if MOZ_UNIFFI_FIXTURES is defined.
pub prefix: &'a str,
pub components: &'a Vec<(ComponentInterface, Config)>,
pub function_ids: &'a FunctionIds<'a>,
pub object_ids: &'a ObjectIds<'a>,
pub callback_ids: &'a CallbackIds<'a>,
}
impl<'a> CPPScaffoldingTemplate<'a> {
fn has_any_objects(&self) -> bool {
self.components
.iter()
.any(|(ci, _)| ci.object_definitions().len() > 0)
}
}
// Define extension traits with methods used in our template code
#[ext(name=ComponentInterfaceCppExt)]
pub impl ComponentInterface {
// C++ pointer type name. This needs to be a valid C++ type name and unique across all UDL
// files.
fn pointer_type(&self, object: &Object) -> String {
self._pointer_type(object.name())
}
fn _pointer_type(&self, name: &str) -> String {
format!(
"k{}{}PointerType",
self.namespace().to_upper_camel_case(),
name.to_upper_camel_case()
)
}
// Iterate over all functions to expose via the UniFFIScaffolding class
//
// This is basically all the user functions, except we don't expose the free methods for
// objects. Freeing is handled by the UniFFIPointer class.
//
// Note: this function should return `impl Iterator<&FfiFunction>`, but that's not currently
// allowed for traits.
fn exposed_functions(&self) -> Vec<&FfiFunction> {
let excluded: HashSet<_> = self
.object_definitions()
.iter()
.map(|o| o.ffi_object_free().name())
.chain(
self.callback_interface_definitions()
.iter()
.map(|cbi| cbi.ffi_init_callback().name()),
)
.collect();
self.iter_user_ffi_function_definitions()
.filter(move |f| !excluded.contains(f.name()))
.collect()
}
// ScaffoldingConverter class
//
// This is used to convert types between the JS code and Rust
fn scaffolding_converter(&self, ffi_type: &FfiType) -> String {
match ffi_type {
FfiType::RustArcPtr(name) => {
format!("ScaffoldingObjectConverter<&{}>", self._pointer_type(name),)
}
_ => format!("ScaffoldingConverter<{}>", ffi_type.rust_type()),
}
}
// ScaffoldingCallHandler class
fn scaffolding_call_handler(&self, func: &FfiFunction) -> String {
let return_param = match func.return_type() {
Some(return_type) => self.scaffolding_converter(return_type),
None => "ScaffoldingConverter<void>".to_string(),
};
let all_params = iter::once(return_param)
.chain(
func.arguments()
.into_iter()
.map(|a| self.scaffolding_converter(&a.type_())),
)
.collect::<Vec<_>>()
.join(", ");
return format!("ScaffoldingCallHandler<{}>", all_params);
}
}
#[ext(name=FFIFunctionCppExt)]
pub impl FfiFunction {
fn nm(&self) -> String {
self.name().to_upper_camel_case()
}
fn rust_name(&self) -> String {
self.name().to_string()
}
fn rust_return_type(&self) -> String {
match self.return_type() {
Some(t) => t.rust_type(),
None => "void".to_owned(),
}
}
fn rust_arg_list(&self) -> String {
let mut parts: Vec<String> = self.arguments().iter().map(|a| a.rust_type()).collect();
parts.push("RustCallStatus*".to_owned());
parts.join(", ")
}
}
#[ext(name=FFITypeCppExt)]
pub impl FfiType {
// Type for the Rust scaffolding code
fn rust_type(&self) -> String {
match self {
FfiType::UInt8 => "uint8_t".to_owned(),
FfiType::Int8 => "int8_t".to_owned(),
FfiType::UInt16 => "uint16_t".to_owned(),
FfiType::Int16 => "int16_t".to_owned(),
FfiType::UInt32 => "uint32_t".to_owned(),
FfiType::Int32 => "int32_t".to_owned(),
FfiType::UInt64 => "uint64_t".to_owned(),
FfiType::Int64 => "int64_t".to_owned(),
FfiType::Float32 => "float".to_owned(),
FfiType::Float64 => "double".to_owned(),
FfiType::RustBuffer(_) => "RustBuffer".to_owned(),
FfiType::RustArcPtr(_) => "void *".to_owned(),
FfiType::ForeignBytes => unimplemented!("ForeignBytes not supported"),
FfiType::Handle => "uint64_t".to_owned(),
FfiType::RustCallStatus => "RustCallStatus".to_owned(),
FfiType::Callback(name) | FfiType::Struct(name) => name.to_owned(),
FfiType::VoidPointer => "void *".to_owned(),
FfiType::Reference(_) => unimplemented!("References not supported"),
}
}
}
#[ext(name=FFIArgumentCppExt)]
pub impl FfiArgument {
fn rust_type(&self) -> String {
self.type_().rust_type()
}
}
#[ext(name=ObjectCppExt)]
pub impl Object {
fn nm(&self) -> String {
self.name().to_upper_camel_case()
}
}
#[ext(name=CallbackInterfaceCppExt)]
pub impl CallbackInterface {
fn nm(&self) -> String {
self.name().to_upper_camel_case()
}
/// Name of the static pointer to the JS callback handler
fn js_handler(&self) -> String {
format!("JS_CALLBACK_HANDLER_{}", self.name().to_shouty_snake_case())
}
/// Name of the C function handler
fn c_handler(&self, prefix: &str) -> String {
format!(
"{prefix}CallbackHandler{}",
self.name().to_upper_camel_case()
)
}
}