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

[proc macros]: support generic type params #436

Merged
merged 34 commits into from
Aug 27, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
faf78f1
PoC support generic type params
niklasad1 Aug 20, 2021
351acaf
more annoying example
niklasad1 Aug 20, 2021
66640e7
add trait bounds for generic params in proc macros
niklasad1 Aug 20, 2021
1f91aac
add compile-time test for complicated trait
niklasad1 Aug 20, 2021
230613e
smarter trait bounds in proc macros
niklasad1 Aug 23, 2021
4ed5b58
add non-working example for now
niklasad1 Aug 24, 2021
f442612
revert nits
niklasad1 Aug 24, 2021
834990e
Update examples/proc_macro.rs
niklasad1 Aug 25, 2021
fbb6f00
Update proc-macros/src/helpers.rs
niklasad1 Aug 25, 2021
6e5bcf1
add messy code but works
niklasad1 Aug 25, 2021
ab0b2ec
cleanup
niklasad1 Aug 25, 2021
63c48bd
add some simple compile check in tests
niklasad1 Aug 25, 2021
c7ce163
Merge branch 'na-proc-macros-generics' of github.com:paritytech/jsonr…
niklasad1 Aug 25, 2021
c843a99
Merge remote-tracking branch 'origin/master' into na-proc-macros-gene…
niklasad1 Aug 25, 2021
f04aeaf
fix doc link
niklasad1 Aug 25, 2021
52f61b2
fix doc link last time
niklasad1 Aug 25, 2021
64b33fb
address grumbles
niklasad1 Aug 26, 2021
c9b00c8
docs
niklasad1 Aug 26, 2021
8bff2e9
Update proc-macros/src/helpers.rs
niklasad1 Aug 26, 2021
fbd9683
Update proc-macros/src/helpers.rs
niklasad1 Aug 26, 2021
9f0b300
Update proc-macros/src/helpers.rs
niklasad1 Aug 26, 2021
3065c04
Update proc-macros/src/helpers.rs
niklasad1 Aug 27, 2021
6971610
Update proc-macros/src/visitor.rs
niklasad1 Aug 27, 2021
6f3fed9
fix nit: | -> ||
niklasad1 Aug 27, 2021
6abda51
Update proc-macros/src/helpers.rs
niklasad1 Aug 27, 2021
d86752a
Update proc-macros/src/helpers.rs
niklasad1 Aug 27, 2021
4c83ecc
Update proc-macros/src/helpers.rs
niklasad1 Aug 27, 2021
6ad7bc2
add issues to introduced TODOs
niklasad1 Aug 27, 2021
f8facb2
generics support where clause on trait
niklasad1 Aug 27, 2021
e197af5
Update proc-macros/src/helpers.rs
niklasad1 Aug 27, 2021
27c3a15
Update proc-macros/src/helpers.rs
niklasad1 Aug 27, 2021
ebd7147
address grumbles
niklasad1 Aug 27, 2021
3626e15
Merge branch 'na-proc-macros-generics' of github.com:paritytech/jsonr…
niklasad1 Aug 27, 2021
ba52568
add more docs
niklasad1 Aug 27, 2021
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
5 changes: 4 additions & 1 deletion examples/proc_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ type ExampleHash = [u8; 32];
type ExampleStorageKey = Vec<u8>;

#[rpc(server, client, namespace = "state")]
pub trait Rpc<Hash: std::fmt::Debug, StorageKey> {
pub trait Rpc<Hash: Clone, StorageKey>
where
Hash: std::fmt::Debug,
{
/// Async method call example.
#[method(name = "getKeys")]
async fn storage_keys(&self, storage_key: StorageKey, hash: Option<Hash>) -> Result<Vec<StorageKey>, Error>;
Expand Down
62 changes: 26 additions & 36 deletions proc-macros/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use proc_macro2::Span;
use proc_macro_crate::{crate_name, FoundCrate};
use quote::quote;
use std::collections::HashSet;
use syn::{parse_quote, punctuated::Punctuated, visit::Visit, GenericParam, Generics, Token};
use syn::{parse_quote, punctuated::Punctuated, visit::Visit, Token};

/// Search for client-side `jsonrpsee` in `Cargo.toml`.
pub(crate) fn find_jsonrpsee_client_crate() -> Result<proc_macro2::TokenStream, syn::Error> {
Expand Down Expand Up @@ -61,12 +61,15 @@ fn find_jsonrpsee_crate(http_name: &str, ws_name: &str) -> Result<proc_macro2::T
}
}

/// Traversing the `trait RPC definition` and applies the required bounds for the generic type parameters.
/// This actually depends on whether the actual type parameter in used a parameter, return value or subscription result.
/// Traverses the RPC trait definition and applies the required bounds for the generic type parameters that are used.
/// The bounds applies depends on whether the type parameter is used as a parameter, return value or subscription result
niklasad1 marked this conversation as resolved.
Show resolved Hide resolved
/// and whether the it's used in client or server mode.
niklasad1 marked this conversation as resolved.
Show resolved Hide resolved
/// Type params get `Send + Sync + 'static` bounds and input/output parameters get `Serialize` and/or `DeserializeOwned` bounds.
/// Inspired by <https://github.com/paritytech/jsonrpc/blob/master/derive/src/to_delegate.rs#L414>
///
/// Example:
///
/// #[rpc(client)]
/// #[rpc(client, server)]
/// pub trait RpcTrait<A, B, C> {
/// #[method(name = "call")]
/// fn call(&self, a: A) -> B;
Expand All @@ -75,36 +78,14 @@ fn find_jsonrpsee_crate(http_name: &str, ws_name: &str) -> Result<proc_macro2::T
/// fn sub(&self);
/// }
///
/// Because `item` is not parsed as ordinary rust syntax, the actual `syn::Type` is traversed to find
/// Because the `item` attribute is not parsed as ordinary rust syntax, the `syn::Type` is traversed to find
/// each generic parameter of it.
/// This is used an additional input before traversing the entire trait.
/// This is used as an additional input before traversing the entire trait.
/// Otherwise, it's not possible to know whether a type parameter is used for subscription result.
pub(crate) fn client_add_trait_bounds(item_trait: &syn::ItemTrait, sub_tys: &[syn::Type]) -> Generics {
let visitor = visit_trait(item_trait, sub_tys);
let mut generics = item_trait.generics.clone();

for param in &mut generics.params {
if let GenericParam::Type(ty) = param {
ty.bounds.push(parse_quote!(Send));
ty.bounds.push(parse_quote!(Sync));
ty.bounds.push(parse_quote!('static));

if visitor.input_params.contains(&ty.ident) {
ty.bounds.push(parse_quote!(jsonrpsee::types::Serialize))
}
if visitor.ret_params.contains(&ty.ident) || visitor.sub_params.contains(&ty.ident) {
ty.bounds.push(parse_quote!(jsonrpsee::types::DeserializeOwned))
}
}
}
generics
}

/// Similar to `client_add_trait_bounds` but the logic is reversed for the trait bounds
/// however in contrast it generates the where clause for the server trait instead of generics.
pub(crate) fn server_generate_where_clause(
pub(crate) fn generate_where_clause(
item_trait: &syn::ItemTrait,
sub_tys: &[syn::Type],
is_client: bool,
) -> Vec<syn::WherePredicate> {
let visitor = visit_trait(item_trait, sub_tys);
let additional_where_clause = item_trait.generics.where_clause.clone();
Expand All @@ -116,12 +97,21 @@ pub(crate) fn server_generate_where_clause(
let ty_path = syn::TypePath { qself: None, path: ty.ident.clone().into() };
let mut bounds: Punctuated<syn::TypeParamBound, Token![+]> = parse_quote!(Send + Sync + 'static);

if visitor.input_params.contains(&ty.ident) {
bounds.push(parse_quote!(jsonrpsee::types::DeserializeOwned))
}
if is_client {
if visitor.input_params.contains(&ty.ident) {
bounds.push(parse_quote!(jsonrpsee::types::Serialize))
}
if visitor.ret_params.contains(&ty.ident) || visitor.sub_params.contains(&ty.ident) {
bounds.push(parse_quote!(jsonrpsee::types::DeserializeOwned))
}
} else {
if visitor.input_params.contains(&ty.ident) {
bounds.push(parse_quote!(jsonrpsee::types::DeserializeOwned))
}

niklasad1 marked this conversation as resolved.
Show resolved Hide resolved
if visitor.ret_params.contains(&ty.ident) || visitor.sub_params.contains(&ty.ident) {
bounds.push(parse_quote!(jsonrpsee::types::Serialize))
if visitor.ret_params.contains(&ty.ident) || visitor.sub_params.contains(&ty.ident) {
bounds.push(parse_quote!(jsonrpsee::types::Serialize))
}
}

// Add the trait bounds specified in the trait.
Expand All @@ -147,7 +137,7 @@ pub(crate) fn server_generate_where_clause(
.collect()
}

/// Traversing the `RPC trait` by first find the subscription parameters and then all elements
/// Traverse the RPC trait by first finding the subscription parameters and then all elements
/// needed for generating the `client` and `server` traits/implementations.
fn visit_trait(item_trait: &syn::ItemTrait, sub_tys: &[syn::Type]) -> FindAllParams {
let type_params: HashSet<_> = item_trait.generics.type_params().map(|t| t.ident.clone()).collect();
Expand Down
13 changes: 6 additions & 7 deletions proc-macros/src/render_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

use crate::helpers::client_add_trait_bounds;
use crate::helpers::generate_where_clause;
use crate::rpc_macro::{RpcDescription, RpcMethod, RpcSubscription};
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
Expand All @@ -36,9 +36,9 @@ impl RpcDescription {
let sub_tys: Vec<syn::Type> = self.subscriptions.clone().into_iter().map(|s| s.item).collect();

let trait_name = quote::format_ident!("{}Client", &self.trait_def.ident);
let tweaked_generics = client_add_trait_bounds(&self.trait_def, &sub_tys);
let type_idents = tweaked_generics.type_params().collect::<Vec<&TypeParam>>();
let (impl_generics, type_generics, where_clause) = tweaked_generics.split_for_impl();
let where_clause = generate_where_clause(&self.trait_def, &sub_tys, true);
let type_idents = self.trait_def.generics.type_params().collect::<Vec<&TypeParam>>();
let (impl_generics, type_generics, _) = self.trait_def.generics.split_for_impl();

let super_trait = if self.subscriptions.is_empty() {
quote! { #jsonrpsee::types::traits::Client }
Expand All @@ -58,13 +58,12 @@ impl RpcDescription {
let trait_impl = quote! {
#[#async_trait]
#[doc = #doc_comment]
pub trait #trait_name #impl_generics: #super_trait where #where_clause {
pub trait #trait_name #impl_generics: #super_trait where #(#where_clause,)* {
#(#method_impls)*
#(#sub_impls)*
}

// TODO(niklasad1): support for where clause on trait def.
impl<T #(,#type_idents)*> #trait_name #type_generics for T where T: #super_trait {}
impl<T #(,#type_idents)*> #trait_name #type_generics for T where T: #super_trait #(,#where_clause)* {}
};

Ok(trait_impl)
Expand Down
9 changes: 5 additions & 4 deletions proc-macros/src/render_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

use super::lifetimes::replace_lifetimes;
use super::RpcDescription;
use crate::helpers::server_generate_where_clause;
use crate::helpers::generate_where_clause;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, quote_spanned};
use std::collections::HashSet;
Expand All @@ -35,6 +35,7 @@ impl RpcDescription {
pub(super) fn render_server(&self) -> Result<TokenStream2, syn::Error> {
let trait_name = quote::format_ident!("{}Server", &self.trait_def.ident);
let generics = self.trait_def.generics.clone();
let (impl_generics, _, where_clause) = generics.split_for_impl();

let method_impls = self.render_methods()?;
let into_rpc_impl = self.render_into_rpc()?;
Expand All @@ -47,7 +48,7 @@ impl RpcDescription {
let trait_impl = quote! {
#[#async_trait]
#[doc = #doc_comment]
pub trait #trait_name #generics: Sized + Send + Sync + 'static {
pub trait #trait_name #impl_generics: Sized + Send + Sync + 'static #where_clause {
#method_impls
#into_rpc_impl
}
Expand Down Expand Up @@ -166,7 +167,7 @@ impl RpcDescription {
and adds them into a single `RpcModule`.";

let sub_tys: Vec<syn::Type> = self.subscriptions.clone().into_iter().map(|s| s.item).collect();
let where_clause = server_generate_where_clause(&self.trait_def, &sub_tys);
let where_clause = generate_where_clause(&self.trait_def, &sub_tys, false);

// NOTE(niklasad1): empty where clause is valid rust syntax.
Ok(quote! {
Expand Down Expand Up @@ -251,7 +252,7 @@ impl RpcDescription {

// Parsing of `serde_json::Value`.
let parsing = quote! {
// TODO(niklasad1): add support for JSON object.
// TODO: https://github.com/paritytech/jsonrpsee/issues/445
/*let (#params_fields) = if params.is_object() {
#decode_map
} else {
Expand Down
24 changes: 23 additions & 1 deletion tests/tests/proc_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,28 @@ mod rpc_impl {
fn sub(&self, hash: Input);
}

/// Trait to ensure that the trait bounds are correct.
#[rpc(client, server, namespace = "generic_with_where_clause")]
pub trait GenericWhereClause<I, R>
where
I: std::fmt::Debug,
R: Copy + Clone,
{
#[method(name = "getHeader")]
fn call(&self, input: I) -> JsonRpcResult<R>;
}

/// Trait to ensure that the trait bounds are correct.
#[rpc(client, server, namespace = "generic_with_where_clause")]
pub trait GenericWhereClauseWithTypeBoundsToo<I: Copy + Clone, R>
where
I: std::fmt::Debug,
R: Copy + Clone,
{
#[method(name = "getHeader")]
fn call(&self, input: I) -> JsonRpcResult<R>;
}

pub struct RpcServerImpl;

#[async_trait]
Expand Down Expand Up @@ -228,7 +250,7 @@ async fn macro_optional_param_parsing() {

assert_eq!(result, r#"{"jsonrpc":"2.0","result":"Called with: 42, None, Some(70)","id":0}"#);

// TODO(niklasad1): support for JSON map/object in proc macros, this will always fail now.
// TODO: https://github.com/paritytech/jsonrpsee/issues/445
// Named params using a map
let params = RawValue::from_string(r#"{"a": 22, "c": 50}"#.into()).ok();
let result = module.call("foo_optional_params", params).await.unwrap();
Expand Down