Skip to content

Commit eccc994

Browse files
authored
Merge pull request #1847 from bendk/impl-lift-for-flat-errors
Implement `Lift` for flat errors
2 parents ed9362a + 6b75f15 commit eccc994

File tree

4 files changed

+93
-3
lines changed

4 files changed

+93
-3
lines changed

fixtures/coverall/src/coverall.udl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ namespace coverall {
2222
void test_getters(Getters g);
2323

2424
sequence<string> ancestor_names(NodeTrait node);
25+
26+
ReturnOnlyDict output_return_only_dict();
27+
ReturnOnlyEnum output_return_only_enum();
28+
29+
void try_input_return_only_dict(ReturnOnlyDict d);
2530
};
2631

2732
dictionary SimpleDict {
@@ -49,6 +54,21 @@ dictionary SimpleDict {
4954
NodeTrait? test_trait;
5055
};
5156

57+
// Create a type that stores `CoverallFlatError` and therefore can only be lowered but not lifted.
58+
// UniFFI should define a `Lower` implemenation but not try to define `Lift`.
59+
dictionary ReturnOnlyDict {
60+
CoverallFlatError e;
61+
};
62+
63+
// More complicated version of the above, each variant is return-only for different reasons
64+
[Enum]
65+
interface ReturnOnlyEnum {
66+
One(CoverallFlatError e);
67+
Two(ReturnOnlyDict d);
68+
Three(sequence<CoverallFlatError> l);
69+
Four(record<string, CoverallFlatError> m);
70+
};
71+
5272
dictionary DictWithDefaults {
5373
string name = "default-value";
5474
string? category = null;

fixtures/coverall/src/lib.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,44 @@ pub struct SimpleDict {
191191
test_trait: Option<Arc<dyn NodeTrait>>,
192192
}
193193

194+
pub struct ReturnOnlyDict {
195+
e: CoverallFlatError,
196+
}
197+
198+
pub enum ReturnOnlyEnum {
199+
One {
200+
e: CoverallFlatError,
201+
},
202+
Two {
203+
d: ReturnOnlyDict,
204+
},
205+
Three {
206+
l: Vec<CoverallFlatError>,
207+
},
208+
Four {
209+
m: HashMap<String, CoverallFlatError>,
210+
},
211+
}
212+
213+
fn output_return_only_dict() -> ReturnOnlyDict {
214+
ReturnOnlyDict {
215+
e: CoverallFlatError::TooManyVariants { num: 1 },
216+
}
217+
}
218+
219+
fn output_return_only_enum() -> ReturnOnlyEnum {
220+
ReturnOnlyEnum::One {
221+
e: CoverallFlatError::TooManyVariants { num: 2 },
222+
}
223+
}
224+
225+
fn try_input_return_only_dict(_d: ReturnOnlyDict) {
226+
// This function can't work because ReturnOnlyDict contains a flat error and therefore
227+
// can't be lifted by Rust. There's a Python test that the UniFFI code panics before we get here.
228+
//
229+
// FIXME: should be a compile-time error rather than a runtime error (#1850)
230+
}
231+
194232
#[derive(Debug, Clone)]
195233
pub struct DictWithDefaults {
196234
name: String,

fixtures/coverall/tests/bindings/test_coverall.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import unittest
66
from datetime import datetime, timezone
7+
78
from coverall import *
89

910
class TestCoverall(unittest.TestCase):
@@ -289,6 +290,13 @@ def test_bytes(self):
289290
coveralls = Coveralls("test_bytes")
290291
self.assertEqual(coveralls.reverse(b"123"), b"321")
291292

293+
def test_return_only_dict(self):
294+
# try_input_return_only_dict can never work, since ReturnOnlyDict should only be returned
295+
# from Rust not inputted. Test that an attempt raises an internal error rather than tries
296+
# to use an invalid value.
297+
with self.assertRaises(InternalError):
298+
try_input_return_only_dict(ReturnOnlyDict(e=CoverallFlatError.TooManyVariants))
299+
292300
class PyGetters:
293301
def get_bool(self, v, arg2):
294302
return v ^ arg2

uniffi_macros/src/error.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ fn flat_error_ffi_converter_impl(
8787
) -> TokenStream {
8888
let name = ident_to_string(ident);
8989
let lower_impl_spec = tagged_impl_header("Lower", ident, udl_mode);
90+
let lift_impl_spec = tagged_impl_header("Lift", ident, udl_mode);
9091
let derive_ffi_traits = derive_ffi_traits(ident, udl_mode, &["ConvertError"]);
9192
let mod_path = match mod_path() {
9293
Ok(p) => p,
@@ -127,8 +128,7 @@ fn flat_error_ffi_converter_impl(
127128
}
128129
};
129130

130-
let lift_impl = implement_lift.then(|| {
131-
let lift_impl_spec = tagged_impl_header("Lift", ident, udl_mode);
131+
let lift_impl = if implement_lift {
132132
let match_arms = enum_.variants.iter().enumerate().map(|(i, v)| {
133133
let v_ident = &v.ident;
134134
let idx = Index::from(i + 1);
@@ -157,7 +157,31 @@ fn flat_error_ffi_converter_impl(
157157
}
158158

159159
}
160-
});
160+
} else {
161+
quote! {
162+
// Lifting flat errors is not currently supported, but we still define the trait so
163+
// that dicts containing flat errors don't cause compile errors (see ReturnOnlyDict in
164+
// coverall.rs).
165+
//
166+
// Note: it would be better to not derive `Lift` for dictionaries containing flat
167+
// errors, but getting the trait bounds and derived impls there would be much harder.
168+
// For now, we just fail at runtime.
169+
#[automatically_derived]
170+
unsafe #lift_impl_spec {
171+
type FfiType = ::uniffi::RustBuffer;
172+
173+
fn try_read(buf: &mut &[::std::primitive::u8]) -> ::uniffi::deps::anyhow::Result<Self> {
174+
panic!("Can't lift flat errors")
175+
}
176+
177+
fn try_lift(v: ::uniffi::RustBuffer) -> ::uniffi::deps::anyhow::Result<Self> {
178+
panic!("Can't lift flat errors")
179+
}
180+
181+
const TYPE_ID_META: ::uniffi::MetadataBuffer = <Self as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META;
182+
}
183+
}
184+
};
161185

162186
quote! {
163187
#lower_impl

0 commit comments

Comments
 (0)