Skip to content

Commit

Permalink
Python: Generate type stubs for all user functions
Browse files Browse the repository at this point in the history
  • Loading branch information
badboy committed Apr 24, 2023
1 parent 5e5a9ba commit 74c8c76
Show file tree
Hide file tree
Showing 8 changed files with 39 additions and 33 deletions.
7 changes: 4 additions & 3 deletions uniffi_bindgen/src/bindings/python/gen_python/compounds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ impl OptionalCodeType {

impl CodeType for OptionalCodeType {
fn type_label(&self, oracle: &dyn CodeOracle) -> String {
oracle.find(&self.inner).type_label(oracle)
format!("typing.Optional[{}]", self.inner.type_label(oracle))
}

fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
Expand Down Expand Up @@ -53,8 +53,9 @@ impl SequenceCodeType {
}

impl CodeType for SequenceCodeType {
fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
"list".to_string()
fn type_label(&self, oracle: &dyn CodeOracle) -> String {
// Python 3.8 and below do not support `list[T]`
format!("typing.List[{}]", self.inner.type_label(oracle))
}

fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
Expand Down
32 changes: 18 additions & 14 deletions uniffi_bindgen/src/bindings/python/gen_python/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,17 @@ fn render_literal(_oracle: &dyn CodeOracle, literal: &Literal) -> String {
}

macro_rules! impl_code_type_for_primitive {
($T:ty, $class_name:literal, $coerce_code:expr) => {
($T:ty, $python_name:literal, $canonical_name:literal, $coerce_code:expr) => {
paste! {
pub struct $T;

impl CodeType for $T {
fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
$class_name.into()
$python_name.into()
}

fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
$canonical_name.into()
}

fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
Expand All @@ -55,15 +59,15 @@ macro_rules! impl_code_type_for_primitive {
};
}

impl_code_type_for_primitive!(BooleanCodeType, "Bool", "bool({})");
impl_code_type_for_primitive!(StringCodeType, "String", "{}");
impl_code_type_for_primitive!(Int8CodeType, "Int8", "int({})");
impl_code_type_for_primitive!(Int16CodeType, "Int16", "int({})");
impl_code_type_for_primitive!(Int32CodeType, "Int32", "int({})");
impl_code_type_for_primitive!(Int64CodeType, "Int64", "int({})");
impl_code_type_for_primitive!(UInt8CodeType, "UInt8", "int({})");
impl_code_type_for_primitive!(UInt16CodeType, "UInt16", "int({})");
impl_code_type_for_primitive!(UInt32CodeType, "UInt32", "int({})");
impl_code_type_for_primitive!(UInt64CodeType, "UInt64", "int({})");
impl_code_type_for_primitive!(Float32CodeType, "Float", "float({})");
impl_code_type_for_primitive!(Float64CodeType, "Double", "float({})");
impl_code_type_for_primitive!(BooleanCodeType, "bool", "Bool", "bool({})");
impl_code_type_for_primitive!(StringCodeType, "str", "String", "{}");
impl_code_type_for_primitive!(Int8CodeType, "int", "Int8", "int({})");
impl_code_type_for_primitive!(Int16CodeType, "int", "Int16", "int({})");
impl_code_type_for_primitive!(Int32CodeType, "int", "Int32", "int({})");
impl_code_type_for_primitive!(Int64CodeType, "int", "Int64", "int({})");
impl_code_type_for_primitive!(UInt8CodeType, "int", "UInt8", "int({})");
impl_code_type_for_primitive!(UInt16CodeType, "int", "UInt16", "int({})");
impl_code_type_for_primitive!(UInt32CodeType, "int", "UInt32", "int({})");
impl_code_type_for_primitive!(UInt64CodeType, "int", "UInt64", "int({})");
impl_code_type_for_primitive!(Float32CodeType, "float", "Float", "float({})");
impl_code_type_for_primitive!(Float64CodeType, "float", "Double", "float({})");
3 changes: 3 additions & 0 deletions uniffi_bindgen/src/bindings/python/templates/CustomType.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{%- match python_config.custom_types.get(name.as_str()) %}
{% when None %}
{#- No custom type config, just forward all methods to our builtin type #}
# Type alias
{{ name }} = {{ builtin|type_name }}

class FfiConverterType{{ name }}:
@staticmethod
def write(value, buf):
Expand Down
4 changes: 2 additions & 2 deletions uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ def __eq__(self, other):
# For each variant, we have an `is_NAME` method for easily checking
# whether an instance is that variant.
{% for variant in e.variants() -%}
def is_{{ variant.name()|var_name }}(self):
def is_{{ variant.name()|var_name }}(self) -> bool:
return isinstance(self, {{ type_name }}.{{ variant.name()|enum_variant_py }})
{% 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_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
{% endfor %}

{% endif %}
Expand Down
6 changes: 3 additions & 3 deletions uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# class separated, then manually add the child classes to the base class's
# __dict__. All of this happens in dummy class to avoid polluting the module
# namespace.
class UniFFIExceptionTmpNamespace:
class UniFFIExceptionTmpNamespace: # type: ignore
class {{ type_name }}(Exception):
pass
{% for variant in e.variants() %}
Expand Down Expand Up @@ -40,9 +40,9 @@ def __str__(self):
{%- endif %}
{%- endif %}

{{ type_name }}.{{ variant_type_name }} = {{ variant_type_name }}
{{ type_name }}.{{ variant_type_name }} = {{ variant_type_name }} # type: ignore
{%- endfor %}
{{ type_name }} = UniFFIExceptionTmpNamespace.{{ type_name }}
{{ type_name }} = UniFFIExceptionTmpNamespace.{{ type_name }} # type: ignore
del UniFFIExceptionTmpNamespace


Expand Down
8 changes: 4 additions & 4 deletions uniffi_bindgen/src/bindings/python/templates/macros.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@
{%- for arg in func.arguments() -%}
{{ arg.name()|var_name }}
{%- match arg.default_value() %}
{%- when Some with(literal) %} = DEFAULT
{%- else %}
{%- when Some with(literal) %}: typing.Union[object, {{ arg|type_name -}}] = DEFAULT
{%- else %}: "{{ arg|type_name -}}"
{%- endmatch %}
{%- if !loop.last %},{% endif -%}
{%- endfor %}
Expand Down Expand Up @@ -77,7 +77,7 @@
if {{ arg.name()|var_name }} is DEFAULT:
{{ arg.name()|var_name }} = {{ literal|literal_py(arg.type_().borrow()) }}
else:
{{ arg.name()|var_name }} = {{ arg.name()|var_name|coerce_py(arg.type_().borrow()) -}}
{{ arg.name()|var_name }} = {{ arg.name()|var_name|coerce_py(arg.type_().borrow()) }} # type: ignore
{%- endmatch %}
{% endfor -%}
{%- endmacro -%}
Expand All @@ -95,7 +95,7 @@
if {{ arg.name()|var_name }} is DEFAULT:
{{ arg.name()|var_name }} = {{ literal|literal_py(arg.type_().borrow()) }}
else:
{{ arg.name()|var_name }} = {{ arg.name()|var_name|coerce_py(arg.type_().borrow()) -}}
{{ arg.name()|var_name }} = {{ arg.name()|var_name|coerce_py(arg.type_().borrow()) }} # type: ignore
{%- endmatch %}
{% endfor -%}
{%- endmacro -%}
6 changes: 1 addition & 5 deletions uniffi_bindgen/src/bindings/python/templates/wrapper.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
# This file was autogenerated by some hot garbage in the `uniffi` crate.
# Trust me, you don't want to mess with it!

# Tell mypy (a type checker) to ignore all errors from this file.
# See https://mypy.readthedocs.io/en/stable/config_file.html?highlight=ignore-errors#confval-ignore_errors
# mypy: ignore-errors

# Common helper code.
#
# Ideally this would live in a separate .py file where it can be unittested etc
Expand All @@ -24,11 +20,11 @@
import struct
import contextlib
import datetime
import typing
{%- if ci.has_async_fns() %}
import asyncio
{%- endif %}
import contextvars
import enum
{%- for req in self.imports() %}
{{ req.render() }}
{%- endfor %}
Expand Down
6 changes: 4 additions & 2 deletions uniffi_bindgen/src/interface/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@ pub enum Type {
String,
Timestamp,
Duration,
// Types defined in the component API, each of which has a string name.
Object(String),
Record(String),
Enum(String),
Error(String),
Expand All @@ -75,6 +73,10 @@ pub enum Type {
name: String,
builtin: Box<Type>,
},
// Types defined in the component API, each of which has a string name.
// Defined last, as its functions might reference any other type from above,
// so should be defined last.
Object(String),
}

#[derive(Debug, Clone, Copy, Eq, PartialEq, Checksum, Ord, PartialOrd)]
Expand Down

0 comments on commit 74c8c76

Please sign in to comment.