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

capnp-conv: proc macro ergonomics for capnproto-rust #157

Open
wants to merge 51 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
8c8f1c7
Initial outline for offst-capnp-conv.
realcr Jun 28, 2019
dfc3e3f
Initial work on capnp_conv_derive. Created basic outline.
realcr Jun 29, 2019
1fd0e8d
Some work on capnp named struct conversion. Not done yet.
realcr Jun 29, 2019
410062f
Renamed functions: into -> write, from -> read.
realcr Jun 29, 2019
3ee502d
Some work on offst-capnp-conv. Currently can serialize basic capnp st…
realcr Jun 29, 2019
5cc1036
Added String support for capnp conversion.
realcr Jun 29, 2019
c67bc99
Added nested struct support for capnp serialization.
realcr Jun 29, 2019
84c72fc
offst-capnp-conv: Added support for Data
realcr Jun 29, 2019
503007f
Removed unused comment.
realcr Jun 29, 2019
7297d70
capnp_conv: Began working on List case. Not working yet.
realcr Jun 29, 2019
c46a20b
Initial implementation for list (primitive + non-primitive) serializa…
realcr Jul 2, 2019
c8ee7c5
Added IntoCapnpBytes and FromCapnpBytes traits
realcr Jul 2, 2019
2f0cb1b
Taking a reference instead of consuming self in to_capnp_bytes().
realcr Jul 2, 2019
7b28927
Removed unused comments.
realcr Jul 2, 2019
b712124
Removed allow(unused) hint.
realcr Jul 2, 2019
11bef77
Removed old comment.
realcr Jul 2, 2019
9720117
Added comments.
realcr Jul 2, 2019
c369eac
Replaced unwrap() with error handling.
realcr Jul 2, 2019
21888c7
Refactored capnp-conv-derive to separate files.
realcr Jul 2, 2019
43ca913
capnp-conv-derive: Initial implementation of Enum support.
realcr Jul 2, 2019
280603d
Added test for external capnp union.
realcr Jul 2, 2019
be1e692
offst-capnp-conv: Added support for lists inside unions.
realcr Jul 2, 2019
d569a11
Added support for floats in capnp conversion.
realcr Jul 2, 2019
56df693
Calling methods correctly inside macros.
realcr Jul 3, 2019
8883047
Changed CapnpResult struct to be private.
realcr Jul 3, 2019
b3f3cd1
capnp-conv-derive: Removed dependency on std::convert::TryFrom
realcr Jul 3, 2019
d84de53
Removed unused comment.
realcr Jul 3, 2019
af43e7c
offst-capnp-conv: Fixed case of Vec<u8> inside an Enum variant.
realcr Jul 9, 2019
129a586
Initial implementation of default args replacement.
realcr Jul 9, 2019
b524936
Extended capnp-conv generic derive test.
realcr Jul 9, 2019
b6533a4
offst-capnp-conv: Added generic enums support.
realcr Jul 9, 2019
d2d8389
offst-capnp-conv: Fixed bug in generic structs.
realcr Jul 9, 2019
d57bb38
Fixed proc macro bug in offst-capnp-conv.
realcr Jul 9, 2019
24bee50
offst-capnp-conv: Fixed bug in case of inline nested enum.
realcr Jul 10, 2019
0ccaf2f
offst-capnp-conv: Fixed bug in case of a String inside an Enum.
realcr Jul 10, 2019
6d12a50
capnp-conv + proto: Serialization should not fail.
realcr Jul 10, 2019
e41a55f
offst-capnp-conv: Some work on with attributes for structs. Not done …
realcr Jul 11, 2019
953ccaf
offst-capnp-conv: Added support for 'with' attributes in named struct…
realcr Jul 13, 2019
a6ba42b
offst-capnp-conv: Added 'with' attributes support to Enums.
realcr Jul 13, 2019
d56d721
Renamed get_list -> get_vec.
realcr Jul 13, 2019
ee94ab5
Fixed bug using 'with' attribute over a Variant(Tuple).
realcr Jul 13, 2019
9bf3cbe
offst-capnp-conv: Added hints to ignore clippy warnings.
realcr Jul 16, 2019
53c427f
capnp_conv: Removed old async_await and await_macro features.
realcr Sep 18, 2019
b80e691
Began porting to stable toolchain. offst-common compiles.
realcr Nov 13, 2019
aae23a8
Some clippy fixes. Not done yet.
realcr Dec 19, 2019
68a3105
Collected into capnp-conv
realcr Jan 15, 2020
fb2099e
Merge branch 'src_repo'
realcr Jan 15, 2020
2385db1
Updated .gitignore to ignore any target directories
realcr Jan 15, 2020
886779a
capnp-conv: Some fixes after migration from offst
realcr Jan 15, 2020
6783a04
capnp-conv: Make dependencies point to local repository
realcr Jan 15, 2020
6361895
capnp-conv: Update authors
realcr Jan 15, 2020
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@

# Generated by Cargo
Cargo.lock
/target/
target/

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"capnpc",
"capnp-futures",
"capnp-rpc",
"capnp-conv",

# testing and examples
"benchmark",
Expand All @@ -20,4 +21,5 @@ members = [
default-members = [
"capnp",
"capnpc",
]
"capnp-conv",
]
20 changes: 20 additions & 0 deletions capnp-conv/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "capnp-conv"
version = "0.11.0"
authors = [ "David Renshaw <[email protected]>", "real <[email protected]>"]
license = "MIT"
edition = "2018"

[dependencies]

capnp_conv_derive = { path = "capnp-conv-derive", version = "0.1.0", package = "capnp-conv-derive" }

capnp = { path = "../capnp", package = "capnp" }
derive_more = "0.15.0"

[build-dependencies]
capnpc = { path = "../capnpc", package = "capnpc" }

[dev-dependencies]

byteorder = {version = "1.3.2", features = ["i128"]}
7 changes: 7 additions & 0 deletions capnp-conv/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
fn main() {
capnpc::CompilerCommand::new()
.src_prefix("tests/")
.file("tests/capnp/test.capnp")
.run()
.unwrap();
}
17 changes: 17 additions & 0 deletions capnp-conv/capnp-conv-derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "capnp-conv-derive"
version = "0.1.0"
authors = [ "David Renshaw <[email protected]>", "real <[email protected]>"]
license = "MIT"
edition = "2018"

[lib]
proc-macro = true

[dependencies]

quote = "0.6.12"
syn = "0.15.38"
proc-macro2 = "0.4"
heck = "0.3.1"

277 changes: 277 additions & 0 deletions capnp-conv/capnp-conv-derive/src/derive_enum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::spanned::Spanned;
// use syn::{parse_macro_input, Data, DeriveInput, Fields, Ident, Index};
use syn::{DataEnum, Fields, Ident, Path, Variant};

use heck::SnakeCase;

use crate::util::{
capnp_result_shim, gen_list_read_iter, gen_list_write_iter, get_vec, is_data, is_primitive,
usize_to_u32_shim, CapnpWithAttribute,
};

// TODO: Deal with the case of multiple with attributes (Should report error)
/// Get the path from a with style field attribute.
/// Example:
/// ```text
/// #[capnp_conv(with = Wrapper<u128>)]
/// ```
/// Will return the path `Wrapper<u128>`
fn get_with_attribute(variant: &syn::Variant) -> Option<syn::Path> {
for attr in &variant.attrs {
if attr.path.is_ident("capnp_conv") {
let tts: proc_macro::TokenStream = attr.tts.clone().into();
let capnp_with_attr = syn::parse::<CapnpWithAttribute>(tts).unwrap();
return Some(capnp_with_attr.path);
}
}
None
}

fn gen_type_write(variant: &Variant, assign_defaults: impl Fn(&mut syn::Path)) -> TokenStream {
let opt_with_path = get_with_attribute(variant);
// let variant_ident = &variant.ident;
let variant_name = &variant.ident;
let variant_snake_name = variant_name.to_string().to_snake_case();

match &variant.fields {
Fields::Unnamed(fields_unnamed) => {
let unnamed = &fields_unnamed.unnamed;
if unnamed.len() != 1 {
unimplemented!("gen_type_write: Amount of unnamed fields is not 1!");
}

let pair = unnamed.last().unwrap();
let last_ident = match pair {
syn::punctuated::Pair::End(last_ident) => last_ident,
_ => unreachable!(),
};

let path = match opt_with_path {
Some(with_path) => with_path,
None => match &last_ident.ty {
syn::Type::Path(type_path) => type_path.path.clone(),
_ => {
panic!("{:?}", opt_with_path);
}
},
};
/*
let path = opt_with_path.clone().unwrap_or(match &last_ident.ty {
syn::Type::Path(type_path) => type_path.path.clone(),
_ => {
panic!("{:?}", opt_with_path);
// unimplemented!("gen_type_write: last ident is not a path!"),
}
});
*/

let mut path = path;
assign_defaults(&mut path);

if is_primitive(&path) {
let set_method =
syn::Ident::new(&format!("set_{}", &variant_snake_name), variant.span());
return quote! {
#variant_name(x) => writer.#set_method(<#path>::from(x.clone())),
};
}

if is_data(&path) {
let set_method =
syn::Ident::new(&format!("set_{}", &variant_snake_name), variant.span());
return quote! {
#variant_name(x) => writer.#set_method(&<#path>::from(x.clone())),
};
}

if path.is_ident("String") {
let set_method =
syn::Ident::new(&format!("set_{}", &variant_snake_name), variant.span());
return quote! {
#variant_name(x) => writer.#set_method(x),
};
}

// The case of list:
if let Some(inner_path) = get_vec(&path) {
let init_method =
syn::Ident::new(&format!("init_{}", &variant_snake_name), variant.span());
let list_write_iter = gen_list_write_iter(&inner_path);

// In the cases of more complicated types, list_builder needs to be mutable.
let let_list_builder =
if is_primitive(&path) || path.is_ident("String") || is_data(&path) {
quote! { let list_builder }
} else {
quote! { let mut list_builder }
};

let usize_to_u32 = usize_to_u32_shim();
return quote! {
#variant_name(vec) => {
#usize_to_u32
#let_list_builder = writer
.reborrow()
.#init_method(usize_to_u32(vec.len()).unwrap());

for (index, item) in vec.iter().enumerate() {
#list_write_iter
}
},
};
}

let init_method =
syn::Ident::new(&format!("init_{}", &variant_snake_name), variant.span());
quote! {
#variant_name(x) => <#path>::from(x.clone()).write_capnp(&mut writer.reborrow().#init_method()),
}
}

Fields::Unit => {
let set_method =
syn::Ident::new(&format!("set_{}", &variant_snake_name), variant.span());
quote! {
#variant_name => writer.#set_method(()),
}
}
// Rust enum variants don't have named fields (?)
Fields::Named(_) => unreachable!(),
}
}

pub fn gen_write_capnp_enum(
data_enum: &DataEnum,
rust_enum: &Ident,
capnp_struct: &Path,
assign_defaults: impl Fn(&mut syn::Path),
) -> TokenStream {
let recurse = data_enum.variants.iter().map(|variant| {
let type_write = gen_type_write(&variant, &assign_defaults);
quote! {
#rust_enum::#type_write
}
});

quote! {
impl<'a> WriteCapnp<'a> for #rust_enum {
type WriterType = #capnp_struct::Builder<'a>;
fn write_capnp(&self, writer: &mut Self::WriterType) {
match &self {
#(#recurse)*
};
}
}
}
}

fn gen_type_read(
variant: &Variant,
rust_enum: &Ident,
assign_defaults: impl Fn(&mut syn::Path),
) -> TokenStream {
let opt_with_path = get_with_attribute(variant);
let variant_name = &variant.ident;
// let variant_snake_name = variant_name.to_string().to_snake_case();

match &variant.fields {
Fields::Unnamed(fields_unnamed) => {
let unnamed = &fields_unnamed.unnamed;
if unnamed.len() != 1 {
unimplemented!("gen_type_read: Amount of unnamed fields is not 1!");
}

let pair = unnamed.last().unwrap();
let last_ident = match pair {
syn::punctuated::Pair::End(last_ident) => last_ident,
_ => unreachable!(),
};

let mut path = match opt_with_path {
Some(with_path) => with_path,
None => match &last_ident.ty {
syn::Type::Path(type_path) => type_path.path.clone(),
_ => {
panic!("{:?}", opt_with_path);
}
},
};

assign_defaults(&mut path);

if is_primitive(&path) {
return quote! {
#variant_name(x) => #rust_enum::#variant_name(x.into()),
};
}

if is_data(&path) || path.is_ident("String") {
return quote! {
#variant_name(x) => #rust_enum::#variant_name(x?.into()),
};
}

if let Some(inner_path) = get_vec(&path) {
// The case of a list:
let list_read_iter = gen_list_read_iter(&inner_path);
return quote! {
#variant_name(list_reader) => {
let mut res_vec = Vec::new();
for item_reader in list_reader? {
// res_vec.push_back(read_named_relay_address(&named_relay_address)?);
#list_read_iter
}
#rust_enum::#variant_name(res_vec)
}
};
}

let capnp_result = capnp_result_shim();

quote! {
#variant_name(variant_reader) => {
#capnp_result

let variant_reader = CapnpResult::from(variant_reader).into_result()?;
#rust_enum::#variant_name(<#path>::read_capnp(&variant_reader)?.into())
},
}
}

Fields::Unit => {
quote! {
#variant_name(()) => #rust_enum::#variant_name,
}
}
// Rust enum variants don't have named fields (?)
Fields::Named(_) => unreachable!(),
}
}

pub fn gen_read_capnp_enum(
data_enum: &DataEnum,
rust_enum: &Ident,
capnp_struct: &Path,
assign_defaults: impl Fn(&mut syn::Path),
) -> TokenStream {
let recurse = data_enum.variants.iter().map(|variant| {
let type_read = gen_type_read(&variant, rust_enum, &assign_defaults);
quote! {
#capnp_struct::#type_read
}
});

quote! {
impl<'a> ReadCapnp<'a> for #rust_enum {
type ReaderType = #capnp_struct::Reader<'a>;

fn read_capnp(reader: &Self::ReaderType) -> Result<Self, CapnpConvError> {
Ok(match reader.which()? {
#(#recurse)*
})
}
}
}
}
Loading