Skip to content

Commit

Permalink
Rust trait support in the AST/HIR/macro, and codegen for C (#621)
Browse files Browse the repository at this point in the history
  • Loading branch information
emarteca authored Sep 10, 2024
1 parent d741c84 commit 9dcee14
Show file tree
Hide file tree
Showing 37 changed files with 1,402 additions and 106 deletions.
34 changes: 33 additions & 1 deletion core/src/ast/lifetimes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use quote::{quote, ToTokens};
use serde::{Deserialize, Serialize};
use std::fmt;

use super::{Attrs, Docs, Ident, Param, SelfParam, TypeName};
use super::{Attrs, Docs, Ident, Param, SelfParam, TraitSelfParam, TypeName};

/// A named lifetime, e.g. `'a`.
///
Expand Down Expand Up @@ -123,6 +123,38 @@ impl LifetimeEnv {
this
}

pub fn from_trait_item(
trait_fct_item: &syn::TraitItem,
self_param: Option<&TraitSelfParam>,
params: &[Param],
return_type: Option<&TypeName>,
) -> Self {
let mut this = LifetimeEnv::new();
if let syn::TraitItem::Fn(_) = trait_fct_item {
if let Some(self_param) = self_param {
this.extend_implicit_lifetime_bounds(&self_param.to_typename(), None);
}
for param in params {
this.extend_implicit_lifetime_bounds(&param.ty, None);
}
if let Some(return_type) = return_type {
this.extend_implicit_lifetime_bounds(return_type, None);
}
} else {
panic!(
"Diplomat traits can only have associated methods and no other associated items."
)
}
this
}

pub fn from_trait(trt: &syn::ItemTrait) -> Self {
if trt.generics.lifetimes().next().is_some() {
panic!("Diplomat traits are not allowed to have any lifetime parameters")
}
LifetimeEnv::new()
}

/// 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
34 changes: 33 additions & 1 deletion core/src/ast/methods.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use serde::Serialize;
use serde::{Deserialize, Serialize};
use std::ops::ControlFlow;

use super::docs::Docs;
Expand Down Expand Up @@ -255,6 +255,38 @@ impl SelfParam {
}
}

/// The `self` parameter taken by a [`TraitMethod`].
#[derive(Clone, PartialEq, Eq, Hash, Serialize, Debug, Deserialize)]
#[non_exhaustive]
pub struct TraitSelfParam {
/// The lifetime and mutability of the `self` param, if it's a reference.
pub reference: Option<(Lifetime, Mutability)>,

/// The trait of the parameter, which will be a named reference to
/// the associated trait,
pub path_trait: PathType,
}

impl TraitSelfParam {
pub fn to_typename(&self) -> TypeName {
let typ = TypeName::ImplTrait(self.path_trait.clone());
if let Some((ref lifetime, ref mutability)) = self.reference {
return TypeName::Reference(lifetime.clone(), *mutability, Box::new(typ));
}
typ
}

pub fn from_syn(rec: &syn::Receiver, path_trait: PathType) -> Self {
TraitSelfParam {
reference: rec
.reference
.as_ref()
.map(|(_, lt)| (lt.into(), Mutability::from_syn(&rec.mutability))),
path_trait,
}
}
}

/// A parameter taken by a [`Method`], not including `self`.
#[derive(Clone, PartialEq, Eq, Hash, Serialize, Debug)]
#[non_exhaustive]
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 @@ -7,14 +7,17 @@ pub(crate) use attrs::AttrInheritContext;
pub use attrs::Attrs;

mod methods;
pub use methods::{BorrowedParams, Method, Param, SelfParam};
pub use methods::{BorrowedParams, Method, Param, SelfParam, TraitSelfParam};

mod modules;
pub use modules::{File, Module};

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

mod traits;
pub use traits::{Trait, TraitMethod};

mod enums;
pub use enums::Enum;

Expand Down
22 changes: 21 additions & 1 deletion core/src/ast/modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use syn::{ImplItem, Item, ItemMod, UseTree, Visibility};

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

Expand Down Expand Up @@ -65,6 +65,7 @@ pub struct Module {
pub name: Ident,
pub imports: Vec<(Path, Ident)>,
pub declared_types: BTreeMap<Ident, CustomType>,
pub declared_traits: BTreeMap<Ident, Trait>,
pub sub_modules: Vec<Module>,
pub attrs: Attrs,
}
Expand Down Expand Up @@ -99,6 +100,15 @@ impl Module {
}
});

self.declared_traits.iter().for_each(|(k, v)| {
if mod_symbols
.insert(k.clone(), ModSymbol::Trait(v.clone()))
.is_some()
{
panic!("Two traits were declared with the same name, this needs to be implemented");
}
});

let path_to_self = in_path.sub_path(self.name.clone());
self.sub_modules.iter().for_each(|m| {
m.insert_all_types(path_to_self.clone(), out);
Expand All @@ -110,6 +120,7 @@ impl Module {

pub fn from_syn(input: &ItemMod, force_analyze: bool) -> Module {
let mut custom_types_by_name = BTreeMap::new();
let mut custom_traits_by_name = BTreeMap::new();
let mut sub_modules = Vec::new();
let mut imports = Vec::new();

Expand Down Expand Up @@ -206,13 +217,22 @@ impl Module {
Item::Mod(item_mod) => {
sub_modules.push(Module::from_syn(item_mod, false));
}
Item::Trait(trt) => {
if analyze_types {
let ident = (&trt.ident).into();
let trt = Trait::new(trt, &type_parent_attrs);
custom_traits_by_name
.insert(ident, trt);
}
}
_ => {}
});

Module {
name: (&input.ident).into(),
imports,
declared_types: custom_types_by_name,
declared_traits: custom_traits_by_name,
sub_modules,
attrs: mod_attrs,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ modules:
methods: []
output_only: false
attrs: {}
declared_traits: {}
sub_modules: []
attrs: {}
other:
name: other
imports: []
declared_types: {}
declared_traits: {}
sub_modules: []
attrs: {}

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
source: core/src/ast/modules.rs
expression: "Module::from_syn(&syn::parse_quote! {\n #[diplomat :: bridge] mod ffi\n {\n struct Foo {} impl Foo\n {\n pub fn pub_fn() { unimplemented! () } pub(crate) fn\n pub_crate_fn() { unimplemented! () } pub(super) fn\n pub_super_fn() { unimplemented! () } fn priv_fn()\n { unimplemented! () }\n }\n }\n }, true)"
assertion_line: 371
expression: "Module::from_syn(&syn::parse_quote! {\n #[diplomat::bridge] mod ffi\n {\n struct Foo {} impl Foo\n {\n pub fn pub_fn() { unimplemented!() } pub(crate) fn\n pub_crate_fn() { unimplemented!() } pub(super) fn\n pub_super_fn() { unimplemented!() } fn priv_fn()\n { unimplemented!() }\n }\n }\n }, true)"
---
name: ffi
imports: []
Expand All @@ -26,6 +27,6 @@ declared_types:
attrs: {}
output_only: false
attrs: {}
declared_traits: {}
sub_modules: []
attrs: {}

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
source: core/src/ast/modules.rs
assertion_line: 326
expression: "Module::from_syn(&syn::parse_quote! {\n mod ffi\n {\n struct NonOpaqueStruct { a: i32, b: Box<NonOpaqueStruct> }\n impl NonOpaqueStruct\n {\n pub fn new(x: i32) -> NonOpaqueStruct { unimplemented!(); }\n pub fn set_a(&mut self, new_a: i32) { self.a = new_a; }\n } #[diplomat::opaque] struct OpaqueStruct\n { a: SomeExternalType } impl OpaqueStruct\n {\n pub fn new() -> Box<OpaqueStruct> { unimplemented!(); } pub\n fn get_string(&self) -> String { unimplemented!() }\n }\n }\n }, true)"
---
name: ffi
Expand Down Expand Up @@ -124,5 +125,6 @@ declared_types:
mutability: Immutable
attrs: {}
dtor_abi_name: OpaqueStruct_destroy
declared_traits: {}
sub_modules: []
attrs: {}
115 changes: 115 additions & 0 deletions core/src/ast/traits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
use serde::Serialize;

use super::docs::Docs;
use super::{Attrs, Ident, LifetimeEnv, Param, PathType, TraitSelfParam, TypeName};

/// A trait declaration in an FFI module.
#[derive(Clone, PartialEq, Eq, Hash, Serialize, Debug)]
#[non_exhaustive]
pub struct Trait {
pub name: Ident,
pub lifetimes: LifetimeEnv,
pub methods: Vec<TraitMethod>,
pub docs: Docs,
pub attrs: Attrs,
}

#[derive(Clone, PartialEq, Eq, Hash, Serialize, Debug)]
#[non_exhaustive]
pub struct TraitMethod {
pub name: Ident,
pub abi_name: Ident,
pub self_param: Option<TraitSelfParam>,
// corresponds to the types in Function(Vec<Box<TypeName>>, Box<TypeName>)
// the callback type; except here the params aren't anonymous
pub params: Vec<Param>,
pub output_type: Option<TypeName>,
pub lifetimes: LifetimeEnv,
pub attrs: Attrs,
}

impl Trait {
/// Extract a [`Trait`] metadata value from an AST node.
pub fn new(trt: &syn::ItemTrait, parent_attrs: &Attrs) -> Self {
let mut attrs = parent_attrs.clone();
attrs.add_attrs(&trt.attrs);

let mut trait_fcts = Vec::new();

let self_ident = &trt.ident;
// TODO check this
let self_path_trait = PathType::from(&syn::TraitBound {
paren_token: None,
modifier: syn::TraitBoundModifier::None,
lifetimes: None, // todo this is an assumption
path: syn::PathSegment {
ident: self_ident.clone(),
arguments: syn::PathArguments::None,
}
.into(),
});
for trait_item in trt.items.iter() {
if let syn::TraitItem::Fn(fct) = trait_item {
let mut fct_attrs = attrs.clone();
fct_attrs.add_attrs(&fct.attrs);
// copied from the method parsing
let fct_ident = &fct.sig.ident;
let concat_fct_ident = format!("{self_ident}_{fct_ident}");
let extern_ident = syn::Ident::new(
&attrs.abi_rename.apply(concat_fct_ident.into()),
fct.sig.ident.span(),
);

let all_params = fct
.sig
.inputs
.iter()
.filter_map(|a| match a {
syn::FnArg::Receiver(_) => None,
syn::FnArg::Typed(ref t) => {
Some(Param::from_syn(t, self_path_trait.clone()))
}
})
.collect::<Vec<_>>();

let self_param = fct
.sig
.receiver()
.map(|rec| TraitSelfParam::from_syn(rec, self_path_trait.clone()));

let output_type = match &fct.sig.output {
syn::ReturnType::Type(_, return_typ) => Some(TypeName::from_syn(
return_typ.as_ref(),
Some(self_path_trait.clone()),
)),
syn::ReturnType::Default => None,
};

let lifetimes = LifetimeEnv::from_trait_item(
trait_item,
self_param.as_ref(),
&all_params[..],
output_type.as_ref(),
);

trait_fcts.push(TraitMethod {
name: fct_ident.into(),
abi_name: (&extern_ident).into(),
self_param,
params: all_params,
output_type,
lifetimes,
attrs: fct_attrs,
});
}
}

Self {
name: (&trt.ident).into(),
methods: trait_fcts,
docs: Docs::from_attrs(&trt.attrs),
lifetimes: LifetimeEnv::from_trait(trt), // TODO
attrs,
}
}
}
Loading

0 comments on commit 9dcee14

Please sign in to comment.