diff --git a/core/src/ast/lifetimes.rs b/core/src/ast/lifetimes.rs index 6e41392a7..07bd352df 100644 --- a/core/src/ast/lifetimes.rs +++ b/core/src/ast/lifetimes.rs @@ -155,6 +155,15 @@ impl LifetimeEnv { LifetimeEnv::new() } + pub fn from_enum_item(enm: &syn::ItemEnum, variant_fields: &[(Option, TypeName, Docs, Attrs)]) -> Self { + let mut this = LifetimeEnv::new(); + this.extend_generics(&enm.generics); + for (_, typ, _, _) in variant_fields { + this.extend_implicit_lifetime_bounds(typ, None); + } + this + } + /// Returns a [`LifetimeEnv`] for a struct, accounding for lifetimes and bounds /// defined in the struct generics, as well as implicit lifetime bounds in /// the struct's fields. For example, the field `&'a Foo<'b>` implies `'b: 'a`. diff --git a/core/src/ast/modules.rs b/core/src/ast/modules.rs index 087992924..91cf402bc 100644 --- a/core/src/ast/modules.rs +++ b/core/src/ast/modules.rs @@ -17,13 +17,20 @@ enum DiplomatStructAttribute { /// The `#[diplomat::out]` attribute, used for non-opaque structs that /// contain an owned opaque in the form of a `Box`. Out, - /// The `#[diplomat::opaque]` attribute, used for marking a struct as opaque. + /// An attribute that can correspond to a type (struct or enum). + TypeAttr(DiplomatTypeAttribute) +} + +/// Custom Diplomat attribute that can be placed on an enum or struct definition. +#[derive(Debug)] +enum DiplomatTypeAttribute { + /// The `#[diplomat::opaque]` attribute, used for marking a type as opaque. /// Note that opaque structs can be borrowed in return types, but cannot /// be passed into a function behind a mutable reference. Opaque, - /// The `#[diplomat::opaque_mut]` attribute, used for marking a struct as + /// The `#[diplomat::opaque_mut]` attribute, used for marking a type as /// opaque and mutable. - /// Note that mutable opaque structs can never be borrowed in return types + /// Note that mutable opaque types can never be borrowed in return types /// (even immutably!), but can be passed into a function behind a mutable /// reference. OpaqueMut, @@ -41,6 +48,35 @@ impl DiplomatStructAttribute { write!(&mut buf, "{}", attr.path().to_token_stream()).unwrap(); let parsed = match buf.as_str() { "diplomat :: out" => Some(Self::Out), + "diplomat :: opaque" => Some(Self::TypeAttr(DiplomatTypeAttribute::Opaque)), + "diplomat :: opaque_mut" => Some(Self::TypeAttr(DiplomatTypeAttribute::OpaqueMut)), + _ => None, + }; + + if let Some(parsed) = parsed { + match res { + Ok(None) => res = Ok(Some(parsed)), + Ok(Some(first)) => res = Err(vec![first, parsed]), + Err(ref mut errors) => errors.push(parsed), + } + } + } + + res + } +} + +impl DiplomatTypeAttribute { + /// Parses a [`DiplomatTypeAttribute`] from an array of [`syn::Attribute`]s. + /// If more than one kind is found, an error is returned containing all the + /// ones encountered, since all the current attributes are disjoint. + fn parse(attrs: &[syn::Attribute]) -> Result, Vec> { + let mut buf = String::with_capacity(32); + let mut res = Ok(None); + for attr in attrs { + buf.clear(); + write!(&mut buf, "{}", attr.path().to_token_stream()).unwrap(); + let parsed = match buf.as_str() { "diplomat :: opaque" => Some(Self::Opaque), "diplomat :: opaque_mut" => Some(Self::OpaqueMut), _ => None, @@ -155,11 +191,11 @@ impl Module { Ok(Some(DiplomatStructAttribute::Out)) => { CustomType::Struct(Struct::new(strct, true, &type_parent_attrs)) } - Ok(Some(DiplomatStructAttribute::Opaque)) => { - CustomType::Opaque(OpaqueStruct::new(strct, Mutability::Immutable, &type_parent_attrs)) + Ok(Some(DiplomatStructAttribute::TypeAttr(DiplomatTypeAttribute::Opaque))) => { + CustomType::Opaque(OpaqueStruct::new_struct(strct, Mutability::Immutable, &type_parent_attrs)) } - Ok(Some(DiplomatStructAttribute::OpaqueMut)) => { - CustomType::Opaque(OpaqueStruct::new(strct, Mutability::Mutable, &type_parent_attrs)) + Ok(Some(DiplomatStructAttribute::TypeAttr(DiplomatTypeAttribute::OpaqueMut))) => { + CustomType::Opaque(OpaqueStruct::new_struct(strct, Mutability::Mutable, &type_parent_attrs)) } Err(errors) => { panic!("Multiple conflicting Diplomat struct attributes, there can be at most one: {errors:?}"); @@ -173,9 +209,20 @@ impl Module { Item::Enum(enm) => { if analyze_types { let ident = (&enm.ident).into(); - let enm = Enum::new(enm, &type_parent_attrs); + let custom_enum = match DiplomatTypeAttribute::parse(&enm.attrs[..]) { + Ok(None) => CustomType::Enum(Enum::new(enm, &type_parent_attrs)), + Ok(Some(DiplomatTypeAttribute::Opaque)) => { + CustomType::Opaque(OpaqueStruct::new_enum(enm, Mutability::Immutable, &type_parent_attrs)) + } + Ok(Some(DiplomatTypeAttribute::OpaqueMut)) => { + CustomType::Opaque(OpaqueStruct::new_enum(enm, Mutability::Mutable, &type_parent_attrs)) + } + Err(errors) => { + panic!("Multiple conflicting Diplomat enum attributes, there can be at most one: {errors:?}"); + } + }; custom_types_by_name - .insert(ident, CustomType::Enum(enm)); + .insert(ident, custom_enum); } } diff --git a/core/src/ast/structs.rs b/core/src/ast/structs.rs index ab0681a47..6abc363ca 100644 --- a/core/src/ast/structs.rs +++ b/core/src/ast/structs.rs @@ -52,8 +52,8 @@ impl Struct { } } -/// A struct annotated with [`diplomat::opaque`] whose fields are not visible. -/// Opaque structs cannot be passed by-value across the FFI boundary, so they +/// A type annotated with [`diplomat::opaque`] whose fields/variants are not visible. +/// Opaque types cannot be passed by-value across the FFI boundary, so they /// must be boxed or passed as references. #[derive(Clone, Serialize, Debug, Hash, PartialEq, Eq)] #[non_exhaustive] @@ -69,8 +69,8 @@ pub struct OpaqueStruct { } impl OpaqueStruct { - /// Extract a [`OpaqueStruct`] metadata value from an AST node. - pub fn new(strct: &syn::ItemStruct, mutability: Mutability, parent_attrs: &Attrs) -> Self { + /// Extract a [`OpaqueStruct`] metadata value from an AST node representing a struct. + pub fn new_struct(strct: &syn::ItemStruct, mutability: Mutability, parent_attrs: &Attrs) -> Self { let mut attrs = parent_attrs.clone(); attrs.add_attrs(&strct.attrs); let name = Ident::from(&strct.ident); @@ -87,6 +87,24 @@ impl OpaqueStruct { dtor_abi_name, } } + + pub fn new_enum(enm: &syn::ItemEnum, mutability: Mutability, parent_attrs: &Attrs) -> Self { + let mut attrs = parent_attrs.clone(); + attrs.add_attrs(&enm.attrs); + let name = Ident::from(&enm.ident); + let dtor_abi_name = format!("{}_destroy", name); + let dtor_abi_name = String::from(attrs.abi_rename.apply(dtor_abi_name.into())); + let dtor_abi_name = Ident::from(dtor_abi_name); + OpaqueStruct { + name, + docs: Docs::from_attrs(&enm.attrs), + lifetimes: LifetimeEnv::from_enum_item(enm, &[]), + methods: vec![], + mutability, + attrs, + dtor_abi_name, + } + } } #[cfg(test)] diff --git a/core/src/hir/lowering.rs b/core/src/hir/lowering.rs index f312eb0cc..efe250de0 100644 --- a/core/src/hir/lowering.rs +++ b/core/src/hir/lowering.rs @@ -416,7 +416,7 @@ impl<'ast> LoweringContext<'ast> { }; let lifetimes = self.lower_type_lifetime_env(&ast_trait.lifetimes); let def = TraitDef::new(ast_trait.docs.clone(), trait_name, fcts, attrs, lifetimes?); - + self.attr_validator .validate(&def.attrs, AttributeContext::Trait(&def), &mut self.errors); Ok(def)