Bug 1766045 - Vendoring in Rust code for uniffi-bindgen-gecko-js r=glandium,supply-chain-reviewers

- Added `--enable-uniffi-fixtures` flag.  When set, we will compile in
  the UniFFI test fixtures into our shared Rust crate and eventually
  into `libxul`.
- Vendoring in the Rust crates needed for `uniffi-bindgen-gecko-js`

Differential Revision: https://phabricator.services.mozilla.com/D144467
This commit is contained in:
Ben Dean-Kawamura 2022-08-03 13:48:27 +00:00
parent 703dbdfe3a
commit 760c603bcb
90 changed files with 3661 additions and 1 deletions

View File

@ -12,6 +12,11 @@ git = "https://github.com/rust-minidump/minidump-writer.git"
replace-with = "vendored-sources"
rev = "75ada456c92a429704691a85e1cb42fef8cafc0d"
[source."https://github.com/mozilla/uniffi-rs.git"]
git = "https://github.com/mozilla/uniffi-rs.git"
replace-with = "vendored-sources"
rev = "bb2039f077a29dba0879372a67e764e6ace8e33f"
[source."https://github.com/mozilla/neqo"]
git = "https://github.com/mozilla/neqo"
replace-with = "vendored-sources"

86
Cargo.lock generated
View File

@ -1549,6 +1549,18 @@ dependencies = [
"serde",
]
[[package]]
name = "extend"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c5216e387a76eebaaf11f6d871ec8a4aae0b25f05456ee21f228e024b1b3610"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "fallible-iterator"
version = "0.2.0"
@ -2164,6 +2176,11 @@ dependencies = [
"tokio-threadpool",
"unic-langid",
"unic-langid-ffi",
"uniffi-example-arithmetic",
"uniffi-example-geometry",
"uniffi-example-rondpoint",
"uniffi-example-sprites",
"uniffi-example-todolist",
"url",
"viaduct",
"webext_storage_bridge",
@ -5602,6 +5619,75 @@ dependencies = [
"log",
"paste",
"static_assertions",
"uniffi_bindgen",
]
[[package]]
name = "uniffi-bindgen-gecko-js"
version = "0.1.0"
dependencies = [
"anyhow",
"askama",
"camino",
"clap",
"extend",
"heck",
"serde",
"toml 0.5.9",
"uniffi_bindgen",
]
[[package]]
name = "uniffi-example-arithmetic"
version = "0.18.0"
source = "git+https://github.com/mozilla/uniffi-rs.git?rev=bb2039f077a29dba0879372a67e764e6ace8e33f#bb2039f077a29dba0879372a67e764e6ace8e33f"
dependencies = [
"thiserror",
"uniffi",
"uniffi_build",
"uniffi_macros",
]
[[package]]
name = "uniffi-example-geometry"
version = "0.18.0"
source = "git+https://github.com/mozilla/uniffi-rs.git?rev=bb2039f077a29dba0879372a67e764e6ace8e33f#bb2039f077a29dba0879372a67e764e6ace8e33f"
dependencies = [
"uniffi",
"uniffi_build",
"uniffi_macros",
]
[[package]]
name = "uniffi-example-rondpoint"
version = "0.18.0"
source = "git+https://github.com/mozilla/uniffi-rs.git?rev=bb2039f077a29dba0879372a67e764e6ace8e33f#bb2039f077a29dba0879372a67e764e6ace8e33f"
dependencies = [
"uniffi",
"uniffi_build",
"uniffi_macros",
]
[[package]]
name = "uniffi-example-sprites"
version = "0.18.0"
source = "git+https://github.com/mozilla/uniffi-rs.git?rev=bb2039f077a29dba0879372a67e764e6ace8e33f#bb2039f077a29dba0879372a67e764e6ace8e33f"
dependencies = [
"uniffi",
"uniffi_build",
"uniffi_macros",
]
[[package]]
name = "uniffi-example-todolist"
version = "0.18.0"
source = "git+https://github.com/mozilla/uniffi-rs.git?rev=bb2039f077a29dba0879372a67e764e6ace8e33f#bb2039f077a29dba0879372a67e764e6ace8e33f"
dependencies = [
"lazy_static",
"thiserror",
"uniffi",
"uniffi_build",
"uniffi_macros",
]
[[package]]

View File

@ -165,3 +165,16 @@ path = "third_party/rust/mio-0.6.23"
# https://github.com/mozilla/neqo/pull/1350
[patch."https://github.com/mozilla/neqo"]
neqo-common = { path = "third_party/rust/neqo-common" }
# These are used to test UniFFI functionality. We haven't figured out how we
# want to publish these yet, so they are only accessible via git. This works
# okay, but it means that their dependencies on UniFFI crates will normally
# also be the git versions. Patch them to use the published versions to avoid
# duplicate crates.
[patch."https://github.com/mozilla/uniffi-rs.git"]
uniffi = "0.19"
uniffi_bindgen = "0.19"
uniffi_build = "0.19"
uniffi_macros = "0.19"
weedle2 = "3.0.0"

View File

@ -99,6 +99,12 @@ criteria = "safe-to-deploy"
delta = "0.4.0 -> 0.5.0"
notes = "The repository for this crate belongs in the Mozilla org."
[[audits.extend]]
who = "Ben Dean-Kawamura <bdk@mozilla.com>"
criteria = "safe-to-deploy"
version = "1.1.2"
notes = "Inspected the crate and noted that the impl block comes directly from the proc-macro input. If no new code can be added by this crate, I don't think there can be any issues."
[[audits.flagset]]
who = "Ryan Hunt <rhunt@eqrion.net>"
criteria = "safe-to-deploy"

View File

@ -0,0 +1 @@
{"files":{"CHANGELOG.md":"910cd2057d356b18cd4f21791bc23956734a83b0f58701ec0d0d03405f5c21df","Cargo.toml":"928b8b97607b3589b85a6d4d00fb1cf64b78bee6354c303d8f49fbfa2ca5bdae","LICENSE":"db0efd0bb80126f32214f478434536d07d86a30658fb209d8b543b7261492a2b","README.md":"0872b2b15ec22732a09b121a31cdd8f8cfc80566f671566e4a7fb6b52a742a2e","deny.toml":"60448e9313f1883a7adf30713fcca789c454e77228105926024ce3abe9293100","rustfmt.toml":"9bbb759b3914c49c8060bcdeeb7786f4aec083d30bcfe236bbd48d8388d06103","src/lib.rs":"85bd23c064c78dcd141cb2fecca6fff2ee7e1dfb4cf19b74ab97ef30d51f6f03","tests/compile_fail/double_vis.rs":"524e8bc996f5a45de3d15f0c741d4f3414004e9c636c52de0f011785cc535109","tests/compile_fail/double_vis.stderr":"3748e67e6f41bd5624946ad7cdc4e2b30a9d0b5e76bc27c19ecc9d62da733978","tests/compile_fail/supertraits_are_actually_included.rs":"c03980316c4e71ceb7e80ab7011f00b6f1f8f89906670e9823267a4118f72853","tests/compile_fail/supertraits_are_actually_included.stderr":"835e79bb1bceb48e9db5f0a410c57af51c856e17917ef72f65e3ae2af79d97ec","tests/compile_pass/associated_constants.rs":"f3e56405650ddd31c33e932846553e88fe7181585e0f09a7414d50d521b0f103","tests/compile_pass/async_trait.rs":"9138801d67dbe20ed87fdb3f48bb968f0bfa11fe2d27414d5ea5ea7a6e44bb35","tests/compile_pass/changing_extension_trait_name.rs":"046e7a66151ce05c3515b4bea36be72f83b98f3bdb06fa6f065ad6fc373e19f2","tests/compile_pass/complex_trait_name.rs":"5a8c3588c26df07973739fbb85619d0de480cc8c992611dfc2dbcb9ab3c6e2b6","tests/compile_pass/double_ext_on_same_type.rs":"0e4d16fe9059f0325e4ad1337d53738876633b0533f9a2d4e9714ddada96eb4d","tests/compile_pass/extension_on_complex_types.rs":"88bdf979e1f399d7f9824165730b33884bc9c46347d246f8cc12e0e2039642a1","tests/compile_pass/generics.rs":"367f089e8010d1c98ccc975b03acb6cf03810793e1198105f75c006eafd52bb8","tests/compile_pass/hello_world.rs":"1961cfa634143974f0d64b26f817ac3148375faa5cf5864d63f0127d3af8099e","tests/compile_pass/issue_2.rs":"a14d5e2179b74ff71c5357e8a4f1a9ac228a711196edd40c12719565c8dfaa21","tests/compile_pass/more_than_one_extension.rs":"1d7486e4e9e4095d7e7f42aac878d836f021c49fbe26c31cb13fe6a0415a8558","tests/compile_pass/multiple_config.rs":"dc3b06e4fafbb7236b5bc49f9a5e9693241c07118da8886dd0752b895ff27425","tests/compile_pass/multiple_generic_params.rs":"2ac052ea7b818b6d45ee8070cf6ec18953419473f711375cafbe7283244b5743","tests/compile_pass/pub_impl.rs":"8dfcba21fbbc45efcf85b66fca2d187022569e72d4b10b57047eddda67a1725b","tests/compile_pass/ref_and_ref_mut.rs":"d1086a23809cbd8f87487677073238c3657a8a98fafc63a688eb9d622bf2eb11","tests/compile_pass/sized.rs":"baceaaabcf368b3c72e6d27fea72aadde11d97d9d153c35df5e2d89fb3da4e3e","tests/compile_pass/super_trait.rs":"07448e1fe2b9018125ccaabf7db1e2a244788519bb42117f8e437e48358eb2f0","tests/compile_pass/visibility_config.rs":"64846014a63327661fb250cdb726cce532ed82b1cea5cb83308526bcba22b4be"},"package":"5c5216e387a76eebaaf11f6d871ec8a4aae0b25f05456ee21f228e024b1b3610"}

79
third_party/rust/extend/CHANGELOG.md vendored Normal file
View File

@ -0,0 +1,79 @@
# Change Log
All user visible changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/), as described
for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md)
## Unreleased
None.
### Breaking changes
None.
## 1.1.2 - 2021-09-02
- Fix using `pub impl` with `#[async_trait]`.
## 1.1.1 - 2021-06-12
- Fix name collision for extensions on `&T` and `&mut T`. The generated traits
now get different names.
## 1.1.0 - 2021-06-12
- Support setting visibility of the generated trait directly on the `impl`
block. For example: `pub impl i32 { ... }`.
- Add `#[ext_sized]` for adding `Sized` supertrait.
## 1.0.1 - 2021-02-14
- Update maintenance status.
## 1.0.0 - 2021-01-30
- Support extensions on bare functions types (things like `fn(i32) -> bool`).
- Support extensions on trait objects (things like `dyn Send + Sync`).
## 0.3.0 - 2020-08-31
- Add async-trait compatibility.
### Breaking changes
- Other attributes put on the `impl` would previously only be included on the generated trait. They're now included on both the trait and the implementation.
## 0.2.1 - 2020-08-29
- Fix documentation link in Cargo.toml.
- Use more correct repository URL in Cargo.toml.
## 0.2.0 - 2020-08-29
- Handle unnamed extensions on the same generic type with different type parameters. For example `Option<i32>` and `Option<String>`. Previously we would generate the same name of both hidden traits which wouldn't compile.
- Support associated constants in extension impls.
### Breaking changes
- Generated traits are no longer sealed and the `sealed` argument previously supported by `#[ext]` has been removed. Making the traits sealed lead to lots of complexity that we didn't think brought much value.
## 0.1.1 - 2020-02-22
- Add support for specifying supertraits of the generated trait [#4](https://github.com/davidpdrsn/extend/pull/4).
## 0.1.0
- Support adding extensions to the ["never type"](https://doc.rust-lang.org/std/primitive.never.html).
### Breaking changes
- Simplify names of traits generates for complex types.
## 0.0.2
- Move "trybuild" to dev-dependency.
## 0.0.1
Initial release.

48
third_party/rust/extend/Cargo.toml vendored Normal file
View File

@ -0,0 +1,48 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies
#
# If you believe there's an error in this file please file an
# issue against the rust-lang/cargo repository. If you're
# editing this file be aware that the upstream Cargo.toml
# will likely look very different (and much more reasonable)
[package]
edition = "2018"
name = "extend"
version = "1.1.2"
authors = ["David Pedersen <david.pdrsn@gmail.com>"]
description = "Create extensions for types you don't own with extension traits but without the boilerplate."
homepage = "https://github.com/davidpdrsn/extend"
documentation = "https://docs.rs/extend"
readme = "README.md"
keywords = ["extension", "trait"]
categories = ["rust-patterns"]
license = "MIT"
repository = "https://github.com/davidpdrsn/extend.git"
[lib]
path = "src/lib.rs"
proc-macro = true
[dependencies.proc-macro-error]
version = "1"
[dependencies.proc-macro2]
version = "1"
[dependencies.quote]
version = "1"
[dependencies.syn]
version = "1"
features = ["full", "extra-traits", "visit"]
[dev-dependencies.async-trait]
version = "0.1.40"
[dev-dependencies.trybuild]
version = "1.0.17"
[badges.maintenance]
status = "passively-maintained"

19
third_party/rust/extend/LICENSE vendored Normal file
View File

@ -0,0 +1,19 @@
MIT License Copyright (c) 2020 David Pedersen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

32
third_party/rust/extend/README.md vendored Normal file
View File

@ -0,0 +1,32 @@
# extend
[![Crates.io](https://img.shields.io/crates/v/extend.svg)](https://crates.io/crates/extend)
[![Docs](https://docs.rs/extend/badge.svg)](https://docs.rs/extend)
[![dependency status](https://deps.rs/repo/github/davidpdrsn/extend/status.svg)](https://deps.rs/repo/github/davidpdrsn/extend)
[![Build status](https://github.com/davidpdrsn/extend/workflows/CI/badge.svg)](https://github.com/davidpdrsn/extend/actions)
![maintenance-status](https://img.shields.io/badge/maintenance-passively--maintained-yellowgreen.svg)
Create extensions for types you don't own with [extension traits] but without the boilerplate.
Example:
```rust
use extend::ext;
#[ext]
impl<T: Ord> Vec<T> {
fn sorted(mut self) -> Self {
self.sort();
self
}
}
fn main() {
assert_eq!(
vec![1, 2, 3],
vec![2, 3, 1].sorted(),
);
}
```
[extension traits]: https://dev.to/matsimitsu/extending-existing-functionality-in-rust-with-traits-in-rust-3622

47
third_party/rust/extend/deny.toml vendored Normal file
View File

@ -0,0 +1,47 @@
targets = []
[advisories]
db-path = "~/.cargo/advisory-db"
db-urls = ["https://github.com/rustsec/advisory-db"]
vulnerability = "deny"
unmaintained = "warn"
yanked = "warn"
notice = "warn"
ignore = []
[licenses]
unlicensed = "deny"
allow = [
"MIT",
"Apache-2.0",
]
deny = []
copyleft = "warn"
allow-osi-fsf-free = "neither"
default = "deny"
confidence-threshold = 0.8
exceptions = []
[licenses.private]
ignore = false
registries = []
[bans]
multiple-versions = "deny"
wildcards = "allow"
highlight = "all"
allow = []
deny = []
skip = []
skip-tree = []
[sources]
unknown-registry = "warn"
unknown-git = "warn"
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
allow-git = []
[sources.allow-org]
github = []
gitlab = []
bitbucket = []

1
third_party/rust/extend/rustfmt.toml vendored Normal file
View File

@ -0,0 +1 @@
merge_imports = true

620
third_party/rust/extend/src/lib.rs vendored Normal file
View File

@ -0,0 +1,620 @@
//! Create extensions for types you don't own with [extension traits] but without the boilerplate.
//!
//! Example:
//!
//! ```rust
//! use extend::ext;
//!
//! #[ext]
//! impl<T: Ord> Vec<T> {
//! fn sorted(mut self) -> Self {
//! self.sort();
//! self
//! }
//! }
//!
//! fn main() {
//! assert_eq!(
//! vec![1, 2, 3],
//! vec![2, 3, 1].sorted(),
//! );
//! }
//! ```
//!
//! # How does it work?
//!
//! Under the hood it generates a trait with methods in your `impl` and implements those for the
//! type you specify. The code shown above expands roughly to:
//!
//! ```rust
//! trait VecExt<T: Ord> {
//! fn sorted(self) -> Self;
//! }
//!
//! impl<T: Ord> VecExt<T> for Vec<T> {
//! fn sorted(mut self) -> Self {
//! self.sort();
//! self
//! }
//! }
//! ```
//!
//! # Supported items
//!
//! Extensions can contain methods or associated constants:
//!
//! ```rust
//! use extend::ext;
//!
//! #[ext]
//! impl String {
//! const CONSTANT: &'static str = "FOO";
//!
//! fn method() {
//! // ...
//! # todo!()
//! }
//! }
//! ```
//!
//! # Configuration
//!
//! You can configure:
//!
//! - The visibility of the trait. Use `pub impl ...` to generate `pub trait ...`. The default
//! visibility is private.
//! - The name of the generated extension trait. Example: `#[ext(name = MyExt)]`. By default we
//! generate a name based on what you extend.
//! - Which supertraits the generated extension trait should have. Default is no supertraits.
//! Example: `#[ext(supertraits = Default + Clone)]`.
//!
//! More examples:
//!
//! ```rust
//! use extend::ext;
//!
//! #[ext(name = SortedVecExt)]
//! impl<T: Ord> Vec<T> {
//! fn sorted(mut self) -> Self {
//! self.sort();
//! self
//! }
//! }
//!
//! #[ext]
//! pub(crate) impl i32 {
//! fn double(self) -> i32 {
//! self * 2
//! }
//! }
//!
//! #[ext(name = ResultSafeUnwrapExt)]
//! pub impl<T> Result<T, std::convert::Infallible> {
//! fn safe_unwrap(self) -> T {
//! match self {
//! Ok(t) => t,
//! Err(_) => unreachable!(),
//! }
//! }
//! }
//!
//! #[ext(supertraits = Default + Clone)]
//! impl String {
//! fn my_length(self) -> usize {
//! self.len()
//! }
//! }
//! ```
//!
//! For backwards compatibility you can also declare the visibility as the first argument to `#[ext]`:
//!
//! ```
//! use extend::ext;
//!
//! #[ext(pub)]
//! impl i32 {
//! fn double(self) -> i32 {
//! self * 2
//! }
//! }
//! ```
//!
//! # async-trait compatibility
//!
//! Async extensions are supported via [async-trait](https://crates.io/crates/async-trait).
//!
//! Be aware that you need to add `#[async_trait]` _below_ `#[ext]`. Otherwise the `ext` macro
//! cannot see the `#[async_trait]` attribute and pass it along in the generated code.
//!
//! Example:
//!
//! ```
//! use extend::ext;
//! use async_trait::async_trait;
//!
//! #[ext]
//! #[async_trait]
//! impl String {
//! async fn read_file() -> String {
//! // ...
//! # todo!()
//! }
//! }
//! ```
//!
//! # Other attributes
//!
//! Other attributes provided _below_ `#[ext]` will be passed along to both the generated trait and
//! the implementation. See [async-trait compatibility](#async-trait-compatibility) above for an
//! example.
//!
//! [extension traits]: https://dev.to/matsimitsu/extending-existing-functionality-in-rust-with-traits-in-rust-3622
#![doc(html_root_url = "https://docs.rs/extend/1.1.2")]
#![allow(clippy::let_and_return)]
#![deny(
unused_variables,
mutable_borrow_reservation_conflict,
dead_code,
unused_must_use,
unused_imports
)]
use proc_macro2::TokenStream;
use proc_macro_error::*;
use quote::{format_ident, quote, ToTokens};
use syn::{
parse::{self, Parse, ParseStream},
parse_macro_input, parse_quote,
punctuated::Punctuated,
spanned::Spanned,
token::{Add, Semi},
Ident, ImplItem, ItemImpl, Token, TraitItemConst, TraitItemMethod, Type, TypeArray, TypeBareFn,
TypeGroup, TypeNever, TypeParamBound, TypeParen, TypePath, TypePtr, TypeReference, TypeSlice,
TypeTraitObject, TypeTuple, Visibility,
};
#[derive(Debug)]
struct Input {
item_impl: ItemImpl,
vis: Option<Visibility>,
}
impl Parse for Input {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut attributes = Vec::new();
if input.peek(syn::Token![#]) {
attributes.extend(syn::Attribute::parse_outer(input)?);
}
let vis = input
.parse::<Visibility>()
.ok()
.filter(|vis| vis != &Visibility::Inherited);
let mut item_impl = input.parse::<ItemImpl>()?;
item_impl.attrs.extend(attributes);
Ok(Self { item_impl, vis })
}
}
/// See crate docs for more info.
#[proc_macro_attribute]
#[proc_macro_error]
#[allow(clippy::unneeded_field_pattern)]
pub fn ext(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let item = parse_macro_input!(item as Input);
let config = parse_macro_input!(attr as Config);
go(item, config)
}
/// Like [`ext`](macro@crate::ext) but always add `Sized` as a supertrait.
///
/// This is provided as a convenience for generating extension traits that require `Self: Sized`
/// such as:
///
/// ```
/// use extend::ext_sized;
///
/// #[ext_sized]
/// impl i32 {
/// fn requires_sized(self) -> Option<Self> {
/// Some(self)
/// }
/// }
/// ```
#[proc_macro_attribute]
#[proc_macro_error]
#[allow(clippy::unneeded_field_pattern)]
pub fn ext_sized(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let item = parse_macro_input!(item as Input);
let mut config: Config = parse_macro_input!(attr as Config);
config.supertraits = if let Some(supertraits) = config.supertraits.take() {
Some(parse_quote!(#supertraits + Sized))
} else {
Some(parse_quote!(Sized))
};
go(item, config)
}
fn go(item: Input, mut config: Config) -> proc_macro::TokenStream {
if let Some(vis) = item.vis {
if config.visibility != Visibility::Inherited {
abort!(
config.visibility.span(),
"Cannot set visibility on `#[ext]` and `impl` block"
);
}
config.visibility = vis;
}
let ItemImpl {
attrs,
unsafety,
generics,
trait_,
self_ty,
items,
// What is defaultness?
defaultness: _,
impl_token: _,
brace_token: _,
} = item.item_impl;
if let Some((_, path, _)) = trait_ {
abort!(path.span(), "Trait impls cannot be used for #[ext]");
}
let self_ty = parse_self_ty(&self_ty);
let ext_trait_name = config
.ext_trait_name
.unwrap_or_else(|| ext_trait_name(&self_ty));
let MethodsAndConsts {
trait_methods,
trait_consts,
} = extract_allowed_items(&items);
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let visibility = &config.visibility;
let mut all_supertraits = Vec::<TypeParamBound>::new();
if let Some(supertraits_from_config) = config.supertraits {
all_supertraits.extend(supertraits_from_config);
}
let supertraits_quoted = if all_supertraits.is_empty() {
quote! {}
} else {
let supertraits_quoted = punctuated_from_iter::<_, _, Add>(all_supertraits);
quote! { : #supertraits_quoted }
};
let code = (quote! {
#[allow(non_camel_case_types)]
#(#attrs)*
#visibility
#unsafety
trait #ext_trait_name #impl_generics #supertraits_quoted #where_clause {
#(
#trait_consts
)*
#(
#[allow(
patterns_in_fns_without_body,
clippy::inline_fn_without_body,
unused_attributes
)]
#trait_methods
)*
}
#(#attrs)*
impl #impl_generics #ext_trait_name #ty_generics for #self_ty #where_clause {
#(#items)*
}
})
.into();
code
}
#[derive(Debug, Clone)]
enum ExtType<'a> {
Array(&'a TypeArray),
Group(&'a TypeGroup),
Never(&'a TypeNever),
Paren(&'a TypeParen),
Path(&'a TypePath),
Ptr(&'a TypePtr),
Reference(&'a TypeReference),
Slice(&'a TypeSlice),
Tuple(&'a TypeTuple),
BareFn(&'a TypeBareFn),
TraitObject(&'a TypeTraitObject),
}
#[allow(clippy::wildcard_in_or_patterns)]
fn parse_self_ty(self_ty: &Type) -> ExtType {
match self_ty {
Type::Array(inner) => ExtType::Array(inner),
Type::Group(inner) => ExtType::Group(inner),
Type::Never(inner) => ExtType::Never(inner),
Type::Paren(inner) => ExtType::Paren(inner),
Type::Path(inner) => ExtType::Path(inner),
Type::Ptr(inner) => ExtType::Ptr(inner),
Type::Reference(inner) => ExtType::Reference(inner),
Type::Slice(inner) => ExtType::Slice(inner),
Type::Tuple(inner) => ExtType::Tuple(inner),
Type::BareFn(inner) => ExtType::BareFn(inner),
Type::TraitObject(inner) => ExtType::TraitObject(inner),
Type::ImplTrait(_) | Type::Infer(_) | Type::Macro(_) | Type::Verbatim(_) | _ => abort!(
self_ty.span(),
"#[ext] is not supported for this kind of type"
),
}
}
impl<'a> From<&'a Type> for ExtType<'a> {
fn from(inner: &'a Type) -> ExtType<'a> {
parse_self_ty(inner)
}
}
impl<'a> ToTokens for ExtType<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
ExtType::Array(inner) => inner.to_tokens(tokens),
ExtType::Group(inner) => inner.to_tokens(tokens),
ExtType::Never(inner) => inner.to_tokens(tokens),
ExtType::Paren(inner) => inner.to_tokens(tokens),
ExtType::Path(inner) => inner.to_tokens(tokens),
ExtType::Ptr(inner) => inner.to_tokens(tokens),
ExtType::Reference(inner) => inner.to_tokens(tokens),
ExtType::Slice(inner) => inner.to_tokens(tokens),
ExtType::Tuple(inner) => inner.to_tokens(tokens),
ExtType::BareFn(inner) => inner.to_tokens(tokens),
ExtType::TraitObject(inner) => inner.to_tokens(tokens),
}
}
}
fn ext_trait_name(self_ty: &ExtType) -> Ident {
fn inner_self_ty(self_ty: &ExtType) -> Ident {
match self_ty {
ExtType::Path(inner) => find_and_combine_idents(inner),
ExtType::Reference(inner) => {
let name = inner_self_ty(&(&*inner.elem).into());
if inner.mutability.is_some() {
format_ident!("RefMut{}", name)
} else {
format_ident!("Ref{}", name)
}
}
ExtType::Array(inner) => {
let name = inner_self_ty(&(&*inner.elem).into());
format_ident!("ListOf{}", name)
}
ExtType::Group(inner) => {
let name = inner_self_ty(&(&*inner.elem).into());
format_ident!("Group{}", name)
}
ExtType::Paren(inner) => {
let name = inner_self_ty(&(&*inner.elem).into());
format_ident!("Paren{}", name)
}
ExtType::Ptr(inner) => {
let name = inner_self_ty(&(&*inner.elem).into());
format_ident!("PointerTo{}", name)
}
ExtType::Slice(inner) => {
let name = inner_self_ty(&(&*inner.elem).into());
format_ident!("SliceOf{}", name)
}
ExtType::Tuple(inner) => {
let mut name = format_ident!("TupleOf");
for elem in &inner.elems {
name = format_ident!("{}{}", name, inner_self_ty(&elem.into()));
}
name
}
ExtType::Never(_) => format_ident!("Never"),
ExtType::BareFn(inner) => {
let mut name = format_ident!("BareFn");
for input in inner.inputs.iter() {
name = format_ident!("{}{}", name, inner_self_ty(&(&input.ty).into()));
}
match &inner.output {
syn::ReturnType::Default => {
name = format_ident!("{}Unit", name);
}
syn::ReturnType::Type(_, ty) => {
name = format_ident!("{}{}", name, inner_self_ty(&(&**ty).into()));
}
}
name
}
ExtType::TraitObject(inner) => {
let mut name = format_ident!("TraitObject");
for bound in inner.bounds.iter() {
match bound {
TypeParamBound::Trait(bound) => {
for segment in bound.path.segments.iter() {
name = format_ident!("{}{}", name, segment.ident);
}
}
TypeParamBound::Lifetime(lifetime) => {
name = format_ident!("{}{}", name, lifetime.ident);
}
}
}
name
}
}
}
format_ident!("{}Ext", inner_self_ty(self_ty))
}
fn find_and_combine_idents(type_path: &TypePath) -> Ident {
use syn::visit::{self, Visit};
struct IdentVisitor<'a>(Vec<&'a Ident>);
impl<'a> Visit<'a> for IdentVisitor<'a> {
fn visit_ident(&mut self, i: &'a Ident) {
self.0.push(i);
}
}
let mut visitor = IdentVisitor(Vec::new());
visit::visit_type_path(&mut visitor, type_path);
let idents = visitor.0;
if idents.is_empty() {
abort!(type_path.span(), "Empty type path")
} else {
let start = &idents[0].span();
let combined_span = idents
.iter()
.map(|i| i.span())
.fold(*start, |a, b| a.join(b).unwrap_or(a));
let combined_name = idents.iter().map(|i| i.to_string()).collect::<String>();
Ident::new(&combined_name, combined_span)
}
}
#[derive(Debug, Default)]
struct MethodsAndConsts {
trait_methods: Vec<TraitItemMethod>,
trait_consts: Vec<TraitItemConst>,
}
#[allow(clippy::wildcard_in_or_patterns)]
fn extract_allowed_items(items: &[ImplItem]) -> MethodsAndConsts {
let mut acc = MethodsAndConsts::default();
for item in items {
match item {
ImplItem::Method(method) => acc.trait_methods.push(TraitItemMethod {
attrs: method.attrs.clone(),
sig: method.sig.clone(),
default: None,
semi_token: Some(Semi::default()),
}),
ImplItem::Const(const_) => acc.trait_consts.push(TraitItemConst {
attrs: const_.attrs.clone(),
const_token: Default::default(),
ident: const_.ident.clone(),
colon_token: Default::default(),
ty: const_.ty.clone(),
default: None,
semi_token: Default::default(),
}),
ImplItem::Type(_) => abort!(
item.span(),
"Associated types are not allowed in #[ext] impls"
),
ImplItem::Macro(_) => abort!(item.span(), "Macros are not allowed in #[ext] impls"),
ImplItem::Verbatim(_) | _ => abort!(item.span(), "Not allowed in #[ext] impls"),
}
}
acc
}
#[derive(Debug)]
struct Config {
ext_trait_name: Option<Ident>,
visibility: Visibility,
supertraits: Option<Punctuated<TypeParamBound, Add>>,
}
impl Parse for Config {
fn parse(input: ParseStream) -> parse::Result<Self> {
let mut config = Config::default();
if let Ok(visibility) = input.parse::<Visibility>() {
config.visibility = visibility;
}
input.parse::<Token![,]>().ok();
while !input.is_empty() {
let ident = input.parse::<Ident>()?;
input.parse::<Token![=]>()?;
match &*ident.to_string() {
"name" => {
config.ext_trait_name = Some(input.parse()?);
}
"supertraits" => {
config.supertraits =
Some(Punctuated::<TypeParamBound, Add>::parse_terminated(input)?);
}
_ => abort!(ident.span(), "Unknown configuration name"),
}
input.parse::<Token![,]>().ok();
}
Ok(config)
}
}
impl Default for Config {
fn default() -> Self {
Self {
ext_trait_name: None,
visibility: Visibility::Inherited,
supertraits: None,
}
}
}
fn punctuated_from_iter<I, T, P>(i: I) -> Punctuated<T, P>
where
P: Default,
I: IntoIterator<Item = T>,
{
let mut iter = i.into_iter().peekable();
let mut acc = Punctuated::default();
while let Some(item) = iter.next() {
acc.push_value(item);
if iter.peek().is_some() {
acc.push_punct(P::default());
}
}
acc
}
#[cfg(test)]
mod test {
#[allow(unused_imports)]
use super::*;
#[test]
fn test_ui() {
let t = trybuild::TestCases::new();
t.pass("tests/compile_pass/*.rs");
t.compile_fail("tests/compile_fail/*.rs");
}
}

View File

@ -0,0 +1,14 @@
mod a {
use extend::ext;
#[ext(pub(super))]
pub impl i32 {
fn foo() -> Foo {
Foo
}
}
pub struct Foo;
}
fn main() {}

View File

@ -0,0 +1,5 @@
error: Cannot set visibility on `#[ext]` and `impl` block
--> $DIR/double_vis.rs:4:11
|
4 | #[ext(pub(super))]
| ^^^

View File

@ -0,0 +1,14 @@
use extend::ext;
trait MyTrait {}
#[ext(supertraits = MyTrait)]
impl String {
fn my_len(&self) -> usize {
self.len()
}
}
fn main() {
assert_eq!(String::new().my_len(), 0);
}

View File

@ -0,0 +1,10 @@
error[E0277]: the trait bound `String: MyTrait` is not satisfied
--> $DIR/supertraits_are_actually_included.rs:6:6
|
5 | #[ext(supertraits = MyTrait)]
| ------- required by this bound in `StringExt`
6 | impl String {
| ^^^^^^
| |
| the trait `MyTrait` is not implemented for `String`
| required by a bound in this

View File

@ -0,0 +1,10 @@
use extend::ext;
#[ext]
impl Option<String> {
const FOO: usize = 1;
}
fn main() {
assert_eq!(Option::<String>::FOO, 1);
}

View File

@ -0,0 +1,25 @@
use extend::ext;
use async_trait::async_trait;
#[ext]
#[async_trait]
impl String {
async fn foo() -> usize {
1
}
}
#[ext]
#[async_trait]
pub impl i32 {
async fn bar() -> usize {
1
}
}
async fn foo() {
let _: usize = String::foo().await;
let _: usize = i32::bar().await;
}
fn main() {}

View File

@ -0,0 +1,10 @@
use extend::ext;
#[ext(name = Foo)]
impl i32 {
fn foo() {}
}
fn main() {
<i32 as Foo>::foo();
}

View File

@ -0,0 +1,16 @@
mod foo {
use extend::ext;
#[ext(pub)]
impl<T1, T2, T3> (T1, T2, T3) {
fn size(&self) -> usize {
3
}
}
}
fn main() {
use foo::TupleOfT1T2T3Ext;
assert_eq!(3, (0, 0, 0).size());
}

View File

@ -0,0 +1,17 @@
use extend::ext;
#[ext]
impl Option<usize> {
fn foo() -> usize {
1
}
}
#[ext]
impl Option<i32> {
fn bar() -> i32 {
1
}
}
fn main() {}

View File

@ -0,0 +1,58 @@
use extend::ext;
#[ext]
impl<'a> &'a str {
fn foo(self) {}
}
#[ext]
impl<T> [T; 3] {
fn foo(self) {}
}
#[ext]
impl *const i32 {
fn foo(self) {}
}
#[ext]
impl<T> [T] {
fn foo(&self) {}
}
#[ext]
impl<'a, T> &'a [T] {
fn foo(self) {}
}
#[ext]
impl (i32, i64) {
fn foo(self) {}
}
#[ext]
impl fn(i32) -> bool {
fn foo(self) {}
}
fn bare_fn(_: i32) -> bool {
false
}
#[ext]
impl dyn Send + Sync + 'static {}
fn main() {
"".foo();
[1, 2, 3].foo();
let ptr: *const i32 = &123;
ptr.foo();
&[1, 2, 3].foo();
(1i32, 1i64).foo();
(bare_fn as fn(i32) -> bool).foo();
}

View File

@ -0,0 +1,15 @@
use extend::ext;
#[ext]
impl<'a, T: Clone> Vec<&'a T>
where
T: 'a + Copy,
{
fn size(&self) -> usize {
self.len()
}
}
fn main() {
assert_eq!(3, vec![&1, &2, &3].size());
}

View File

@ -0,0 +1,20 @@
use extend::ext;
#[ext]
impl i32 {
fn add_one(&self) -> Self {
self + 1
}
fn foo() -> MyType {
MyType
}
}
#[derive(Debug, Eq, PartialEq)]
struct MyType;
fn main() {
assert_eq!(i32::foo(), MyType);
assert_eq!(1.add_one(), 2);
}

View File

@ -0,0 +1,33 @@
#![allow(unused_variables)]
use extend::ext;
use std::iter::FromIterator;
#[ext]
impl<T, K, F, C> C
where
C: IntoIterator<Item = T>,
K: Eq,
F: Fn(&T) -> K,
{
fn group_by<Out>(self, f: F) -> Out
where
Out: FromIterator<(K, Vec<T>)>,
{
todo!()
}
fn group_by_and_map_values<Out, G, T2>(self, f: F, g: G) -> Out
where
G: Fn(T) -> T2 + Copy,
Out: FromIterator<(K, Vec<T2>)>,
{
todo!()
}
fn group_by_and_return_groups(self, f: F) -> Vec<Vec<T>> {
todo!()
}
}
fn main() {}

View File

@ -0,0 +1,13 @@
use extend::ext;
#[ext]
impl i32 {
fn foo() {}
}
#[ext]
impl i64 {
fn bar() {}
}
fn main() {}

View File

@ -0,0 +1,13 @@
use extend::ext;
#[ext(pub(crate), name = Foo)]
impl i32 {
fn foo() {}
}
#[ext(pub, name = Bar)]
impl i64 {
fn foo() {}
}
fn main() {}

View File

@ -0,0 +1,11 @@
use extend::ext;
use std::marker::PhantomData;
struct Foo<T>(PhantomData<T>);
#[ext]
impl<T, K> T {
fn some_method(&self, _: Foo<K>) {}
}
fn main() {}

View File

@ -0,0 +1,15 @@
mod a {
use extend::ext;
#[ext]
pub impl i32 {
fn foo() -> Foo { Foo }
}
pub struct Foo;
}
fn main() {
use a::i32Ext;
i32::foo();
}

View File

@ -0,0 +1,13 @@
use extend::ext;
#[ext]
impl &i32 {
fn foo() {}
}
#[ext]
impl &mut i32 {
fn bar() {}
}
fn main() {}

View File

@ -0,0 +1,36 @@
use extend::ext_sized;
#[ext_sized(name = One)]
impl i32 {
fn requires_sized(self) -> Option<Self> {
Some(self)
}
}
#[ext_sized(name = Two, supertraits = Default)]
impl i32 {
fn with_another_supertrait(self) -> Option<Self> {
Some(self)
}
}
#[ext_sized(name = Three, supertraits = Default + Clone + Copy)]
impl i32 {
fn multiple_supertraits(self) -> Option<Self> {
Some(self)
}
}
#[ext_sized(name = Four, supertraits = Sized)]
impl i32 {
fn already_sized(self) -> Option<Self> {
Some(self)
}
}
fn main() {
1.requires_sized();
1.with_another_supertrait();
1.multiple_supertraits();
1.already_sized();
}

View File

@ -0,0 +1,16 @@
use extend::ext;
trait MyTrait {}
impl MyTrait for String {}
#[ext(supertraits = Default + Clone + MyTrait)]
impl String {
fn my_len(&self) -> usize {
self.len()
}
}
fn main() {
assert_eq!(String::new().my_len(), 0);
}

View File

@ -0,0 +1,15 @@
mod a {
use extend::ext;
#[ext(pub)]
impl i32 {
fn foo() -> Foo { Foo }
}
pub struct Foo;
}
fn main() {
use a::i32Ext;
i32::foo();
}

View File

@ -0,0 +1 @@
{"files":{"Cargo.toml":"90295a750f9c69ee799ac767d56eaee2c3218df7964f11c885b6910a31d07a25","build.rs":"05089b35ac197b5ff83c75872a70df023834141831d0c2051faa35b4c9df55ba","src/arithmetic.udl":"8554c6907ece627645f6b896f71430e5412bf19b0ac6becf63eb9a69868d0f7a","src/lib.rs":"92ebd9fc4d3403ab1960d2d8520c1217971147ea3aebff89228a61fcbb8b2af3","tests/bindings/test_arithmetic.kts":"e0e9347755db4e18f70b1b74c2d5a4aa328373015090ed959b46d65c2a205d92","tests/bindings/test_arithmetic.py":"3e41d69e21e96a6830197c760f3b7bddd754edc0c5515b7bd33b79cccb10f941","tests/bindings/test_arithmetic.rb":"ea0fdce0a4c7b557b427db77521da05240cd6e87d60a128ac2307fab3bbbc76d","tests/bindings/test_arithmetic.swift":"455b87d95fc690af9c35f9e43676e9c855dedddd2fc1c9e1cbaa6a02835c2d4c","tests/test_generated_bindings.rs":"09b0e79c7e769bcf5f3a8b768247a05892d408d48e0295f6737de3c8dab28479","uniffi.toml":"ad149df611a6e3a853a029d90a88a694660f6a4ebe18dcb5f9f503819761dacd"},"package":null}

View File

@ -0,0 +1,19 @@
[package]
name = "uniffi-example-arithmetic"
edition = "2021"
version = "0.18.0"
authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
license = "MPL-2.0"
publish = false
[lib]
crate-type = ["lib", "cdylib"]
name = "arithmetical"
[dependencies]
uniffi_macros = {path = "../../uniffi_macros"}
uniffi = {path = "../../uniffi", features=["builtin-bindgen"]}
thiserror = "1.0"
[build-dependencies]
uniffi_build = {path = "../../uniffi_build", features=["builtin-bindgen"]}

View File

@ -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_build::generate_scaffolding("./src/arithmetic.udl").unwrap();
}

View File

@ -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);
};

View File

@ -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_macros::include_scaffolding!("arithmetic");

View File

@ -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))

View File

@ -0,0 +1,37 @@
from arithmetic import *
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)

View File

@ -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)

View File

@ -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")

View File

@ -0,0 +1,9 @@
uniffi_macros::build_foreign_language_testcases!(
["src/arithmetic.udl",],
[
"tests/bindings/test_arithmetic.rb",
"tests/bindings/test_arithmetic.py",
"tests/bindings/test_arithmetic.kts",
"tests/bindings/test_arithmetic.swift",
]
);

View File

@ -0,0 +1,12 @@
[bindings.kotlin]
package_name = "org.mozilla.uniffi.example.arithmetic"
cdylib_name = "arithmetical"
[bindings.python]
cdylib_name = "arithmetical"
[bindings.ruby]
cdylib_name = "arithmetical"
[bindings.swift]
cdylib_name = "arithmetical"

View File

@ -0,0 +1 @@
{"files":{"Cargo.toml":"15bbf6e8c0d438e7f776dfdcf8da5fefb41e4b5bacafe97d1fcb10389d07ed9b","build.rs":"94ca4e70e538a2159cb77157c227d487973756373c4b27ea5cea05a6698cb60b","src/geometry.udl":"7da7a6ec080c7117ec3c25206e23f9ed436e60b1a26fba34f991547680443550","src/lib.rs":"be9c624f691a8d1ebe3be6dbbcde44033759b8321a3a298453018dd69b9c14c7","tests/bindings/test_geometry.kts":"e537185e3c699df1c0468525700e8a38f9a504b2a663c38442146b951e38e9a7","tests/bindings/test_geometry.py":"3ea483b8a4fbe13aefa6641177ae149f75f734bc32bf0da533b97c1abf3dc317","tests/bindings/test_geometry.rb":"17c2fe8a7b477419a6646983dd88f1b07a0304b58a568c03e9bfa640d5b2df5c","tests/bindings/test_geometry.swift":"a61fec6bfe16020809e20e4da372748c24366767138c5672a0bfff85c4b62d78","tests/test_generated_bindings.rs":"8ba67396105b96cc554f78078c7a8c6e8ce86ecc868d97a069a5f60fd87c1a36","uniffi.toml":"5b28f45d3c2581a52cf886a502f034778a002815b66994e5da2081a5c9f5284b"},"package":null}

View File

@ -0,0 +1,18 @@
[package]
name = "uniffi-example-geometry"
edition = "2021"
version = "0.18.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_macros = {path = "../../uniffi_macros"}
uniffi = {path = "../../uniffi", features=["builtin-bindgen"]}
[build-dependencies]
uniffi_build = {path = "../../uniffi_build", features=["builtin-bindgen"]}

View File

@ -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_build::generate_scaffolding("./src/geometry.udl").unwrap();
}

View File

@ -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;
};

View File

@ -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,
})
}
include!(concat!(env!("OUT_DIR"), "/geometry.uniffi.rs"));

View File

@ -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 )

View File

@ -0,0 +1,10 @@
from geometry import *
ln1 = Line(Point(0,0), Point(1,2))
ln2 = Line(Point(1,1), Point(2,2))
assert gradient(ln1) == 2
assert gradient(ln2) == 1
assert intersection(ln1, ln2) == Point(0, 0)
assert intersection(ln1, ln1) is None

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
require 'test/unit'
require 'geometry'
include Test::Unit::Assertions
include Geometry
ln1 = Line.new(Point.new(0.0, 0.0), Point.new(1.0, 2.0))
ln2 = Line.new(Point.new(1.0, 1.0), Point.new(2.0, 2.0))
assert_equal Geometry.gradient(ln1), 2
assert_equal Geometry.gradient(ln2), 1
assert_equal Geometry.intersection(ln1, ln2), Point.new(0, 0)
assert Geometry.intersection(ln1, ln1).nil?

View File

@ -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)

View File

@ -0,0 +1,9 @@
uniffi_macros::build_foreign_language_testcases!(
["src/geometry.udl",],
[
"tests/bindings/test_geometry.py",
"tests/bindings/test_geometry.rb",
"tests/bindings/test_geometry.kts",
"tests/bindings/test_geometry.swift",
]
);

View File

@ -0,0 +1 @@
[bindings.swift]

View File

@ -0,0 +1 @@
{"files":{"Cargo.toml":"e26e9e2826e89bf73aed693d2cea2aa1e2ce3d32cb0f03136cc29352f851295c","build.rs":"c48df8045c6cf73a8f3b8e93af7878f4ce1e7baa11f0688d661405f339df4ba4","src/lib.rs":"70b9ab1fb944d3af1ce53ce097caf52d1ebbf0e18be1e70bf57e5cec96e76502","src/rondpoint.udl":"ca4d8720758608b06ffd5f81bfc73881fbd0693a7a5b21bfe61a4557ea052048","tests/bindings/test_rondpoint.kts":"87b3d507f4260aae4e4711263c13b7158b9a814cb97bf5219d1451f348cd7372","tests/bindings/test_rondpoint.py":"d618274170af767f8a5614a2565ea698b26ea3e1a222d5c110e7b2d00763e73b","tests/bindings/test_rondpoint.rb":"9cc49df311823d6caedbe7b05ff8c4da6329063c2ce16810192aaaa7edcdf5f5","tests/bindings/test_rondpoint.swift":"471cf430be35dcdc19b3166adbc08561e0bd939931edc37da974e0fd16a49331","tests/test_generated_bindings.rs":"5843b2c9d6b86b35ab932349d278d9f281493880a1395e0b7566e297fad36c7d"},"package":null}

View File

@ -0,0 +1,18 @@
[package]
name = "uniffi-example-rondpoint"
edition = "2021"
version = "0.18.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_macros = {path = "../../uniffi_macros"}
uniffi = {path = "../../uniffi", features=["builtin-bindgen"]}
[build-dependencies]
uniffi_build = {path = "../../uniffi_build", features=["builtin-bindgen"]}

View File

@ -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_build::generate_scaffolding("./src/rondpoint.udl").unwrap();
}

View File

@ -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>,
}
include!(concat!(env!("OUT_DIR"), "/rondpoint.uniffi.rs"));

View File

@ -0,0 +1,146 @@
namespace rondpoint {
Dictionnaire copie_dictionnaire(Dictionnaire d);
Enumeration copie_enumeration(Enumeration e);
sequence<Enumeration> copie_enumerations(sequence<Enumeration> e);
record<DOMString, EnumerationAvecDonnees> copie_carte(record<DOMString, 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;
};

View File

@ -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_VAUE 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 explicite = 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 == explicite)
// …and makes sure they travel across and back the FFI.
val rt2 = Retourneur()
listOf(defaultes).affirmAllerRetour(rt2::identiqueOptionneurDictionnaire)
rt2.destroy()

View File

@ -0,0 +1,146 @@
import sys
import ctypes
from rondpoint import *
dico = Dictionnaire(Enumeration.DEUX, True, 0, 123456789)
copyDico = copie_dictionnaire(dico)
assert dico == copyDico
assert copie_enumeration(Enumeration.DEUX) == Enumeration.DEUX
assert copie_enumerations([Enumeration.UN, Enumeration.DEUX]) == [Enumeration.UN, Enumeration.DEUX]
assert copie_carte({
"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,
)

View File

@ -0,0 +1,142 @@
# frozen_string_literal: true
require 'test/unit'
require 'rondpoint'
include Test::Unit::Assertions
include Rondpoint
dico = Dictionnaire.new Enumeration::DEUX, true, 0, 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)

View File

@ -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 explicite = 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 == explicite)
[defaultes].affirmAllerRetour(rt.identiqueOptionneurDictionnaire)

View File

@ -0,0 +1,9 @@
uniffi_macros::build_foreign_language_testcases!(
["src/rondpoint.udl",],
[
"tests/bindings/test_rondpoint.kts",
"tests/bindings/test_rondpoint.swift",
"tests/bindings/test_rondpoint.py",
"tests/bindings/test_rondpoint.rb",
]
);

View File

@ -0,0 +1 @@
{"files":{"Cargo.toml":"b288fc6b1e909cffdb5171a247f268884a2483d9f97f1a24a679681fb418bd8d","build.rs":"d36e4ff477007611d216e60998d6d8a51b33895bb95159e3d7732e2b8cbc1783","src/lib.rs":"79ae1ea584942b25c108a26df79a781a63d66f8fb37c4dacfc04d180752effcb","src/sprites.udl":"bfd35f04ba0549301189dfb8fc45b0f39bad00956c324f33be0e845fb7ff78aa","tests/bindings/test_sprites.kts":"06ed115325f37ce59ed6f33e2d651cd2aa352fddcc644580f62a6da6ca075844","tests/bindings/test_sprites.py":"2e6ce838cfb387586257703c3500062438e840dd7ae57d185cdc244dc0745b8f","tests/bindings/test_sprites.rb":"6289a1833c7c8f4583ee4f0488d680de2ee46cfb203095a9b66d7234e2f07d53","tests/bindings/test_sprites.swift":"b2c0a6f4d5edfd7de7c2ba77b838865ffda153a6f364f273456175192d3e6e00","tests/test_generated_bindings.rs":"920c532011c8a6c20bdb1f39c50ef7a2803729a62a1bd425d81e40300958778a"},"package":null}

View File

@ -0,0 +1,18 @@
[package]
name = "uniffi-example-sprites"
edition = "2021"
version = "0.18.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_macros = {path = "../../uniffi_macros"}
uniffi = {path = "../../uniffi", features=["builtin-bindgen"]}
[build-dependencies]
uniffi_build = {path = "../../uniffi_build", features=["builtin-bindgen"]}

View File

@ -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_build::generate_scaffolding("./src/sprites.udl").unwrap();
}

View File

@ -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)
}
}
include!(concat!(env!("OUT_DIR"), "/sprites.uniffi.rs"));

View File

@ -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);
};

View File

@ -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) )

View File

@ -0,0 +1,17 @@
from sprites import *
sempty = Sprite(None)
assert sempty.get_position() == Point(0, 0)
s = Sprite(Point(0, 1))
assert s.get_position() == Point(0, 1)
s.move_to(Point(1, 2))
assert s.get_position() == Point(1, 2)
s.move_by(Vector(-4, 2))
assert s.get_position() == Point(-3, 4)
srel = Sprite.new_relative_to(Point(0, 1), Vector(1, 1.5))
assert srel.get_position() == Point(1, 2.5)

View File

@ -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(0, 0)
s = Sprite.new(Point.new(0, 1))
assert_equal s.get_position, Point.new(0, 1)
s.move_to(Point.new(1, 2))
assert_equal s.get_position, Point.new(1, 2)
s.move_by(Vector.new(-4, 2))
assert_equal s.get_position, Point.new(-3, 4)
srel = Sprite.new_relative_to(Point.new(0, 1), Vector.new(1, 1.5))
assert_equal srel.get_position, Point.new(1, 2.5)

View File

@ -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) )

View File

@ -0,0 +1,9 @@
uniffi_macros::build_foreign_language_testcases!(
["src/sprites.udl",],
[
"tests/bindings/test_sprites.py",
"tests/bindings/test_sprites.rb",
"tests/bindings/test_sprites.kts",
"tests/bindings/test_sprites.swift",
]
);

View File

@ -0,0 +1 @@
{"files":{"Cargo.toml":"4f4fc40bccf4ca5b105ea2abdbcdc6758ba7271d7d4ceb1e8ed45e3de227c2a6","build.rs":"ca26833c671cfe14a8275c276c197ab228cb1639b34cae31bb3805221ecc1245","src/lib.rs":"6a38904924681aed26b74876ad0480fedf8e8b2640cd2b04531a55aba8fc5592","src/todolist.udl":"1f8a24049c2340b9184e95facfc191ecdcb91541729ae7f20b4625d67685f13c","tests/bindings/test_todolist.kts":"f3d29b48e0193563fc4f131d91ea697f758174dcdb80ea554f233949e575bf55","tests/bindings/test_todolist.py":"f7430af9347df0daa954d38bc2203ce400affbb9a53fced4bb67a6796afa0664","tests/bindings/test_todolist.rb":"6524b5271a9cc0e2d78ca9f86ccb6973889926688a0843b4505a4f62d48f6dcb","tests/bindings/test_todolist.swift":"d1911b85fe0c8c0b42e5421b5af5d7359c9a65bba477d23560eb4b0f52e80662","tests/test_generated_bindings.rs":"25108de454213659c3eacd643a24c49099757a6b6da501fdb50874f574bd30c5"},"package":null}

View File

@ -0,0 +1,20 @@
[package]
name = "uniffi-example-todolist"
edition = "2021"
version = "0.18.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_macros = {path = "../../uniffi_macros"}
uniffi = {path = "../../uniffi", features=["builtin-bindgen"]}
thiserror = "1.0"
lazy_static = "1.4"
[build-dependencies]
uniffi_build = {path = "../../uniffi_build", features=["builtin-bindgen"]}

View File

@ -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_build::generate_scaffolding("./src/todolist.udl").unwrap();
}

View File

@ -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};
#[derive(Debug, Clone)]
pub struct TodoEntry {
text: String,
}
lazy_static::lazy_static! {
// 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 ref DEFAULT_LIST: RwLock<Option<Arc<TodoList>>> = 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);
}
}
include!(concat!(env!("OUT_DIR"), "/todolist.uniffi.rs"));

View File

@ -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();
};

View File

@ -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()

View File

@ -0,0 +1,44 @@
from todolist import *
todo = TodoList()
entry = TodoEntry("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("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")

View File

@ -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 '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("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"

View File

@ -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")

View File

@ -0,0 +1,9 @@
uniffi_macros::build_foreign_language_testcases!(
["src/todolist.udl",],
[
"tests/bindings/test_todolist.kts",
"tests/bindings/test_todolist.swift",
"tests/bindings/test_todolist.rb",
"tests/bindings/test_todolist.py"
]
);

View File

@ -13,7 +13,7 @@ askama = { version = "0.11", default-features = false, features = ["config"] }
clap = { version = "3.1", features = ["std", "derive"] }
extend = "1.1"
heck = "0.4"
uniffi_bindgen = "0.18"
uniffi_bindgen = "0.19"
serde = "1"
toml = "0.5"
camino = "1.0.8"

View File

@ -74,6 +74,8 @@ if CONFIG["MOZ_CRASHREPORTER"] and CONFIG['MOZ_OXIDIZED_BREAKPAD']:
if CONFIG["MOZ_WEBMIDI_MIDIR_IMPL"]:
gkrust_features += ['webmidi_midir_impl']
if CONFIG["MOZ_UNIFFI_FIXTURES"]:
gkrust_features += ['uniffi_fixtures']
# This must remain last.
gkrust_features = ["gkrust-shared/%s" % f for f in gkrust_features]

View File

@ -75,6 +75,11 @@ origin-trials-ffi = { path = "../../../../dom/origin-trials/ffi" }
jog = { path = "../../../components/glean/bindings/jog" }
dap_ffi = { path = "../../../components/telemetry/dap/ffi" }
data-encoding-ffi = { path = "../../../../dom/fs/parent/rust/data-encoding-ffi" }
uniffi-example-arithmetic = { git = "https://github.com/mozilla/uniffi-rs.git", rev = "bb2039f077a29dba0879372a67e764e6ace8e33f", optional = true }
uniffi-example-geometry = { git = "https://github.com/mozilla/uniffi-rs.git", rev = "bb2039f077a29dba0879372a67e764e6ace8e33f", optional = true }
uniffi-example-rondpoint = { git = "https://github.com/mozilla/uniffi-rs.git", rev = "bb2039f077a29dba0879372a67e764e6ace8e33f", optional = true }
uniffi-example-sprites = { git = "https://github.com/mozilla/uniffi-rs.git", rev = "bb2039f077a29dba0879372a67e764e6ace8e33f", optional = true }
uniffi-example-todolist = { git = "https://github.com/mozilla/uniffi-rs.git", rev = "bb2039f077a29dba0879372a67e764e6ace8e33f", optional = true }
# Note: `modern_sqlite` means rusqlite's bindings file be for a sqlite with
# version less than or equal to what we link to. This isn't a problem because we
@ -127,6 +132,10 @@ glean_with_gecko = ["fog_control/with_gecko", "jog/with_gecko"]
oxidized_breakpad = ["rust_minidump_writer_linux"]
with_dbus = ["audio_thread_priority/with_dbus"]
thread_sanitizer = ["xpcom/thread_sanitizer"]
uniffi_fixtures = [
"uniffi-example-arithmetic", "uniffi-example-geometry", "uniffi-example-rondpoint", "uniffi-example-sprites",
"uniffi-example-todolist",
]
webmidi_midir_impl = ["midir_impl"]
[lib]

View File

@ -99,6 +99,21 @@ extern crate dap_ffi;
extern crate data_encoding_ffi;
#[cfg(feature = "uniffi_fixtures")]
mod uniffi_fixtures {
extern crate arithmetical;
extern crate uniffi_geometry;
extern crate uniffi_rondpoint;
extern crate uniffi_sprites;
extern crate uniffi_todolist;
arithmetical::uniffi_reexport_scaffolding!();
uniffi_geometry::uniffi_reexport_scaffolding!();
uniffi_rondpoint::uniffi_reexport_scaffolding!();
uniffi_sprites::uniffi_reexport_scaffolding!();
uniffi_todolist::uniffi_reexport_scaffolding!();
}
extern crate log;
use log::info;

View File

@ -3202,6 +3202,19 @@ def disable_smart_cards(build_project):
set_config("MOZ_NO_SMART_CARDS", True, when=disable_smart_cards)
set_define("MOZ_NO_SMART_CARDS", True, when=disable_smart_cards)
# Enable UniFFI fixtures
# ==============================================================
# These are used to test the uniffi-bindgen-gecko-js code generation. They
# should not be enabled in release builds.
option(
"--enable-uniffi-fixtures",
help="Enable UniFFI Fixtures/Examples",
)
set_config("MOZ_UNIFFI_FIXTURES", True, when="--enable-uniffi-fixtures")
# Checks for library functions
# ==============================================================
with only_when(compile_environment & depends(target.os)(lambda os: os != "WINNT")):