Repository: paritytech/jsonrpsee (Rust)

Issue:
Can't use `params` as parameter name when using `subscription` proc-macro attribute
Rust toolchain

```
stable-aarch64-apple-darwin (default)
rustc 1.77.2 (25ef9e3d8 2024-04-09)
```

Example:

```toml
[package]
name = "rpc-demo"
version = "0.1.0"
edition = "2021"

[dependencies]
jsonrpsee = { git = "https://github.com/paritytech/jsonrpsee", branch = "master", features = ["client-core", "server-core", "macros"] }
serde = { version = "1.0", features = ["derive"] }
```

```rust
use jsonrpsee::{core::SubscriptionResult, proc_macros::rpc};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
pub enum PubSubKind {
    A,
    B,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PubSubParams {
    params: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PubSubItem {
    result: String,
}

#[rpc(client, server, namespace = "foo")]
#[async_trait]
pub trait FooPubSubApi {
    // Error
    // #[subscription(name = "subscribe" => "subscription", unsubscribe = "unsubscribe", item = PubSubItem)]
    // async fn sub(&self, kind: PubSubKind, params: Option<PubSubParams>) -> SubscriptionResult;
    
    // OK
    #[subscription(name = "subscribe" => "subscription", unsubscribe = "unsubscribe", item = PubSubItem)]
    async fn sub(&self, kind: PubSubKind, pubsub_params: Option<PubSubParams>) -> SubscriptionResult;
}
```

Error:
```
error[E0277]: the trait bound `ArrayParams: Serialize` is not satisfied
   --> src/lib.rs:24:40
    |
20  | #[rpc(client, server, namespace = "foo")]
    | ----------------------------------------- required by a bound introduced by this call
...
24  |     async fn sub(&self, kind: PubSubKind, params: Option<PubSubParams>) -> Subscript...
    |                                           ^^^^^^ the trait `Serialize` is not implemented for `ArrayParams`
    |
    = help: the following other types implement trait `Serialize`:
              bool
              char
              isize
              i8
              i16
              i32
              i64
              i128
            and 148 others
note: required by a bound in `ArrayParams::insert`
   --> /Users/qinxuan/.cargo/git/checkouts/jsonrpsee-5ae67701a3cedc92/c908eeb/core/src/params.rs:200:19
    |
200 |     pub fn insert<P: Serialize>(&mut self, value: P) -> Result<(), serde_json::Error> {
    |                      ^^^^^^^^^ required by this bound in `ArrayParams::insert`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `rpc-demo` (lib) due to 1 previous error
```



Discussion:
cc @niklasad1 
It seems to be a naming conflict somewhere because this works:

```rust
#[derive(Debug, Serialize, Deserialize)]
pub enum PubSubKind {
	A,
	B,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PubSubParams {
	params: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PubSubItem {
	result: String,
}

#[rpc(client, server, namespace = "foo")]
#[async_trait]
pub trait FooPubSubApi {
	#[subscription(name = "subscribe2" => "subscription2", unsubscribe = "unsubscribe2", item = PubSubItem)]
	async fn sub2(&self, kind: PubSubKind, _params: Option<PubSubParams>) -> SubscriptionResult;

	// OK
	#[subscription(name = "subscribe" => "subscription", unsubscribe = "unsubscribe", item = PubSubItem)]
	async fn sub(&self, kind: PubSubKind, pubsub_params: Option<PubSubParams>) -> SubscriptionResult;
}
```
> It seems to be a naming conflict somewhere because this works:

Yes, I think so too

https://github.com/paritytech/jsonrpsee/blob/c908eebc7f00b4ac7fdda4c6cead27af2019f796/proc-macros/src/render_client.rs#L242-L254


@niklasad1 Could you publish a patch release?
Done



Patch:
diff --git a/proc-macros/src/render_client.rs b/proc-macros/src/render_client.rs
index 41312f32ed..4bad4626d3 100644
--- a/proc-macros/src/render_client.rs
+++ b/proc-macros/src/render_client.rs
@@ -29,7 +29,7 @@ use crate::rpc_macro::{RpcDescription, RpcMethod, RpcSubscription};
 use proc_macro2::TokenStream as TokenStream2;
 use quote::{quote, quote_spanned};
 use syn::spanned::Spanned;
-use syn::{AngleBracketedGenericArguments, FnArg, Pat, PatIdent, PatType, PathArguments, TypeParam};
+use syn::{AngleBracketedGenericArguments, FnArg, Ident, Pat, PatIdent, PatType, PathArguments, TypeParam};
 
 impl RpcDescription {
 	pub(super) fn render_client(&self) -> Result<TokenStream2, syn::Error> {
@@ -163,6 +163,8 @@ impl RpcDescription {
 		let method = quote! {
 			#docs
 			#deprecated
+			#[allow(non_snake_case)]
+			#[allow(clippy::used_underscore_binding)]
 			async fn #rust_method_name(#rust_method_params) -> #returns {
 				let params = { #parameter_builder };
 				self.#called_method(#rpc_method_name, params).await
@@ -196,6 +198,8 @@ impl RpcDescription {
 
 		let method = quote! {
 			#docs
+			#[allow(non_snake_case)]
+			#[allow(clippy::used_underscore_binding)]
 			async fn #rust_method_name(#rust_method_params) -> #returns {
 				let params = #parameter_builder;
 				self.subscribe(#rpc_sub_name, params, #rpc_unsub_name).await
@@ -210,7 +214,10 @@ impl RpcDescription {
 		param_kind: &ParamKind,
 		signature: &syn::TraitItemFn,
 	) -> TokenStream2 {
+		const ILLEGAL_PARAM_NAME: &str = "__RpcParams__";
+
 		let jsonrpsee = self.jsonrpsee_client_path.as_ref().unwrap();
+		let p = Ident::new(ILLEGAL_PARAM_NAME, proc_macro2::Span::call_site());
 
 		if params.is_empty() {
 			return quote!({
@@ -218,6 +225,12 @@ impl RpcDescription {
 			});
 		}
 
+		if params.iter().any(|(param, _)| param.ident == p) {
+			panic!(
+				"Cannot use `{}` as a parameter name because it's overlapping with an internal variable in the generated code. Change it something else to make it work", ILLEGAL_PARAM_NAME
+			);
+		}
+
 		match param_kind {
 			ParamKind::Map => {
 				// Extract parameter names.
@@ -229,27 +242,37 @@ impl RpcDescription {
 					let (value, _value_type) = pair.1;
 					quote!(#name, #value)
 				});
+
+				// It's possible that the user has a parameter named `ILLEGAL_PARAM_NAME` in there API
+				// which would conflict with our internal parameter name
+				//
+				// We will throw an error if that is the case.
+				if param_names.iter().any(|name| name == ILLEGAL_PARAM_NAME) {
+					panic!("Cannot use `{}` as a parameter name", ILLEGAL_PARAM_NAME);
+				}
+
 				quote!({
-					let mut params = #jsonrpsee::core::params::ObjectParams::new();
+					let mut #p = #jsonrpsee::core::params::ObjectParams::new();
 					#(
-						if let Err(err) = params.insert( #params_insert ) {
+						if let Err(err) = #p.insert( #params_insert ) {
 							panic!("Parameter `{}` cannot be serialized: {:?}", stringify!( #params_insert ), err);
 						}
 					)*
-					params
+					#p
 				})
 			}
 			ParamKind::Array => {
 				// Throw away the type.
 				let params = params.iter().map(|(param, _param_type)| param);
+
 				quote!({
-					let mut params = #jsonrpsee::core::params::ArrayParams::new();
+					let mut #p = #jsonrpsee::core::params::ArrayParams::new();
 					#(
-						if let Err(err) = params.insert( #params ) {
+						if let Err(err) = #p.insert( #params ) {
 							panic!("Parameter `{}` cannot be serialized: {:?}", stringify!( #params ), err);
 						}
 					)*
-					params
+					#p
 				})
 			}
 		}


Test patch:
diff --git a/tests/proc-macro-core/src/lib.rs b/tests/proc-macro-core/src/lib.rs
index b98efa8eda..c7266847a1 100644
--- a/tests/proc-macro-core/src/lib.rs
+++ b/tests/proc-macro-core/src/lib.rs
@@ -31,7 +31,7 @@ pub trait Api {
 	async fn async_call(&self, a: String) -> Result<String, ErrorObjectOwned>;
 
 	#[subscription(name = "subscribe", item = PubSubItem)]
-	async fn sub(&self, kind: PubSubKind, p: PubSubParams) -> SubscriptionResult;
+	async fn sub(&self, kind: PubSubKind, params: PubSubParams) -> SubscriptionResult;
 
 	#[subscription(name = "subscribeSync", item = String)]
 	fn sync_sub(&self, a: String) -> SubscriptionResult;