From 058ef59b13cf5db15ae2e74ae59d86338bf0ba5f Mon Sep 17 00:00:00 2001 From: NikVolf Date: Fri, 15 Apr 2016 16:16:58 +0300 Subject: [PATCH] codegen for service with generics --- ipc/codegen/src/codegen.rs | 232 +++++++++++++++++++++++-------------- ipc/codegen/src/typegen.rs | 18 ++- ipc/tests/build.rs | 19 +++ ipc/tests/examples.rs | 22 ++++ ipc/tests/nested.rs | 17 +++ ipc/tests/nested.rs.in | 44 +++++++ ipc/tests/run.rs | 3 + 7 files changed, 265 insertions(+), 90 deletions(-) create mode 100644 ipc/tests/nested.rs create mode 100644 ipc/tests/nested.rs.in diff --git a/ipc/codegen/src/codegen.rs b/ipc/codegen/src/codegen.rs index c20514097..df62b947d 100644 --- a/ipc/codegen/src/codegen.rs +++ b/ipc/codegen/src/codegen.rs @@ -25,6 +25,9 @@ use syntax::ast::{ PatKind, FunctionRetTy, Ty, + TraitRef, + Ident, + Generics, }; use syntax::ast; @@ -57,15 +60,15 @@ pub fn expand_ipc_implementation( let builder = aster::AstBuilder::new().span(span); - let (impl_item, dispatches, replacements) = match implement_interface(cx, &builder, &item, push) { - Ok((item, dispatches, replacements)) => (item, dispatches, replacements), + let interface_map = match implement_interface(cx, &builder, &item, push) { + Ok(interface_map) => interface_map, Err(Error) => { return; } }; - push_client(cx, &builder, &item, &dispatches, push, &replacements); + push_client(cx, &builder, &interface_map, push); push_handshake_struct(cx, push); - push(Annotatable::Item(impl_item)); + push(Annotatable::Item(interface_map.item)); } fn push_handshake_struct(cx: &ExtCtxt, push: &mut FnMut(Annotatable)) { @@ -95,7 +98,6 @@ fn push_invoke_signature_aster( replacements: &HashMap>, push: &mut FnMut(Annotatable), ) -> Dispatch { - let inputs = &signature.decl.inputs; let (input_type_name, input_arg_names, input_arg_tys) = if inputs.len() > 0 { let first_field_name = field_name(builder, &inputs[0]).name.as_str(); @@ -294,33 +296,6 @@ fn implement_dispatch_arms( .map(|dispatch| { index = index + 1; implement_dispatch_arm(cx, builder, index as u32, dispatch, buffer, replacements) }).collect() } -/// generates client type for specified server type -/// for say `Service` it generates `ServiceClient` -fn push_client_struct(cx: &ExtCtxt, builder: &aster::AstBuilder, item: &Item, push: &mut FnMut(Annotatable)) { - let (_, client_ident) = get_item_idents(builder, item); - let client_struct_item = quote_item!(cx, - pub struct $client_ident { - socket: ::std::cell::RefCell, - phantom: ::std::marker::PhantomData, - }); - - push(Annotatable::Item(client_struct_item.expect(&format!("could not generate client struct for {:?}", client_ident.name)))); -} - -/// pushes generated code for the client class (type declaration and method invocation implementations) -fn push_client( - cx: &ExtCtxt, - builder: &aster::AstBuilder, - item: &Item, - dispatches: &[Dispatch], - push: &mut FnMut(Annotatable), - replacements: &HashMap>, -) { - push_client_struct(cx, builder, item, push); - push_client_implementation(cx, builder, dispatches, item, push, replacements); - push_with_socket_client_implementation(cx, builder, item, push); -} - /// returns an expression with the body for single operation that is being sent to server /// operation itself serializes input, writes to socket and waits for socket to respond /// (the latter only if original method signature returns anyting) @@ -344,10 +319,10 @@ fn implement_client_method_body( cx: &ExtCtxt, builder: &aster::AstBuilder, index: u16, - dispatch: &Dispatch, - replacements: &HashMap>, + interface_map: &InterfaceMap, ) -> P { + let dispatch = &interface_map.dispatches[index as usize]; let request = if dispatch.input_arg_names.len() > 0 { let arg_name = dispatch.input_arg_names[0].as_str(); @@ -356,7 +331,7 @@ fn implement_client_method_body( .ty().ref_() .lifetime("'a") .ty() - .build(typegen::argument_replacement(builder, replacements, static_ty).unwrap_or(static_ty.clone())); + .build(typegen::argument_replacement(builder, &interface_map.replacements, static_ty).unwrap_or(static_ty.clone())); let mut tree = builder.item() .attr().word("derive(Serialize)") @@ -374,7 +349,7 @@ fn implement_client_method_body( .ty().ref_() .lifetime("'a") .ty() - .build(typegen::argument_replacement(builder, replacements, static_ty).unwrap_or(static_ty.clone())); + .build(typegen::argument_replacement(builder, &interface_map.replacements, static_ty).unwrap_or(static_ty.clone())); tree = tree.field(arg_name).ty().build(arg_ty); } @@ -408,7 +383,7 @@ fn implement_client_method_body( 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); + let replacement = typegen::argument_replacement(builder, &interface_map.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))); @@ -474,13 +449,13 @@ fn implement_client_method( cx: &ExtCtxt, builder: &aster::AstBuilder, index: u16, - dispatch: &Dispatch, - replacements: &HashMap>, + interface_map: &InterfaceMap, ) -> ast::ImplItem { + let dispatch = &interface_map.dispatches[index as usize]; let method_name = builder.id(dispatch.function_name.as_str()); - let body = implement_client_method_body(cx, builder, index, dispatch, replacements); + let body = implement_client_method_body(cx, builder, index, interface_map); let ext_cx = &*cx; // expanded version of this @@ -527,18 +502,76 @@ fn implement_client_method( signature.unwrap() } +fn client_generics(builder: &aster::AstBuilder, interface_map: &InterfaceMap) -> Generics { + let ty_param = aster::ty_param::TyParamBuilder::new( + builder.id("S")).trait_bound( + builder.path().global().ids(&["ipc", "IpcSocket"]).build() + ).build().build(); + + builder.from_generics(interface_map.generics.clone()) + .with_ty_param(ty_param) + .build() +} + +fn client_qualified_ident(builder: &aster::AstBuilder, interface_map: &InterfaceMap) -> P { + let generics = client_generics(builder, interface_map); + aster::ty::TyBuilder::new().path().segment(interface_map.ident_map.client_ident(builder)) + .with_generics(generics).build() + .build() +} + +fn client_phantom_ident(builder: &aster::AstBuilder, interface_map: &InterfaceMap) -> P { + let generics = client_generics(builder, interface_map); + aster::ty::TyBuilder::new().phantom_data() + .tuple().with_tys(generics.ty_params.iter().map(|x| aster::ty::TyBuilder::new().id(x.ident))) + .build() +} + +/// generates client type for specified server type +/// for say `Service` it generates `ServiceClient` +fn push_client_struct(cx: &ExtCtxt, builder: &aster::AstBuilder, interface_map: &InterfaceMap, push: &mut FnMut(Annotatable)) { + let generics = client_generics(builder, interface_map); + let client_short_ident = interface_map.ident_map.client_ident(builder); + let where_clause = &generics.where_clause; + let phantom = client_phantom_ident(builder, interface_map); + + let client_struct_item = quote_item!(cx, + pub struct $client_short_ident $generics { + socket: ::std::cell::RefCell, + phantom: $phantom, + }); + + push(Annotatable::Item(client_struct_item.expect(&format!("could not generate client struct for {:?}", client_short_ident.name)))); +} + +/// pushes generated code for the client class (type declaration and method invocation implementations) +fn push_client( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + interface_map: &InterfaceMap, + push: &mut FnMut(Annotatable), +) { + push_client_struct(cx, builder, interface_map, push); + push_client_implementation(cx, builder, interface_map, push); + push_with_socket_client_implementation(cx, builder, interface_map, push); +} + + fn push_with_socket_client_implementation( cx: &ExtCtxt, builder: &aster::AstBuilder, - item: &Item, + interface_map: &InterfaceMap, push: &mut FnMut(Annotatable)) { - let (_, client_ident) = get_item_idents(builder, item); + let generics = client_generics(builder, interface_map); + let client_ident = client_qualified_ident(builder, interface_map); + let where_clause = &generics.where_clause; + let client_short_ident = interface_map.ident_map.client_ident(builder); let implement = quote_item!(cx, - impl ::ipc::WithSocket for $client_ident where S: ::ipc::IpcSocket { - fn init(socket: S) -> $client_ident { - $client_ident { + impl $generics ::ipc::WithSocket for $client_ident $where_clause { + fn init(socket: S) -> $client_ident { + $client_short_ident { socket: ::std::cell::RefCell::new(socket), phantom: ::std::marker::PhantomData, } @@ -551,20 +584,22 @@ fn push_with_socket_client_implementation( fn push_client_implementation( cx: &ExtCtxt, builder: &aster::AstBuilder, - dispatches: &[Dispatch], - item: &Item, + interface_map: &InterfaceMap, push: &mut FnMut(Annotatable), - replacements: &HashMap>, ) { - let (item_ident, client_ident) = get_item_idents(builder, item); + let (item_ident, client_ident) = (interface_map.ident_map.qualified_ident(builder), interface_map.ident_map.client_ident(builder)); let mut index = -1i32; - let items = dispatches.iter() - .map(|dispatch| { index = index + 1; P(implement_client_method(cx, builder, index as u16, dispatch, replacements)) }) + let items = interface_map.dispatches.iter() + .map(|dispatch| { index = index + 1; P(implement_client_method(cx, builder, index as u16, interface_map)) }) .collect::>>(); + let generics = client_generics(builder, interface_map); + let client_ident = client_qualified_ident(builder, interface_map); + let where_clause = &generics.where_clause; + let implement = quote_item!(cx, - impl $client_ident where S: ::ipc::IpcSocket { + impl $generics $client_ident $where_clause { pub fn handshake(&self) -> Result<(), ::ipc::Error> { let payload = BinHandshake { protocol_version: $item_ident::protocol_version().to_string(), @@ -632,24 +667,6 @@ fn implement_handshake_arm( ) } -fn get_item_idents(builder: &aster::AstBuilder, item: &Item) -> (::syntax::ast::Ident, ::syntax::ast::Ident) { - let ty = match item.node { - ast::ItemKind::Impl(_, _, _, _, ref ty, _) => ty.clone(), - _ => { builder.ty().id("") } - }; - - let (item_ident, client_ident) = match ty.node { - ::syntax::ast::TyKind::Path(_, ref path) => { - ( - builder.id(format!("{}", path.segments[0].identifier)), - builder.id(format!("{}Client", path.segments[0].identifier)) - ) - }, - _ => { panic!("incompatible implementation"); } - }; - (item_ident, client_ident) -} - fn collect_tys(items: &[&MethodSig]) -> Vec> { let mut result = Vec::new(); for signature in items { @@ -661,15 +678,59 @@ fn collect_tys(items: &[&MethodSig]) -> Vec> { result } +struct InterfaceMap { + pub original_item: Item, + pub item: P, + pub dispatches: Vec, + pub replacements: HashMap>, + pub generics: Generics, + pub impl_trait: Option, + pub ident_map: IdentMap, +} + +struct IdentMap { + original_path: ast::Path, +} + +impl IdentMap { + fn ident(&self, builder: &aster::AstBuilder) -> Ident { + builder.id(format!("{}", ::syntax::print::pprust::path_to_string(&self.original_path))) + } + + fn client_ident(&self, builder: &aster::AstBuilder) -> Ident { + builder.id(format!("{}Client", self.original_path.segments[0].identifier)) + } + + fn qualified_ident(&self, builder: &aster::AstBuilder) -> Ident { + builder.id(format!("{}", ::syntax::print::pprust::path_to_string(&self.original_path).replace("<", "::<"))) + } +} + +fn ty_ident_map(original_ty: &P) -> IdentMap { + let original_path = match original_ty.node { + ::syntax::ast::TyKind::Path(_, ref path) => path.clone(), + _ => { panic!("incompatible implementation"); } + }; + let ident_map = IdentMap { original_path: original_path }; + ident_map +} + +fn item_ident_map(item: &Item) -> IdentMap { + match item.node { + ast::ItemKind::Impl(_, _, _, _, ref ty, _) => ty_ident_map(ty), + _ => { panic!("Queried for non-implementation item!"); } + } +} + /// implements `IpcInterface` for the given class `C` fn implement_interface( cx: &ExtCtxt, builder: &aster::AstBuilder, item: &Item, push: &mut FnMut(Annotatable), -) -> Result<(P, Vec, HashMap>), Error> { - let (generics, impl_items) = match item.node { - ast::ItemKind::Impl(_, _, ref generics, _, _, ref impl_items) => (generics, impl_items), +) -> Result { + let (generics, impl_trait, original_ty, impl_items) = match item.node { + ast::ItemKind::Impl(_, _, ref generics, ref impl_trait, ref ty, ref impl_items) => (generics, impl_trait, ty, impl_items), _ => { cx.span_err( item.span, @@ -677,17 +738,9 @@ fn implement_interface( return Err(Error); } }; - - let impl_generics = builder.from_generics(generics.clone()) - .add_ty_param_bound( - builder.path().global().ids(&["ethcore_ipc"]).build() - ) - .build(); - + let impl_generics = builder.from_generics(generics.clone()).build(); let where_clause = &impl_generics.where_clause; - let (ty, _) = get_item_idents(builder, item); - let mut method_signatures = Vec::new(); for impl_item in impl_items { if let ImplItemKind::Method(ref signature, _) = impl_item.node { @@ -712,7 +765,8 @@ fn implement_interface( let (handshake_arm, handshake_arm_buf) = implement_handshake_arm(cx); - Ok((quote_item!(cx, + let ty = ty_ident_map(&original_ty).ident(builder); + let ipc_item = quote_item!(cx, impl $impl_generics ::ipc::IpcInterface<$ty> for $ty $where_clause { fn dispatch(&self, r: &mut R) -> Vec where R: ::std::io::Read @@ -743,5 +797,15 @@ fn implement_interface( } } } - ).unwrap(), dispatch_table, replacements)) + ).unwrap(); + + Ok(InterfaceMap { + ident_map: ty_ident_map(&original_ty), + original_item: item.clone(), + item: ipc_item, + dispatches: dispatch_table, + replacements: replacements, + generics: generics.clone(), + impl_trait: impl_trait.clone(), + }) } diff --git a/ipc/codegen/src/typegen.rs b/ipc/codegen/src/typegen.rs index 6e09c9fe6..55e3e01d4 100644 --- a/ipc/codegen/src/typegen.rs +++ b/ipc/codegen/src/typegen.rs @@ -36,6 +36,10 @@ fn is_new_entry(path: &Path) -> Option { false } else { let ident = format!("{}", path.segments[0].identifier.name.as_str()); + ident == "u8" || + ident == "i8" || + ident == "u16" || + ident == "i16" || ident == "u32" || ident == "u64" || ident == "usize" || @@ -199,12 +203,14 @@ pub fn match_unknown_tys( } }, 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); - } + if path.segments.len() > 0 && { + let first_segment = path.segments[0].identifier.name.as_str(); + first_segment == "Option" || first_segment == "Result" || first_segment == "Vec" + } + { + let extra_type = &path.segments[0].parameters.types()[0]; + if !stop_list.contains(extra_type) { + fringe.push(extra_type); } continue; } diff --git a/ipc/tests/build.rs b/ipc/tests/build.rs index a960c4202..8515c14dd 100644 --- a/ipc/tests/build.rs +++ b/ipc/tests/build.rs @@ -24,6 +24,24 @@ use std::path::Path; pub fn main() { let out_dir = env::var_os("OUT_DIR").unwrap(); + // ipc pass + { + let src = Path::new("nested.rs.in"); + let dst = Path::new(&out_dir).join("nested_ipc.rs"); + let mut registry = syntex::Registry::new(); + codegen::register(&mut registry); + registry.expand("", &src, &dst).unwrap(); + } + + // serde pass + { + let src = Path::new(&out_dir).join("nested_ipc.rs"); + let dst = Path::new(&out_dir).join("nested_cg.rs"); + let mut registry = syntex::Registry::new(); + serde_codegen::register(&mut registry); + registry.expand("", &src, &dst).unwrap(); + } + // ipc pass { let src = Path::new("service.rs.in"); @@ -41,4 +59,5 @@ pub fn main() { serde_codegen::register(&mut registry); registry.expand("", &src, &dst).unwrap(); } + } diff --git a/ipc/tests/examples.rs b/ipc/tests/examples.rs index d2fdd54d2..8f333022b 100644 --- a/ipc/tests/examples.rs +++ b/ipc/tests/examples.rs @@ -18,6 +18,7 @@ mod tests { use super::super::service::*; + use super::super::nested::DBClient; use ipc::*; use devtools::*; use semver::Version; @@ -121,4 +122,25 @@ mod tests { service_client.socket().borrow().write_buffer.clone()); assert_eq!(true, result); } + + #[test] + fn can_invoke_generic_service() { + let mut socket = TestSocket::new(); + socket.read_buffer = vec![0, 0, 0, 0]; + let db_client = DBClient::::init(socket); + + let result = db_client.write(vec![0u8; 100]); + + assert!(result.is_ok()); + } + #[test] + fn can_handshake_generic_service() { + let mut socket = TestSocket::new(); + socket.read_buffer = vec![1]; + let db_client = DBClient::::init(socket); + + let result = db_client.handshake(); + + assert!(result.is_ok()); + } } diff --git a/ipc/tests/nested.rs b/ipc/tests/nested.rs new file mode 100644 index 000000000..1deff75bb --- /dev/null +++ b/ipc/tests/nested.rs @@ -0,0 +1,17 @@ +// 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 . + +include!(concat!(env!("OUT_DIR"), "/nested_cg.rs")); diff --git a/ipc/tests/nested.rs.in b/ipc/tests/nested.rs.in new file mode 100644 index 000000000..2f59ce6b0 --- /dev/null +++ b/ipc/tests/nested.rs.in @@ -0,0 +1,44 @@ +// 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 std::sync::RwLock; +use std::ops::*; +use ipc::IpcConfig; + +pub struct DB { + pub writes: RwLock, + pub reads: RwLock, + pub holdings: L, +} + +trait DBWriter { + fn write(&self, data: Vec) -> Result<(), DBError>; +} + +impl IpcConfig for DB {} + +#[derive(Serialize, Deserialize)] +pub enum DBError { Write, Read } + +#[derive(Ipc)] +impl DBWriter for DB { + fn write(&self, data: Vec) -> Result<(), DBError> { + let mut writes = self.writes.write().unwrap(); + *writes = *writes + data.len() as u64; + Ok(()) + } +} + diff --git a/ipc/tests/run.rs b/ipc/tests/run.rs index ce95f35d4..e694f32b0 100644 --- a/ipc/tests/run.rs +++ b/ipc/tests/run.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +#![allow(dead_code)] + extern crate bincode; extern crate ethcore_ipc as ipc; extern crate serde; @@ -26,3 +28,4 @@ extern crate ethcore_util as util; pub mod service; mod examples; mod over_nano; +mod nested;