From d8bbc7858622b6d9c278469aab701ca0b609cddf Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 15 May 2024 05:35:49 +0200 Subject: Merging upstream version 126.0. Signed-off-by: Daniel Baumann --- .../src/bindings/python/gen_python/compounds.rs | 16 +- .../src/bindings/python/gen_python/executor.rs | 18 --- .../src/bindings/python/gen_python/external.rs | 2 +- .../src/bindings/python/gen_python/mod.rs | 177 +++++++++++++++++++-- 4 files changed, 178 insertions(+), 35 deletions(-) delete mode 100644 third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/executor.rs (limited to 'third_party/rust/uniffi_bindgen/src/bindings/python/gen_python') diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs index b91bcbe18f..16adeca9a5 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs @@ -3,7 +3,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use super::CodeType; -use crate::backend::{Literal, Type}; +use crate::{ + backend::{Literal, Type}, + bindings::python::gen_python::AsCodeType, +}; #[derive(Debug)] pub struct OptionalCodeType { @@ -33,8 +36,9 @@ impl CodeType for OptionalCodeType { fn literal(&self, literal: &Literal) -> String { match literal { - Literal::Null => "None".into(), - _ => super::PythonCodeOracle.find(&self.inner).literal(literal), + Literal::None => "None".into(), + Literal::Some { inner } => super::PythonCodeOracle.find(&self.inner).literal(inner), + _ => panic!("Invalid literal for Optional type: {literal:?}"), } } } @@ -88,7 +92,11 @@ impl MapCodeType { impl CodeType for MapCodeType { fn type_label(&self) -> String { - "dict".to_string() + format!( + "dict[{}, {}]", + self.key.as_codetype().type_label(), + self.value.as_codetype().type_label() + ) } fn canonical_name(&self) -> String { diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/executor.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/executor.rs deleted file mode 100644 index be3ba1d791..0000000000 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/executor.rs +++ /dev/null @@ -1,18 +0,0 @@ -/* 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 super::CodeType; - -#[derive(Debug)] -pub struct ForeignExecutorCodeType; - -impl CodeType for ForeignExecutorCodeType { - fn type_label(&self) -> String { - "asyncio.BaseEventLoop".into() - } - - fn canonical_name(&self) -> String { - "ForeignExecutor".into() - } -} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs index 0d19c4bb3c..0a46251d6d 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs @@ -17,7 +17,7 @@ impl ExternalCodeType { impl CodeType for ExternalCodeType { fn type_label(&self) -> String { - self.name.clone() + super::PythonCodeOracle.class_name(&self.name) } fn canonical_name(&self) -> String { diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs index 8178fcc102..6a10a38e7f 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs @@ -2,8 +2,9 @@ * 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 anyhow::{Context, Result}; +use anyhow::{bail, Context, Result}; use askama::Template; +use camino::Utf8Path; use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; @@ -13,20 +14,43 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use std::fmt::Debug; use crate::backend::TemplateExpression; +use crate::bindings::python; use crate::interface::*; -use crate::BindingsConfig; +use crate::{BindingGenerator, BindingsConfig}; mod callback_interface; mod compounds; mod custom; mod enum_; -mod executor; mod external; mod miscellany; mod object; mod primitives; mod record; +pub struct PythonBindingGenerator; + +impl BindingGenerator for PythonBindingGenerator { + type Config = Config; + + fn write_bindings( + &self, + ci: &ComponentInterface, + config: &Config, + out_dir: &Utf8Path, + try_format_code: bool, + ) -> Result<()> { + python::write_bindings(config, ci, out_dir, try_format_code) + } + + fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()> { + if cdylib_name.is_none() { + bail!("Generate bindings for Python requires a cdylib, but {library_path} was given"); + } + Ok(()) + } +} + /// A trait tor the implementation. trait CodeType: Debug { /// The language specific label used to reference this type. This will be used in @@ -114,6 +138,8 @@ pub struct Config { cdylib_name: Option, #[serde(default)] custom_types: HashMap, + #[serde(default)] + external_packages: HashMap, } #[derive(Debug, Clone, Default, Serialize, Deserialize)] @@ -133,6 +159,16 @@ impl Config { "uniffi".into() } } + + /// Get the package name for a given external namespace. + pub fn module_for_namespace(&self, ns: &str) -> String { + let ns = ns.to_string().to_snake_case(); + match self.external_packages.get(&ns) { + None => format!(".{ns}"), + Some(value) if value.is_empty() => ns, + Some(value) => format!("{value}.{ns}"), + } + } } impl BindingsConfig for Config { @@ -326,7 +362,19 @@ impl PythonCodeOracle { fixup_keyword(nm.to_string().to_shouty_snake_case()) } - fn ffi_type_label(ffi_type: &FfiType) -> String { + /// Get the idiomatic Python rendering of an FFI callback function name + fn ffi_callback_name(&self, nm: &str) -> String { + format!("UNIFFI_{}", nm.to_shouty_snake_case()) + } + + /// Get the idiomatic Python rendering of an FFI struct name + fn ffi_struct_name(&self, nm: &str) -> String { + // The ctypes docs use both SHOUTY_SNAKE_CASE AND UpperCamelCase for structs. Let's use + // UpperCamelCase and reserve shouting for global variables + format!("Uniffi{}", nm.to_upper_camel_case()) + } + + fn ffi_type_label(&self, ffi_type: &FfiType) -> String { match ffi_type { FfiType::Int8 => "ctypes.c_int8".to_string(), FfiType::UInt8 => "ctypes.c_uint8".to_string(), @@ -338,19 +386,64 @@ impl PythonCodeOracle { FfiType::UInt64 => "ctypes.c_uint64".to_string(), FfiType::Float32 => "ctypes.c_float".to_string(), FfiType::Float64 => "ctypes.c_double".to_string(), + FfiType::Handle => "ctypes.c_uint64".to_string(), FfiType::RustArcPtr(_) => "ctypes.c_void_p".to_string(), FfiType::RustBuffer(maybe_suffix) => match maybe_suffix { Some(suffix) => format!("_UniffiRustBuffer{suffix}"), None => "_UniffiRustBuffer".to_string(), }, + FfiType::RustCallStatus => "_UniffiRustCallStatus".to_string(), FfiType::ForeignBytes => "_UniffiForeignBytes".to_string(), - FfiType::ForeignCallback => "_UNIFFI_FOREIGN_CALLBACK_T".to_string(), + FfiType::Callback(name) => self.ffi_callback_name(name), + FfiType::Struct(name) => self.ffi_struct_name(name), // Pointer to an `asyncio.EventLoop` instance - FfiType::ForeignExecutorHandle => "ctypes.c_size_t".to_string(), - FfiType::ForeignExecutorCallback => "_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T".to_string(), - FfiType::RustFutureHandle => "ctypes.c_void_p".to_string(), - FfiType::RustFutureContinuationCallback => "_UNIFFI_FUTURE_CONTINUATION_T".to_string(), - FfiType::RustFutureContinuationData => "ctypes.c_size_t".to_string(), + FfiType::Reference(inner) => format!("ctypes.POINTER({})", self.ffi_type_label(inner)), + FfiType::VoidPointer => "ctypes.c_void_p".to_string(), + } + } + + /// Default values for FFI types + /// + /// Used to set a default return value when returning an error + fn ffi_default_value(&self, return_type: Option<&FfiType>) -> String { + match return_type { + Some(t) => match t { + FfiType::UInt8 + | FfiType::Int8 + | FfiType::UInt16 + | FfiType::Int16 + | FfiType::UInt32 + | FfiType::Int32 + | FfiType::UInt64 + | FfiType::Int64 => "0".to_owned(), + FfiType::Float32 | FfiType::Float64 => "0.0".to_owned(), + FfiType::RustArcPtr(_) => "ctypes.c_void_p()".to_owned(), + FfiType::RustBuffer(maybe_suffix) => match maybe_suffix { + Some(suffix) => format!("_UniffiRustBuffer{suffix}.default()"), + None => "_UniffiRustBuffer.default()".to_owned(), + }, + _ => unimplemented!("FFI return type: {t:?}"), + }, + // When we need to use a value for void returns, we use a `u8` placeholder + None => "0".to_owned(), + } + } + + /// Get the name of the protocol and class name for an object. + /// + /// If we support callback interfaces, the protocol name is the object name, and the class name is derived from that. + /// Otherwise, the class name is the object name and the protocol name is derived from that. + /// + /// This split determines what types `FfiConverter.lower()` inputs. If we support callback + /// interfaces, `lower` must lower anything that implements the protocol. If not, then lower + /// only lowers the concrete class. + fn object_names(&self, obj: &Object) -> (String, String) { + let class_name = self.class_name(obj.name()); + if obj.has_callback_interface() { + let impl_name = format!("{class_name}Impl"); + (class_name, impl_name) + } else { + (format!("{class_name}Protocol"), class_name) } } } @@ -392,7 +485,6 @@ impl AsCodeType for T { Type::CallbackInterface { name, .. } => { Box::new(callback_interface::CallbackInterfaceCodeType::new(name)) } - Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType), Type::Optional { inner_type } => { Box::new(compounds::OptionalCodeType::new(*inner_type)) } @@ -429,6 +521,10 @@ pub mod filters { Ok(format!("{}.lift", ffi_converter_name(as_ct)?)) } + pub(super) fn check_lower_fn(as_ct: &impl AsCodeType) -> Result { + Ok(format!("{}.check_lower", ffi_converter_name(as_ct)?)) + } + pub(super) fn lower_fn(as_ct: &impl AsCodeType) -> Result { Ok(format!("{}.lower", ffi_converter_name(as_ct)?)) } @@ -448,8 +544,18 @@ pub mod filters { Ok(as_ct.as_codetype().literal(literal)) } + // Get the idiomatic Python rendering of an individual enum variant's discriminant + pub fn variant_discr_literal(e: &Enum, index: &usize) -> Result { + let literal = e.variant_discr(*index).expect("invalid index"); + Ok(Type::UInt64.as_codetype().literal(&literal)) + } + pub fn ffi_type_name(type_: &FfiType) -> Result { - Ok(PythonCodeOracle::ffi_type_label(type_)) + Ok(PythonCodeOracle.ffi_type_label(type_)) + } + + pub fn ffi_default_value(return_type: Option) -> Result { + Ok(PythonCodeOracle.ffi_default_value(return_type.as_ref())) } /// Get the idiomatic Python rendering of a class name (for enums, records, errors, etc). @@ -471,4 +577,51 @@ pub mod filters { pub fn enum_variant_py(nm: &str) -> Result { Ok(PythonCodeOracle.enum_variant_name(nm)) } + + /// Get the idiomatic Python rendering of an FFI callback function name + pub fn ffi_callback_name(nm: &str) -> Result { + Ok(PythonCodeOracle.ffi_callback_name(nm)) + } + + /// Get the idiomatic Python rendering of an FFI struct name + pub fn ffi_struct_name(nm: &str) -> Result { + Ok(PythonCodeOracle.ffi_struct_name(nm)) + } + + /// Get the idiomatic Python rendering of an individual enum variant. + pub fn object_names(obj: &Object) -> Result<(String, String), askama::Error> { + Ok(PythonCodeOracle.object_names(obj)) + } + + /// Get the idiomatic Python rendering of docstring + pub fn docstring(docstring: &str, spaces: &i32) -> Result { + let docstring = textwrap::dedent(docstring); + // Escape triple quotes to avoid syntax error + let escaped = docstring.replace(r#"""""#, r#"\"\"\""#); + + let wrapped = format!("\"\"\"\n{escaped}\n\"\"\""); + + let spaces = usize::try_from(*spaces).unwrap_or_default(); + Ok(textwrap::indent(&wrapped, &" ".repeat(spaces))) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_docstring_escape() { + let docstring = r#""""This is a docstring beginning with triple quotes. +Contains "quotes" in it. +It also has a triple quote: """ +And a even longer quote: """"""#; + + let expected = r#"""" +\"\"\"This is a docstring beginning with triple quotes. +Contains "quotes" in it. +It also has a triple quote: \"\"\" +And a even longer quote: \"\"\""" +""""#; + + assert_eq!(super::filters::docstring(docstring, &0).unwrap(), expected); + } } -- cgit v1.2.3