summaryrefslogtreecommitdiffstats
path: root/toolkit/components/uniffi-fixtures
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/uniffi-fixtures')
-rw-r--r--toolkit/components/uniffi-fixtures/arithmetic/Cargo.toml21
-rw-r--r--toolkit/components/uniffi-fixtures/arithmetic/build.rs7
-rw-r--r--toolkit/components/uniffi-fixtures/arithmetic/src/arithmetic.udl16
-rw-r--r--toolkit/components/uniffi-fixtures/arithmetic/src/lib.rs34
-rw-r--r--toolkit/components/uniffi-fixtures/arithmetic/tests/bindings/test_arithmetic.kts29
-rw-r--r--toolkit/components/uniffi-fixtures/arithmetic/tests/bindings/test_arithmetic.py37
-rw-r--r--toolkit/components/uniffi-fixtures/arithmetic/tests/bindings/test_arithmetic.rb31
-rw-r--r--toolkit/components/uniffi-fixtures/arithmetic/tests/bindings/test_arithmetic.swift32
-rw-r--r--toolkit/components/uniffi-fixtures/arithmetic/tests/test_generated_bindings.rs6
-rw-r--r--toolkit/components/uniffi-fixtures/arithmetic/uniffi.toml2
-rw-r--r--toolkit/components/uniffi-fixtures/callbacks/Cargo.toml14
-rw-r--r--toolkit/components/uniffi-fixtures/callbacks/build.rs7
-rw-r--r--toolkit/components/uniffi-fixtures/callbacks/src/callbacks.udl12
-rw-r--r--toolkit/components/uniffi-fixtures/callbacks/src/lib.rs23
-rw-r--r--toolkit/components/uniffi-fixtures/custom-types/Cargo.toml21
-rw-r--r--toolkit/components/uniffi-fixtures/custom-types/build.rs7
-rw-r--r--toolkit/components/uniffi-fixtures/custom-types/src/custom-types.udl14
-rw-r--r--toolkit/components/uniffi-fixtures/custom-types/src/lib.rs52
-rw-r--r--toolkit/components/uniffi-fixtures/custom-types/tests/bindings/test_custom_types.kts21
-rw-r--r--toolkit/components/uniffi-fixtures/custom-types/tests/bindings/test_custom_types.swift20
-rw-r--r--toolkit/components/uniffi-fixtures/custom-types/tests/test_generated_bindings.rs7
-rw-r--r--toolkit/components/uniffi-fixtures/custom-types/uniffi.toml40
-rw-r--r--toolkit/components/uniffi-fixtures/external-types/Cargo.toml15
-rw-r--r--toolkit/components/uniffi-fixtures/external-types/build.rs7
-rw-r--r--toolkit/components/uniffi-fixtures/external-types/src/external-types.udl11
-rw-r--r--toolkit/components/uniffi-fixtures/external-types/src/lib.rs18
-rw-r--r--toolkit/components/uniffi-fixtures/geometry/Cargo.toml20
-rw-r--r--toolkit/components/uniffi-fixtures/geometry/build.rs7
-rw-r--r--toolkit/components/uniffi-fixtures/geometry/src/geometry.udl15
-rw-r--r--toolkit/components/uniffi-fixtures/geometry/src/lib.rs47
-rw-r--r--toolkit/components/uniffi-fixtures/geometry/tests/bindings/test_geometry.kts10
-rw-r--r--toolkit/components/uniffi-fixtures/geometry/tests/bindings/test_geometry.py10
-rw-r--r--toolkit/components/uniffi-fixtures/geometry/tests/bindings/test_geometry.rb16
-rw-r--r--toolkit/components/uniffi-fixtures/geometry/tests/bindings/test_geometry.swift10
-rw-r--r--toolkit/components/uniffi-fixtures/geometry/tests/test_generated_bindings.rs6
-rw-r--r--toolkit/components/uniffi-fixtures/refcounts/Cargo.toml12
-rw-r--r--toolkit/components/uniffi-fixtures/refcounts/build.rs7
-rw-r--r--toolkit/components/uniffi-fixtures/refcounts/src/lib.rs32
-rw-r--r--toolkit/components/uniffi-fixtures/refcounts/src/refcounts.udl8
-rw-r--r--toolkit/components/uniffi-fixtures/rondpoint/Cargo.toml20
-rw-r--r--toolkit/components/uniffi-fixtures/rondpoint/build.rs7
-rw-r--r--toolkit/components/uniffi-fixtures/rondpoint/src/lib.rs293
-rw-r--r--toolkit/components/uniffi-fixtures/rondpoint/src/rondpoint.udl146
-rw-r--r--toolkit/components/uniffi-fixtures/rondpoint/tests/bindings/test_rondpoint.kts250
-rw-r--r--toolkit/components/uniffi-fixtures/rondpoint/tests/bindings/test_rondpoint.py183
-rw-r--r--toolkit/components/uniffi-fixtures/rondpoint/tests/bindings/test_rondpoint.rb147
-rw-r--r--toolkit/components/uniffi-fixtures/rondpoint/tests/bindings/test_rondpoint.swift232
-rw-r--r--toolkit/components/uniffi-fixtures/rondpoint/tests/test_generated_bindings.rs6
-rw-r--r--toolkit/components/uniffi-fixtures/sprites/Cargo.toml20
-rw-r--r--toolkit/components/uniffi-fixtures/sprites/build.rs7
-rw-r--r--toolkit/components/uniffi-fixtures/sprites/src/lib.rs65
-rw-r--r--toolkit/components/uniffi-fixtures/sprites/src/sprites.udl22
-rw-r--r--toolkit/components/uniffi-fixtures/sprites/tests/bindings/test_sprites.kts25
-rw-r--r--toolkit/components/uniffi-fixtures/sprites/tests/bindings/test_sprites.py16
-rw-r--r--toolkit/components/uniffi-fixtures/sprites/tests/bindings/test_sprites.rb22
-rw-r--r--toolkit/components/uniffi-fixtures/sprites/tests/bindings/test_sprites.swift16
-rw-r--r--toolkit/components/uniffi-fixtures/sprites/tests/test_generated_bindings.rs6
-rw-r--r--toolkit/components/uniffi-fixtures/todolist/Cargo.toml22
-rw-r--r--toolkit/components/uniffi-fixtures/todolist/build.rs7
-rw-r--r--toolkit/components/uniffi-fixtures/todolist/src/lib.rs150
-rw-r--r--toolkit/components/uniffi-fixtures/todolist/src/todolist.udl38
-rw-r--r--toolkit/components/uniffi-fixtures/todolist/tests/bindings/test_todolist.kts83
-rw-r--r--toolkit/components/uniffi-fixtures/todolist/tests/bindings/test_todolist.py56
-rw-r--r--toolkit/components/uniffi-fixtures/todolist/tests/bindings/test_todolist.rb47
-rw-r--r--toolkit/components/uniffi-fixtures/todolist/tests/bindings/test_todolist.swift69
-rw-r--r--toolkit/components/uniffi-fixtures/todolist/tests/test_generated_bindings.rs6
66 files changed, 2665 insertions, 0 deletions
diff --git a/toolkit/components/uniffi-fixtures/arithmetic/Cargo.toml b/toolkit/components/uniffi-fixtures/arithmetic/Cargo.toml
new file mode 100644
index 0000000000..b497b8cccc
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/arithmetic/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "uniffi-example-arithmetic"
+edition = "2021"
+version = "0.22.0"
+authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
+license = "MPL-2.0"
+publish = false
+
+[lib]
+crate-type = ["lib", "cdylib"]
+name = "arithmetical"
+
+[dependencies]
+uniffi = { workspace = true }
+thiserror = "1.0"
+
+[build-dependencies]
+uniffi = { workspace = true, features = ["build"] }
+
+[dev-dependencies]
+uniffi = { workspace = true, features = ["bindgen-tests"] }
diff --git a/toolkit/components/uniffi-fixtures/arithmetic/build.rs b/toolkit/components/uniffi-fixtures/arithmetic/build.rs
new file mode 100644
index 0000000000..303ac22d8d
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/arithmetic/build.rs
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+fn main() {
+ uniffi::generate_scaffolding("src/arithmetic.udl").unwrap();
+}
diff --git a/toolkit/components/uniffi-fixtures/arithmetic/src/arithmetic.udl b/toolkit/components/uniffi-fixtures/arithmetic/src/arithmetic.udl
new file mode 100644
index 0000000000..117df4834a
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/arithmetic/src/arithmetic.udl
@@ -0,0 +1,16 @@
+[Error]
+enum ArithmeticError {
+ "IntegerOverflow",
+};
+
+namespace arithmetic {
+ [Throws=ArithmeticError]
+ u64 add(u64 a, u64 b);
+
+ [Throws=ArithmeticError]
+ u64 sub(u64 a, u64 b);
+
+ u64 div(u64 dividend, u64 divisor);
+
+ boolean equal(u64 a, u64 b);
+};
diff --git a/toolkit/components/uniffi-fixtures/arithmetic/src/lib.rs b/toolkit/components/uniffi-fixtures/arithmetic/src/lib.rs
new file mode 100644
index 0000000000..92ab8c072b
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/arithmetic/src/lib.rs
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#[derive(Debug, thiserror::Error)]
+pub enum ArithmeticError {
+ #[error("Integer overflow on an operation with {a} and {b}")]
+ IntegerOverflow { a: u64, b: u64 },
+}
+
+fn add(a: u64, b: u64) -> Result<u64> {
+ a.checked_add(b)
+ .ok_or(ArithmeticError::IntegerOverflow { a, b })
+}
+
+fn sub(a: u64, b: u64) -> Result<u64> {
+ a.checked_sub(b)
+ .ok_or(ArithmeticError::IntegerOverflow { a, b })
+}
+
+fn div(dividend: u64, divisor: u64) -> u64 {
+ if divisor == 0 {
+ panic!("Can't divide by zero");
+ }
+ dividend / divisor
+}
+
+fn equal(a: u64, b: u64) -> bool {
+ a == b
+}
+
+type Result<T, E = ArithmeticError> = std::result::Result<T, E>;
+
+uniffi::include_scaffolding!("arithmetic");
diff --git a/toolkit/components/uniffi-fixtures/arithmetic/tests/bindings/test_arithmetic.kts b/toolkit/components/uniffi-fixtures/arithmetic/tests/bindings/test_arithmetic.kts
new file mode 100644
index 0000000000..ef11850ae2
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/arithmetic/tests/bindings/test_arithmetic.kts
@@ -0,0 +1,29 @@
+import org.mozilla.uniffi.example.arithmetic.*;
+
+assert(add(2u, 4u) == 6uL)
+assert(add(4u, 8u) == 12uL)
+
+try {
+ sub(0u, 2u)
+ throw RuntimeException("Should have thrown a IntegerOverflow exception!")
+} catch (e: ArithmeticException) {
+ // It's okay!
+}
+
+assert(sub(4u, 2u) == 2uL)
+assert(sub(8u, 4u) == 4uL)
+
+assert(div(8u, 4u) == 2uL)
+
+try {
+ div(8u, 0u)
+ throw RuntimeException("Should have panicked when dividing by zero")
+} catch (e: InternalException) {
+ // It's okay!
+}
+
+assert(equal(2u, 2uL))
+assert(equal(4u, 4uL))
+
+assert(!equal(2u, 4uL))
+assert(!equal(4u, 8uL))
diff --git a/toolkit/components/uniffi-fixtures/arithmetic/tests/bindings/test_arithmetic.py b/toolkit/components/uniffi-fixtures/arithmetic/tests/bindings/test_arithmetic.py
new file mode 100644
index 0000000000..e1b4f71277
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/arithmetic/tests/bindings/test_arithmetic.py
@@ -0,0 +1,37 @@
+from arithmetic import InternalError, add, div, equal, sub
+
+try:
+ add(18446744073709551615, 1)
+ assert not ("Should have thrown a IntegerOverflow exception!")
+except ArithmeticError.IntegerOverflow:
+ # It's okay!
+ pass
+
+assert add(2, 4) == 6
+assert add(4, 8) == 12
+
+try:
+ sub(0, 1)
+ assert not ("Should have thrown a IntegerOverflow exception!")
+except ArithmeticError.IntegerOverflow:
+ # It's okay!
+ pass
+
+assert sub(4, 2) == 2
+assert sub(8, 4) == 4
+
+assert div(8, 4) == 2
+
+try:
+ div(8, 0)
+except InternalError:
+ # It's okay!
+ pass
+else:
+ assert not ("Should have panicked when dividing by zero")
+
+assert equal(2, 2)
+assert equal(4, 4)
+
+assert not equal(2, 4)
+assert not equal(4, 8)
diff --git a/toolkit/components/uniffi-fixtures/arithmetic/tests/bindings/test_arithmetic.rb b/toolkit/components/uniffi-fixtures/arithmetic/tests/bindings/test_arithmetic.rb
new file mode 100644
index 0000000000..6669eb279f
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/arithmetic/tests/bindings/test_arithmetic.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'test/unit'
+require 'arithmetic'
+
+include Test::Unit::Assertions
+
+assert_raise Arithmetic::ArithmeticError::IntegerOverflow do
+ Arithmetic.add 18_446_744_073_709_551_615, 1
+end
+
+assert_equal Arithmetic.add(2, 4), 6
+assert_equal Arithmetic.add(4, 8), 12
+
+assert_raise Arithmetic::ArithmeticError::IntegerOverflow do
+ Arithmetic.sub 0, 1
+end
+
+assert_equal Arithmetic.sub(4, 2), 2
+assert_equal Arithmetic.sub(8, 4), 4
+assert_equal Arithmetic.div(8, 4), 2
+
+assert_raise Arithmetic::InternalError do
+ Arithmetic.div 8, 0
+end
+
+assert Arithmetic.equal(2, 2)
+assert Arithmetic.equal(4, 4)
+
+assert !Arithmetic.equal(2, 4)
+assert !Arithmetic.equal(4, 8)
diff --git a/toolkit/components/uniffi-fixtures/arithmetic/tests/bindings/test_arithmetic.swift b/toolkit/components/uniffi-fixtures/arithmetic/tests/bindings/test_arithmetic.swift
new file mode 100644
index 0000000000..a8e34680e4
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/arithmetic/tests/bindings/test_arithmetic.swift
@@ -0,0 +1,32 @@
+import arithmetic
+
+do {
+ let _ = try add(a: 18446744073709551615, b: 1)
+ fatalError("Should have thrown a IntegerOverflow exception!")
+} catch ArithmeticError.IntegerOverflow {
+ // It's okay!
+}
+
+assert(try! add(a: 2, b: 4) == 6, "add work")
+assert(try! add(a: 4, b: 8) == 12, "add work")
+
+do {
+ let _ = try sub(a: 0, b: 1)
+ fatalError("Should have thrown a IntegerOverflow exception!")
+} catch ArithmeticError.IntegerOverflow {
+ // It's okay!
+}
+
+assert(try! sub(a: 4, b: 2) == 2, "sub work")
+assert(try! sub(a: 8, b: 4) == 4, "sub work")
+
+assert(div(dividend: 8, divisor: 4) == 2, "div works")
+
+// We can't test panicking in Swift because we force unwrap the error in
+// `div`, which we can't catch.
+
+assert(equal(a: 2, b: 2), "equal works")
+assert(equal(a: 4, b: 4), "equal works")
+
+assert(!equal(a: 2, b: 4), "non-equal works")
+assert(!equal(a: 4, b: 8), "non-equal works")
diff --git a/toolkit/components/uniffi-fixtures/arithmetic/tests/test_generated_bindings.rs b/toolkit/components/uniffi-fixtures/arithmetic/tests/test_generated_bindings.rs
new file mode 100644
index 0000000000..168e6e1d4c
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/arithmetic/tests/test_generated_bindings.rs
@@ -0,0 +1,6 @@
+uniffi::build_foreign_language_testcases!(
+ "tests/bindings/test_arithmetic.rb",
+ "tests/bindings/test_arithmetic.py",
+ "tests/bindings/test_arithmetic.kts",
+ "tests/bindings/test_arithmetic.swift",
+);
diff --git a/toolkit/components/uniffi-fixtures/arithmetic/uniffi.toml b/toolkit/components/uniffi-fixtures/arithmetic/uniffi.toml
new file mode 100644
index 0000000000..883231dcaa
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/arithmetic/uniffi.toml
@@ -0,0 +1,2 @@
+[bindings.kotlin]
+package_name = "org.mozilla.uniffi.example.arithmetic"
diff --git a/toolkit/components/uniffi-fixtures/callbacks/Cargo.toml b/toolkit/components/uniffi-fixtures/callbacks/Cargo.toml
new file mode 100644
index 0000000000..19a8d61070
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/callbacks/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "uniffi-fixture-callbacks"
+edition = "2021"
+version = "0.21.0"
+authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
+license = "MPL-2.0"
+publish = false
+
+[dependencies]
+uniffi = { workspace = true }
+thiserror = "1.0"
+
+[build-dependencies]
+uniffi = { workspace = true, features = ["build"] }
diff --git a/toolkit/components/uniffi-fixtures/callbacks/build.rs b/toolkit/components/uniffi-fixtures/callbacks/build.rs
new file mode 100644
index 0000000000..e7152d922a
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/callbacks/build.rs
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+fn main() {
+ uniffi::generate_scaffolding("./src/callbacks.udl").unwrap();
+}
diff --git a/toolkit/components/uniffi-fixtures/callbacks/src/callbacks.udl b/toolkit/components/uniffi-fixtures/callbacks/src/callbacks.udl
new file mode 100644
index 0000000000..a6d44ef598
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/callbacks/src/callbacks.udl
@@ -0,0 +1,12 @@
+callback interface Logger {
+ void log(string message);
+ void finished();
+ };
+
+namespace fixture_callbacks {
+ // Log all even numbers in a vec, then call the finished() method
+ void log_even_numbers(Logger logger, sequence<i32> items);
+ // Works exactly the same as `log_even_numbers()`, except we configure this
+ // to run on the main thread in `uniffi-bindgen-gecko-js/config.toml`
+ void log_even_numbers_main_thread(Logger logger, sequence<i32> items);
+};
diff --git a/toolkit/components/uniffi-fixtures/callbacks/src/lib.rs b/toolkit/components/uniffi-fixtures/callbacks/src/lib.rs
new file mode 100644
index 0000000000..9ada66bca5
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/callbacks/src/lib.rs
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+trait Logger {
+ fn log(&self, message: String);
+ fn finished(&self);
+}
+
+fn log_even_numbers(logger: Box<dyn Logger>, items: Vec<i32>) {
+ for i in items {
+ if i % 2 == 0 {
+ logger.log(format!("Saw even number: {i}"))
+ }
+ }
+ logger.finished();
+}
+
+fn log_even_numbers_main_thread(logger: Box<dyn Logger>, items: Vec<i32>) {
+ log_even_numbers(logger, items)
+}
+
+include!(concat!(env!("OUT_DIR"), "/callbacks.uniffi.rs"));
diff --git a/toolkit/components/uniffi-fixtures/custom-types/Cargo.toml b/toolkit/components/uniffi-fixtures/custom-types/Cargo.toml
new file mode 100644
index 0000000000..a05324952e
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/custom-types/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "uniffi-example-custom-types"
+edition = "2021"
+version = "0.19.6"
+authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
+license = "MPL-2.0"
+publish = false
+
+[lib]
+crate-type = ["lib", "cdylib"]
+name = "uniffi_custom_types"
+
+[dependencies]
+anyhow = "1"
+bytes = "1.0"
+serde_json = "1"
+uniffi = { workspace = true }
+url = "2.4"
+
+[build-dependencies]
+uniffi = { workspace = true, features = ["build"] }
diff --git a/toolkit/components/uniffi-fixtures/custom-types/build.rs b/toolkit/components/uniffi-fixtures/custom-types/build.rs
new file mode 100644
index 0000000000..10b6d220da
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/custom-types/build.rs
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+fn main() {
+ uniffi::generate_scaffolding("./src/custom-types.udl").unwrap();
+}
diff --git a/toolkit/components/uniffi-fixtures/custom-types/src/custom-types.udl b/toolkit/components/uniffi-fixtures/custom-types/src/custom-types.udl
new file mode 100644
index 0000000000..8fef8ff41a
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/custom-types/src/custom-types.udl
@@ -0,0 +1,14 @@
+[Custom]
+typedef string Url;
+
+[Custom]
+typedef i64 Handle;
+
+dictionary CustomTypesDemo {
+ Url url;
+ Handle handle;
+};
+
+namespace custom_types {
+ CustomTypesDemo get_custom_types_demo(CustomTypesDemo? demo);
+};
diff --git a/toolkit/components/uniffi-fixtures/custom-types/src/lib.rs b/toolkit/components/uniffi-fixtures/custom-types/src/lib.rs
new file mode 100644
index 0000000000..61aea7a11a
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/custom-types/src/lib.rs
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use url::Url;
+
+// Custom Handle type which trivially wraps an i64.
+pub struct Handle(pub i64);
+
+// We must implement the UniffiCustomTypeConverter trait for each custom type on the scaffolding side
+impl UniffiCustomTypeConverter for Handle {
+ // The `Builtin` type will be used to marshall values across the FFI
+ type Builtin = i64;
+
+ // Convert Builtin to our custom type
+ fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {
+ Ok(Handle(val))
+ }
+
+ // Convert our custom type to Builtin
+ fn from_custom(obj: Self) -> Self::Builtin {
+ obj.0
+ }
+}
+
+// Use `url::Url` as a custom type, with `String` as the Builtin
+impl UniffiCustomTypeConverter for Url {
+ type Builtin = String;
+
+ fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {
+ Ok(Url::parse(&val)?)
+ }
+
+ fn from_custom(obj: Self) -> Self::Builtin {
+ obj.to_string()
+ }
+}
+
+// And a little struct and function that ties them together.
+pub struct CustomTypesDemo {
+ url: Url,
+ handle: Handle,
+}
+
+pub fn get_custom_types_demo(v: Option<CustomTypesDemo>) -> CustomTypesDemo {
+ v.unwrap_or_else(|| CustomTypesDemo {
+ url: Url::parse("http://example.com/").unwrap(),
+ handle: Handle(123),
+ })
+}
+
+include!(concat!(env!("OUT_DIR"), "/custom-types.uniffi.rs"));
diff --git a/toolkit/components/uniffi-fixtures/custom-types/tests/bindings/test_custom_types.kts b/toolkit/components/uniffi-fixtures/custom-types/tests/bindings/test_custom_types.kts
new file mode 100644
index 0000000000..d75bd99eee
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/custom-types/tests/bindings/test_custom_types.kts
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import java.net.URL
+
+import customtypes.*
+
+// TODO: use an actual test runner.
+
+// Get the custom types and check their data
+val demo = getCustomTypesDemo(null)
+// URL is customized on the bindings side
+assert(demo.url == URL("http://example.com/"))
+// Handle isn't so it appears as a plain Long
+assert(demo.handle == 123L)
+
+// Change some data and ensure that the round-trip works
+demo.url = URL("http://new.example.com/")
+demo.handle = 456;
+assert(demo == getCustomTypesDemo(demo))
diff --git a/toolkit/components/uniffi-fixtures/custom-types/tests/bindings/test_custom_types.swift b/toolkit/components/uniffi-fixtures/custom-types/tests/bindings/test_custom_types.swift
new file mode 100644
index 0000000000..5aaf6ff3b0
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/custom-types/tests/bindings/test_custom_types.swift
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import custom_types
+import Foundation
+
+// TODO: use an actual test runner.
+
+do {
+ // Test simple values.
+ var demo = getCustomTypesDemo(demo: nil)
+ assert(demo.url == URL(string: "http://example.com/"))
+ assert(demo.handle == 123)
+
+ // Change some data and ensure that the round-trip works
+ demo.url = URL(string: "http://new.example.com/")!
+ demo.handle = 456
+ assert(demo == getCustomTypesDemo(demo: demo))
+}
diff --git a/toolkit/components/uniffi-fixtures/custom-types/tests/test_generated_bindings.rs b/toolkit/components/uniffi-fixtures/custom-types/tests/test_generated_bindings.rs
new file mode 100644
index 0000000000..38db89790e
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/custom-types/tests/test_generated_bindings.rs
@@ -0,0 +1,7 @@
+uniffi::build_foreign_language_testcases!(
+ ["src/custom-types.udl"],
+ [
+ "tests/bindings/test_custom_types.kts",
+ "tests/bindings/test_custom_types.swift",
+ ]
+);
diff --git a/toolkit/components/uniffi-fixtures/custom-types/uniffi.toml b/toolkit/components/uniffi-fixtures/custom-types/uniffi.toml
new file mode 100644
index 0000000000..873fa090fb
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/custom-types/uniffi.toml
@@ -0,0 +1,40 @@
+[bindings.swift]
+cdylib_name = "custom_types"
+
+[bindings.swift.custom_types.Url]
+# Name of the type in the Swift code
+type_name = "URL"
+# Modules that need to be imported
+imports = ["Foundation"]
+# Functions to convert between strings and URLs
+into_custom = "URL(string: {})!"
+from_custom = "String(describing: {})"
+
+[bindings.kotlin]
+cdylib_name = "custom_types"
+package_name = "customtypes"
+
+[bindings.kotlin.custom_types.Url]
+# Name of the type in the Kotlin code
+type_name = "URL"
+# Classes that need to be imported
+imports = [ "java.net.URL" ]
+# Functions to convert between strings and URLs
+into_custom = "URL({})"
+from_custom = "{}.toString()"
+
+[bindings.python]
+cdylib_name = "custom_types"
+
+[bindings.python.custom_types.Url]
+# We're going to be the urllib.parse.ParseResult class, which is the closest
+# thing Python has to a Url class. No need to specify `type_name` though,
+# since Python is loosely typed.
+# modules to import
+imports = ["urllib.parse"]
+# Functions to convert between strings and the ParsedUrl class
+into_custom = "urllib.parse.urlparse({})"
+from_custom = "urllib.parse.urlunparse({})"
+
+[bindings.ruby]
+cdylib_name = "custom_types"
diff --git a/toolkit/components/uniffi-fixtures/external-types/Cargo.toml b/toolkit/components/uniffi-fixtures/external-types/Cargo.toml
new file mode 100644
index 0000000000..b70673374e
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/external-types/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "uniffi-fixture-external-types"
+edition = "2021"
+version = "0.21.0"
+authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
+license = "MPL-2.0"
+publish = false
+
+[dependencies]
+uniffi-example-geometry = { path = "../geometry/" }
+uniffi = { workspace = true }
+thiserror = "1.0"
+
+[build-dependencies]
+uniffi = { workspace = true, features = ["build"] }
diff --git a/toolkit/components/uniffi-fixtures/external-types/build.rs b/toolkit/components/uniffi-fixtures/external-types/build.rs
new file mode 100644
index 0000000000..13f56ca56b
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/external-types/build.rs
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+fn main() {
+ uniffi::generate_scaffolding("./src/external-types.udl").unwrap();
+}
diff --git a/toolkit/components/uniffi-fixtures/external-types/src/external-types.udl b/toolkit/components/uniffi-fixtures/external-types/src/external-types.udl
new file mode 100644
index 0000000000..d50fc680f7
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/external-types/src/external-types.udl
@@ -0,0 +1,11 @@
+// A type defined in the `geometry` component
+[External="uniffi_geometry"]
+typedef extern Point;
+
+[External="uniffi_geometry"]
+typedef extern Line;
+
+namespace external_types {
+ double gradient(Line? value);
+ Point? intersection(Line ln1, Line ln2);
+};
diff --git a/toolkit/components/uniffi-fixtures/external-types/src/lib.rs b/toolkit/components/uniffi-fixtures/external-types/src/lib.rs
new file mode 100644
index 0000000000..2d12332312
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/external-types/src/lib.rs
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use uniffi_geometry::{Line, Point};
+
+pub fn gradient(value: Option<Line>) -> f64 {
+ match value {
+ None => 0.0,
+ Some(value) => uniffi_geometry::gradient(value),
+ }
+}
+
+pub fn intersection(ln1: Line, ln2: Line) -> Option<Point> {
+ uniffi_geometry::intersection(ln1, ln2)
+}
+
+include!(concat!(env!("OUT_DIR"), "/external-types.uniffi.rs"));
diff --git a/toolkit/components/uniffi-fixtures/geometry/Cargo.toml b/toolkit/components/uniffi-fixtures/geometry/Cargo.toml
new file mode 100644
index 0000000000..a35ce906aa
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/geometry/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "uniffi-example-geometry"
+edition = "2021"
+version = "0.22.0"
+authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
+license = "MPL-2.0"
+publish = false
+
+[lib]
+crate-type = ["lib", "cdylib"]
+name = "uniffi_geometry"
+
+[dependencies]
+uniffi = { workspace = true }
+
+[build-dependencies]
+uniffi = { workspace = true, features = ["build"] }
+
+[dev-dependencies]
+uniffi = { workspace = true, features = ["bindgen-tests"] }
diff --git a/toolkit/components/uniffi-fixtures/geometry/build.rs b/toolkit/components/uniffi-fixtures/geometry/build.rs
new file mode 100644
index 0000000000..2dd1c96bc3
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/geometry/build.rs
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+fn main() {
+ uniffi::generate_scaffolding("src/geometry.udl").unwrap();
+}
diff --git a/toolkit/components/uniffi-fixtures/geometry/src/geometry.udl b/toolkit/components/uniffi-fixtures/geometry/src/geometry.udl
new file mode 100644
index 0000000000..af60d429bf
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/geometry/src/geometry.udl
@@ -0,0 +1,15 @@
+
+namespace geometry {
+ double gradient(Line ln);
+ Point? intersection(Line ln1, Line ln2);
+};
+
+dictionary Point {
+ double coord_x;
+ double coord_y;
+};
+
+dictionary Line {
+ Point start;
+ Point end;
+};
diff --git a/toolkit/components/uniffi-fixtures/geometry/src/lib.rs b/toolkit/components/uniffi-fixtures/geometry/src/lib.rs
new file mode 100644
index 0000000000..d710b150bb
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/geometry/src/lib.rs
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp
+// Silence, clippy!
+const EPSILON: f64 = 0.0001f64;
+
+#[derive(Debug, Clone)]
+pub struct Point {
+ coord_x: f64,
+ coord_y: f64,
+}
+
+#[derive(Debug, Clone)]
+pub struct Line {
+ start: Point,
+ end: Point,
+}
+
+pub fn gradient(ln: Line) -> f64 {
+ let rise = ln.end.coord_y - ln.start.coord_y;
+ let run = ln.end.coord_x - ln.start.coord_x;
+ rise / run
+}
+
+pub fn intersection(ln1: Line, ln2: Line) -> Option<Point> {
+ // TODO: yuck, should be able to take &Line as argument here
+ // and have rust figure it out with a bunch of annotations...
+ let g1 = gradient(ln1.clone());
+ let z1 = ln1.start.coord_y - g1 * ln1.start.coord_x;
+ let g2 = gradient(ln2.clone());
+ let z2 = ln2.start.coord_y - g2 * ln2.start.coord_x;
+ // Parallel lines do not intersect.
+ if (g1 - g2).abs() < EPSILON {
+ return None;
+ }
+ // Otherwise, they intersect at this fancy calculation that
+ // I found on wikipedia.
+ let x = (z2 - z1) / (g1 - g2);
+ Some(Point {
+ coord_x: x,
+ coord_y: g1 * x + z1,
+ })
+}
+
+uniffi::include_scaffolding!("geometry");
diff --git a/toolkit/components/uniffi-fixtures/geometry/tests/bindings/test_geometry.kts b/toolkit/components/uniffi-fixtures/geometry/tests/bindings/test_geometry.kts
new file mode 100644
index 0000000000..77bb9932ec
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/geometry/tests/bindings/test_geometry.kts
@@ -0,0 +1,10 @@
+import uniffi.geometry.*;
+
+val ln1 = Line(Point(0.0,0.0), Point(1.0,2.0))
+val ln2 = Line(Point(1.0,1.0), Point(2.0,2.0))
+
+assert( gradient(ln1) == 2.0 )
+assert( gradient(ln2) == 1.0 )
+
+assert( intersection(ln1, ln2) == Point(0.0, 0.0) )
+assert( intersection(ln1, ln1) == null )
diff --git a/toolkit/components/uniffi-fixtures/geometry/tests/bindings/test_geometry.py b/toolkit/components/uniffi-fixtures/geometry/tests/bindings/test_geometry.py
new file mode 100644
index 0000000000..bfb6560626
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/geometry/tests/bindings/test_geometry.py
@@ -0,0 +1,10 @@
+from geometry import Line, Point, gradient, intersection
+
+ln1 = Line(start=Point(coord_x=0, coord_y=0), end=Point(coord_x=1, coord_y=2))
+ln2 = Line(start=Point(coord_x=1, coord_y=1), end=Point(coord_x=2, coord_y=2))
+
+assert gradient(ln1) == 2
+assert gradient(ln2) == 1
+
+assert intersection(ln1, ln2) == Point(coord_x=0, coord_y=0)
+assert intersection(ln1, ln1) is None
diff --git a/toolkit/components/uniffi-fixtures/geometry/tests/bindings/test_geometry.rb b/toolkit/components/uniffi-fixtures/geometry/tests/bindings/test_geometry.rb
new file mode 100644
index 0000000000..90fdff684e
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/geometry/tests/bindings/test_geometry.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'test/unit'
+require 'geometry'
+
+include Test::Unit::Assertions
+include Geometry
+
+ln1 = Line.new(start: Point.new(coord_x: 0.0, coord_y: 0.0), _end: Point.new(coord_x: 1.0, coord_y: 2.0))
+ln2 = Line.new(start: Point.new(coord_x: 1.0, coord_y: 1.0), _end: Point.new(coord_x: 2.0, coord_y: 2.0))
+
+assert_equal Geometry.gradient(ln1), 2
+assert_equal Geometry.gradient(ln2), 1
+
+assert_equal Geometry.intersection(ln1, ln2), Point.new(coord_x: 0, coord_y: 0)
+assert Geometry.intersection(ln1, ln1).nil?
diff --git a/toolkit/components/uniffi-fixtures/geometry/tests/bindings/test_geometry.swift b/toolkit/components/uniffi-fixtures/geometry/tests/bindings/test_geometry.swift
new file mode 100644
index 0000000000..58bd65607f
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/geometry/tests/bindings/test_geometry.swift
@@ -0,0 +1,10 @@
+import geometry
+
+let ln1 = Line(start: Point(coordX: 0, coordY: 0), end: Point(coordX: 1, coordY: 2))
+let ln2 = Line(start: Point(coordX: 1, coordY: 1), end: Point(coordX: 2, coordY: 2))
+
+assert(gradient(ln: ln1) == 2.0)
+assert(gradient(ln: ln2) == 1.0)
+
+assert(intersection(ln1: ln1, ln2: ln2) == Point(coordX: 0, coordY: 0))
+assert(intersection(ln1: ln1, ln2: ln1) == nil)
diff --git a/toolkit/components/uniffi-fixtures/geometry/tests/test_generated_bindings.rs b/toolkit/components/uniffi-fixtures/geometry/tests/test_generated_bindings.rs
new file mode 100644
index 0000000000..4638d847c8
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/geometry/tests/test_generated_bindings.rs
@@ -0,0 +1,6 @@
+uniffi::build_foreign_language_testcases!(
+ "tests/bindings/test_geometry.py",
+ "tests/bindings/test_geometry.rb",
+ "tests/bindings/test_geometry.kts",
+ "tests/bindings/test_geometry.swift",
+);
diff --git a/toolkit/components/uniffi-fixtures/refcounts/Cargo.toml b/toolkit/components/uniffi-fixtures/refcounts/Cargo.toml
new file mode 100644
index 0000000000..877e502711
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/refcounts/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "uniffi-fixture-refcounts"
+edition = "2021"
+version = "0.21.0"
+license = "MPL-2.0"
+publish = false
+
+[dependencies]
+uniffi = { workspace = true }
+
+[build-dependencies]
+uniffi = { workspace = true, features = ["build"] }
diff --git a/toolkit/components/uniffi-fixtures/refcounts/build.rs b/toolkit/components/uniffi-fixtures/refcounts/build.rs
new file mode 100644
index 0000000000..9ea03e12de
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/refcounts/build.rs
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+fn main() {
+ uniffi::generate_scaffolding("./src/refcounts.udl").unwrap();
+}
diff --git a/toolkit/components/uniffi-fixtures/refcounts/src/lib.rs b/toolkit/components/uniffi-fixtures/refcounts/src/lib.rs
new file mode 100644
index 0000000000..6554625c62
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/refcounts/src/lib.rs
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/// This crate exists to test managing the Rust Arc strong counts as JS objects are
+/// created/destroyed. See `test_refcounts.js` for how it's used.
+use std::sync::{Arc, Mutex};
+
+pub struct SingletonObject;
+
+impl SingletonObject {
+ pub fn method(&self) {}
+}
+
+static SINGLETON: Mutex<Option<Arc<SingletonObject>>> = Mutex::new(None);
+
+pub fn get_singleton() -> Arc<SingletonObject> {
+ Arc::clone(
+ SINGLETON
+ .lock()
+ .unwrap()
+ .get_or_insert_with(|| Arc::new(SingletonObject)),
+ )
+}
+
+pub fn get_js_refcount() -> i32 {
+ // Subtract 2: one for the reference in the Mutex and one for the temporary reference that
+ // we're calling Arc::strong_count on.
+ (Arc::strong_count(&get_singleton()) as i32) - 2
+}
+
+include!(concat!(env!("OUT_DIR"), "/refcounts.uniffi.rs"));
diff --git a/toolkit/components/uniffi-fixtures/refcounts/src/refcounts.udl b/toolkit/components/uniffi-fixtures/refcounts/src/refcounts.udl
new file mode 100644
index 0000000000..25ec83cfcc
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/refcounts/src/refcounts.udl
@@ -0,0 +1,8 @@
+namespace refcounts {
+ SingletonObject get_singleton();
+ i32 get_js_refcount();
+};
+
+interface SingletonObject {
+ void method();
+};
diff --git a/toolkit/components/uniffi-fixtures/rondpoint/Cargo.toml b/toolkit/components/uniffi-fixtures/rondpoint/Cargo.toml
new file mode 100644
index 0000000000..1d0be84785
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/rondpoint/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "uniffi-example-rondpoint"
+edition = "2021"
+version = "0.22.0"
+authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
+license = "MPL-2.0"
+publish = false
+
+[lib]
+crate-type = ["lib", "cdylib"]
+name = "uniffi_rondpoint"
+
+[dependencies]
+uniffi = { workspace = true }
+
+[build-dependencies]
+uniffi = { workspace = true, features = ["build"] }
+
+[dev-dependencies]
+uniffi = { workspace = true, features = ["bindgen-tests"] }
diff --git a/toolkit/components/uniffi-fixtures/rondpoint/build.rs b/toolkit/components/uniffi-fixtures/rondpoint/build.rs
new file mode 100644
index 0000000000..f830879d09
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/rondpoint/build.rs
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+fn main() {
+ uniffi::generate_scaffolding("src/rondpoint.udl").unwrap();
+}
diff --git a/toolkit/components/uniffi-fixtures/rondpoint/src/lib.rs b/toolkit/components/uniffi-fixtures/rondpoint/src/lib.rs
new file mode 100644
index 0000000000..3f2233ddaa
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/rondpoint/src/lib.rs
@@ -0,0 +1,293 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::collections::HashMap;
+
+#[derive(Debug, Clone)]
+pub struct Dictionnaire {
+ un: Enumeration,
+ deux: bool,
+ petit_nombre: u8,
+ gros_nombre: u64,
+}
+
+#[derive(Debug, Clone)]
+pub struct DictionnaireNombres {
+ petit_nombre: u8,
+ court_nombre: u16,
+ nombre_simple: u32,
+ gros_nombre: u64,
+}
+
+#[derive(Debug, Clone)]
+pub struct DictionnaireNombresSignes {
+ petit_nombre: i8,
+ court_nombre: i16,
+ nombre_simple: i32,
+ gros_nombre: i64,
+}
+
+#[derive(Debug, Clone)]
+pub enum Enumeration {
+ Un,
+ Deux,
+ Trois,
+}
+
+#[derive(Debug, Clone)]
+pub enum EnumerationAvecDonnees {
+ Zero,
+ Un { premier: u32 },
+ Deux { premier: u32, second: String },
+}
+
+#[allow(non_camel_case_types)]
+#[allow(non_snake_case)]
+pub struct minusculeMAJUSCULEDict {
+ minusculeMAJUSCULEField: bool,
+}
+
+#[allow(non_camel_case_types)]
+pub enum minusculeMAJUSCULEEnum {
+ minusculeMAJUSCULEVariant,
+}
+
+fn copie_enumeration(e: Enumeration) -> Enumeration {
+ e
+}
+
+fn copie_enumerations(e: Vec<Enumeration>) -> Vec<Enumeration> {
+ e
+}
+
+fn copie_carte(
+ e: HashMap<String, EnumerationAvecDonnees>,
+) -> HashMap<String, EnumerationAvecDonnees> {
+ e
+}
+
+fn copie_dictionnaire(d: Dictionnaire) -> Dictionnaire {
+ d
+}
+
+fn switcheroo(b: bool) -> bool {
+ !b
+}
+
+// Test that values can traverse both ways across the FFI.
+// Even if roundtripping works, it's possible we have
+// symmetrical errors that cancel each other out.
+#[derive(Debug, Clone)]
+struct Retourneur;
+impl Retourneur {
+ fn new() -> Self {
+ Retourneur
+ }
+ fn identique_i8(&self, value: i8) -> i8 {
+ value
+ }
+ fn identique_u8(&self, value: u8) -> u8 {
+ value
+ }
+ fn identique_i16(&self, value: i16) -> i16 {
+ value
+ }
+ fn identique_u16(&self, value: u16) -> u16 {
+ value
+ }
+ fn identique_i32(&self, value: i32) -> i32 {
+ value
+ }
+ fn identique_u32(&self, value: u32) -> u32 {
+ value
+ }
+ fn identique_i64(&self, value: i64) -> i64 {
+ value
+ }
+ fn identique_u64(&self, value: u64) -> u64 {
+ value
+ }
+ fn identique_float(&self, value: f32) -> f32 {
+ value
+ }
+ fn identique_double(&self, value: f64) -> f64 {
+ value
+ }
+ fn identique_boolean(&self, value: bool) -> bool {
+ value
+ }
+ fn identique_string(&self, value: String) -> String {
+ value
+ }
+ fn identique_nombres_signes(
+ &self,
+ value: DictionnaireNombresSignes,
+ ) -> DictionnaireNombresSignes {
+ value
+ }
+ fn identique_nombres(&self, value: DictionnaireNombres) -> DictionnaireNombres {
+ value
+ }
+ fn identique_optionneur_dictionnaire(
+ &self,
+ value: OptionneurDictionnaire,
+ ) -> OptionneurDictionnaire {
+ value
+ }
+}
+
+#[derive(Debug, Clone)]
+struct Stringifier;
+
+#[allow(dead_code)]
+impl Stringifier {
+ fn new() -> Self {
+ Stringifier
+ }
+ fn to_string_i8(&self, value: i8) -> String {
+ value.to_string()
+ }
+ fn to_string_u8(&self, value: u8) -> String {
+ value.to_string()
+ }
+ fn to_string_i16(&self, value: i16) -> String {
+ value.to_string()
+ }
+ fn to_string_u16(&self, value: u16) -> String {
+ value.to_string()
+ }
+ fn to_string_i32(&self, value: i32) -> String {
+ value.to_string()
+ }
+ fn to_string_u32(&self, value: u32) -> String {
+ value.to_string()
+ }
+ fn to_string_i64(&self, value: i64) -> String {
+ value.to_string()
+ }
+ fn to_string_u64(&self, value: u64) -> String {
+ value.to_string()
+ }
+ fn to_string_float(&self, value: f32) -> String {
+ value.to_string()
+ }
+ fn to_string_double(&self, value: f64) -> String {
+ value.to_string()
+ }
+ fn to_string_boolean(&self, value: bool) -> String {
+ value.to_string()
+ }
+ fn well_known_string(&self, value: String) -> String {
+ format!("uniffi 💚 {value}!")
+ }
+}
+
+#[derive(Debug, Clone)]
+struct Optionneur;
+impl Optionneur {
+ fn new() -> Self {
+ Optionneur
+ }
+ fn sinon_string(&self, value: String) -> String {
+ value
+ }
+ fn sinon_null(&self, value: Option<String>) -> Option<String> {
+ value
+ }
+ fn sinon_boolean(&self, value: bool) -> bool {
+ value
+ }
+ fn sinon_sequence(&self, value: Vec<String>) -> Vec<String> {
+ value
+ }
+
+ fn sinon_zero(&self, value: Option<i32>) -> Option<i32> {
+ value
+ }
+
+ fn sinon_u8_dec(&self, value: u8) -> u8 {
+ value
+ }
+ fn sinon_i8_dec(&self, value: i8) -> i8 {
+ value
+ }
+ fn sinon_u16_dec(&self, value: u16) -> u16 {
+ value
+ }
+ fn sinon_i16_dec(&self, value: i16) -> i16 {
+ value
+ }
+ fn sinon_u32_dec(&self, value: u32) -> u32 {
+ value
+ }
+ fn sinon_i32_dec(&self, value: i32) -> i32 {
+ value
+ }
+ fn sinon_u64_dec(&self, value: u64) -> u64 {
+ value
+ }
+ fn sinon_i64_dec(&self, value: i64) -> i64 {
+ value
+ }
+
+ fn sinon_u8_hex(&self, value: u8) -> u8 {
+ value
+ }
+ fn sinon_i8_hex(&self, value: i8) -> i8 {
+ value
+ }
+ fn sinon_u16_hex(&self, value: u16) -> u16 {
+ value
+ }
+ fn sinon_i16_hex(&self, value: i16) -> i16 {
+ value
+ }
+ fn sinon_u32_hex(&self, value: u32) -> u32 {
+ value
+ }
+ fn sinon_i32_hex(&self, value: i32) -> i32 {
+ value
+ }
+ fn sinon_u64_hex(&self, value: u64) -> u64 {
+ value
+ }
+ fn sinon_i64_hex(&self, value: i64) -> i64 {
+ value
+ }
+
+ fn sinon_u32_oct(&self, value: u32) -> u32 {
+ value
+ }
+
+ fn sinon_f32(&self, value: f32) -> f32 {
+ value
+ }
+ fn sinon_f64(&self, value: f64) -> f64 {
+ value
+ }
+
+ fn sinon_enum(&self, value: Enumeration) -> Enumeration {
+ value
+ }
+}
+
+pub struct OptionneurDictionnaire {
+ i8_var: i8,
+ u8_var: u8,
+ i16_var: i16,
+ u16_var: u16,
+ i32_var: i32,
+ u32_var: u32,
+ i64_var: i64,
+ u64_var: u64,
+ float_var: f32,
+ double_var: f64,
+ boolean_var: bool,
+ string_var: String,
+ list_var: Vec<String>,
+ enumeration_var: Enumeration,
+ dictionnaire_var: Option<minusculeMAJUSCULEEnum>,
+}
+
+uniffi::include_scaffolding!("rondpoint");
diff --git a/toolkit/components/uniffi-fixtures/rondpoint/src/rondpoint.udl b/toolkit/components/uniffi-fixtures/rondpoint/src/rondpoint.udl
new file mode 100644
index 0000000000..7c8261d74e
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/rondpoint/src/rondpoint.udl
@@ -0,0 +1,146 @@
+namespace rondpoint {
+ Dictionnaire copie_dictionnaire(Dictionnaire d);
+ Enumeration copie_enumeration(Enumeration e);
+ sequence<Enumeration> copie_enumerations(sequence<Enumeration> e);
+ record<string, EnumerationAvecDonnees> copie_carte(record<string, EnumerationAvecDonnees> c);
+ boolean switcheroo(boolean b);
+};
+
+dictionary minusculeMAJUSCULEDict {
+ boolean minusculeMAJUSCULEField;
+};
+
+enum minusculeMAJUSCULEEnum {
+ "minusculeMAJUSCULEVariant",
+};
+
+enum Enumeration {
+ "Un",
+ "Deux",
+ "Trois",
+};
+
+[Enum]
+interface EnumerationAvecDonnees {
+ Zero();
+ Un(u32 premier);
+ Deux(u32 premier, string second);
+};
+
+dictionary Dictionnaire {
+ Enumeration un;
+ boolean deux;
+ u8 petit_nombre;
+ u64 gros_nombre;
+};
+
+dictionary DictionnaireNombres {
+ u8 petit_nombre;
+ u16 court_nombre;
+ u32 nombre_simple;
+ u64 gros_nombre;
+};
+
+dictionary DictionnaireNombresSignes {
+ i8 petit_nombre;
+ i16 court_nombre;
+ i32 nombre_simple;
+ i64 gros_nombre;
+};
+
+interface Retourneur {
+ constructor();
+ i8 identique_i8(i8 value);
+ u8 identique_u8(u8 value);
+ i16 identique_i16(i16 value);
+ u16 identique_u16(u16 value);
+ i32 identique_i32(i32 value);
+ u32 identique_u32(u32 value);
+ i64 identique_i64(i64 value);
+ u64 identique_u64(u64 value);
+ float identique_float(float value);
+ double identique_double(double value);
+ boolean identique_boolean(boolean value);
+ string identique_string(string value);
+
+ DictionnaireNombresSignes identique_nombres_signes(DictionnaireNombresSignes value);
+ DictionnaireNombres identique_nombres(DictionnaireNombres value);
+ OptionneurDictionnaire identique_optionneur_dictionnaire(OptionneurDictionnaire value);
+};
+
+interface Stringifier {
+ constructor();
+ string well_known_string(string value);
+
+ string to_string_i8(i8 value);
+ string to_string_u8(u8 value);
+ string to_string_i16(i16 value);
+ string to_string_u16(u16 value);
+ string to_string_i32(i32 value);
+ string to_string_u32(u32 value);
+ string to_string_i64(i64 value);
+ string to_string_u64(u64 value);
+ string to_string_float(float value);
+ string to_string_double(double value);
+ string to_string_boolean(boolean value);
+};
+
+interface Optionneur {
+ constructor();
+ boolean sinon_boolean(optional boolean value = false);
+ string sinon_string(optional string value = "default");
+
+ sequence<string> sinon_sequence(optional sequence<string> value = []);
+
+ // Either sides of nullable.
+ string? sinon_null(optional string? value = null);
+ i32? sinon_zero(optional i32? value = 0);
+
+ // Decimal integers, all 42.
+ u8 sinon_u8_dec(optional u8 value = 42);
+ i8 sinon_i8_dec(optional i8 value = -42);
+ u16 sinon_u16_dec(optional u16 value = 42);
+ i16 sinon_i16_dec(optional i16 value = 42);
+ u32 sinon_u32_dec(optional u32 value = 42);
+ i32 sinon_i32_dec(optional i32 value = 42);
+ u64 sinon_u64_dec(optional u64 value = 42);
+ i64 sinon_i64_dec(optional i64 value = 42);
+
+ // Hexadecimal, including negatgives.
+ u8 sinon_u8_hex(optional u8 value = 0xff);
+ i8 sinon_i8_hex(optional i8 value = -0x7f);
+ u16 sinon_u16_hex(optional u16 value = 0xffff);
+ i16 sinon_i16_hex(optional i16 value = 0x7f);
+ u32 sinon_u32_hex(optional u32 value = 0xffffffff);
+ i32 sinon_i32_hex(optional i32 value = 0x7fffffff);
+ u64 sinon_u64_hex(optional u64 value = 0xffffffffffffffff);
+ i64 sinon_i64_hex(optional i64 value = 0x7fffffffffffffff);
+
+ // Octal, FWIW.
+ u32 sinon_u32_oct(optional u32 value = 0755);
+
+ // Floats
+ f32 sinon_f32(optional f32 value = 42.0);
+ f64 sinon_f64(optional f64 value = 42.1);
+
+ // Enums, which we have to treat as strings in the UDL frontend.
+ Enumeration sinon_enum(optional Enumeration value = "Trois");
+};
+
+dictionary OptionneurDictionnaire {
+ i8 i8_var = -8;
+ u8 u8_var = 8;
+ i16 i16_var = -0x10;
+ u16 u16_var = 0x10;
+ i32 i32_var = -32;
+ u32 u32_var = 32;
+ i64 i64_var = -64;
+ u64 u64_var = 64;
+ float float_var = 4.0;
+ double double_var = 8.0;
+ boolean boolean_var = true;
+ string string_var = "default";
+ sequence<string> list_var = [];
+ Enumeration enumeration_var = "DEUX";
+ minusculeMAJUSCULEEnum? dictionnaire_var = null;
+};
diff --git a/toolkit/components/uniffi-fixtures/rondpoint/tests/bindings/test_rondpoint.kts b/toolkit/components/uniffi-fixtures/rondpoint/tests/bindings/test_rondpoint.kts
new file mode 100644
index 0000000000..cc5ddf2a86
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/rondpoint/tests/bindings/test_rondpoint.kts
@@ -0,0 +1,250 @@
+import uniffi.rondpoint.*
+
+val dico = Dictionnaire(Enumeration.DEUX, true, 0u, 123456789u)
+val copyDico = copieDictionnaire(dico)
+assert(dico == copyDico)
+
+assert(copieEnumeration(Enumeration.DEUX) == Enumeration.DEUX)
+assert(copieEnumerations(listOf(Enumeration.UN, Enumeration.DEUX)) == listOf(Enumeration.UN, Enumeration.DEUX))
+assert(copieCarte(mapOf(
+ "0" to EnumerationAvecDonnees.Zero,
+ "1" to EnumerationAvecDonnees.Un(1u),
+ "2" to EnumerationAvecDonnees.Deux(2u, "deux")
+)) == mapOf(
+ "0" to EnumerationAvecDonnees.Zero,
+ "1" to EnumerationAvecDonnees.Un(1u),
+ "2" to EnumerationAvecDonnees.Deux(2u, "deux")
+))
+
+val var1: EnumerationAvecDonnees = EnumerationAvecDonnees.Zero
+val var2: EnumerationAvecDonnees = EnumerationAvecDonnees.Un(1u)
+val var3: EnumerationAvecDonnees = EnumerationAvecDonnees.Un(2u)
+assert(var1 != var2)
+assert(var2 != var3)
+assert(var1 == EnumerationAvecDonnees.Zero)
+assert(var1 != EnumerationAvecDonnees.Un(1u))
+assert(var2 == EnumerationAvecDonnees.Un(1u))
+
+assert(switcheroo(false))
+
+// 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 kotlin and lifting into rust is symmetrical with
+// lowering from rust and lifting into kotlin.
+val rt = Retourneur()
+
+fun <T> List<T>.affirmAllerRetour(fn: (T) -> T) {
+ this.forEach { v ->
+ assert(fn.invoke(v) == v) { "$fn($v)" }
+ }
+}
+
+// Booleans
+listOf(true, false).affirmAllerRetour(rt::identiqueBoolean)
+
+// Bytes.
+listOf(Byte.MIN_VALUE, Byte.MAX_VALUE).affirmAllerRetour(rt::identiqueI8)
+listOf(0x00, 0xFF).map { it.toUByte() }.affirmAllerRetour(rt::identiqueU8)
+
+// Shorts
+listOf(Short.MIN_VALUE, Short.MAX_VALUE).affirmAllerRetour(rt::identiqueI16)
+listOf(0x0000, 0xFFFF).map { it.toUShort() }.affirmAllerRetour(rt::identiqueU16)
+
+// Ints
+listOf(0, 1, -1, Int.MIN_VALUE, Int.MAX_VALUE).affirmAllerRetour(rt::identiqueI32)
+listOf(0x00000000, 0xFFFFFFFF).map { it.toUInt() }.affirmAllerRetour(rt::identiqueU32)
+
+// Longs
+listOf(0L, 1L, -1L, Long.MIN_VALUE, Long.MAX_VALUE).affirmAllerRetour(rt::identiqueI64)
+listOf(0u, 1u, ULong.MIN_VALUE, ULong.MAX_VALUE).affirmAllerRetour(rt::identiqueU64)
+
+// Floats
+listOf(0.0F, 0.5F, 0.25F, Float.MIN_VALUE, Float.MAX_VALUE).affirmAllerRetour(rt::identiqueFloat)
+
+// Doubles
+listOf(0.0, 1.0, Double.MIN_VALUE, Double.MAX_VALUE).affirmAllerRetour(rt::identiqueDouble)
+
+// Strings
+listOf("", "abc", "null\u0000byte", "été", "ښي لاس ته لوستلو لوستل", "😻emoji 👨‍👧‍👦multi-emoji, 🇨🇭a flag, a canal, panama")
+ .affirmAllerRetour(rt::identiqueString)
+
+listOf(-1, 0, 1).map { DictionnaireNombresSignes(it.toByte(), it.toShort(), it.toInt(), it.toLong()) }
+ .affirmAllerRetour(rt::identiqueNombresSignes)
+
+listOf(0, 1).map { DictionnaireNombres(it.toUByte(), it.toUShort(), it.toUInt(), it.toULong()) }
+ .affirmAllerRetour(rt::identiqueNombres)
+
+
+rt.destroy()
+
+// 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 kotlin.
+//
+// This shows that the values are transformed into strings the same way in both kotlin and rust.
+// i.e. if we assume that the string return works (we test this assumption elsewhere)
+// we show that lowering from kotlin and lifting into rust has values that both kotlin 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 t 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.
+val st = Stringifier()
+
+typealias StringyEquals<T> = (observed: String, expected: T) -> Boolean
+fun <T> List<T>.affirmEnchaine(
+ fn: (T) -> String,
+ equals: StringyEquals<T> = { obs, exp -> obs == exp.toString() }
+) {
+ this.forEach { exp ->
+ val obs = fn.invoke(exp)
+ assert(equals(obs, exp)) { "$fn($exp): observed=$obs, expected=$exp" }
+ }
+}
+
+// Test the efficacy of the string transport from rust. If this fails, but everything else
+// works, then things are very weird.
+val wellKnown = st.wellKnownString("kotlin")
+assert("uniffi 💚 kotlin!" == wellKnown) { "wellKnownString 'uniffi 💚 kotlin!' == '$wellKnown'" }
+
+// Booleans
+listOf(true, false).affirmEnchaine(st::toStringBoolean)
+
+// Bytes.
+listOf(Byte.MIN_VALUE, Byte.MAX_VALUE).affirmEnchaine(st::toStringI8)
+listOf(UByte.MIN_VALUE, UByte.MAX_VALUE).affirmEnchaine(st::toStringU8)
+
+// Shorts
+listOf(Short.MIN_VALUE, Short.MAX_VALUE).affirmEnchaine(st::toStringI16)
+listOf(UShort.MIN_VALUE, UShort.MAX_VALUE).affirmEnchaine(st::toStringU16)
+
+// Ints
+listOf(0, 1, -1, Int.MIN_VALUE, Int.MAX_VALUE).affirmEnchaine(st::toStringI32)
+listOf(0u, 1u, UInt.MIN_VALUE, UInt.MAX_VALUE).affirmEnchaine(st::toStringU32)
+
+// Longs
+listOf(0L, 1L, -1L, Long.MIN_VALUE, Long.MAX_VALUE).affirmEnchaine(st::toStringI64)
+listOf(0u, 1u, ULong.MIN_VALUE, ULong.MAX_VALUE).affirmEnchaine(st::toStringU64)
+
+// Floats
+// MIN_VALUE is 1.4E-45. Accuracy and formatting get weird at small sizes.
+listOf(0.0F, 1.0F, -1.0F, Float.MIN_VALUE, Float.MAX_VALUE).affirmEnchaine(st::toStringFloat) { s, n -> s.toFloat() == n }
+
+// Doubles
+// MIN_VALUE is 4.9E-324. Accuracy and formatting get weird at small sizes.
+listOf(0.0, 1.0, -1.0, Double.MIN_VALUE, Double.MAX_VALUE).affirmEnchaine(st::toStringDouble) { s, n -> s.toDouble() == n }
+
+st.destroy()
+
+// Prove to ourselves that default arguments are being used.
+// Step 1: call the methods without arguments, and check against the UDL.
+val op = Optionneur()
+
+assert(op.sinonString() == "default")
+
+assert(op.sinonBoolean() == false)
+
+assert(op.sinonSequence() == listOf<String>())
+
+// optionals
+assert(op.sinonNull() == null)
+assert(op.sinonZero() == 0)
+
+// decimal integers
+assert(op.sinonI8Dec() == (-42).toByte())
+assert(op.sinonU8Dec() == 42.toUByte())
+assert(op.sinonI16Dec() == 42.toShort())
+assert(op.sinonU16Dec() == 42.toUShort())
+assert(op.sinonI32Dec() == 42)
+assert(op.sinonU32Dec() == 42.toUInt())
+assert(op.sinonI64Dec() == 42L)
+assert(op.sinonU64Dec() == 42uL)
+
+// hexadecimal integers
+assert(op.sinonI8Hex() == (-0x7f).toByte())
+assert(op.sinonU8Hex() == 0xff.toUByte())
+assert(op.sinonI16Hex() == 0x7f.toShort())
+assert(op.sinonU16Hex() == 0xffff.toUShort())
+assert(op.sinonI32Hex() == 0x7fffffff)
+assert(op.sinonU32Hex() == 0xffffffff.toUInt())
+assert(op.sinonI64Hex() == 0x7fffffffffffffffL)
+assert(op.sinonU64Hex() == 0xffffffffffffffffuL)
+
+// octal integers
+assert(op.sinonU32Oct() == 493u) // 0o755
+
+// floats
+assert(op.sinonF32() == 42.0f)
+assert(op.sinonF64() == 42.1)
+
+// enums
+assert(op.sinonEnum() == Enumeration.TROIS)
+
+// Step 2. Convince ourselves that if we pass something else, then that changes the output.
+// We have shown something coming out of the sinon methods, but without eyeballing the Rust
+// we can't be sure that the arguments will change the return value.
+listOf("foo", "bar").affirmAllerRetour(op::sinonString)
+listOf(true, false).affirmAllerRetour(op::sinonBoolean)
+listOf(listOf("a", "b"), listOf()).affirmAllerRetour(op::sinonSequence)
+
+// optionals
+listOf("0", "1").affirmAllerRetour(op::sinonNull)
+listOf(0, 1).affirmAllerRetour(op::sinonZero)
+
+// integers
+listOf(0, 1).map { it.toUByte() }.affirmAllerRetour(op::sinonU8Dec)
+listOf(0, 1).map { it.toByte() }.affirmAllerRetour(op::sinonI8Dec)
+listOf(0, 1).map { it.toUShort() }.affirmAllerRetour(op::sinonU16Dec)
+listOf(0, 1).map { it.toShort() }.affirmAllerRetour(op::sinonI16Dec)
+listOf(0, 1).map { it.toUInt() }.affirmAllerRetour(op::sinonU32Dec)
+listOf(0, 1).map { it.toInt() }.affirmAllerRetour(op::sinonI32Dec)
+listOf(0, 1).map { it.toULong() }.affirmAllerRetour(op::sinonU64Dec)
+listOf(0, 1).map { it.toLong() }.affirmAllerRetour(op::sinonI64Dec)
+
+listOf(0, 1).map { it.toUByte() }.affirmAllerRetour(op::sinonU8Hex)
+listOf(0, 1).map { it.toByte() }.affirmAllerRetour(op::sinonI8Hex)
+listOf(0, 1).map { it.toUShort() }.affirmAllerRetour(op::sinonU16Hex)
+listOf(0, 1).map { it.toShort() }.affirmAllerRetour(op::sinonI16Hex)
+listOf(0, 1).map { it.toUInt() }.affirmAllerRetour(op::sinonU32Hex)
+listOf(0, 1).map { it.toInt() }.affirmAllerRetour(op::sinonI32Hex)
+listOf(0, 1).map { it.toULong() }.affirmAllerRetour(op::sinonU64Hex)
+listOf(0, 1).map { it.toLong() }.affirmAllerRetour(op::sinonI64Hex)
+
+listOf(0, 1).map { it.toUInt() }.affirmAllerRetour(op::sinonU32Oct)
+
+// floats
+listOf(0.0f, 1.0f).affirmAllerRetour(op::sinonF32)
+listOf(0.0, 1.0).affirmAllerRetour(op::sinonF64)
+
+// enums
+Enumeration.values().toList().affirmAllerRetour(op::sinonEnum)
+
+op.destroy()
+
+// Testing defaulting properties in record types.
+val defaultes = OptionneurDictionnaire()
+val explicites = OptionneurDictionnaire(
+ i8Var = -8,
+ u8Var = 8u,
+ i16Var = -16,
+ u16Var = 0x10u,
+ i32Var = -32,
+ u32Var = 32u,
+ i64Var = -64L,
+ u64Var = 64uL,
+ floatVar = 4.0f,
+ doubleVar = 8.0,
+ booleanVar = true,
+ stringVar = "default",
+ listVar = listOf(),
+ enumerationVar = Enumeration.DEUX,
+ dictionnaireVar = null
+)
+assert(defaultes == explicites)
+
+// …and makes sure they travel across and back the FFI.
+val rt2 = Retourneur()
+listOf(defaultes).affirmAllerRetour(rt2::identiqueOptionneurDictionnaire)
+
+rt2.destroy()
diff --git a/toolkit/components/uniffi-fixtures/rondpoint/tests/bindings/test_rondpoint.py b/toolkit/components/uniffi-fixtures/rondpoint/tests/bindings/test_rondpoint.py
new file mode 100644
index 0000000000..df3e3fab18
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/rondpoint/tests/bindings/test_rondpoint.py
@@ -0,0 +1,183 @@
+import ctypes
+import sys
+
+from rondpoint import (
+ Dictionnaire,
+ Enumeration,
+ EnumerationAvecDonnees,
+ Retourneur,
+ Stringifier,
+ copie_carte,
+ copie_dictionnaire,
+ copie_enumeration,
+ copie_enumerations,
+ switcheroo,
+)
+
+dico = Dictionnaire(
+ un=Enumeration.DEUX, deux=True, petit_nombre=0, gros_nombre=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(
+ {
+ "0": EnumerationAvecDonnees.ZERO(),
+ "1": EnumerationAvecDonnees.UN(1),
+ "2": EnumerationAvecDonnees.DEUX(2, "deux"),
+ }
+) == {
+ "0": EnumerationAvecDonnees.ZERO(),
+ "1": EnumerationAvecDonnees.UN(1),
+ "2": EnumerationAvecDonnees.DEUX(2, "deux"),
+}
+
+assert switcheroo(False) is True
+
+assert EnumerationAvecDonnees.ZERO() != EnumerationAvecDonnees.UN(1)
+assert EnumerationAvecDonnees.UN(1) == EnumerationAvecDonnees.UN(1)
+assert EnumerationAvecDonnees.UN(1) != EnumerationAvecDonnees.UN(2)
+
+# 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,
+)
diff --git a/toolkit/components/uniffi-fixtures/rondpoint/tests/bindings/test_rondpoint.rb b/toolkit/components/uniffi-fixtures/rondpoint/tests/bindings/test_rondpoint.rb
new file mode 100644
index 0000000000..faa4062019
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/rondpoint/tests/bindings/test_rondpoint.rb
@@ -0,0 +1,147 @@
+# frozen_string_literal: true
+
+require 'test/unit'
+require 'rondpoint'
+
+include Test::Unit::Assertions
+include Rondpoint
+
+dico = Dictionnaire.new(
+ un: Enumeration::DEUX,
+ deux: true,
+ petit_nombre: 0,
+ gros_nombre: 123_456_789
+)
+
+assert_equal dico, Rondpoint.copie_dictionnaire(dico)
+
+assert_equal Rondpoint.copie_enumeration(Enumeration::DEUX), Enumeration::DEUX
+
+assert_equal Rondpoint.copie_enumerations([
+ Enumeration::UN,
+ Enumeration::DEUX
+ ]), [Enumeration::UN, Enumeration::DEUX]
+
+assert_equal Rondpoint.copie_carte({
+ '0' => EnumerationAvecDonnees::ZERO.new,
+ '1' => EnumerationAvecDonnees::UN.new(1),
+ '2' => EnumerationAvecDonnees::DEUX.new(2, 'deux')
+ }), {
+ '0' => EnumerationAvecDonnees::ZERO.new,
+ '1' => EnumerationAvecDonnees::UN.new(1),
+ '2' => EnumerationAvecDonnees::DEUX.new(2, 'deux')
+ }
+
+assert Rondpoint.switcheroo(false)
+
+assert_not_equal EnumerationAvecDonnees::ZERO.new, EnumerationAvecDonnees::UN.new(1)
+assert_equal EnumerationAvecDonnees::UN.new(1), EnumerationAvecDonnees::UN.new(1)
+assert_not_equal EnumerationAvecDonnees::UN.new(1), EnumerationAvecDonnees::UN.new(2)
+
+# 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 ruby and lifting into rust is symmetrical with
+# lowering from rust and lifting into ruby.
+RT = Retourneur.new
+
+def affirm_aller_retour(vals, fn_name)
+ vals.each do |v|
+ id_v = RT.public_send fn_name, v
+
+ assert_equal id_v, v, "Round-trip failure: #{v} => #{id_v}"
+ end
+end
+
+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
+
+# Ruby floats are always doubles, so won't round-trip through f32 correctly.
+# This truncates them appropriately.
+F32_ONE_THIRD = [1.0 / 3].pack('f').unpack('f')[0]
+
+# Booleans
+affirm_aller_retour([true, false], :identique_boolean)
+
+# Bytes.
+affirm_aller_retour([MIN_I8, -1, 0, 1, MAX_I8], :identique_i8)
+affirm_aller_retour([0x00, 0x12, 0xFF], :identique_u8)
+
+# Shorts
+affirm_aller_retour([MIN_I16, -1, 0, 1, MAX_I16], :identique_i16)
+affirm_aller_retour([0x0000, 0x1234, 0xFFFF], :identique_u16)
+
+# Ints
+affirm_aller_retour([MIN_I32, -1, 0, 1, MAX_I32], :identique_i32)
+affirm_aller_retour([0x00000000, 0x12345678, 0xFFFFFFFF], :identique_u32)
+
+# Longs
+affirm_aller_retour([MIN_I64, -1, 0, 1, MAX_I64], :identique_i64)
+affirm_aller_retour([0x0000000000000000, 0x1234567890ABCDEF, 0xFFFFFFFFFFFFFFFF], :identique_u64)
+
+# Floats
+affirm_aller_retour([0.0, 0.5, 0.25, 1.0, F32_ONE_THIRD], :identique_float)
+
+# Doubles
+affirm_aller_retour(
+ [0.0, 0.5, 0.25, 1.0, 1.0 / 3, Float::MAX, Float::MIN],
+ :identique_double
+)
+
+# Strings
+affirm_aller_retour(
+ ['', 'abc', 'été', 'ښي لاس ته لوستلو لوستل',
+ '😻emoji 👨‍👧‍👦multi-emoji, 🇨🇭a flag, a canal, panama'],
+ :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 ruby.
+#
+# This shows that the values are transformed into strings the same way in both ruby and rust.
+# i.e. if we assume that the string return works (we test this assumption elsewhere)
+# we show that lowering from ruby and lifting into rust has values that both ruby 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.new
+
+def affirm_enchaine(vals, fn_name)
+ vals.each do |v|
+ str_v = ST.public_send fn_name, v
+
+ assert_equal v.to_s, str_v, "String compare error #{v} => #{str_v}"
+ end
+end
+
+# Test the efficacy of the string transport from rust. If this fails, but everything else
+# works, then things are very weird.
+assert_equal ST.well_known_string('ruby'), 'uniffi 💚 ruby!'
+
+# Booleans
+affirm_enchaine([true, false], :to_string_boolean)
+
+# Bytes.
+affirm_enchaine([MIN_I8, -1, 0, 1, MAX_I8], :to_string_i8)
+affirm_enchaine([0x00, 0x12, 0xFF], :to_string_u8)
+
+# Shorts
+affirm_enchaine([MIN_I16, -1, 0, 1, MAX_I16], :to_string_i16)
+affirm_enchaine([0x0000, 0x1234, 0xFFFF], :to_string_u16)
+
+# Ints
+affirm_enchaine([MIN_I32, -1, 0, 1, MAX_I32], :to_string_i32)
+affirm_enchaine([0x00000000, 0x12345678, 0xFFFFFFFF], :to_string_u32)
+
+# Longs
+affirm_enchaine([MIN_I64, -1, 0, 1, MAX_I64], :to_string_i64)
+affirm_enchaine([0x0000000000000000, 0x1234567890ABCDEF, 0xFFFFFFFFFFFFFFFF], :to_string_u64)
diff --git a/toolkit/components/uniffi-fixtures/rondpoint/tests/bindings/test_rondpoint.swift b/toolkit/components/uniffi-fixtures/rondpoint/tests/bindings/test_rondpoint.swift
new file mode 100644
index 0000000000..d9f47058ed
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/rondpoint/tests/bindings/test_rondpoint.swift
@@ -0,0 +1,232 @@
+import rondpoint
+
+let dico = Dictionnaire(un: .deux, deux: false, petitNombre: 0, grosNombre: 123456789)
+let copyDico = copieDictionnaire(d: dico)
+assert(dico == copyDico)
+
+assert(copieEnumeration(e: .deux) == .deux)
+assert(copieEnumerations(e: [.un, .deux]) == [.un, .deux])
+assert(copieCarte(c:
+ ["0": .zero,
+ "1": .un(premier: 1),
+ "2": .deux(premier: 2, second: "deux")
+]) == [
+ "0": .zero,
+ "1": .un(premier: 1),
+ "2": .deux(premier: 2, second: "deux")
+])
+
+assert(EnumerationAvecDonnees.zero != EnumerationAvecDonnees.un(premier: 1))
+assert(EnumerationAvecDonnees.un(premier: 1) == EnumerationAvecDonnees.un(premier: 1))
+assert(EnumerationAvecDonnees.un(premier: 1) != EnumerationAvecDonnees.un(premier: 2))
+
+
+assert(switcheroo(b: false))
+
+// 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 swift and lifting into rust is symmetrical with
+// lowering from rust and lifting into swift.
+let rt = Retourneur()
+
+// Booleans
+[true, false].affirmAllerRetour(rt.identiqueBoolean)
+
+// Bytes.
+[.min, .max].affirmAllerRetour(rt.identiqueI8)
+[0x00, 0xFF].map { $0 as UInt8 }.affirmAllerRetour(rt.identiqueU8)
+
+// Shorts
+[.min, .max].affirmAllerRetour(rt.identiqueI16)
+[0x0000, 0xFFFF].map { $0 as UInt16 }.affirmAllerRetour(rt.identiqueU16)
+
+// Ints
+[0, 1, -1, .min, .max].affirmAllerRetour(rt.identiqueI32)
+[0x00000000, 0xFFFFFFFF].map { $0 as UInt32 }.affirmAllerRetour(rt.identiqueU32)
+
+// Longs
+[.zero, 1, -1, .min, .max].affirmAllerRetour(rt.identiqueI64)
+[.zero, 1, .min, .max].affirmAllerRetour(rt.identiqueU64)
+
+// Floats
+[.zero, 1, 0.25, .leastNonzeroMagnitude, .greatestFiniteMagnitude].affirmAllerRetour(rt.identiqueFloat)
+
+// Doubles
+[0.0, 1.0, .leastNonzeroMagnitude, .greatestFiniteMagnitude].affirmAllerRetour(rt.identiqueDouble)
+
+// Strings
+["", "abc", "null\0byte", "été", "ښي لاس ته لوستلو لوستل", "😻emoji 👨‍👧‍👦multi-emoji, 🇨🇭a flag, a canal, panama"]
+ .affirmAllerRetour(rt.identiqueString)
+
+// 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 swift.
+//
+// This shows that the values are transformed into strings the same way in both swift and rust.
+// i.e. if we assume that the string return works (we test this assumption elsewhere)
+// we show that lowering from swift and lifting into rust has values that both swift 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 t 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.
+let st = Stringifier()
+
+// Test the effigacy of the string transport from rust. If this fails, but everything else
+// works, then things are very weird.
+let wellKnown = st.wellKnownString(value: "swift")
+assert("uniffi 💚 swift!" == wellKnown, "wellKnownString 'uniffi 💚 swift!' == '\(wellKnown)'")
+
+// Booleans
+[true, false].affirmEnchaine(st.toStringBoolean)
+
+// Bytes.
+[.min, .max].affirmEnchaine(st.toStringI8)
+[.min, .max].affirmEnchaine(st.toStringU8)
+
+// Shorts
+[.min, .max].affirmEnchaine(st.toStringI16)
+[.min, .max].affirmEnchaine(st.toStringU16)
+
+// Ints
+[0, 1, -1, .min, .max].affirmEnchaine(st.toStringI32)
+[0, 1, .min, .max].affirmEnchaine(st.toStringU32)
+
+// Longs
+[.zero, 1, -1, .min, .max].affirmEnchaine(st.toStringI64)
+[.zero, 1, .min, .max].affirmEnchaine(st.toStringU64)
+
+// Floats
+[.zero, 1, -1, .leastNonzeroMagnitude, .greatestFiniteMagnitude].affirmEnchaine(st.toStringFloat) { Float.init($0) == $1 }
+
+// Doubles
+[.zero, 1, -1, .leastNonzeroMagnitude, .greatestFiniteMagnitude].affirmEnchaine(st.toStringDouble) { Double.init($0) == $1 }
+
+// Some extension functions for testing the results of roundtripping and stringifying
+extension Array where Element: Equatable {
+ static func defaultEquals(_ observed: String, expected: Element) -> Bool {
+ let exp = "\(expected)"
+ return observed == exp
+ }
+
+ func affirmEnchaine(_ fn: (Element) -> String, equals: (String, Element) -> Bool = defaultEquals) {
+ self.forEach { v in
+ let obs = fn(v)
+ assert(equals(obs, v), "toString_\(type(of:v))(\(v)): observed=\(obs), expected=\(v)")
+ }
+ }
+
+ func affirmAllerRetour(_ fn: (Element) -> Element) {
+ self.forEach { v in
+ assert(fn(v) == v, "identique_\(type(of:v))(\(v))")
+ }
+ }
+}
+
+// Prove to ourselves that default arguments are being used.
+// Step 1: call the methods without arguments, and check against the UDL.
+let op = Optionneur()
+
+assert(op.sinonString() == "default")
+
+assert(op.sinonBoolean() == false)
+
+assert(op.sinonSequence() == [])
+
+// optionals
+assert(op.sinonNull() == nil)
+assert(op.sinonZero() == 0)
+
+// decimal integers
+assert(op.sinonU8Dec() == UInt8(42))
+assert(op.sinonI8Dec() == Int8(-42))
+assert(op.sinonU16Dec() == UInt16(42))
+assert(op.sinonI16Dec() == Int16(42))
+assert(op.sinonU32Dec() == UInt32(42))
+assert(op.sinonI32Dec() == Int32(42))
+assert(op.sinonU64Dec() == UInt64(42))
+assert(op.sinonI64Dec() == Int64(42))
+
+// hexadecimal integers
+assert(op.sinonU8Hex() == UInt8(0xff))
+assert(op.sinonI8Hex() == Int8(-0x7f))
+assert(op.sinonU16Hex() == UInt16(0xffff))
+assert(op.sinonI16Hex() == Int16(0x7f))
+assert(op.sinonU32Hex() == UInt32(0xffffffff))
+assert(op.sinonI32Hex() == Int32(0x7fffffff))
+assert(op.sinonU64Hex() == UInt64(0xffffffffffffffff))
+assert(op.sinonI64Hex() == Int64(0x7fffffffffffffff))
+
+// octal integers
+assert(op.sinonU32Oct() == UInt32(0o755))
+
+// floats
+assert(op.sinonF32() == 42.0)
+assert(op.sinonF64() == Double(42.1))
+
+// enums
+assert(op.sinonEnum() == .trois)
+
+// Step 2. Convince ourselves that if we pass something else, then that changes the output.
+// We have shown something coming out of the sinon methods, but without eyeballing the Rust
+// we can't be sure that the arguments will change the return value.
+["foo", "bar"].affirmAllerRetour(op.sinonString)
+[true, false].affirmAllerRetour(op.sinonBoolean)
+[["a", "b"], []].affirmAllerRetour(op.sinonSequence)
+
+// optionals
+["0", "1"].affirmAllerRetour(op.sinonNull)
+[0, 1].affirmAllerRetour(op.sinonZero)
+
+// integers
+[0, 1].affirmAllerRetour(op.sinonU8Dec)
+[0, 1].affirmAllerRetour(op.sinonI8Dec)
+[0, 1].affirmAllerRetour(op.sinonU16Dec)
+[0, 1].affirmAllerRetour(op.sinonI16Dec)
+[0, 1].affirmAllerRetour(op.sinonU32Dec)
+[0, 1].affirmAllerRetour(op.sinonI32Dec)
+[0, 1].affirmAllerRetour(op.sinonU64Dec)
+[0, 1].affirmAllerRetour(op.sinonI64Dec)
+
+[0, 1].affirmAllerRetour(op.sinonU8Hex)
+[0, 1].affirmAllerRetour(op.sinonI8Hex)
+[0, 1].affirmAllerRetour(op.sinonU16Hex)
+[0, 1].affirmAllerRetour(op.sinonI16Hex)
+[0, 1].affirmAllerRetour(op.sinonU32Hex)
+[0, 1].affirmAllerRetour(op.sinonI32Hex)
+[0, 1].affirmAllerRetour(op.sinonU64Hex)
+[0, 1].affirmAllerRetour(op.sinonI64Hex)
+
+[0, 1].affirmAllerRetour(op.sinonU32Oct)
+
+// floats
+[0.0, 1.0].affirmAllerRetour(op.sinonF32)
+[0.0, 1.0].affirmAllerRetour(op.sinonF64)
+
+// enums
+[.un, .deux, .trois].affirmAllerRetour(op.sinonEnum)
+
+// Testing defaulting properties in record types.
+let defaultes = OptionneurDictionnaire()
+let explicites = OptionneurDictionnaire(
+ i8Var: Int8(-8),
+ u8Var: UInt8(8),
+ i16Var: Int16(-16),
+ u16Var: UInt16(0x10),
+ i32Var: -32,
+ u32Var: UInt32(32),
+ i64Var: Int64(-64),
+ u64Var: UInt64(64),
+ floatVar: Float(4.0),
+ doubleVar: Double(8.0),
+ booleanVar: true,
+ stringVar: "default",
+ listVar: [],
+ enumerationVar: .deux,
+ dictionnaireVar: nil
+)
+
+// …and makes sure they travel across and back the FFI.
+assert(defaultes == explicites)
+[defaultes].affirmAllerRetour(rt.identiqueOptionneurDictionnaire)
diff --git a/toolkit/components/uniffi-fixtures/rondpoint/tests/test_generated_bindings.rs b/toolkit/components/uniffi-fixtures/rondpoint/tests/test_generated_bindings.rs
new file mode 100644
index 0000000000..d337374334
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/rondpoint/tests/test_generated_bindings.rs
@@ -0,0 +1,6 @@
+uniffi::build_foreign_language_testcases!(
+ "tests/bindings/test_rondpoint.kts",
+ "tests/bindings/test_rondpoint.swift",
+ "tests/bindings/test_rondpoint.py",
+ "tests/bindings/test_rondpoint.rb",
+);
diff --git a/toolkit/components/uniffi-fixtures/sprites/Cargo.toml b/toolkit/components/uniffi-fixtures/sprites/Cargo.toml
new file mode 100644
index 0000000000..3b4f96e143
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/sprites/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "uniffi-example-sprites"
+edition = "2021"
+version = "0.22.0"
+authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
+license = "MPL-2.0"
+publish = false
+
+[lib]
+crate-type = ["lib", "cdylib"]
+name = "uniffi_sprites"
+
+[dependencies]
+uniffi = { workspace = true }
+
+[build-dependencies]
+uniffi = { workspace = true, features = ["build"] }
+
+[dev-dependencies]
+uniffi = { workspace = true, features = ["bindgen-tests"] }
diff --git a/toolkit/components/uniffi-fixtures/sprites/build.rs b/toolkit/components/uniffi-fixtures/sprites/build.rs
new file mode 100644
index 0000000000..26ac3085b8
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/sprites/build.rs
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+fn main() {
+ uniffi::generate_scaffolding("src/sprites.udl").unwrap();
+}
diff --git a/toolkit/components/uniffi-fixtures/sprites/src/lib.rs b/toolkit/components/uniffi-fixtures/sprites/src/lib.rs
new file mode 100644
index 0000000000..d3cc11e408
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/sprites/src/lib.rs
@@ -0,0 +1,65 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::sync::RwLock;
+
+// A point in two-dimensional space.
+#[derive(Debug, Clone)]
+pub struct Point {
+ x: f64,
+ y: f64,
+}
+
+// A magnitude and direction in two-dimensional space.
+// For simplicity we represent this as a point relative to the origin.
+#[derive(Debug, Clone)]
+pub struct Vector {
+ dx: f64,
+ dy: f64,
+}
+
+// Move from the given Point, according to the given Vector.
+pub fn translate(p: &Point, v: Vector) -> Point {
+ Point {
+ x: p.x + v.dx,
+ y: p.y + v.dy,
+ }
+}
+
+// An entity in our imaginary world, which occupies a position in space
+// and which can move about over time.
+#[derive(Debug)]
+pub struct Sprite {
+ // We must use interior mutability for managing mutable state, hence the `RwLock`.
+ current_position: RwLock<Point>,
+}
+
+impl Sprite {
+ fn new(initial_position: Option<Point>) -> Sprite {
+ Sprite {
+ current_position: RwLock::new(initial_position.unwrap_or(Point { x: 0.0, y: 0.0 })),
+ }
+ }
+
+ fn new_relative_to(reference: Point, direction: Vector) -> Sprite {
+ Sprite {
+ current_position: RwLock::new(translate(&reference, direction)),
+ }
+ }
+
+ fn get_position(&self) -> Point {
+ self.current_position.read().unwrap().clone()
+ }
+
+ fn move_to(&self, position: Point) {
+ *self.current_position.write().unwrap() = position;
+ }
+
+ fn move_by(&self, direction: Vector) {
+ let mut current_position = self.current_position.write().unwrap();
+ *current_position = translate(&current_position, direction)
+ }
+}
+
+uniffi::include_scaffolding!("sprites");
diff --git a/toolkit/components/uniffi-fixtures/sprites/src/sprites.udl b/toolkit/components/uniffi-fixtures/sprites/src/sprites.udl
new file mode 100644
index 0000000000..6781c6cee5
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/sprites/src/sprites.udl
@@ -0,0 +1,22 @@
+
+namespace sprites {
+ Point translate([ByRef] Point position, Vector direction);
+};
+
+dictionary Point {
+ double x;
+ double y;
+};
+
+dictionary Vector {
+ double dx;
+ double dy;
+};
+
+interface Sprite {
+ constructor(Point? initial_position);
+ [Name=new_relative_to] constructor(Point reference, Vector direction);
+ Point get_position();
+ void move_to(Point position);
+ void move_by(Vector direction);
+};
diff --git a/toolkit/components/uniffi-fixtures/sprites/tests/bindings/test_sprites.kts b/toolkit/components/uniffi-fixtures/sprites/tests/bindings/test_sprites.kts
new file mode 100644
index 0000000000..42451f28dd
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/sprites/tests/bindings/test_sprites.kts
@@ -0,0 +1,25 @@
+import uniffi.sprites.*;
+
+val sempty = Sprite(null)
+assert( sempty.getPosition() == Point(0.0, 0.0) )
+
+val s = Sprite(Point(0.0, 1.0))
+assert( s.getPosition() == Point(0.0, 1.0) )
+
+s.moveTo(Point(1.0, 2.0))
+assert( s.getPosition() == Point(1.0, 2.0) )
+
+s.moveBy(Vector(-4.0, 2.0))
+assert( s.getPosition() == Point(-3.0, 4.0) )
+
+s.destroy()
+try {
+ s.moveBy(Vector(0.0, 0.0))
+ assert(false) { "Should not be able to call anything after `destroy`" }
+} catch(e: IllegalStateException) {
+ assert(true)
+}
+
+val srel = Sprite.newRelativeTo(Point(0.0, 1.0), Vector(1.0, 1.5))
+assert( srel.getPosition() == Point(1.0, 2.5) )
+
diff --git a/toolkit/components/uniffi-fixtures/sprites/tests/bindings/test_sprites.py b/toolkit/components/uniffi-fixtures/sprites/tests/bindings/test_sprites.py
new file mode 100644
index 0000000000..1e91997001
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/sprites/tests/bindings/test_sprites.py
@@ -0,0 +1,16 @@
+from sprites import Point, Sprite, Vector
+
+sempty = Sprite(None)
+assert sempty.get_position() == Point(x=0, y=0)
+
+s = Sprite(Point(x=0, y=1))
+assert s.get_position() == Point(x=0, y=1)
+
+s.move_to(Point(x=1, y=2))
+assert s.get_position() == Point(x=1, y=2)
+
+s.move_by(Vector(dx=-4, dy=2))
+assert s.get_position() == Point(x=-3, y=4)
+
+srel = Sprite.new_relative_to(Point(x=0, y=1), Vector(dx=1, dy=1.5))
+assert srel.get_position() == Point(x=1, y=2.5)
diff --git a/toolkit/components/uniffi-fixtures/sprites/tests/bindings/test_sprites.rb b/toolkit/components/uniffi-fixtures/sprites/tests/bindings/test_sprites.rb
new file mode 100644
index 0000000000..fa73043979
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/sprites/tests/bindings/test_sprites.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'test/unit'
+require 'sprites'
+
+include Test::Unit::Assertions
+include Sprites
+
+sempty = Sprite.new(nil)
+assert_equal sempty.get_position, Point.new(x: 0, y: 0)
+
+s = Sprite.new(Point.new(x: 0, y: 1))
+assert_equal s.get_position, Point.new(x: 0, y: 1)
+
+s.move_to(Point.new(x: 1, y: 2))
+assert_equal s.get_position, Point.new(x: 1, y: 2)
+
+s.move_by(Vector.new(dx: -4, dy: 2))
+assert_equal s.get_position, Point.new(x: -3, y: 4)
+
+srel = Sprite.new_relative_to(Point.new(x: 0, y: 1), Vector.new(dx: 1, dy: 1.5))
+assert_equal srel.get_position, Point.new(x: 1, y: 2.5)
diff --git a/toolkit/components/uniffi-fixtures/sprites/tests/bindings/test_sprites.swift b/toolkit/components/uniffi-fixtures/sprites/tests/bindings/test_sprites.swift
new file mode 100644
index 0000000000..d5428ac679
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/sprites/tests/bindings/test_sprites.swift
@@ -0,0 +1,16 @@
+import sprites
+
+let sempty = Sprite(initialPosition: nil)
+assert( sempty.getPosition() == Point(x: 0, y: 0))
+
+let s = Sprite(initialPosition: Point(x: 0, y: 1))
+assert( s.getPosition() == Point(x: 0, y: 1))
+
+s.moveTo(position: Point(x: 1.0, y: 2.0))
+assert( s.getPosition() == Point(x: 1, y: 2))
+
+s.moveBy(direction: Vector(dx: -4, dy: 2))
+assert( s.getPosition() == Point(x: -3, y: 4))
+
+let srel = Sprite.newRelativeTo(reference: Point(x: 0.0, y: 1.0), direction: Vector(dx: 1, dy: 1.5))
+assert( srel.getPosition() == Point(x: 1.0, y: 2.5) )
diff --git a/toolkit/components/uniffi-fixtures/sprites/tests/test_generated_bindings.rs b/toolkit/components/uniffi-fixtures/sprites/tests/test_generated_bindings.rs
new file mode 100644
index 0000000000..00dd779d68
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/sprites/tests/test_generated_bindings.rs
@@ -0,0 +1,6 @@
+uniffi::build_foreign_language_testcases!(
+ "tests/bindings/test_sprites.py",
+ "tests/bindings/test_sprites.rb",
+ "tests/bindings/test_sprites.kts",
+ "tests/bindings/test_sprites.swift",
+);
diff --git a/toolkit/components/uniffi-fixtures/todolist/Cargo.toml b/toolkit/components/uniffi-fixtures/todolist/Cargo.toml
new file mode 100644
index 0000000000..6673d01e07
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/todolist/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "uniffi-example-todolist"
+edition = "2021"
+version = "0.22.0"
+authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
+license = "MPL-2.0"
+publish = false
+
+[lib]
+crate-type = ["lib", "cdylib"]
+name = "uniffi_todolist"
+
+[dependencies]
+uniffi = { workspace = true }
+once_cell = "1.12"
+thiserror = "1.0"
+
+[build-dependencies]
+uniffi = { workspace = true, features = ["build"] }
+
+[dev-dependencies]
+uniffi = { workspace = true, features = ["bindgen-tests"] }
diff --git a/toolkit/components/uniffi-fixtures/todolist/build.rs b/toolkit/components/uniffi-fixtures/todolist/build.rs
new file mode 100644
index 0000000000..2dd2f68b75
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/todolist/build.rs
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+fn main() {
+ uniffi::generate_scaffolding("src/todolist.udl").unwrap();
+}
diff --git a/toolkit/components/uniffi-fixtures/todolist/src/lib.rs b/toolkit/components/uniffi-fixtures/todolist/src/lib.rs
new file mode 100644
index 0000000000..11cdc63aee
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/todolist/src/lib.rs
@@ -0,0 +1,150 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::sync::{Arc, RwLock};
+
+use once_cell::sync::Lazy;
+
+#[derive(Debug, Clone)]
+pub struct TodoEntry {
+ text: String,
+}
+
+// There is a single "default" TodoList that can be shared
+// by all consumers of this component. Depending on requirements,
+// a real app might like to use a `Weak<>` rather than an `Arc<>`
+// here to reduce the risk of circular references.
+static DEFAULT_LIST: Lazy<RwLock<Option<Arc<TodoList>>>> = Lazy::new(|| RwLock::new(None));
+
+#[derive(Debug, thiserror::Error)]
+pub enum TodoError {
+ #[error("The todo does not exist!")]
+ TodoDoesNotExist,
+ #[error("The todolist is empty!")]
+ EmptyTodoList,
+ #[error("That todo already exists!")]
+ DuplicateTodo,
+ #[error("Empty String error!: {0}")]
+ EmptyString(String),
+ #[error("I am a delegated Error: {0}")]
+ DeligatedError(#[from] std::io::Error),
+}
+
+/// Get a reference to the global default TodoList, if set.
+///
+fn get_default_list() -> Option<Arc<TodoList>> {
+ DEFAULT_LIST.read().unwrap().clone()
+}
+
+/// Set the global default TodoList.
+///
+/// This will silently drop any previously set value.
+///
+fn set_default_list(list: Arc<TodoList>) {
+ *DEFAULT_LIST.write().unwrap() = Some(list);
+}
+
+/// Create a new TodoEntry from the given string.
+///
+fn create_entry_with<S: Into<String>>(item: S) -> Result<TodoEntry> {
+ let text = item.into();
+ if text.is_empty() {
+ return Err(TodoError::EmptyString(
+ "Cannot add empty string as entry".to_string(),
+ ));
+ }
+ Ok(TodoEntry { text })
+}
+
+type Result<T, E = TodoError> = std::result::Result<T, E>;
+
+// A simple Todolist.
+// UniFFI requires that we use interior mutability for managing mutable state, so we wrap our `Vec` in a RwLock.
+// (A Mutex would also work, but a RwLock is more appropriate for this use-case, so we use it).
+#[derive(Debug)]
+pub struct TodoList {
+ items: RwLock<Vec<String>>,
+}
+
+impl TodoList {
+ fn new() -> Self {
+ Self {
+ items: RwLock::new(Vec::new()),
+ }
+ }
+
+ fn add_item<S: Into<String>>(&self, item: S) -> Result<()> {
+ let item = item.into();
+ if item.is_empty() {
+ return Err(TodoError::EmptyString(
+ "Cannot add empty string as item".to_string(),
+ ));
+ }
+ let mut items = self.items.write().unwrap();
+ if items.contains(&item) {
+ return Err(TodoError::DuplicateTodo);
+ }
+ items.push(item);
+ Ok(())
+ }
+
+ fn get_last(&self) -> Result<String> {
+ let items = self.items.read().unwrap();
+ items.last().cloned().ok_or(TodoError::EmptyTodoList)
+ }
+
+ fn get_first(&self) -> Result<String> {
+ let items = self.items.read().unwrap();
+ items.first().cloned().ok_or(TodoError::EmptyTodoList)
+ }
+
+ fn add_entries(&self, entries: Vec<TodoEntry>) {
+ let mut items = self.items.write().unwrap();
+ items.extend(entries.into_iter().map(|e| e.text))
+ }
+
+ fn add_entry(&self, entry: TodoEntry) -> Result<()> {
+ self.add_item(entry.text)
+ }
+
+ fn add_items<S: Into<String>>(&self, items: Vec<S>) {
+ let mut my_items = self.items.write().unwrap();
+ my_items.extend(items.into_iter().map(Into::into))
+ }
+
+ fn get_items(&self) -> Vec<String> {
+ let items = self.items.read().unwrap();
+ items.clone()
+ }
+
+ fn get_entries(&self) -> Vec<TodoEntry> {
+ let items = self.items.read().unwrap();
+ items
+ .iter()
+ .map(|text| TodoEntry { text: text.clone() })
+ .collect()
+ }
+
+ fn get_last_entry(&self) -> Result<TodoEntry> {
+ let text = self.get_last()?;
+ Ok(TodoEntry { text })
+ }
+
+ fn clear_item<S: Into<String>>(&self, item: S) -> Result<()> {
+ let item = item.into();
+ let mut items = self.items.write().unwrap();
+ let idx = items
+ .iter()
+ .position(|s| s == &item)
+ .ok_or(TodoError::TodoDoesNotExist)?;
+ items.remove(idx);
+ Ok(())
+ }
+
+ fn make_default(self: Arc<Self>) {
+ set_default_list(self);
+ }
+}
+
+uniffi::include_scaffolding!("todolist");
diff --git a/toolkit/components/uniffi-fixtures/todolist/src/todolist.udl b/toolkit/components/uniffi-fixtures/todolist/src/todolist.udl
new file mode 100644
index 0000000000..5c923314cd
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/todolist/src/todolist.udl
@@ -0,0 +1,38 @@
+namespace todolist {
+ TodoList? get_default_list();
+ undefined set_default_list(TodoList list);
+
+ [Throws=TodoError]
+ TodoEntry create_entry_with(string todo);
+};
+
+dictionary TodoEntry {
+ string text;
+};
+
+[Error]
+enum TodoError {
+ "TodoDoesNotExist", "EmptyTodoList", "DuplicateTodo", "EmptyString", "DeligatedError"
+};
+
+interface TodoList {
+ constructor();
+ [Throws=TodoError]
+ void add_item(string todo);
+ [Throws=TodoError]
+ void add_entry(TodoEntry entry);
+ sequence<TodoEntry> get_entries();
+ sequence<string> get_items();
+ void add_entries(sequence<TodoEntry> entries);
+ void add_items(sequence<string> items);
+ [Throws=TodoError]
+ TodoEntry get_last_entry();
+ [Throws=TodoError]
+ string get_last();
+ [Throws=TodoError]
+ string get_first();
+ [Throws=TodoError]
+ void clear_item(string todo);
+ [Self=ByArc]
+ undefined make_default();
+};
diff --git a/toolkit/components/uniffi-fixtures/todolist/tests/bindings/test_todolist.kts b/toolkit/components/uniffi-fixtures/todolist/tests/bindings/test_todolist.kts
new file mode 100644
index 0000000000..bb2b292224
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/todolist/tests/bindings/test_todolist.kts
@@ -0,0 +1,83 @@
+import uniffi.todolist.*
+
+val todo = TodoList()
+
+// This throws an exception:
+try {
+ todo.getLast()
+ throw RuntimeException("Should have thrown a TodoError!")
+} catch (e: TodoException.EmptyTodoList) {
+ // It's okay, we don't have any items yet!
+}
+
+try {
+ createEntryWith("")
+ throw RuntimeException("Should have thrown a TodoError!")
+} catch (e: TodoException) {
+ // It's okay, the string was empty!
+ assert(e is TodoException.EmptyString)
+ assert(e !is TodoException.EmptyTodoList)
+}
+
+todo.addItem("Write strings support")
+
+assert(todo.getLast() == "Write strings support")
+
+todo.addItem("Write tests for strings support")
+
+assert(todo.getLast() == "Write tests for strings support")
+
+val entry = createEntryWith("Write bindings for strings as record members")
+
+todo.addEntry(entry)
+assert(todo.getLast() == "Write bindings for strings as record members")
+assert(todo.getLastEntry().text == "Write bindings for strings as record members")
+
+todo.addItem("Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣")
+assert(todo.getLast() == "Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣")
+
+val entry2 = TodoEntry("Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣")
+todo.addEntry(entry2)
+assert(todo.getLastEntry().text == "Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣")
+
+assert(todo.getEntries().size == 5)
+
+todo.addEntries(listOf(TodoEntry("foo"), TodoEntry("bar")))
+assert(todo.getEntries().size == 7)
+assert(todo.getLastEntry().text == "bar")
+
+todo.addItems(listOf("bobo", "fofo"))
+assert(todo.getItems().size == 9)
+assert(todo.getItems()[7] == "bobo")
+
+assert(getDefaultList() == null)
+
+// Note that each individual object instance needs to be explicitly destroyed,
+// either by using the `.use` helper or explicitly calling its `.destroy` method.
+// Failure to do so will leak the underlying Rust object.
+TodoList().use { todo2 ->
+ setDefaultList(todo)
+ getDefaultList()!!.use { default ->
+ assert(todo.getEntries() == default.getEntries())
+ assert(todo2.getEntries() != default.getEntries())
+ }
+
+ todo2.makeDefault()
+ getDefaultList()!!.use { default ->
+ assert(todo.getEntries() != default.getEntries())
+ assert(todo2.getEntries() == default.getEntries())
+ }
+
+ todo.addItem("Test liveness after being demoted from default")
+ assert(todo.getLast() == "Test liveness after being demoted from default")
+
+ todo2.addItem("Test shared state through local vs default reference")
+ getDefaultList()!!.use { default ->
+ assert(default.getLast() == "Test shared state through local vs default reference")
+ }
+}
+
+// Ensure the kotlin version of deinit doesn't crash, and is idempotent.
+todo.destroy()
+todo.destroy()
+
diff --git a/toolkit/components/uniffi-fixtures/todolist/tests/bindings/test_todolist.py b/toolkit/components/uniffi-fixtures/todolist/tests/bindings/test_todolist.py
new file mode 100644
index 0000000000..e4e2cda6d6
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/todolist/tests/bindings/test_todolist.py
@@ -0,0 +1,56 @@
+from todolist import TodoEntry, TodoList, get_default_list, set_default_list
+
+todo = TodoList()
+
+entry = TodoEntry(text="Write bindings for strings in records")
+
+todo.add_item("Write python bindings")
+
+assert todo.get_last() == "Write python bindings"
+
+todo.add_item("Write tests for bindings")
+
+assert todo.get_last() == "Write tests for bindings"
+
+todo.add_entry(entry)
+
+assert todo.get_last() == "Write bindings for strings in records"
+assert todo.get_last_entry().text == "Write bindings for strings in records"
+
+todo.add_item(
+ "Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣"
+)
+assert (
+ todo.get_last()
+ == "Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣"
+)
+
+entry2 = TodoEntry(
+ text="Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣"
+)
+todo.add_entry(entry2)
+assert (
+ todo.get_last_entry().text
+ == "Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣"
+)
+
+todo2 = TodoList()
+assert todo != todo2
+assert todo is not todo2
+
+assert get_default_list() is None
+
+set_default_list(todo)
+assert todo.get_items() == get_default_list().get_items()
+
+todo2.make_default()
+assert todo2.get_items() == get_default_list().get_items()
+
+todo.add_item("Test liveness after being demoted from default")
+assert todo.get_last() == "Test liveness after being demoted from default"
+
+todo2.add_item("Test shared state through local vs default reference")
+assert (
+ get_default_list().get_last()
+ == "Test shared state through local vs default reference"
+)
diff --git a/toolkit/components/uniffi-fixtures/todolist/tests/bindings/test_todolist.rb b/toolkit/components/uniffi-fixtures/todolist/tests/bindings/test_todolist.rb
new file mode 100644
index 0000000000..fc1a823f52
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/todolist/tests/bindings/test_todolist.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'test/unit'
+require 'todolist'
+
+include Test::Unit::Assertions
+include Todolist
+
+todo = TodoList.new
+entry = TodoEntry.new(text: 'Write bindings for strings in records')
+
+todo.add_item('Write ruby bindings')
+
+assert_equal todo.get_last, 'Write ruby bindings'
+
+todo.add_item('Write tests for bindings')
+
+assert_equal todo.get_last, 'Write tests for bindings'
+
+todo.add_entry(entry)
+
+assert_equal todo.get_last, 'Write bindings for strings in records'
+assert_equal todo.get_last_entry.text, 'Write bindings for strings in records'
+
+todo.add_item("Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣")
+assert_equal todo.get_last, "Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣"
+
+entry2 = TodoEntry.new(text: "Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣")
+todo.add_entry(entry2)
+assert_equal todo.get_last_entry.text, "Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣"
+
+todo2 = TodoList.new
+assert todo2.get_items != todo.get_items
+
+assert Todolist.get_default_list == nil
+
+Todolist.set_default_list todo
+assert todo.get_items == Todolist.get_default_list.get_items
+
+todo2.make_default
+assert todo2.get_items == Todolist.get_default_list.get_items
+
+todo.add_item "Test liveness after being demoted from default"
+assert todo.get_last == "Test liveness after being demoted from default"
+
+todo2.add_item "Test shared state through local vs default reference"
+assert Todolist.get_default_list.get_last == "Test shared state through local vs default reference"
diff --git a/toolkit/components/uniffi-fixtures/todolist/tests/bindings/test_todolist.swift b/toolkit/components/uniffi-fixtures/todolist/tests/bindings/test_todolist.swift
new file mode 100644
index 0000000000..6ce72cadb2
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/todolist/tests/bindings/test_todolist.swift
@@ -0,0 +1,69 @@
+import todolist
+
+
+let todo = TodoList()
+do {
+ let _ = try todo.getLast()
+ fatalError("Should have thrown an EmptyTodoList error!")
+} catch TodoError.EmptyTodoList{
+ //It's okay! There are not todos!
+}
+try! todo.addItem(todo: "Write swift bindings")
+assert( try! todo.getLast() == "Write swift bindings")
+
+try! todo.addItem(todo: "Write tests for bindings")
+assert(try! todo.getLast() == "Write tests for bindings")
+
+let entry = TodoEntry(text: "Write bindings for strings as record members")
+try! todo.addEntry(entry: entry)
+assert(try! todo.getLast() == "Write bindings for strings as record members")
+
+try! todo.addItem(todo: "Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣")
+assert(try! todo.getLast() == "Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣")
+
+do {
+ let _ = try createEntryWith(todo: "")
+ fatalError("Should have thrown an EmptyString error!")
+} catch TodoError.EmptyString {
+ // It's okay! It was an empty string
+}
+
+let entry2 = TodoEntry(text: "Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣")
+try! todo.addEntry(entry: entry2)
+assert(try! todo.getLastEntry() == entry2)
+
+assert(todo.getEntries().count == 5)
+
+todo.addEntries(entries: [TodoEntry(text: "foo"), TodoEntry(text: "bar")])
+assert(todo.getEntries().count == 7)
+assert(todo.getItems().count == 7)
+assert(try! todo.getLast() == "bar")
+
+todo.addItems(items: ["bobo", "fofo"])
+assert(todo.getItems().count == 9)
+assert(todo.getItems()[7] == "bobo")
+
+// Ensure deinit doesn't crash.
+for _ in 0..<10 {
+ let list = TodoList()
+ try! list.addItem(todo: "todo")
+}
+
+let todo2 = TodoList()
+
+assert(getDefaultList() == nil)
+
+setDefaultList(list: todo)
+assert(todo.getItems() == getDefaultList()!.getItems())
+assert(todo2.getItems() != getDefaultList()!.getItems())
+
+todo2.makeDefault()
+assert(todo.getItems() != getDefaultList()!.getItems())
+assert(todo2.getItems() == getDefaultList()!.getItems())
+
+try! todo.addItem(todo: "Test liveness after being demoted from default")
+assert(try! todo.getLast() == "Test liveness after being demoted from default")
+
+try! todo2.addItem(todo: "Test shared state through local vs default reference")
+assert(try! getDefaultList()!.getLast() == "Test shared state through local vs default reference")
+
diff --git a/toolkit/components/uniffi-fixtures/todolist/tests/test_generated_bindings.rs b/toolkit/components/uniffi-fixtures/todolist/tests/test_generated_bindings.rs
new file mode 100644
index 0000000000..cefdbfe1dc
--- /dev/null
+++ b/toolkit/components/uniffi-fixtures/todolist/tests/test_generated_bindings.rs
@@ -0,0 +1,6 @@
+uniffi::build_foreign_language_testcases!(
+ "tests/bindings/test_todolist.kts",
+ "tests/bindings/test_todolist.swift",
+ "tests/bindings/test_todolist.rb",
+ "tests/bindings/test_todolist.py"
+);