1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 use anyhow::{Context, Result};
7 use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
8 use once_cell::sync::Lazy;
9 use serde::{Deserialize, Serialize};
10 use std::borrow::Borrow;
11 use std::cell::RefCell;
12 use std::collections::{BTreeSet, HashMap, HashSet};
15 use crate::backend::TemplateExpression;
16 use crate::interface::*;
17 use crate::BindingsConfig;
19 mod callback_interface;
30 /// A trait tor the implementation.
31 trait CodeType: Debug {
32 /// The language specific label used to reference this type. This will be used in
33 /// method signatures and property declarations.
34 fn type_label(&self) -> String;
36 /// A representation of this type label that can be used as part of another
37 /// identifier. e.g. `read_foo()`, or `FooInternals`.
39 /// This is especially useful when creating specialized objects or methods to deal
40 /// with this type only.
41 fn canonical_name(&self) -> String {
45 fn literal(&self, _literal: &Literal) -> String {
46 unimplemented!("Unimplemented for {}", self.type_label())
49 /// Name of the FfiConverter
51 /// This is the object that contains the lower, write, lift, and read methods for this type.
52 fn ffi_converter_name(&self) -> String {
53 format!("FfiConverter{}", self.canonical_name())
56 /// A list of imports that are needed if this type is in use.
57 /// Classes are imported exactly once.
58 fn imports(&self) -> Option<Vec<String>> {
62 /// Function to run at startup
63 fn initialization_fn(&self) -> Option<String> {
68 // Taken from Python's `keyword.py` module.
69 static KEYWORDS: Lazy<HashSet<String>> = Lazy::new(|| {
108 HashSet::from_iter(kwlist.into_iter().map(|s| s.to_string()))
111 // Config options to customize the generated python.
112 #[derive(Debug, Clone, Default, Serialize, Deserialize)]
114 cdylib_name: Option<String>,
116 custom_types: HashMap<String, CustomTypeConfig>,
119 #[derive(Debug, Clone, Default, Serialize, Deserialize)]
120 pub struct CustomTypeConfig {
121 // This `CustomTypeConfig` doesn't have a `type_name` like the others -- which is why we have
122 // separate structs rather than a shared one.
123 imports: Option<Vec<String>>,
124 into_custom: TemplateExpression,
125 from_custom: TemplateExpression,
129 pub fn cdylib_name(&self) -> String {
130 if let Some(cdylib_name) = &self.cdylib_name {
138 impl BindingsConfig for Config {
139 fn update_from_ci(&mut self, ci: &ComponentInterface) {
141 .get_or_insert_with(|| format!("uniffi_{}", ci.namespace()));
144 fn update_from_cdylib_name(&mut self, cdylib_name: &str) {
146 .get_or_insert_with(|| cdylib_name.to_string());
149 fn update_from_dependency_configs(&mut self, _config_map: HashMap<&str, &Self>) {}
152 // Generate python bindings for the given ComponentInterface, as a string.
153 pub fn generate_python_bindings(config: &Config, ci: &ComponentInterface) -> Result<String> {
154 PythonWrapper::new(config.clone(), ci)
156 .context("failed to render python bindings")
159 /// A struct to record a Python import statement.
160 #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
161 pub enum ImportRequirement {
162 /// A simple module import.
163 Module { mod_name: String },
164 /// A single symbol from a module.
169 /// A single symbol from a module with the specified local name.
177 impl ImportRequirement {
178 /// Render the Python import statement.
179 fn render(&self) -> String {
181 ImportRequirement::Module { mod_name } => format!("import {mod_name}"),
182 ImportRequirement::Symbol {
185 } => format!("from {mod_name} import {symbol_name}"),
186 ImportRequirement::SymbolAs {
190 } => format!("from {mod_name} import {symbol_name} as {as_name}"),
195 /// Renders Python helper code for all types
197 /// This template is a bit different than others in that it stores internal state from the render
198 /// process. Make sure to only call `render()` once.
200 #[template(syntax = "py", escape = "none", path = "Types.py")]
201 pub struct TypeRenderer<'a> {
202 python_config: &'a Config,
203 ci: &'a ComponentInterface,
204 // Track included modules for the `include_once()` macro
205 include_once_names: RefCell<HashSet<String>>,
206 // Track imports added with the `add_import()` macro
207 imports: RefCell<BTreeSet<ImportRequirement>>,
210 impl<'a> TypeRenderer<'a> {
211 fn new(python_config: &'a Config, ci: &'a ComponentInterface) -> Self {
215 include_once_names: RefCell::new(HashSet::new()),
216 imports: RefCell::new(BTreeSet::new()),
220 // The following methods are used by the `Types.py` macros.
222 // Helper for the including a template, but only once.
224 // The first time this is called with a name it will return true, indicating that we should
225 // include the template. Subsequent calls will return false.
226 fn include_once_check(&self, name: &str) -> bool {
227 self.include_once_names
229 .insert(name.to_string())
232 // Helper to add an import statement
234 // Call this inside your template to cause an import statement to be added at the top of the
235 // file. Imports will be sorted and de-deuped.
237 // Returns an empty string so that it can be used inside an askama `{{ }}` block.
238 fn add_import(&self, name: &str) -> &str {
239 self.imports.borrow_mut().insert(ImportRequirement::Module {
240 mod_name: name.to_owned(),
245 // Like add_import, but arranges for `from module import name`.
246 fn add_import_of(&self, mod_name: &str, name: &str) -> &str {
247 self.imports.borrow_mut().insert(ImportRequirement::Symbol {
248 mod_name: mod_name.to_owned(),
249 symbol_name: name.to_owned(),
254 // Like add_import, but arranges for `from module import name as other`.
255 fn add_import_of_as(&self, mod_name: &str, symbol_name: &str, as_name: &str) -> &str {
258 .insert(ImportRequirement::SymbolAs {
259 mod_name: mod_name.to_owned(),
260 symbol_name: symbol_name.to_owned(),
261 as_name: as_name.to_owned(),
268 #[template(syntax = "py", escape = "none", path = "wrapper.py")]
269 pub struct PythonWrapper<'a> {
270 ci: &'a ComponentInterface,
272 type_helper_code: String,
273 type_imports: BTreeSet<ImportRequirement>,
275 impl<'a> PythonWrapper<'a> {
276 pub fn new(config: Config, ci: &'a ComponentInterface) -> Self {
277 let type_renderer = TypeRenderer::new(&config, ci);
278 let type_helper_code = type_renderer.render().unwrap();
279 let type_imports = type_renderer.imports.into_inner();
288 pub fn imports(&self) -> Vec<ImportRequirement> {
289 self.type_imports.iter().cloned().collect()
293 fn fixup_keyword(name: String) -> String {
294 if KEYWORDS.contains(&name) {
301 #[derive(Clone, Default)]
302 pub struct PythonCodeOracle;
304 impl PythonCodeOracle {
305 fn find(&self, type_: &Type) -> Box<dyn CodeType> {
306 type_.clone().as_type().as_codetype()
309 /// Get the idiomatic Python rendering of a class name (for enums, records, errors, etc).
310 fn class_name(&self, nm: &str) -> String {
311 fixup_keyword(nm.to_string().to_upper_camel_case())
314 /// Get the idiomatic Python rendering of a function name.
315 fn fn_name(&self, nm: &str) -> String {
316 fixup_keyword(nm.to_string().to_snake_case())
319 /// Get the idiomatic Python rendering of a variable name.
320 fn var_name(&self, nm: &str) -> String {
321 fixup_keyword(nm.to_string().to_snake_case())
324 /// Get the idiomatic Python rendering of an individual enum variant.
325 fn enum_variant_name(&self, nm: &str) -> String {
326 fixup_keyword(nm.to_string().to_shouty_snake_case())
329 fn ffi_type_label(ffi_type: &FfiType) -> String {
331 FfiType::Int8 => "ctypes.c_int8".to_string(),
332 FfiType::UInt8 => "ctypes.c_uint8".to_string(),
333 FfiType::Int16 => "ctypes.c_int16".to_string(),
334 FfiType::UInt16 => "ctypes.c_uint16".to_string(),
335 FfiType::Int32 => "ctypes.c_int32".to_string(),
336 FfiType::UInt32 => "ctypes.c_uint32".to_string(),
337 FfiType::Int64 => "ctypes.c_int64".to_string(),
338 FfiType::UInt64 => "ctypes.c_uint64".to_string(),
339 FfiType::Float32 => "ctypes.c_float".to_string(),
340 FfiType::Float64 => "ctypes.c_double".to_string(),
341 FfiType::RustArcPtr(_) => "ctypes.c_void_p".to_string(),
342 FfiType::RustBuffer(maybe_suffix) => match maybe_suffix {
343 Some(suffix) => format!("_UniffiRustBuffer{suffix}"),
344 None => "_UniffiRustBuffer".to_string(),
346 FfiType::ForeignBytes => "_UniffiForeignBytes".to_string(),
347 FfiType::ForeignCallback => "_UNIFFI_FOREIGN_CALLBACK_T".to_string(),
348 // Pointer to an `asyncio.EventLoop` instance
349 FfiType::ForeignExecutorHandle => "ctypes.c_size_t".to_string(),
350 FfiType::ForeignExecutorCallback => "_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T".to_string(),
351 FfiType::RustFutureHandle => "ctypes.c_void_p".to_string(),
352 FfiType::RustFutureContinuationCallback => "_UNIFFI_FUTURE_CONTINUATION_T".to_string(),
353 FfiType::RustFutureContinuationData => "ctypes.c_size_t".to_string(),
359 fn as_codetype(&self) -> Box<dyn CodeType>;
362 impl<T: AsType> AsCodeType for T {
363 fn as_codetype(&self) -> Box<dyn CodeType> {
364 // Map `Type` instances to a `Box<dyn CodeType>` for that type.
366 // There is a companion match in `templates/Types.py` which performs a similar function for the
369 // - When adding additional types here, make sure to also add a match arm to the `Types.py` template.
370 // - To keep things manageable, let's try to limit ourselves to these 2 mega-matches
371 match self.as_type() {
372 Type::UInt8 => Box::new(primitives::UInt8CodeType),
373 Type::Int8 => Box::new(primitives::Int8CodeType),
374 Type::UInt16 => Box::new(primitives::UInt16CodeType),
375 Type::Int16 => Box::new(primitives::Int16CodeType),
376 Type::UInt32 => Box::new(primitives::UInt32CodeType),
377 Type::Int32 => Box::new(primitives::Int32CodeType),
378 Type::UInt64 => Box::new(primitives::UInt64CodeType),
379 Type::Int64 => Box::new(primitives::Int64CodeType),
380 Type::Float32 => Box::new(primitives::Float32CodeType),
381 Type::Float64 => Box::new(primitives::Float64CodeType),
382 Type::Boolean => Box::new(primitives::BooleanCodeType),
383 Type::String => Box::new(primitives::StringCodeType),
384 Type::Bytes => Box::new(primitives::BytesCodeType),
386 Type::Timestamp => Box::new(miscellany::TimestampCodeType),
387 Type::Duration => Box::new(miscellany::DurationCodeType),
389 Type::Enum { name, .. } => Box::new(enum_::EnumCodeType::new(name)),
390 Type::Object { name, .. } => Box::new(object::ObjectCodeType::new(name)),
391 Type::Record { name, .. } => Box::new(record::RecordCodeType::new(name)),
392 Type::CallbackInterface { name, .. } => {
393 Box::new(callback_interface::CallbackInterfaceCodeType::new(name))
395 Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType),
396 Type::Optional { inner_type } => {
397 Box::new(compounds::OptionalCodeType::new(*inner_type))
399 Type::Sequence { inner_type } => {
400 Box::new(compounds::SequenceCodeType::new(*inner_type))
405 } => Box::new(compounds::MapCodeType::new(*key_type, *value_type)),
406 Type::External { name, .. } => Box::new(external::ExternalCodeType::new(name)),
407 Type::Custom { name, .. } => Box::new(custom::CustomCodeType::new(name)),
414 pub use crate::backend::filters::*;
416 pub(super) fn type_name(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
417 Ok(as_ct.as_codetype().type_label())
420 pub(super) fn ffi_converter_name(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
421 Ok(String::from("_Uniffi") + &as_ct.as_codetype().ffi_converter_name()[3..])
424 pub(super) fn canonical_name(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
425 Ok(as_ct.as_codetype().canonical_name())
428 pub(super) fn lift_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
429 Ok(format!("{}.lift", ffi_converter_name(as_ct)?))
432 pub(super) fn lower_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
433 Ok(format!("{}.lower", ffi_converter_name(as_ct)?))
436 pub(super) fn read_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
437 Ok(format!("{}.read", ffi_converter_name(as_ct)?))
440 pub(super) fn write_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
441 Ok(format!("{}.write", ffi_converter_name(as_ct)?))
444 pub(super) fn literal_py(
446 as_ct: &impl AsCodeType,
447 ) -> Result<String, askama::Error> {
448 Ok(as_ct.as_codetype().literal(literal))
451 pub fn ffi_type_name(type_: &FfiType) -> Result<String, askama::Error> {
452 Ok(PythonCodeOracle::ffi_type_label(type_))
455 /// Get the idiomatic Python rendering of a class name (for enums, records, errors, etc).
456 pub fn class_name(nm: &str) -> Result<String, askama::Error> {
457 Ok(PythonCodeOracle.class_name(nm))
460 /// Get the idiomatic Python rendering of a function name.
461 pub fn fn_name(nm: &str) -> Result<String, askama::Error> {
462 Ok(PythonCodeOracle.fn_name(nm))
465 /// Get the idiomatic Python rendering of a variable name.
466 pub fn var_name(nm: &str) -> Result<String, askama::Error> {
467 Ok(PythonCodeOracle.var_name(nm))
470 /// Get the idiomatic Python rendering of an individual enum variant.
471 pub fn enum_variant_py(nm: &str) -> Result<String, askama::Error> {
472 Ok(PythonCodeOracle.enum_variant_name(nm))