Skip to content

Commit

Permalink
Merge pull request #2191 from mozilla/transform-ci
Browse files Browse the repository at this point in the history
Replace templating filters for Python
  • Loading branch information
gruberb authored Aug 1, 2024
2 parents 3cb19d8 + 2d16dd4 commit 106f0b2
Show file tree
Hide file tree
Showing 24 changed files with 489 additions and 94 deletions.
120 changes: 98 additions & 22 deletions uniffi_bindgen/src/bindings/python/gen_python/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use std::fmt::Debug;
use crate::backend::TemplateExpression;

use crate::interface::*;
use crate::VisitMut;

mod callback_interface;
mod compounds;
Expand Down Expand Up @@ -148,7 +149,7 @@ impl Config {
}

// Generate python bindings for the given ComponentInterface, as a string.
pub fn generate_python_bindings(config: &Config, ci: &ComponentInterface) -> Result<String> {
pub fn generate_python_bindings(config: &Config, ci: &mut ComponentInterface) -> Result<String> {
PythonWrapper::new(config.clone(), ci)
.render()
.context("failed to render python bindings")
Expand Down Expand Up @@ -298,10 +299,13 @@ pub struct PythonWrapper<'a> {
type_imports: BTreeSet<ImportRequirement>,
}
impl<'a> PythonWrapper<'a> {
pub fn new(config: Config, ci: &'a ComponentInterface) -> Self {
pub fn new(config: Config, ci: &'a mut ComponentInterface) -> Self {
ci.visit_mut(&PythonCodeOracle);

let type_renderer = TypeRenderer::new(&config, ci);
let type_helper_code = type_renderer.render().unwrap();
let type_imports = type_renderer.imports.into_inner();

Self {
config,
ci,
Expand Down Expand Up @@ -439,6 +443,84 @@ impl PythonCodeOracle {
}
}

impl VisitMut for PythonCodeOracle {
fn visit_record(&self, record: &mut Record) {
record.rename(self.class_name(record.name()));
}

fn visit_object(&self, object: &mut Object) {
object.rename(self.class_name(object.name()));
}

fn visit_field(&self, field: &mut Field) {
field.rename(self.var_name(field.name()));
}

fn visit_ffi_field(&self, ffi_field: &mut FfiField) {
ffi_field.rename(self.var_name(ffi_field.name()));
}

fn visit_ffi_argument(&self, ffi_argument: &mut FfiArgument) {
ffi_argument.rename(self.class_name(ffi_argument.name()));
}

fn visit_enum(&self, is_error: bool, enum_: &mut Enum) {
if is_error {
enum_.rename(self.class_name(enum_.name()));
} else {
enum_.rename(self.enum_variant_name(enum_.name()));
}
}

fn visit_enum_key(&self, key: &mut String) -> String {
self.class_name(key)
}

fn visit_variant(&self, is_error: bool, variant: &mut Variant) {
//TODO: If we want to remove the last var_name filter
// in the template, this is it. We need an additional
// attribute for the `Variant` so we can
// display Python is_NAME functions
// variant.set_is_name(self.var_name(variant.name()));

if is_error {
variant.rename(self.class_name(variant.name()));
} else {
variant.rename(self.enum_variant_name(variant.name()));
}
}

fn visit_type(&self, type_: &mut Type) {
// Renaming Types is a special case. We have simple types with names like
// an Object, but we also have types which have inner_types and builtin types.
// Which in turn have a different name. Therefore we pass the patterns as a
// function down to the renaming operation of the type itself, which can apply it
// to all its nested names if needed.
let name_transformer = |name: &str| self.class_name(name);
type_.rename_recursive(&name_transformer);
}

fn visit_method(&self, method: &mut Method) {
method.rename(self.fn_name(method.name()));
}

fn visit_argument(&self, argument: &mut Argument) {
argument.rename(self.var_name(argument.name()));
}

fn visit_constructor(&self, constructor: &mut Constructor) {
if !constructor.is_primary_constructor() {
constructor.rename(self.fn_name(constructor.name()));
}
}

fn visit_function(&self, function: &mut Function) {
// Conversions for wrapper.py
//TODO: Renaming the function name in wrapper.py is not currently tested
function.rename(self.fn_name(function.name()));
}
}

trait AsCodeType {
fn as_codetype(&self) -> Box<dyn CodeType>;
}
Expand Down Expand Up @@ -500,6 +582,20 @@ pub mod filters {
Ok(as_ct.as_codetype().type_label())
}

//TODO: Remove. Currently just being used by EnumTemplate.py to
// display is_NAME_OF_ENUM.
/// Get the idiomatic Python rendering of a variable name.
pub fn var_name(nm: &str) -> Result<String, askama::Error> {
Ok(PythonCodeOracle.var_name(nm))
}

//TODO: Remove. Currently just being used by wrapper.py to display the
// callback_interface function names.
/// Get the idiomatic Python rendering of a class name (for enums, records, errors, etc).
pub fn class_name(nm: &str) -> Result<String, askama::Error> {
Ok(PythonCodeOracle.class_name(nm))
}

pub(super) fn ffi_converter_name(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
Ok(String::from("_Uniffi") + &as_ct.as_codetype().ffi_converter_name()[3..])
}
Expand Down Expand Up @@ -549,26 +645,6 @@ pub mod filters {
Ok(PythonCodeOracle.ffi_default_value(return_type.as_ref()))
}

/// Get the idiomatic Python rendering of a class name (for enums, records, errors, etc).
pub fn class_name(nm: &str) -> Result<String, askama::Error> {
Ok(PythonCodeOracle.class_name(nm))
}

/// Get the idiomatic Python rendering of a function name.
pub fn fn_name(nm: &str) -> Result<String, askama::Error> {
Ok(PythonCodeOracle.fn_name(nm))
}

/// Get the idiomatic Python rendering of a variable name.
pub fn var_name(nm: &str) -> Result<String, askama::Error> {
Ok(PythonCodeOracle.var_name(nm))
}

/// Get the idiomatic Python rendering of an individual enum variant.
pub fn enum_variant_py(nm: &str) -> Result<String, askama::Error> {
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<String, askama::Error> {
Ok(PythonCodeOracle.ffi_callback_name(nm))
Expand Down
7 changes: 4 additions & 3 deletions uniffi_bindgen/src/bindings/python/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ use fs_err as fs;
mod gen_python;
#[cfg(feature = "bindgen-tests")]
pub mod test;
use crate::{Component, GenerationSettings};
use crate::{BindingGenerator, Component, GenerationSettings};

use gen_python::{generate_python_bindings, Config};

pub struct PythonBindingGenerator;

impl crate::BindingGenerator for PythonBindingGenerator {
impl BindingGenerator for PythonBindingGenerator {
type Config = Config;

fn new_config(&self, root_toml: &toml::Value) -> Result<Self::Config> {
Expand Down Expand Up @@ -50,7 +51,7 @@ impl crate::BindingGenerator for PythonBindingGenerator {
) -> Result<()> {
for Component { ci, config, .. } in components {
let py_file = settings.out_dir.join(format!("{}.py", ci.namespace()));
fs::write(&py_file, generate_python_bindings(config, ci)?)?;
fs::write(&py_file, generate_python_bindings(config, &mut ci.clone())?)?;

if settings.try_format_code {
if let Err(e) = Command::new("yapf").arg(&py_file).output() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ class {{ trait_impl }}:
{%- for (ffi_callback, meth) in vtable_methods.iter() %}

@{{ ffi_callback.name()|ffi_callback_name }}
def {{ meth.name()|fn_name }}(
def {{ meth.name() }}(
{%- for arg in ffi_callback.arguments() %}
{{ arg.name()|var_name }},
{{ arg.name() }},
{%- endfor -%}
{%- if ffi_callback.has_rust_call_status_arg() %}
uniffi_call_status_ptr,
{%- endif %}
):
uniffi_obj = {{ ffi_converter_name }}._handle_map.get(uniffi_handle)
def make_call():
args = ({% for arg in meth.arguments() %}{{ arg|lift_fn }}({{ arg.name()|var_name }}), {% endfor %})
method = uniffi_obj.{{ meth.name()|fn_name }}
args = ({% for arg in meth.arguments() %}{{ arg|lift_fn }}({{ arg.name() }}), {% endfor %})
method = uniffi_obj.{{ meth.name() }}
return method(*args)

{% if !meth.is_async() %}
Expand Down Expand Up @@ -89,7 +89,7 @@ def _uniffi_free(uniffi_handle):
# Generate the FFI VTable. This has a field for each callback interface method.
_uniffi_vtable = {{ vtable|ffi_type_name }}(
{%- for (_, meth) in vtable_methods.iter() %}
{{ meth.name()|fn_name }},
{{ meth.name() }},
{%- endfor %}
_uniffi_free
)
Expand Down
32 changes: 16 additions & 16 deletions uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
class {{ type_name }}(enum.Enum):
{%- call py::docstring(e, 4) %}
{%- for variant in e.variants() %}
{{ variant.name()|enum_variant_py }} = {{ e|variant_discr_literal(loop.index0) }}
{{ variant.name() }} = {{ e|variant_discr_literal(loop.index0) }}
{%- call py::docstring(variant, 4) %}
{% endfor %}
{% else %}
Expand All @@ -21,7 +21,7 @@ def __init__(self):

# Each enum variant is a nested class of the enum itself.
{% for variant in e.variants() -%}
class {{ variant.name()|enum_variant_py }}:
class {{ variant.name() }}:
{%- call py::docstring(variant, 8) %}

{%- if variant.has_nameless_fields() %}
Expand All @@ -38,7 +38,7 @@ def __getitem__(self, index):
return self._values[index]

def __str__(self):
return f"{{ type_name }}.{{ variant.name()|enum_variant_py }}{self._values!r}"
return f"{{ type_name }}.{{ variant.name() }}{self._values!r}"

def __eq__(self, other):
if not other.is_{{ variant.name()|var_name }}():
Expand All @@ -47,27 +47,27 @@ def __eq__(self, other):

{%- else -%}
{%- for field in variant.fields() %}
{{ field.name()|var_name }}: "{{ field|type_name }}"
{{ field.name() }}: "{{ field|type_name }}"
{%- call py::docstring(field, 8) %}
{%- endfor %}

def __init__(self,{% for field in variant.fields() %}{{ field.name()|var_name }}: "{{- field|type_name }}"{% if loop.last %}{% else %}, {% endif %}{% endfor %}):
def __init__(self,{% for field in variant.fields() %}{{ field.name() }}: "{{- field|type_name }}"{% if loop.last %}{% else %}, {% endif %}{% endfor %}):
{%- if variant.has_fields() %}
{%- for field in variant.fields() %}
self.{{ field.name()|var_name }} = {{ field.name()|var_name }}
self.{{ field.name() }} = {{ field.name() }}
{%- endfor %}
{%- else %}
pass
{%- endif %}

def __str__(self):
return "{{ type_name }}.{{ variant.name()|enum_variant_py }}({% for field in variant.fields() %}{{ field.name()|var_name }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in variant.fields() %}self.{{ field.name()|var_name }}{% if loop.last %}{% else %}, {% endif %}{% endfor %})
return "{{ type_name }}.{{ variant.name() }}({% for field in variant.fields() %}{{ field.name() }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in variant.fields() %}self.{{ field.name() }}{% if loop.last %}{% else %}, {% endif %}{% endfor %})

def __eq__(self, other):
if not other.is_{{ variant.name()|var_name }}():
return False
{%- for field in variant.fields() %}
if self.{{ field.name()|var_name }} != other.{{ field.name()|var_name }}:
if self.{{ field.name() }} != other.{{ field.name() }}:
return False
{%- endfor %}
return True
Expand All @@ -78,14 +78,14 @@ def __eq__(self, other):
# whether an instance is that variant.
{% for variant in e.variants() -%}
def is_{{ variant.name()|var_name }}(self) -> bool:
return isinstance(self, {{ type_name }}.{{ variant.name()|enum_variant_py }})
return isinstance(self, {{ type_name }}.{{ variant.name() }})
{% endfor %}

# Now, a little trick - we make each nested variant class be a subclass of the main
# enum class, so that method calls and instance checks etc will work intuitively.
# We might be able to do this a little more neatly with a metaclass, but this'll do.
{% for variant in e.variants() -%}
{{ type_name }}.{{ variant.name()|enum_variant_py }} = type("{{ type_name }}.{{ variant.name()|enum_variant_py }}", ({{ type_name }}.{{variant.name()|enum_variant_py}}, {{ type_name }},), {}) # type: ignore
{{ type_name }}.{{ variant.name() }} = type("{{ type_name }}.{{ variant.name() }}", ({{ type_name }}.{{variant.name()}}, {{ type_name }},), {}) # type: ignore
{% endfor %}

{% endif %}
Expand All @@ -98,9 +98,9 @@ def read(buf):
{%- for variant in e.variants() %}
if variant == {{ loop.index }}:
{%- if e.is_flat() %}
return {{ type_name }}.{{variant.name()|enum_variant_py}}
return {{ type_name }}.{{variant.name()}}
{%- else %}
return {{ type_name }}.{{variant.name()|enum_variant_py}}(
return {{ type_name }}.{{variant.name()}}(
{%- for field in variant.fields() %}
{{ field|read_fn }}(buf),
{%- endfor %}
Expand All @@ -116,15 +116,15 @@ def check_lower(value):
{%- else %}
{%- for variant in e.variants() %}
{%- if e.is_flat() %}
if value == {{ type_name }}.{{ variant.name()|enum_variant_py }}:
if value == {{ type_name }}.{{ variant.name() }}:
{%- else %}
if value.is_{{ variant.name()|var_name }}():
{%- endif %}
{%- for field in variant.fields() %}
{%- if variant.has_nameless_fields() %}
{{ field|check_lower_fn }}(value._values[{{ loop.index0 }}])
{%- else %}
{{ field|check_lower_fn }}(value.{{ field.name()|var_name }})
{{ field|check_lower_fn }}(value.{{ field.name() }})
{%- endif %}
{%- endfor %}
return
Expand All @@ -136,7 +136,7 @@ def check_lower(value):
def write(value, buf):
{%- for variant in e.variants() %}
{%- if e.is_flat() %}
if value == {{ type_name }}.{{ variant.name()|enum_variant_py }}:
if value == {{ type_name }}.{{ variant.name() }}:
buf.write_i32({{ loop.index }})
{%- else %}
if value.is_{{ variant.name()|var_name }}():
Expand All @@ -145,7 +145,7 @@ def write(value, buf):
{%- if variant.has_nameless_fields() %}
{{ field|write_fn }}(value._values[{{ loop.index0 }}], buf)
{%- else %}
{{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
{{ field|write_fn }}(value.{{ field.name() }}, buf)
{%- endif %}
{%- endfor %}
{%- endif %}
Expand Down
Loading

0 comments on commit 106f0b2

Please sign in to comment.