Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow opaque enums #743

Merged
merged 4 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions core/src/ast/lifetimes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,18 @@ impl LifetimeEnv {
LifetimeEnv::new()
}

pub fn from_enum_item(
enm: &syn::ItemEnum,
variant_fields: &[(Option<Ident>, 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`.
Expand Down
5 changes: 4 additions & 1 deletion core/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ mod modules;
pub use modules::{File, Module};

mod structs;
pub use structs::{OpaqueStruct, Struct};
pub use structs::Struct;

mod opaque;
pub use opaque::OpaqueType;

mod traits;
pub use traits::{Trait, TraitMethod};
Expand Down
69 changes: 58 additions & 11 deletions core/src/ast/modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use serde::Serialize;
use syn::{ImplItem, Item, ItemMod, UseTree, Visibility};

use super::{
AttrInheritContext, Attrs, CustomType, Enum, Ident, Method, ModSymbol, Mutability,
OpaqueStruct, Path, PathType, RustLink, Struct, Trait,
AttrInheritContext, Attrs, CustomType, Enum, Ident, Method, ModSymbol, Mutability, OpaqueType,
Path, PathType, RustLink, Struct, Trait,
};
use crate::environment::*;

Expand All @@ -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,
Expand All @@ -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<Option<Self>, Vec<Self>> {
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,
Expand Down Expand Up @@ -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(OpaqueType::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(OpaqueType::new_struct(strct, Mutability::Mutable, &type_parent_attrs))
}
Err(errors) => {
panic!("Multiple conflicting Diplomat struct attributes, there can be at most one: {errors:?}");
Expand All @@ -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(OpaqueType::new_enum(enm, Mutability::Immutable, &type_parent_attrs))
}
Ok(Some(DiplomatTypeAttribute::OpaqueMut)) => {
CustomType::Opaque(OpaqueType::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);
}
}

Expand Down
64 changes: 64 additions & 0 deletions core/src/ast/opaque.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use serde::Serialize;

use super::docs::Docs;
use super::{Attrs, Ident, LifetimeEnv, Method, Mutability};

/// 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]
pub struct OpaqueType {
pub name: Ident,
pub docs: Docs,
pub lifetimes: LifetimeEnv,
pub methods: Vec<Method>,
pub mutability: Mutability,
pub attrs: Attrs,
/// The ABI name of the generated destructor
pub dtor_abi_name: Ident,
}

impl OpaqueType {
/// Extract a [`OpaqueType`] 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);
OpaqueType {
dtor_abi_name: Self::dtor_abi_name(&name, &attrs),
name,
docs: Docs::from_attrs(&strct.attrs),
lifetimes: LifetimeEnv::from_struct_item(strct, &[]),
methods: vec![],
mutability,
attrs,
}
}

/// Extract a [`OpaqueType`] metadata value from an AST node representing an enum.
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);
OpaqueType {
dtor_abi_name: Self::dtor_abi_name(&name, &attrs),
name,
docs: Docs::from_attrs(&enm.attrs),
lifetimes: LifetimeEnv::from_enum_item(enm, &[]),
methods: vec![],
mutability,
attrs,
}
}

fn dtor_abi_name(name: &Ident, attrs: &Attrs) -> Ident {
let dtor_abi_name = format!("{}_destroy", name);
let dtor_abi_name = String::from(attrs.abi_rename.apply(dtor_abi_name.into()));
Ident::from(dtor_abi_name)
}
}
39 changes: 1 addition & 38 deletions core/src/ast/structs.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use serde::Serialize;

use super::docs::Docs;
use super::{Attrs, Ident, LifetimeEnv, Method, Mutability, PathType, TypeName};
use super::{Attrs, Ident, LifetimeEnv, Method, PathType, TypeName};

/// A struct declaration in an FFI module that is not opaque.
#[derive(Clone, PartialEq, Eq, Hash, Serialize, Debug)]
Expand Down Expand Up @@ -52,43 +52,6 @@ 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
/// must be boxed or passed as references.
#[derive(Clone, Serialize, Debug, Hash, PartialEq, Eq)]
#[non_exhaustive]
pub struct OpaqueStruct {
pub name: Ident,
pub docs: Docs,
pub lifetimes: LifetimeEnv,
pub methods: Vec<Method>,
pub mutability: Mutability,
pub attrs: Attrs,
/// The ABI name of the generated destructor
pub dtor_abi_name: Ident,
}

impl OpaqueStruct {
/// Extract a [`OpaqueStruct`] metadata value from an AST node.
pub fn new(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);
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(&strct.attrs),
lifetimes: LifetimeEnv::from_struct_item(strct, &[]),
methods: vec![],
mutability,
attrs,
dtor_abi_name,
}
}
}

#[cfg(test)]
mod tests {
use insta::{self, Settings};
Expand Down
6 changes: 3 additions & 3 deletions core/src/ast/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::str::FromStr;

use super::{
Attrs, Docs, Enum, Ident, Lifetime, LifetimeEnv, LifetimeTransitivity, Method, NamedLifetime,
OpaqueStruct, Path, RustLink, Struct, Trait,
OpaqueType, Path, RustLink, Struct, Trait,
};
use crate::Env;

Expand All @@ -19,8 +19,8 @@ use crate::Env;
pub enum CustomType {
/// A non-opaque struct whose fields will be visible across the FFI boundary.
Struct(Struct),
/// A struct annotated with [`diplomat::opaque`] whose fields are not visible.
Opaque(OpaqueStruct),
/// A type annotated with [`diplomat::opaque`] whose fields are not visible.
Opaque(OpaqueType),
/// A fieldless enum.
Enum(Enum),
}
Expand Down
7 changes: 2 additions & 5 deletions core/src/hir/lowering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ impl<'ast> LoweringContext<'ast> {
}
pub(super) fn lower_all_opaques(
&mut self,
ast_defs: impl ExactSizeIterator<Item = ItemAndInfo<'ast, ast::OpaqueStruct>>,
ast_defs: impl ExactSizeIterator<Item = ItemAndInfo<'ast, ast::OpaqueType>>,
) -> Result<Vec<OpaqueDef>, ()> {
self.lower_all(ast_defs, Self::lower_opaque)
}
Expand Down Expand Up @@ -257,10 +257,7 @@ impl<'ast> LoweringContext<'ast> {
Ok(def)
}

fn lower_opaque(
&mut self,
item: ItemAndInfo<'ast, ast::OpaqueStruct>,
) -> Result<OpaqueDef, ()> {
fn lower_opaque(&mut self, item: ItemAndInfo<'ast, ast::OpaqueType>) -> Result<OpaqueDef, ()> {
let ast_opaque = item.item;
self.errors.set_item(ast_opaque.name.as_str());
let name = self.lower_ident(&ast_opaque.name, "opaque name");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
---
source: core/src/hir/type_context.rs
assertion_line: 691
expression: output
---
Lowering error in MyOpaqueEnum::new_broken: Opaque passed by value in input: MyOpaqueEnum
Lowering error in MyOpaqueEnum::do_thing_broken: Method `MyOpaqueEnum_do_thing_broken` takes an opaque by value as the self parameter, but opaques as inputs must be behind refs
Lowering error in MyOpaqueStruct::new_broken: Opaque passed by value in input: MyOpaqueStruct
Lowering error in MyOpaqueStruct::do_thing_broken: Method `MyOpaqueStruct_do_thing_broken` takes an opaque by value as the self parameter, but opaques as inputs must be behind refs

21 changes: 18 additions & 3 deletions core/src/hir/type_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ impl TypeContext {
pub(super) struct LookupId<'ast> {
out_struct_map: HashMap<&'ast ast::Struct, OutStructId>,
struct_map: HashMap<&'ast ast::Struct, StructId>,
opaque_map: HashMap<&'ast ast::OpaqueStruct, OpaqueId>,
opaque_map: HashMap<&'ast ast::OpaqueType, OpaqueId>,
enum_map: HashMap<&'ast ast::Enum, EnumId>,
trait_map: HashMap<&'ast ast::Trait, TraitId>,
}
Expand All @@ -469,7 +469,7 @@ impl<'ast> LookupId<'ast> {
fn new(
out_structs: &[ItemAndInfo<'ast, ast::Struct>],
structs: &[ItemAndInfo<'ast, ast::Struct>],
opaques: &[ItemAndInfo<'ast, ast::OpaqueStruct>],
opaques: &[ItemAndInfo<'ast, ast::OpaqueType>],
enums: &[ItemAndInfo<'ast, ast::Enum>],
traits: &[ItemAndInfo<'ast, ast::Trait>],
) -> Self {
Expand Down Expand Up @@ -510,7 +510,7 @@ impl<'ast> LookupId<'ast> {
self.struct_map.get(strct).copied()
}

pub(super) fn resolve_opaque(&self, opaque: &ast::OpaqueStruct) -> Option<OpaqueId> {
pub(super) fn resolve_opaque(&self, opaque: &ast::OpaqueType) -> Option<OpaqueId> {
self.opaque_map.get(opaque).copied()
}

Expand Down Expand Up @@ -701,6 +701,21 @@ mod tests {
pub fn do_thing_broken(self) {}
pub fn broken_differently(&self, x: &MyOpaqueStruct) {}
}

#[diplomat::opaque]
enum MyOpaqueEnum {
A(UnknownType),
B,
C(i32, i32, UnknownType2),
}

impl MyOpaqueEnum {
pub fn new() -> Box<MyOpaqueEnum> {}
pub fn new_broken() -> MyOpaqueEnum {}
pub fn do_thing(&self) {}
pub fn do_thing_broken(self) {}
pub fn broken_differently(&self, x: &MyOpaqueEnum) {}
}
}
}
}
Expand Down
19 changes: 19 additions & 0 deletions feature_tests/c/include/MyOpaqueEnum.d.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading