-
Notifications
You must be signed in to change notification settings - Fork 240
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Clean up python bindings generator, add support for remaining data ty…
…pes. This is a significant refactor of the python bindings generator, with the aim of making the generated code a bit cleaner and of better hiding many of our implementation details from the public API. To help prove out the approach, it adds previously-missing support for data types like sequences and maps. We use the new `TypeUniverse` structure to iterate over all types used in the interface, and generate various internal helper functions as methods on our utility classes. This keeps them out of the public API as seen by consumers. For example, for each type that lowers into a `RustBuffer`, there is a corresponding `RustBuffer.allocFrom{{ type_name }}` staticmethod for lowering it and a `RustBuffer.consumeInto{{ type_name }}` for lifting it.
- Loading branch information
Showing
14 changed files
with
696 additions
and
246 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
import sys | ||
import ctypes | ||
from rondpoint import * | ||
|
||
dico = Dictionnaire(Enumeration.DEUX, True, 0, 123456789) | ||
copyDico = copie_dictionnaire(dico) | ||
assert dico == copyDico | ||
|
||
assert copie_enumeration(Enumeration.DEUX) == Enumeration.DEUX | ||
assert copie_enumerations([Enumeration.UN, Enumeration.DEUX]) == [Enumeration.UN, Enumeration.DEUX] | ||
assert copie_carte({"1": Enumeration.UN, "2": Enumeration.DEUX}) == {"1": Enumeration.UN, "2": Enumeration.DEUX} | ||
|
||
assert switcheroo(False) is True | ||
|
||
# Test the roundtrip across the FFI. | ||
# This shows that the values we send come back in exactly the same state as we sent them. | ||
# i.e. it shows that lowering from python and lifting into rust is symmetrical with | ||
# lowering from rust and lifting into python. | ||
rt = Retourneur() | ||
|
||
def affirmAllerRetour(vals, identique): | ||
for v in vals: | ||
id_v = identique(v) | ||
assert id_v == v, f"Round-trip failure: {v} => {id_v}" | ||
|
||
MIN_I8 = -1 * 2**7 | ||
MAX_I8 = 2**7 - 1 | ||
MIN_I16 = -1 * 2**15 | ||
MAX_I16 = 2**15 - 1 | ||
MIN_I32 = -1 * 2**31 | ||
MAX_I32 = 2**31 - 1 | ||
MIN_I64 = -1 * 2**31 | ||
MAX_I64 = 2**31 - 1 | ||
|
||
# Python floats are always doubles, so won't round-trip through f32 correctly. | ||
# This truncates them appropriately. | ||
F32_ONE_THIRD = ctypes.c_float(1.0 / 3).value | ||
|
||
# Booleans | ||
affirmAllerRetour([True, False], rt.identique_boolean) | ||
|
||
# Bytes. | ||
affirmAllerRetour([MIN_I8, -1, 0, 1, MAX_I8], rt.identique_i8) | ||
affirmAllerRetour([0x00, 0x12, 0xFF], rt.identique_u8) | ||
|
||
# Shorts | ||
affirmAllerRetour([MIN_I16, -1, 0, 1, MAX_I16], rt.identique_i16) | ||
affirmAllerRetour([0x0000, 0x1234, 0xFFFF], rt.identique_u16) | ||
|
||
# Ints | ||
affirmAllerRetour([MIN_I32, -1, 0, 1, MAX_I32], rt.identique_i32) | ||
affirmAllerRetour([0x00000000, 0x12345678, 0xFFFFFFFF], rt.identique_u32) | ||
|
||
# Longs | ||
affirmAllerRetour([MIN_I64, -1, 0, 1, MAX_I64], rt.identique_i64) | ||
affirmAllerRetour([0x0000000000000000, 0x1234567890ABCDEF, 0xFFFFFFFFFFFFFFFF], rt.identique_u64) | ||
|
||
# Floats | ||
affirmAllerRetour([0.0, 0.5, 0.25, 1.0, F32_ONE_THIRD], rt.identique_float) | ||
|
||
# Doubles | ||
affirmAllerRetour( | ||
[0.0, 0.5, 0.25, 1.0, 1.0 / 3, sys.float_info.max, sys.float_info.min], | ||
rt.identique_double | ||
) | ||
|
||
# Strings | ||
affirmAllerRetour( | ||
["", "abc", "été", "ښي لاس ته لوستلو لوستل", "😻emoji 👨👧👦multi-emoji, 🇨🇭a flag, a canal, panama"], | ||
rt.identique_string | ||
) | ||
|
||
# Test one way across the FFI. | ||
# | ||
# We send one representation of a value to lib.rs, and it transforms it into another, a string. | ||
# lib.rs sends the string back, and then we compare here in python. | ||
# | ||
# This shows that the values are transformed into strings the same way in both python and rust. | ||
# i.e. if we assume that the string return works (we test this assumption elsewhere) | ||
# we show that lowering from python and lifting into rust has values that both python and rust | ||
# both stringify in the same way. i.e. the same values. | ||
# | ||
# If we roundtripping proves the symmetry of our lowering/lifting from here to rust, and lowering/lifting from rust to here, | ||
# and this convinces us that lowering/lifting from here to rust is correct, then | ||
# together, we've shown the correctness of the return leg. | ||
st = Stringifier() | ||
|
||
def affirmEnchaine(vals, toString, rustyStringify=lambda v: str(v).lower()): | ||
for v in vals: | ||
str_v = toString(v) | ||
assert rustyStringify(v) == str_v, f"String compare error {v} => {str_v}" | ||
|
||
# Test the efficacy of the string transport from rust. If this fails, but everything else | ||
# works, then things are very weird. | ||
wellKnown = st.well_known_string("python") | ||
assert "uniffi 💚 python!" == wellKnown | ||
|
||
# Booleans | ||
affirmEnchaine([True, False], st.to_string_boolean) | ||
|
||
# Bytes. | ||
affirmEnchaine([MIN_I8, -1, 0, 1, MAX_I8], st.to_string_i8) | ||
affirmEnchaine([0x00, 0x12, 0xFF], st.to_string_u8) | ||
|
||
# Shorts | ||
affirmEnchaine([MIN_I16, -1, 0, 1, MAX_I16], st.to_string_i16) | ||
affirmEnchaine([0x0000, 0x1234, 0xFFFF], st.to_string_u16) | ||
|
||
# Ints | ||
affirmEnchaine([MIN_I32, -1, 0, 1, MAX_I32], st.to_string_i32) | ||
affirmEnchaine([0x00000000, 0x12345678, 0xFFFFFFFF], st.to_string_u32) | ||
|
||
# Longs | ||
affirmEnchaine([MIN_I64, -1, 0, 1, MAX_I64], st.to_string_i64) | ||
affirmEnchaine([0x0000000000000000, 0x1234567890ABCDEF, 0xFFFFFFFFFFFFFFFF], st.to_string_u64) | ||
|
||
# Floats | ||
def rustyFloatToStr(v): | ||
"""Stringify a float in the same way that rust seems to.""" | ||
# Rust doesn't include the decimal part of whole enumber floats when stringifying. | ||
if int(v) == v: | ||
return str(int(v)) | ||
return str(v) | ||
|
||
affirmEnchaine([0.0, 0.5, 0.25, 1.0], st.to_string_float, rustyFloatToStr) | ||
assert st.to_string_float(F32_ONE_THIRD) == "0.33333334" # annoyingly different string repr | ||
|
||
# Doubles | ||
# TODO: float_info.max/float_info.min don't stringify-roundtrip properly yet, TBD. | ||
affirmEnchaine( | ||
[0.0, 0.5, 0.25, 1.0, 1.0 / 3], | ||
st.to_string_double, | ||
rustyFloatToStr, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
class {{ e.name() }}(enum.Enum): | ||
class {{ e.name()|class_name_py }}(enum.Enum): | ||
{% for variant in e.variants() -%} | ||
{{ variant|enum_name_py }} = {{ loop.index }} | ||
{% endfor -%} | ||
{% endfor %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 2 additions & 36 deletions
38
uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,49 +1,15 @@ | ||
class {{ rec.name() }}(object): | ||
class {{ rec.name()|class_name_py }}(object): | ||
def __init__(self,{% for field in rec.fields() %}{{ field.name()|var_name_py }}{% if loop.last %}{% else %}, {% endif %}{% endfor %}): | ||
{%- for field in rec.fields() %} | ||
self.{{ field.name()|var_name_py }} = {{ field.name()|var_name_py }} | ||
{%- endfor %} | ||
|
||
def __str__(self): | ||
return "{{ rec.name() }}({% for field in rec.fields() %}{{ field.name() }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in rec.fields() %}self.{{ field.name() }}{% if loop.last %}{% else %}, {% endif %}{% endfor %}) | ||
return "{{ rec.name()|class_name_py }}({% for field in rec.fields() %}{{ field.name() }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in rec.fields() %}self.{{ field.name() }}{% if loop.last %}{% else %}, {% endif %}{% endfor %}) | ||
|
||
def __eq__(self, other): | ||
{%- for field in rec.fields() %} | ||
if self.{{ field.name()|var_name_py }} != other.{{ field.name()|var_name_py }}: | ||
return False | ||
return True | ||
{%- endfor %} | ||
|
||
@classmethod | ||
def _coerce(cls, v): | ||
# TODO: maybe we could do a bit of duck-typing here, details TBD | ||
assert isinstance(v, {{ rec.name() }}) | ||
return v | ||
|
||
@classmethod | ||
def _lift(cls, rbuf): | ||
return cls._liftFrom(RustBufferStream(rbuf)) | ||
|
||
@classmethod | ||
def _liftFrom(cls, buf): | ||
return cls( | ||
{%- for field in rec.fields() %} | ||
{{ "buf"|lift_from_py(field.type_()) }}{% if loop.last %}{% else %},{% endif %} | ||
{%- endfor %} | ||
) | ||
|
||
@classmethod | ||
def _lower(cls, v): | ||
buf = RustBufferBuilder() | ||
try: | ||
cls._lowerInto(v, buf) | ||
return buf.finalize() | ||
except Exception: | ||
buf.discard() | ||
raise | ||
|
||
@classmethod | ||
def _lowerInto(cls, v, buf): | ||
{%- for field in rec.fields() %} | ||
{{ "(v.{})"|format(field.name())|lower_into_py("buf", field.type_()) }} | ||
{%- endfor %} |
Oops, something went wrong.