diff --git a/ipc/codegen/src/codegen.rs b/ipc/codegen/src/codegen.rs index 1d2c4765c..85ea86f16 100644 --- a/ipc/codegen/src/codegen.rs +++ b/ipc/codegen/src/codegen.rs @@ -33,6 +33,9 @@ use syntax::ext::base::{Annotatable, ExtCtxt}; use syntax::ext::build::AstBuilder; use syntax::ptr::P; +use super::typegen; +use std::collections::HashMap; + pub struct Error; const RESERVED_MESSAGE_IDS: u16 = 16; @@ -54,15 +57,15 @@ pub fn expand_ipc_implementation( let builder = aster::AstBuilder::new().span(span); - let (impl_item, dispatches) = match implement_interface(cx, &builder, &item, push) { - Ok((item, dispatches)) => (item, dispatches), + let (impl_item, dispatches, replacements) = match implement_interface(cx, &builder, &item, push) { + Ok((item, dispatches, replacements)) => (item, dispatches, replacements), Err(Error) => { return; } }; - push_client(cx, &builder, &item, &dispatches, push); + push_client(cx, &builder, &item, &dispatches, push, &replacements); push_handshake_struct(cx, push); - push(Annotatable::Item(impl_item)) + push(Annotatable::Item(impl_item)); } fn push_handshake_struct(cx: &ExtCtxt, push: &mut FnMut(Annotatable)) { @@ -89,6 +92,7 @@ fn push_invoke_signature_aster( builder: &aster::AstBuilder, implement: &ImplItem, signature: &MethodSig, + replacements: &HashMap>, push: &mut FnMut(Annotatable), ) -> Dispatch { @@ -102,19 +106,27 @@ fn push_invoke_signature_aster( let mut arg_names = Vec::new(); let mut arg_tys = Vec::new(); + let arg_name = format!("{}", field_name(builder, &inputs[skip-1]).name); let arg_ty = inputs[skip-1].ty.clone(); + let mut tree = builder.item() .attr().word("derive(Serialize, Deserialize)") .attr().word("allow(non_camel_case_types)") .struct_(name_str.as_str()) - .field(arg_name.as_str()).ty().build(arg_ty.clone()); + .field(arg_name.as_str()).ty() + .build(typegen::argument_replacement(builder, replacements, &arg_ty).unwrap_or(arg_ty.clone())); + arg_names.push(arg_name); - arg_tys.push(arg_ty.clone()); + arg_tys.push(arg_ty); for arg in inputs.iter().skip(skip) { let arg_name = format!("{}", field_name(builder, &arg)); - let arg_ty = arg.ty.clone(); - tree = tree.field(arg_name.as_str()).ty().build(arg_ty.clone()); + + let mut arg_ty = arg.ty.clone(); + arg_ty = typegen::argument_replacement(builder, replacements, &arg_ty).unwrap_or(arg_ty); + + tree = tree.field(arg_name.as_str()).ty() + .build(typegen::argument_replacement(builder, replacements, &arg_ty).unwrap_or(arg_ty.clone())); arg_names.push(arg_name); arg_tys.push(arg_ty); } @@ -172,14 +184,20 @@ fn implement_dispatch_arm_invoke_stmt( cx: &ExtCtxt, builder: &aster::AstBuilder, dispatch: &Dispatch, + replacements: &HashMap>, ) -> ast::Stmt { let function_name = builder.id(dispatch.function_name.as_str()); let output_type_id = builder.id(dispatch.return_type_name.clone().unwrap().as_str()); - let input_args_exprs = dispatch.input_arg_names.iter().map(|ref arg_name| { + let input_args_exprs = dispatch.input_arg_names.iter().enumerate().map(|(arg_index, arg_name)| { let arg_ident = builder.id(arg_name); - quote_expr!(cx, input. $arg_ident) + if typegen::argument_replacement(builder, replacements, &dispatch.input_arg_tys[arg_index]).is_some() { + quote_expr!(cx, input. $arg_ident .into()) + } + else { + quote_expr!(cx, input. $arg_ident) + } }).collect::>>(); let ext_cx = &*cx; @@ -236,6 +254,7 @@ fn implement_dispatch_arm_invoke( builder: &aster::AstBuilder, dispatch: &Dispatch, buffer: bool, + replacements: &HashMap>, ) -> P { let deserialize_expr = if buffer { @@ -246,7 +265,7 @@ fn implement_dispatch_arm_invoke( let input_type_id = builder.id(dispatch.input_type_name.clone().unwrap().as_str()); - let invoke_serialize_stmt = implement_dispatch_arm_invoke_stmt(cx, builder, dispatch); + let invoke_serialize_stmt = implement_dispatch_arm_invoke_stmt(cx, builder, dispatch, replacements); quote_expr!(cx, { let input: $input_type_id = $deserialize_expr; $invoke_serialize_stmt @@ -260,10 +279,11 @@ fn implement_dispatch_arm( index: u32, dispatch: &Dispatch, buffer: bool, + replacements: &HashMap>, ) -> ast::Arm { let index_ident = builder.id(format!("{}", index + (RESERVED_MESSAGE_IDS as u32)).as_str()); - let invoke_expr = implement_dispatch_arm_invoke(cx, builder, dispatch, buffer); + let invoke_expr = implement_dispatch_arm_invoke(cx, builder, dispatch, buffer, replacements); quote_arm!(cx, $index_ident => { $invoke_expr } ) } @@ -272,11 +292,12 @@ fn implement_dispatch_arms( builder: &aster::AstBuilder, dispatches: &[Dispatch], buffer: bool, + replacements: &HashMap>, ) -> Vec { let mut index = -1; dispatches.iter() - .map(|dispatch| { index = index + 1; implement_dispatch_arm(cx, builder, index as u32, dispatch, buffer) }).collect() + .map(|dispatch| { index = index + 1; implement_dispatch_arm(cx, builder, index as u32, dispatch, buffer, replacements) }).collect() } /// generates client type for specified server type @@ -298,10 +319,11 @@ fn push_client( builder: &aster::AstBuilder, item: &Item, dispatches: &[Dispatch], - push: &mut FnMut(Annotatable)) -{ + push: &mut FnMut(Annotatable), + replacements: &HashMap>, +) { push_client_struct(cx, builder, item, push); - push_client_implementation(cx, builder, dispatches, item, push); + push_client_implementation(cx, builder, dispatches, item, push, replacements); push_with_socket_client_implementation(cx, builder, item, push); } @@ -329,15 +351,18 @@ fn implement_client_method_body( builder: &aster::AstBuilder, index: u16, dispatch: &Dispatch, - ) - -> P + replacements: &HashMap>, +) -> P { let request = if dispatch.input_arg_names.len() > 0 { + let arg_name = dispatch.input_arg_names[0].as_str(); + let static_ty = &dispatch.input_arg_tys[0]; let arg_ty = builder .ty().ref_() .lifetime("'a") - .ty().build(dispatch.input_arg_tys[0].clone()); + .ty() + .build(typegen::argument_replacement(builder, replacements, static_ty).unwrap_or(static_ty.clone())); let mut tree = builder.item() .attr().word("derive(Serialize)") @@ -345,15 +370,19 @@ fn implement_client_method_body( .generics() .lifetime_name("'a") .build() - .field(arg_name).ty().build(arg_ty); + .field(arg_name).ty() + .build(arg_ty); for arg_idx in 1..dispatch.input_arg_names.len() { let arg_name = dispatch.input_arg_names[arg_idx].as_str(); + let static_ty = &dispatch.input_arg_tys[arg_idx]; let arg_ty = builder .ty().ref_() .lifetime("'a") - .ty().build(dispatch.input_arg_tys[arg_idx].clone()); + .ty() + .build(typegen::argument_replacement(builder, replacements, static_ty).unwrap_or(static_ty.clone())); tree = tree.field(arg_name).ty().build(arg_ty); + } let mut request_serialization_statements = Vec::new(); @@ -378,11 +407,28 @@ fn implement_client_method_body( tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("Request"), ::syntax::parse::token::Plain))); tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Brace))); - for arg in dispatch.input_arg_names.iter() { + for (idx, arg) in dispatch.input_arg_names.iter().enumerate() { tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of(arg.as_str()), ::syntax::parse::token::Plain))); tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Colon)); + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::BinOp(::syntax::parse::token::And))); + + let arg_ty = &dispatch.input_arg_tys[idx]; + let replacement = typegen::argument_replacement(builder, replacements, arg_ty); + if let Some(ref replacement_ty) = replacement { + let replacor_ident = ::syntax::print::pprust::ty_to_string(replacement_ty); + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of(&replacor_ident), ::syntax::parse::token::Plain))); + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::ModSep)); + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("from"), ::syntax::parse::token::Plain))); + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Paren))); + } + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of(arg.as_str()), ::syntax::parse::token::Plain))); + + if replacement.is_some() { + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Paren))); + } + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Comma)); } @@ -434,11 +480,13 @@ fn implement_client_method( cx: &ExtCtxt, builder: &aster::AstBuilder, index: u16, - dispatch: &Dispatch) + dispatch: &Dispatch, + replacements: &HashMap>, +) -> ast::ImplItem { let method_name = builder.id(dispatch.function_name.as_str()); - let body = implement_client_method_body(cx, builder, index, dispatch); + let body = implement_client_method_body(cx, builder, index, dispatch, replacements); let ext_cx = &*cx; // expanded version of this @@ -511,13 +559,14 @@ fn push_client_implementation( builder: &aster::AstBuilder, dispatches: &[Dispatch], item: &Item, - push: &mut FnMut(Annotatable)) -{ + push: &mut FnMut(Annotatable), + replacements: &HashMap>, +) { let (item_ident, client_ident) = get_item_idents(builder, item); let mut index = -1i32; let items = dispatches.iter() - .map(|dispatch| { index = index + 1; P(implement_client_method(cx, builder, index as u16, dispatch)) }) + .map(|dispatch| { index = index + 1; P(implement_client_method(cx, builder, index as u16, dispatch, replacements)) }) .collect::>>(); let implement = quote_item!(cx, @@ -607,13 +656,24 @@ fn get_item_idents(builder: &aster::AstBuilder, item: &Item) -> (::syntax::ast:: (item_ident, client_ident) } +fn collect_tys(items: &[&MethodSig]) -> Vec> { + let mut result = Vec::new(); + for signature in items { + result.extend(signature.decl.inputs.iter().map(|input_arg| input_arg.ty.clone())); + if let FunctionRetTy::Ty(ref ty) = signature.decl.output { + result.push(ty.clone()) + }; + } + result +} + /// implements `IpcInterface` for the given class `C` fn implement_interface( cx: &ExtCtxt, builder: &aster::AstBuilder, item: &Item, push: &mut FnMut(Annotatable), -) -> Result<(P, Vec), Error> { +) -> Result<(P, Vec, HashMap>), Error> { let (generics, impl_items) = match item.node { ast::ItemKind::Impl(_, _, ref generics, _, _, ref impl_items) => (generics, impl_items), _ => { @@ -634,15 +694,27 @@ fn implement_interface( let (ty, _) = get_item_idents(builder, item); - let mut dispatch_table = Vec::new(); + let mut method_signatures = Vec::new(); for impl_item in impl_items { if let ImplItemKind::Method(ref signature, _) = impl_item.node { - dispatch_table.push(push_invoke_signature_aster(builder, &impl_item, signature, push)); + method_signatures.push((impl_item, signature)) } } - let dispatch_arms = implement_dispatch_arms(cx, builder, &dispatch_table, false); - let dispatch_arms_buffered = implement_dispatch_arms(cx, builder, &dispatch_table, true); + let all_tys = collect_tys( + &method_signatures + .iter() + .map(|&(_, signature)| signature) + .collect::>()); + + let replacements = typegen::match_unknown_tys(cx, builder, &all_tys, push); + + let dispatch_table = method_signatures.iter().map(|&(impl_item, signature)| + push_invoke_signature_aster(builder, impl_item, signature, &replacements, push)) + .collect::>(); + + let dispatch_arms = implement_dispatch_arms(cx, builder, &dispatch_table, false, &replacements); + let dispatch_arms_buffered = implement_dispatch_arms(cx, builder, &dispatch_table, true, &replacements); let (handshake_arm, handshake_arm_buf) = implement_handshake_arm(cx); @@ -676,5 +748,5 @@ fn implement_interface( } } } - ).unwrap(), dispatch_table)) + ).unwrap(), dispatch_table, replacements)) } diff --git a/ipc/codegen/src/lib.rs.in b/ipc/codegen/src/lib.rs.in index c942157b1..46a2c9ca5 100644 --- a/ipc/codegen/src/lib.rs.in +++ b/ipc/codegen/src/lib.rs.in @@ -15,3 +15,4 @@ // along with Parity. If not, see . mod codegen; +pub mod typegen; diff --git a/ipc/codegen/src/typegen.rs b/ipc/codegen/src/typegen.rs new file mode 100644 index 000000000..6e09c9fe6 --- /dev/null +++ b/ipc/codegen/src/typegen.rs @@ -0,0 +1,231 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . +use aster; + +use syntax::ast::{ + Ty, + TyKind, + Path, + DUMMY_NODE_ID, +}; + +use syntax::ast; +use syntax::ext::base::{Annotatable, ExtCtxt}; +use syntax::ext::build::AstBuilder; +use syntax::ptr::P; + +use std::collections::{HashMap, HashSet}; +use std::ops::Deref; + +fn is_new_entry(path: &Path) -> Option { + let known = { + if path.segments.len() > 1 { + false + } else { + let ident = format!("{}", path.segments[0].identifier.name.as_str()); + ident == "u32" || + ident == "u64" || + ident == "usize" || + ident == "i32" || + ident == "i64" || + ident == "String" || + ident == "bool" + } + }; + + if known { None } + else { Some(::syntax::print::pprust::path_to_string(path)) } +} + +pub fn argument_replacement( + builder: &aster::AstBuilder, + replacements: &HashMap>, + ty: &P, +) -> Option> { + match ty.node { + TyKind::Vec(ref nested_ty) => { + argument_replacement(builder, replacements, nested_ty).and_then(|replaced_with| { + let mut inplace_ty = nested_ty.deref().clone(); + inplace_ty.node = TyKind::Vec(replaced_with); + inplace_ty.id = DUMMY_NODE_ID; + Some(P(inplace_ty)) + }) + }, + TyKind::FixedLengthVec(ref nested_ty, ref len_expr) => { + argument_replacement(builder, replacements, nested_ty).and_then(|replaced_with| { + let mut inplace_ty = nested_ty.deref().clone(); + inplace_ty.node = TyKind::FixedLengthVec(replaced_with, len_expr.clone()); + inplace_ty.id = DUMMY_NODE_ID; + Some(P(inplace_ty)) + }) + }, + TyKind::Path(_, ref path) => { + if path.segments.len() > 0 && path.segments[0].identifier.name.as_str() == "Option" || + path.segments[0].identifier.name.as_str() == "Result" { + + let nested_ty = &path.segments[0].parameters.types()[0]; + argument_replacement(builder, replacements, nested_ty).and_then(|replaced_with| { + let mut inplace_path = path.clone(); + match inplace_path.segments[0].parameters { + ast::PathParameters::AngleBracketed(ref mut data) => { + data.types = data.types.map(|_| replaced_with.clone()); + }, + _ => {} + } + let mut inplace_ty = nested_ty.deref().deref().clone(); + inplace_ty.node = TyKind::Path(None, inplace_path); + inplace_ty.id = DUMMY_NODE_ID; + Some(P(inplace_ty)) + }) + } + else { + replacements.get(&::syntax::print::pprust::path_to_string(path)).and_then(|replaced_with| { + Some(replaced_with.clone()) + }) + } + } + _ => { None } + } +} + +pub fn push_bin_box( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + ty: &Ty, + bbox_name: &str, + push: &mut FnMut(Annotatable), +) { + let ident = builder.id(bbox_name); + let bin_box_struct = quote_item!(cx, + struct $ident ($ty); + ).unwrap(); + push(Annotatable::Item(bin_box_struct)); + push(Annotatable::Item(quote_item!(cx, + impl From<$ty> for $ident { + fn from(val: $ty) -> $ident { + $ident(val) + } + }).unwrap())); + + push(Annotatable::Item(quote_item!(cx, + impl Into<$ty> for $ident { + fn into(self) -> $ty { + let $ident(val) = self; + val + } + }).unwrap())); + + let serialize_impl = quote_item!(cx, + impl ::serde::ser::Serialize for $ident { + fn serialize<__S>(&self, _serializer: &mut __S) -> ::std::result::Result<(), __S::Error> + where __S: ::serde::ser::Serializer + { + let &$ident(ref val) = self; + _serializer.serialize_bytes(val.as_slice()) + } + }).unwrap(); + + let ident_expr = builder.id(::syntax::print::pprust::ty_to_string(ty)); + + let deserialize_impl = quote_item!(cx, + impl ::serde::de::Deserialize for $ident { + fn deserialize<__D>(deserializer: &mut __D) -> ::std::result::Result<$ident, __D::Error> + where __D: ::serde::de::Deserializer + { + struct __Visitor<__D: ::serde::de::Deserializer>(::std::marker::PhantomData<__D>); + + impl <__D: ::serde::de::Deserializer> ::serde::de::Visitor for __Visitor<__D> { + type Value = $ident; + #[inline] + fn visit_seq<__V>(&mut self, mut visitor: __V) -> ::std::result::Result<$ident, __V::Error> + where __V: ::serde::de::SeqVisitor + { + let raw_bytes: Vec = try!(visitor.visit()).unwrap_or_else(|| Vec::new()); + let inner = $ident_expr ::from_bytes(&raw_bytes).unwrap(); + Ok($ident (inner)) + } + + } + deserializer.deserialize_bytes(__Visitor::<__D>(::std::marker::PhantomData)) + } + + }).unwrap(); + + push(Annotatable::Item(serialize_impl)); + push(Annotatable::Item(deserialize_impl)); +} + +pub fn match_unknown_tys( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + tys: &[P], + push: &mut FnMut(Annotatable), +) -> HashMap> +{ + let mut hash_map = HashMap::new(); + let mut fringe = Vec::new(); + fringe.extend(tys); + let mut stop_list = HashSet::new(); + let mut index = 0; + + loop { + if fringe.len() == 0 { break; } + let drained = fringe.drain(..1).collect::>>(); + let ty = drained[0]; + stop_list.insert(ty); + + match ty.node { + TyKind::Vec(ref nested_ty) => { + if !stop_list.contains(nested_ty) { + fringe.push(nested_ty); + } + }, + TyKind::FixedLengthVec(ref nested_ty, _) => { + if !stop_list.contains(nested_ty) { + fringe.push(nested_ty); + } + }, + TyKind::Path(_, ref path) => { + if path.segments.len() > 0 && path.segments[0].identifier.name.as_str() == "Option" || + path.segments[0].identifier.name.as_str() == "Result" { + for extra_type in path.segments[0].parameters.types() { + if !stop_list.contains(extra_type) { + fringe.push(extra_type); + } + } + continue; + } + + match is_new_entry(path) { + Some(old_path) => { + if hash_map.get(&old_path).is_some() { + continue; + } + + let bin_box_name = format!("BinBox{}", index); + push_bin_box(cx, builder, &ty, &bin_box_name, push); + hash_map.insert(old_path, builder.ty().id(&bin_box_name)); + index = index + 1; + }, + None => {} + } + }, + _ => { } + } + } + + hash_map +} diff --git a/ipc/tests/Cargo.toml b/ipc/tests/Cargo.toml index 5ea6f2c81..286fffd18 100644 --- a/ipc/tests/Cargo.toml +++ b/ipc/tests/Cargo.toml @@ -15,7 +15,7 @@ ethcore-devtools = { path = "../../devtools" } semver = "0.2.0" nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git" } ethcore-ipc-nano = { path = "../nano" } - +ethcore-util = { path = "../../util" } [build-dependencies] syntex = "*" diff --git a/ipc/tests/examples.rs b/ipc/tests/examples.rs index 9d5eb37c4..d2fdd54d2 100644 --- a/ipc/tests/examples.rs +++ b/ipc/tests/examples.rs @@ -101,4 +101,24 @@ mod tests { assert!(result.is_ok()); } + + #[test] + fn can_use_custom_params() { + let mut socket = TestSocket::new(); + socket.read_buffer = vec![1]; + let service_client = ServiceClient::init(socket); + + let result = service_client.push_custom(CustomData { a: 3, b: 11}); + + assert_eq!(vec![ + // message num.. + 0, 18, + // payload length + 0, 0, 0, 0, 0, 0, 0, 16, + // structure raw bytes (bigendians :( ) + 3, 0, 0, 0, 0, 0, 0, 0, + 11, 0, 0, 0, 0, 0, 0, 0], + service_client.socket().borrow().write_buffer.clone()); + assert_eq!(true, result); + } } diff --git a/ipc/tests/run.rs b/ipc/tests/run.rs index d3eb15287..ce95f35d4 100644 --- a/ipc/tests/run.rs +++ b/ipc/tests/run.rs @@ -21,6 +21,7 @@ extern crate ethcore_devtools as devtools; extern crate semver; extern crate nanomsg; extern crate ethcore_ipc_nano as nanoipc; +extern crate ethcore_util as util; pub mod service; mod examples; diff --git a/ipc/tests/service.rs.in b/ipc/tests/service.rs.in index f48596e6c..6bfa72680 100644 --- a/ipc/tests/service.rs.in +++ b/ipc/tests/service.rs.in @@ -16,13 +16,42 @@ use std::sync::RwLock; use std::ops::*; +use std::convert::*; use ipc::IpcConfig; +use util::bytes::{FromRawBytes, BytesConvertable, FromBytesError}; pub struct Service { pub commits: RwLock, pub rollbacks: RwLock, } +pub struct CustomData { + pub a: usize, + pub b: usize, +} + +impl FromRawBytes for CustomData { + fn from_bytes(bytes: &[u8]) -> Result { + Ok(CustomData { + a: bytes[0] as usize * 256 + bytes[1] as usize, + b: bytes[2] as usize * 256 + bytes[3] as usize + }) + } +} + +impl BytesConvertable for CustomData { + fn bytes(&self) -> &[u8] { + let ip: *const CustomData = self; + let ptr: *const u8 = ip as *const _; + unsafe { + ::std::slice::from_raw_parts( + ptr, + ::std::mem::size_of::() + ) + } + } +} + #[derive(Ipc)] impl Service { fn commit(&self, f: u32) -> u32 { @@ -36,6 +65,15 @@ impl Service { *lock = *lock + a_0 as usize - b as usize; (a_0 - b) as i32 } + pub fn push_custom(&self, data: CustomData) -> bool { + let mut clock = self.commits.write().unwrap(); + let mut rlock = self.commits.write().unwrap(); + + *clock = data.a; + *rlock = data.b; + + true + } } impl Service {