Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
* Methods now have typing annotations for return values ([#2625](https://github.com/mozilla/uniffi-rs/issues/2625))
* Fix relative imports ([#2657](https://github.com/mozilla/uniffi-rs/pull/2657))
* Fix shadowing param names with internal variables in Python (#2628/#2645)
* Don't allow objects to be passed as arguments when traits are expected (#2649)
- Swift:
* All object protocol conformances are public ([#2671](https://github.com/mozilla/uniffi-rs/pull/2671))
* Initialization functions now have a stable ordering when using external types.
Expand Down
30 changes: 30 additions & 0 deletions fixtures/coverall/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,33 @@ impl StringUtil for StringUtilImpl2 {
format!("{a}{b}")
}
}

/// Object that implements `StringUtil`
/// This lets us test what happens if foreign bindings pass in `StringUtilImpl` when `StringUtil` was expected.
/// Ideally, languages should coerce the object to it's trait, however that's not supported at this time.
/// The current test is simply that this doesn't cause a segfault (#2649)
#[derive(uniffi::Object)]
pub struct StringUtilObject {
separator: String,
}

#[uniffi::export]
impl StringUtilObject {
#[uniffi::constructor]
pub fn new(separator: String) -> Self {
Self { separator }
}
}

#[uniffi::export]
impl StringUtil for StringUtilObject {
fn concat(&self, a: &str, b: &str) -> String {
let separator = &self.separator;
format!("{a}{separator}{b}")
}
}

#[uniffi::export]
pub fn concat_with_string_util(string_util: Arc<dyn StringUtil>, a: &str, b: &str) -> String {
string_util.concat(a, b)
}
Copy link
Member

@mhammond mhammond Oct 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up kinda accidentally adding these kinds of tests here too. I might have a go at consolidating that and this coveralls traits stuff into a dedicated traits fixture?

11 changes: 11 additions & 0 deletions fixtures/coverall/tests/bindings/test_coverall.py
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,17 @@ def test_rust_only_traits(self):
self.assertEqual(traits[0].concat("cow", "boy"), "cowboy")
self.assertEqual(traits[1].concat("cow", "boy"), "cowboy")

def test_pass_object_to_function_that_input_trait(self):
string_util_obj = StringUtilObject("--")

# StringUtilObject implements StringUtil.
# We currently don't support passing a `StringUtilObject` to a function that inputs a `dyn
# StringUtil`.
# This test checks that the result is a TypeError rather than a segfault (#2649)

with self.assertRaises(TypeError):
concat_with_string_util(string_util_obj, "cow", "boy"),

def test_html_error(self):
with self.assertRaises(HtmlError):
validate_html("test")
Expand Down
1 change: 0 additions & 1 deletion fixtures/proc-macro/tests/bindings/test_proc_macro.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
assert trait_impl.concat_strings("foo", "bar") == "foobar"
assert obj.get_trait(trait_impl).concat_strings("foo", "bar") == "foobar"
assert concat_strings_by_ref(trait_impl, "foo", "bar") == "foobar"
assert issubclass(StructWithTrait, Trait)
assert StructWithTrait("me").concat_strings("foo", "bar") == "me: foobar"

trait_impl2 = obj.get_trait_with_foreign(None)
Expand Down
16 changes: 14 additions & 2 deletions uniffi_bindgen/src/bindings/python/pipeline/interfaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,25 @@ pub fn pass(int: &mut Interface) -> Result<()> {
Type::Interface {
name,
external_package_name,
imp,
..
} => (name, external_package_name),
} => {
// For trait interfaces implement in Rust-only, the protocol has `Protocol` appended.
// Trait interfaces with foreign implementations don't have that
match imp {
ObjectImpl::Trait => (format!("{name}Protocol"), external_package_name),
ObjectImpl::CallbackTrait => (name.to_string(), external_package_name),
ObjectImpl::Struct => {
bail!("Objects can only inherit from traits, not other objects")
}
}
}

Type::CallbackInterface {
name,
external_package_name,
..
} => (name, external_package_name),
} => (name.to_string(), external_package_name),
_ => bail!("trait_ty {:?} isn't a trait", t),
};
let fq = match external_package_name {
Expand Down
2 changes: 1 addition & 1 deletion uniffi_bindgen/src/bindings/python/pipeline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ pub fn pipeline() -> Pipeline<initial::Root, Root> {
.pass(config::pass)
.pass(external_types::pass)
.pass(names::pass)
.pass(modules::pass)
.pass(interfaces::pass)
.pass(modules::pass)
.pass(callback_interfaces::pass)
.pass(types::pass)
.pass(default::pass)
Expand Down
5 changes: 4 additions & 1 deletion uniffi_bindgen/src/bindings/python/pipeline/modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ pub fn pass(root: &mut Root) -> Result<()> {
namespace.visit(|e: &Enum| exported_names.push(e.name.clone()));
namespace.visit(|r: &Record| exported_names.push(r.name.clone()));
namespace.visit(|f: &Function| exported_names.push(f.callable.name.clone()));
namespace.visit(|i: &Interface| exported_names.push(i.name.clone()));
namespace.visit(|i: &Interface| {
exported_names.push(i.name.clone());
exported_names.push(i.protocol.name.clone());
});
namespace.visit(|c: &CallbackInterface| exported_names.push(c.name.clone()));
namespace.exported_names = exported_names;

Expand Down