From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../rust/uniffi_bindgen/src/interface/enum_.rs | 567 +++++++++++++++++++++ 1 file changed, 567 insertions(+) create mode 100644 third_party/rust/uniffi_bindgen/src/interface/enum_.rs (limited to 'third_party/rust/uniffi_bindgen/src/interface/enum_.rs') diff --git a/third_party/rust/uniffi_bindgen/src/interface/enum_.rs b/third_party/rust/uniffi_bindgen/src/interface/enum_.rs new file mode 100644 index 0000000000..82baf1dd50 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/enum_.rs @@ -0,0 +1,567 @@ +/* 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/. */ + +//! # Enum definitions for a `ComponentInterface`. +//! +//! This module converts enum definition from UDL into structures that can be +//! added to a `ComponentInterface`. A declaration in the UDL like this: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! enum Example { +//! "one", +//! "two" +//! }; +//! # "##, "crate_name")?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in a [`Enum`] member being added to the resulting [`crate::ComponentInterface`]: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # enum Example { +//! # "one", +//! # "two" +//! # }; +//! # "##, "crate_name")?; +//! let e = ci.get_enum_definition("Example").unwrap(); +//! assert_eq!(e.name(), "Example"); +//! assert_eq!(e.variants().len(), 2); +//! assert_eq!(e.variants()[0].name(), "one"); +//! assert_eq!(e.variants()[1].name(), "two"); +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Like in Rust, UniFFI enums can contain associated data, but this needs to be +//! declared with a different syntax in order to work within the restrictions of +//! WebIDL. A declaration like this: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! [Enum] +//! interface Example { +//! Zero(); +//! One(u32 first); +//! Two(u32 first, string second); +//! }; +//! # "##, "crate_name")?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in an [`Enum`] member whose variants have associated fields: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # [Enum] +//! # interface ExampleWithData { +//! # Zero(); +//! # One(u32 first); +//! # Two(u32 first, string second); +//! # }; +//! # "##, "crate_name")?; +//! let e = ci.get_enum_definition("ExampleWithData").unwrap(); +//! assert_eq!(e.name(), "ExampleWithData"); +//! assert_eq!(e.variants().len(), 3); +//! assert_eq!(e.variants()[0].name(), "Zero"); +//! assert_eq!(e.variants()[0].fields().len(), 0); +//! assert_eq!(e.variants()[1].name(), "One"); +//! assert_eq!(e.variants()[1].fields().len(), 1); +//! assert_eq!(e.variants()[1].fields()[0].name(), "first"); +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! # Enums are also used to represent error definitions for a `ComponentInterface`. +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! [Error] +//! enum Example { +//! "one", +//! "two" +//! }; +//! # "##, "crate_name")?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in an [`Enum`] member with fieldless variants being added to the resulting [`crate::ComponentInterface`]: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # [Error] +//! # enum Example { +//! # "one", +//! # "two" +//! # }; +//! # "##, "crate_name")?; +//! let err = ci.get_enum_definition("Example").unwrap(); +//! assert_eq!(err.name(), "Example"); +//! assert_eq!(err.variants().len(), 2); +//! assert_eq!(err.variants()[0].name(), "one"); +//! assert_eq!(err.variants()[1].name(), "two"); +//! assert_eq!(err.is_flat(), true); +//! assert!(ci.is_name_used_as_error(&err.name())); +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! A declaration in the UDL like this: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! [Error] +//! interface Example { +//! one(i16 code); +//! two(string reason); +//! three(i32 x, i32 y); +//! }; +//! # "##, "crate_name")?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in an [`Enum`] member with variants that have fields being added to the resulting [`crate::ComponentInterface`]: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # [Error] +//! # interface Example { +//! # one(); +//! # two(string reason); +//! # three(i32 x, i32 y); +//! # }; +//! # "##, "crate_name")?; +//! let err = ci.get_enum_definition("Example").unwrap(); +//! assert_eq!(err.name(), "Example"); +//! assert_eq!(err.variants().len(), 3); +//! assert_eq!(err.variants()[0].name(), "one"); +//! assert_eq!(err.variants()[1].name(), "two"); +//! assert_eq!(err.variants()[2].name(), "three"); +//! assert_eq!(err.variants()[0].fields().len(), 0); +//! assert_eq!(err.variants()[1].fields().len(), 1); +//! assert_eq!(err.variants()[1].fields()[0].name(), "reason"); +//! assert_eq!(err.variants()[2].fields().len(), 2); +//! assert_eq!(err.variants()[2].fields()[0].name(), "x"); +//! assert_eq!(err.variants()[2].fields()[1].name(), "y"); +//! assert_eq!(err.is_flat(), false); +//! assert!(ci.is_name_used_as_error(err.name())); +//! # Ok::<(), anyhow::Error>(()) +//! ``` + +use anyhow::Result; +use uniffi_meta::Checksum; + +use super::record::Field; +use super::{AsType, Type, TypeIterator}; + +/// Represents an enum with named variants, each of which may have named +/// and typed fields. +/// +/// Enums are passed across the FFI by serializing to a bytebuffer, with a +/// i32 indicating the variant followed by the serialization of each field. +#[derive(Debug, Clone, PartialEq, Eq, Checksum)] +pub struct Enum { + pub(super) name: String, + pub(super) module_path: String, + pub(super) variants: Vec, + // NOTE: `flat` is a misleading name and to make matters worse, has 2 different + // meanings depending on the context :( + // * When used as part of Rust scaffolding generation, it means "is this enum + // used with an Error, and that error should we lowered to foreign bindings + // by converting each variant to a string and lowering the variant with that + // string?". In that context, it should probably be called `lowered_as_string` or + // similar. + // * When used as part of bindings generation, it means "does this enum have only + // variants with no associated data"? The foreign binding generators are likely + // to generate significantly different versions of the enum based on that value. + // + // The reason it is described as "has 2 different meanings" by way of example: + // * For an Enum described as being a flat error, but the enum itself has variants with data, + // `flat` will be `true` for the Enum when generating scaffolding and `false` when + // generating bindings. + // * For an Enum not used as an error but which has no variants with data, `flat` will be + // false when generating the scaffolding but `true` when generating bindings. + pub(super) flat: bool, +} + +impl Enum { + pub fn name(&self) -> &str { + &self.name + } + + pub fn variants(&self) -> &[Variant] { + &self.variants + } + + pub fn is_flat(&self) -> bool { + self.flat + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + Box::new(self.variants.iter().flat_map(Variant::iter_types)) + } + + // Sadly can't use TryFrom due to the 'is_flat' complication. + pub fn try_from_meta(meta: uniffi_meta::EnumMetadata, flat: bool) -> Result { + // This is messy - error enums are considered "flat" if the user + // opted in via a special attribute, regardless of whether the enum + // is actually flat. + // Real enums are considered flat iff they are actually flat. + // We don't have that context here, so this is handled by our caller. + Ok(Self { + name: meta.name, + module_path: meta.module_path, + variants: meta + .variants + .into_iter() + .map(TryInto::try_into) + .collect::>()?, + flat, + }) + } +} + +impl AsType for Enum { + fn as_type(&self) -> Type { + Type::Enum { + name: self.name.clone(), + module_path: self.module_path.clone(), + } + } +} + +/// Represents an individual variant in an Enum. +/// +/// Each variant has a name and zero or more fields. +#[derive(Debug, Clone, Default, PartialEq, Eq, Checksum)] +pub struct Variant { + pub(super) name: String, + pub(super) fields: Vec, +} + +impl Variant { + pub fn name(&self) -> &str { + &self.name + } + + pub fn fields(&self) -> &[Field] { + &self.fields + } + + pub fn has_fields(&self) -> bool { + !self.fields.is_empty() + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + Box::new(self.fields.iter().flat_map(Field::iter_types)) + } +} + +impl TryFrom for Variant { + type Error = anyhow::Error; + + fn try_from(meta: uniffi_meta::VariantMetadata) -> Result { + Ok(Self { + name: meta.name, + fields: meta + .fields + .into_iter() + .map(TryInto::try_into) + .collect::>()?, + }) + } +} + +#[cfg(test)] +mod test { + use super::super::{ComponentInterface, FfiType}; + use super::*; + + #[test] + fn test_duplicate_variants() { + const UDL: &str = r#" + namespace test{}; + // Weird, but currently allowed! + // We should probably disallow this... + enum Testing { "one", "two", "one" }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.enum_definitions().count(), 1); + assert_eq!( + ci.get_enum_definition("Testing").unwrap().variants().len(), + 3 + ); + } + + #[test] + fn test_associated_data() { + const UDL: &str = r#" + namespace test { + void takes_an_enum(TestEnum e); + void takes_an_enum_with_data(TestEnumWithData ed); + TestEnum returns_an_enum(); + TestEnumWithData returns_an_enum_with_data(); + }; + + enum TestEnum { "one", "two" }; + + [Enum] + interface TestEnumWithData { + Zero(); + One(u32 first); + Two(u32 first, string second); + }; + + [Enum] + interface TestEnumWithoutData { + One(); + Two(); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.enum_definitions().count(), 3); + assert_eq!(ci.function_definitions().len(), 4); + + // The "flat" enum with no associated data. + let e = ci.get_enum_definition("TestEnum").unwrap(); + assert!(e.is_flat()); + assert_eq!(e.variants().len(), 2); + assert_eq!( + e.variants().iter().map(|v| v.name()).collect::>(), + vec!["one", "two"] + ); + assert_eq!(e.variants()[0].fields().len(), 0); + assert_eq!(e.variants()[1].fields().len(), 0); + + // The enum with associated data. + let ed = ci.get_enum_definition("TestEnumWithData").unwrap(); + assert!(!ed.is_flat()); + assert_eq!(ed.variants().len(), 3); + assert_eq!( + ed.variants().iter().map(|v| v.name()).collect::>(), + vec!["Zero", "One", "Two"] + ); + assert_eq!(ed.variants()[0].fields().len(), 0); + assert_eq!( + ed.variants()[1] + .fields() + .iter() + .map(|f| f.name()) + .collect::>(), + vec!["first"] + ); + assert_eq!( + ed.variants()[1] + .fields() + .iter() + .map(|f| f.as_type()) + .collect::>(), + vec![Type::UInt32] + ); + assert_eq!( + ed.variants()[2] + .fields() + .iter() + .map(|f| f.name()) + .collect::>(), + vec!["first", "second"] + ); + assert_eq!( + ed.variants()[2] + .fields() + .iter() + .map(|f| f.as_type()) + .collect::>(), + vec![Type::UInt32, Type::String] + ); + + // The enum declared via interface, but with no associated data. + let ewd = ci.get_enum_definition("TestEnumWithoutData").unwrap(); + assert_eq!(ewd.variants().len(), 2); + assert_eq!( + ewd.variants().iter().map(|v| v.name()).collect::>(), + vec!["One", "Two"] + ); + assert_eq!(ewd.variants()[0].fields().len(), 0); + assert_eq!(ewd.variants()[1].fields().len(), 0); + + // Flat enums pass over the FFI as bytebuffers. + // (It might be nice to optimize these to pass as plain integers, but that's + // difficult atop the current factoring of `ComponentInterface` and friends). + let farg = ci.get_function_definition("takes_an_enum").unwrap(); + assert_eq!( + farg.arguments()[0].as_type(), + Type::Enum { + name: "TestEnum".into(), + module_path: "crate_name".into() + } + ); + assert_eq!( + farg.ffi_func().arguments()[0].type_(), + FfiType::RustBuffer(None) + ); + let fret = ci.get_function_definition("returns_an_enum").unwrap(); + assert!( + matches!(fret.return_type(), Some(Type::Enum { name, .. }) if name == "TestEnum" && !ci.is_name_used_as_error(name)) + ); + assert!(matches!( + fret.ffi_func().return_type(), + Some(FfiType::RustBuffer(None)) + )); + + // Enums with associated data pass over the FFI as bytebuffers. + let farg = ci + .get_function_definition("takes_an_enum_with_data") + .unwrap(); + assert_eq!( + farg.arguments()[0].as_type(), + Type::Enum { + name: "TestEnumWithData".into(), + module_path: "crate_name".into() + } + ); + assert_eq!( + farg.ffi_func().arguments()[0].type_(), + FfiType::RustBuffer(None) + ); + let fret = ci + .get_function_definition("returns_an_enum_with_data") + .unwrap(); + assert!( + matches!(fret.return_type(), Some(Type::Enum { name, .. }) if name == "TestEnumWithData" && !ci.is_name_used_as_error(name)) + ); + assert!(matches!( + fret.ffi_func().return_type(), + Some(FfiType::RustBuffer(None)) + )); + } + + // Tests for [Error], which are represented as `Enum` + #[test] + fn test_variants() { + const UDL: &str = r#" + namespace test{}; + [Error] + enum Testing { "one", "two", "three" }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.enum_definitions().count(), 1); + let error = ci.get_enum_definition("Testing").unwrap(); + assert_eq!( + error + .variants() + .iter() + .map(|v| v.name()) + .collect::>(), + vec!("one", "two", "three") + ); + assert!(error.is_flat()); + assert!(ci.is_name_used_as_error(&error.name)); + } + + #[test] + fn test_duplicate_error_variants() { + const UDL: &str = r#" + namespace test{}; + // Weird, but currently allowed! + // We should probably disallow this... + [Error] + enum Testing { "one", "two", "one" }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.enum_definitions().count(), 1); + assert_eq!( + ci.get_enum_definition("Testing").unwrap().variants().len(), + 3 + ); + } + + #[test] + fn test_variant_data() { + const UDL: &str = r#" + namespace test{}; + + [Error] + interface Testing { + One(string reason); + Two(u8 code); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.enum_definitions().count(), 1); + let error: &Enum = ci.get_enum_definition("Testing").unwrap(); + assert_eq!( + error + .variants() + .iter() + .map(|v| v.name()) + .collect::>(), + vec!("One", "Two") + ); + assert!(!error.is_flat()); + assert!(ci.is_name_used_as_error(&error.name)); + } + + #[test] + fn test_enum_variant_named_error() { + const UDL: &str = r#" + namespace test{}; + + [Enum] + interface Testing { + Normal(string first); + Error(string first); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.enum_definitions().count(), 1); + let testing: &Enum = ci.get_enum_definition("Testing").unwrap(); + assert_eq!( + testing.variants()[0] + .fields() + .iter() + .map(|f| f.name()) + .collect::>(), + vec!["first"] + ); + assert_eq!( + testing.variants()[0] + .fields() + .iter() + .map(|f| f.as_type()) + .collect::>(), + vec![Type::String] + ); + assert_eq!( + testing.variants()[1] + .fields() + .iter() + .map(|f| f.name()) + .collect::>(), + vec!["first"] + ); + assert_eq!( + testing.variants()[1] + .fields() + .iter() + .map(|f| f.as_type()) + .collect::>(), + vec![Type::String] + ); + assert_eq!( + testing + .variants() + .iter() + .map(|v| v.name()) + .collect::>(), + vec!["Normal", "Error"] + ); + } +} -- cgit v1.2.3