Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/third_party/rust/uniffi_bindgen/src/interface/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 23 kB image not shown  

Quelle  enum_.rs   Sprache: unbekannt

 
/* 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 {
//! #   [Throws=Example] void func();
//! # };
//! # [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 {
//! #   [Throws=Example] void func();
//! # };
//! # [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, EnumShape};

use super::record::Field;
use super::{AsType, Literal, 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) discr_type: Option<Type>,
    pub(super) variants: Vec<Variant>,
    pub(super) shape: EnumShape,
    pub(super) non_exhaustive: bool,
    #[checksum_ignore]
    pub(super) docstring: Option<String>,
}

impl Enum {
    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn rename(&mut self, name: String) {
        self.name = name;
    }

    pub fn variants(&self) -> &[Variant] {
        &self.variants
    }

    // Get the literal value to use for the specified variant's discriminant.
    // Follows Rust's rules when mixing specified and unspecified values; please
    // file a bug if you find a case where it does not.
    // However, it *does not* attempt to handle error cases - either cases where
    // a discriminant is not unique, or where a discriminant would overflow the
    // repr. The intention is that the Rust compiler itself will fail to build
    // in those cases, so by the time this get's run we can be confident these
    // error cases can't exist.
    pub fn variant_discr(&self, variant_index: usize) -> Result<Literal> {
        if variant_index >= self.variants.len() {
            anyhow::bail!("Invalid variant index {variant_index}");
        }
        let mut next = 0;
        let mut this;
        let mut this_lit = Literal::new_uint(0);
        for v in self.variants().iter().take(variant_index + 1) {
            (this, this_lit) = match v.discr {
                None => (
                    next,
                    if (next as i64) < 0 {
                        Literal::new_int(next as i64)
                    } else {
                        Literal::new_uint(next)
                    },
                ),
                Some(Literal::UInt(v, _, _)) => (v, Literal::new_uint(v)),
                // in-practice, Literal::Int == a negative number.
                Some(Literal::Int(v, _, _)) => (v as u64, Literal::new_int(v)),
                _ => anyhow::bail!("Invalid literal type {v:?}"),
            };
            next = this.wrapping_add(1);
        }
        Ok(this_lit)
    }

    pub fn variant_discr_type(&self) -> &Option<Type> {
        &self.discr_type
    }

    pub fn is_flat(&self) -> bool {
        match self.shape {
            EnumShape::Error { flat } => flat,
            EnumShape::Enum => self.variants.iter().all(|v| v.fields.is_empty()),
        }
    }

    pub fn is_non_exhaustive(&self) -> bool {
        self.non_exhaustive
    }

    pub fn iter_types(&self) -> TypeIterator<'_> {
        Box::new(self.variants.iter().flat_map(Variant::iter_types))
    }

    pub fn docstring(&self) -> Option<&str> {
        self.docstring.as_deref()
    }
}

impl TryFrom<uniffi_meta::EnumMetadata> for Enum {
    type Error = anyhow::Error;

    fn try_from(meta: uniffi_meta::EnumMetadata) -> Result<Self> {
        Ok(Self {
            name: meta.name,
            module_path: meta.module_path,
            discr_type: meta.discr_type,
            variants: meta
                .variants
                .into_iter()
                .map(TryInto::try_into)
                .collect::<Result<_>>()?,
            shape: meta.shape,
            non_exhaustive: meta.non_exhaustive,
            docstring: meta.docstring.clone(),
        })
    }
}

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) discr: Option<Literal>,
    pub(super) fields: Vec<Field>,
    #[checksum_ignore]
    pub(super) docstring: Option<String>,
}

impl Variant {
    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn rename(&mut self, new_name: String) {
        self.name = new_name;
    }

    pub fn fields(&self) -> &[Field] {
        &self.fields
    }

    pub fn has_fields(&self) -> bool {
        !self.fields.is_empty()
    }

    pub fn has_nameless_fields(&self) -> bool {
        self.fields.iter().any(|f| f.name.is_empty())
    }

    pub fn docstring(&self) -> Option<&str> {
        self.docstring.as_deref()
    }

    pub fn iter_types(&self) -> TypeIterator<'_> {
        Box::new(self.fields.iter().flat_map(Field::iter_types))
    }
}

impl TryFrom<uniffi_meta::VariantMetadata> for Variant {
    type Error = anyhow::Error;

    fn try_from(meta: uniffi_meta::VariantMetadata) -> Result<Self> {
        Ok(Self {
            name: meta.name,
            discr: meta.discr,
            fields: meta
                .fields
                .into_iter()
                .map(TryInto::try_into)
                .collect::<Result<_>>()?,
            docstring: meta.docstring.clone(),
        })
    }
}

#[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<_>>(),
            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.shape, EnumShape::Enum);
        assert_eq!(ed.variants().len(), 3);
        assert_eq!(
            ed.variants().iter().map(|v| v.name()).collect::<Vec<_>>(),
            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<_>>(),
            vec!["first"]
        );
        assert_eq!(
            ed.variants()[1]
                .fields()
                .iter()
                .map(|f| f.as_type())
                .collect::<Vec<_>>(),
            vec![Type::UInt32]
        );
        assert_eq!(
            ed.variants()[2]
                .fields()
                .iter()
                .map(|f| f.name())
                .collect::<Vec<_>>(),
            vec!["first", "second"]
        );
        assert_eq!(
            ed.variants()[2]
                .fields()
                .iter()
                .map(|f| f.as_type())
                .collect::<Vec<_>>(),
            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<_>>(),
            vec!["One", "Two"]
        );
        assert_eq!(ewd.variants()[0].fields().len(), 0);
        assert_eq!(ewd.variants()[1].fields().len(), 0);
        assert!(ewd.is_flat());
        assert_eq!(ewd.shape, EnumShape::Enum);

        // 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{
                [Throws=Testing]
                void func();
            };
            [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<&str>>(),
            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{
                [Throws=Testing]
                void func();
            };

            [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<&str>>(),
            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<_>>(),
            vec!["first"]
        );
        assert_eq!(
            testing.variants()[0]
                .fields()
                .iter()
                .map(|f| f.as_type())
                .collect::<Vec<_>>(),
            vec![Type::String]
        );
        assert_eq!(
            testing.variants()[1]
                .fields()
                .iter()
                .map(|f| f.name())
                .collect::<Vec<_>>(),
            vec!["first"]
        );
        assert_eq!(
            testing.variants()[1]
                .fields()
                .iter()
                .map(|f| f.as_type())
                .collect::<Vec<_>>(),
            vec![Type::String]
        );
        assert_eq!(
            testing
                .variants()
                .iter()
                .map(|v| v.name())
                .collect::<Vec<_>>(),
            vec!["Normal", "Error"]
        );
    }

    fn variant(val: Option<u64>) -> Variant {
        Variant {
            name: "v".to_string(),
            discr: val.map(Literal::new_uint),
            fields: vec![],
            docstring: None,
        }
    }

    fn check_discrs(e: &mut Enum, vs: Vec<Variant>) -> Vec<u64> {
        e.variants = vs;
        (0..e.variants.len())
            .map(|i| e.variant_discr(i).unwrap())
            .map(|l| match l {
                Literal::UInt(v, _, _) => v,
                _ => unreachable!(),
            })
            .collect()
    }

    #[test]
    fn test_variant_values() {
        let mut e = Enum {
            module_path: "test".to_string(),
            name: "test".to_string(),
            discr_type: None,
            variants: vec![],
            shape: EnumShape::Enum,
            non_exhaustive: false,
            docstring: None,
        };

        assert!(e.variant_discr(0).is_err());

        // single values
        assert_eq!(check_discrs(&mut e, vec![variant(None)]), vec![0]);
        assert_eq!(check_discrs(&mut e, vec![variant(Some(3))]), vec![3]);

        // no values
        assert_eq!(
            check_discrs(&mut e, vec![variant(None), variant(None)]),
            vec![0, 1]
        );

        // values
        assert_eq!(
            check_discrs(&mut e, vec![variant(Some(1)), variant(Some(3))]),
            vec![1, 3]
        );

        // mixed values
        assert_eq!(
            check_discrs(&mut e, vec![variant(None), variant(Some(3)), variant(None)]),
            vec![0, 3, 4]
        );

        assert_eq!(
            check_discrs(
                &mut e,
                vec![variant(Some(4)), variant(None), variant(Some(1))]
            ),
            vec![4, 5, 1]
        );
    }

    #[test]
    fn test_docstring_enum() {
        const UDL: &str = r#"
            namespace test{};
            /// informative docstring
            enum Testing { "foo" };
        "#;
        let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
        assert_eq!(
            ci.get_enum_definition("Testing")
                .unwrap()
                .docstring()
                .unwrap(),
            "informative docstring"
        );
    }

    #[test]
    fn test_docstring_enum_variant() {
        const UDL: &str = r#"
            namespace test{};
            enum Testing {
                /// informative docstring
                "foo"
            };
        "#;
        let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
        assert_eq!(
            ci.get_enum_definition("Testing").unwrap().variants()[0]
                .docstring()
                .unwrap(),
            "informative docstring"
        );
    }

    #[test]
    fn test_docstring_associated_enum() {
        const UDL: &str = r#"
            namespace test{};
            /// informative docstring
            [Enum]
            interface Testing { };
        "#;
        let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
        assert_eq!(
            ci.get_enum_definition("Testing")
                .unwrap()
                .docstring()
                .unwrap(),
            "informative docstring"
        );
    }

    #[test]
    fn test_docstring_associated_enum_variant() {
        const UDL: &str = r#"
            namespace test{};
            [Enum]
            interface Testing {
                /// informative docstring
                testing();
            };
        "#;
        let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
        assert_eq!(
            ci.get_enum_definition("Testing").unwrap().variants()[0]
                .docstring()
                .unwrap(),
            "informative docstring"
        );
    }
}

[ Dauer der Verarbeitung: 0.33 Sekunden  (vorverarbeitet)  ]