From 5c1d3a0018c3a502b185e69c4f57c3b75b35e97b Mon Sep 17 00:00:00 2001 From: Shane Date: Fri, 23 Feb 2024 10:12:00 -0800 Subject: [PATCH] Support custom derives on Request and Response Replace the bespoke "derive_serde" with a more flexible "derive = [, , ...]" form. Deprecate the old "derive_serde" form, and emit deprecation warnings. Add trybuild tests in a separate test crate to ensure the deprecation, features, and error messages all behave as expected. This technique of a separate trybuild test crate is borrowed from tokio/tests-build. Update CI to run this new crate's tests under the serde matrix. --- .github/workflows/main.yml | 11 +- Cargo.toml | 1 + plugins/Cargo.toml | 1 + plugins/src/lib.rs | 305 ++++++++++++++---- plugins/tests/service.rs | 53 ++- tests-build/Cargo.toml | 13 + tests-build/src/lib.rs | 1 + tests-build/tests/service.rs | 13 + .../tests/trybuild/service/deprecated.rs | 8 + .../tests/trybuild/service/deprecated.stderr | 15 + .../tests/trybuild/service/incompatible.rs | 7 + .../trybuild/service/incompatible.stderr | 7 + .../tests/trybuild/service/opt_out_serde.rs | 10 + .../trybuild/service/opt_out_serde.stderr | 12 + .../no_explicit_serde_without_feature.rs | 9 + .../no_explicit_serde_without_feature.stderr | 11 + .../no_implicit_serde_without_feature.rs | 9 + .../no_implicit_serde_without_feature.stderr | 8 + 18 files changed, 423 insertions(+), 71 deletions(-) create mode 100644 tests-build/Cargo.toml create mode 100644 tests-build/src/lib.rs create mode 100644 tests-build/tests/service.rs create mode 100644 tests-build/tests/trybuild/service/deprecated.rs create mode 100644 tests-build/tests/trybuild/service/deprecated.stderr create mode 100644 tests-build/tests/trybuild/service/incompatible.rs create mode 100644 tests-build/tests/trybuild/service/incompatible.stderr create mode 100644 tests-build/tests/trybuild/service/opt_out_serde.rs create mode 100644 tests-build/tests/trybuild/service/opt_out_serde.stderr create mode 100644 tests-build/tests/trybuild/service_no_serde1/no_explicit_serde_without_feature.rs create mode 100644 tests-build/tests/trybuild/service_no_serde1/no_explicit_serde_without_feature.stderr create mode 100644 tests-build/tests/trybuild/service_no_serde1/no_implicit_serde_without_feature.rs create mode 100644 tests-build/tests/trybuild/service_no_serde1/no_implicit_serde_without_feature.stderr diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5446aab5..f84e712e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -56,11 +56,12 @@ jobs: access_token: ${{ github.token }} - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - run: > - cargo test --manifest-path tarpc/Cargo.toml - ${{ matrix.serde }} ${{ matrix.tokio }} ${{ matrix.serde-transport }} - ${{ matrix.serde-transport-json }} ${{ matrix.serde-transport-bincode }} - ${{ matrix.tcp }} ${{ matrix.unix }} + - run: | + cargo test --manifest-path tarpc/Cargo.toml \ + ${{ matrix.serde }} ${{ matrix.tokio }} ${{ matrix.serde-transport }} \ + ${{ matrix.serde-transport-json }} ${{ matrix.serde-transport-bincode }} \ + ${{ matrix.tcp }} ${{ matrix.unix }} \ + cargo test --manifest-path tests-build/Cargo.toml $ {{ matrix.serde }} list-examples: name: List Examples diff --git a/Cargo.toml b/Cargo.toml index c7557d06..174e0faf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "example-service", "tarpc", "plugins", + "tests-build", ] [profile.dev] diff --git a/plugins/Cargo.toml b/plugins/Cargo.toml index 287a1eaf..daccafd0 100644 --- a/plugins/Cargo.toml +++ b/plugins/Cargo.toml @@ -23,6 +23,7 @@ travis-ci = { repository = "google/tarpc" } proc-macro2 = "1.0" quote = "1.0" syn = { version = "2.0", features = ["full"] } +proc-macro-warning = "1" [lib] proc-macro = true diff --git a/plugins/src/lib.rs b/plugins/src/lib.rs index 8befe78f..331a385d 100644 --- a/plugins/src/lib.rs +++ b/plugins/src/lib.rs @@ -13,6 +13,7 @@ extern crate syn; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; +use proc_macro_warning::Warning; use quote::{format_ident, quote, ToTokens}; use syn::{ braced, @@ -22,7 +23,7 @@ use syn::{ parse_macro_input, parse_quote, spanned::Spanned, token::Comma, - AttrStyle, Attribute, Expr, FnArg, Ident, Lit, LitBool, MetaNameValue, Pat, PatType, + AttrStyle, Attribute, Expr, FnArg, Ident, Lit, LitBool, MetaNameValue, Pat, PatType, Path, ReturnType, Token, Type, Visibility, }; @@ -141,14 +142,37 @@ impl Parse for RpcMethod { } } -// If `derive_serde` meta item is not present, defaults to cfg!(feature = "serde1"). -// `derive_serde` can only be true when serde1 is enabled. -struct DeriveSerde(bool); +#[derive(Default)] +struct DeriveMeta { + derive: Option, + warnings: Vec, +} + +impl DeriveMeta { + fn with_derives(mut self, new: Vec) -> Self { + match self.derive.as_mut() { + Some(Derive::Explicit(old)) => old.extend(new), + _ => self.derive = Some(Derive::Explicit(new)), + } + + self + } +} + +enum Derive { + Explicit(Vec), + Serde(bool), +} -impl Parse for DeriveSerde { +impl Parse for DeriveMeta { fn parse(input: ParseStream) -> syn::Result { - let mut result = Ok(None); + let mut result = Ok(DeriveMeta::default()); + + let mut derives = Vec::new(); let mut derive_serde = Vec::new(); + let mut has_derive_serde = false; + let mut has_explicit_derives = false; + let meta_items = input.parse_terminated(MetaNameValue::parse, Comma)?; for meta in meta_items { if meta.path.segments.len() != 1 { @@ -162,47 +186,112 @@ impl Parse for DeriveSerde { continue; } let segment = meta.path.segments.first().unwrap(); - if segment.ident != "derive_serde" { - extend_errors!( - result, - syn::Error::new( - meta.span(), - "tarpc::service does not support this meta item" - ) - ); - continue; - } - let Expr::Lit(expr_lit) = &meta.value else { - extend_errors!( - result, - syn::Error::new(meta.value.span(), "expected literal") - ); - continue; - }; - match expr_lit.lit { - Lit::Bool(LitBool { value: true, .. }) if cfg!(feature = "serde1") => { - result = result.and(Ok(Some(true))) - } - Lit::Bool(LitBool { value: true, .. }) => { + if segment.ident == "derive" { + has_explicit_derives = true; + let Expr::Array(ref array) = meta.value else { extend_errors!( result, syn::Error::new( meta.span(), - "To enable serde, first enable the `serde1` feature of tarpc" + "tarpc::service does not support this meta item" ) ); + continue; + }; + + let paths = array + .elems + .iter() + .filter_map(|e| { + if let Expr::Path(path) = e { + Some(path.path.clone()) + } else { + extend_errors!( + result, + syn::Error::new(e.span(), "Expected Path or Type") + ); + None + } + }) + .collect::>(); + + result = result.map(|d| d.with_derives(paths)); + derives.push(meta); + } else if segment.ident == "derive_serde" { + has_derive_serde = true; + let Expr::Lit(expr_lit) = &meta.value else { + extend_errors!( + result, + syn::Error::new(meta.value.span(), "expected literal") + ); + continue; + }; + match expr_lit.lit { + Lit::Bool(LitBool { value: true, .. }) if cfg!(feature = "serde1") => { + result = result.map(|d| DeriveMeta { + derive: Some(Derive::Serde(true)), + ..d + }) + } + Lit::Bool(LitBool { value: true, .. }) => { + extend_errors!( + result, + syn::Error::new( + meta.span(), + "To enable serde, first enable the `serde1` feature of tarpc" + ) + ); + } + Lit::Bool(LitBool { value: false, .. }) => { + result = result.map(|d| DeriveMeta { + derive: Some(Derive::Serde(false)), + ..d + }) + } + _ => extend_errors!( + result, + syn::Error::new( + expr_lit.lit.span(), + "`derive_serde` expects a value of type `bool`" + ) + ), } - Lit::Bool(LitBool { value: false, .. }) => result = result.and(Ok(Some(false))), - _ => extend_errors!( + derive_serde.push(meta); + } else { + extend_errors!( result, syn::Error::new( - expr_lit.lit.span(), - "`derive_serde` expects a value of type `bool`" + meta.span(), + "tarpc::service does not support this meta item" ) - ), + ); + continue; } - derive_serde.push(meta); } + + if has_derive_serde { + let warning = Warning::new_deprecated("UseDeriveSerde") + .old("use tarpc::service(derive_serde = true)") + .new("use tarpc::service(derive = [Serialize, Deserialize])") + .span(input.span()) + .build_or_panic(); + + result = result.map(|mut d| { + d.warnings.push(warning); + d + }); + } + + if has_explicit_derives & has_derive_serde { + extend_errors!( + result, + syn::Error::new( + input.span(), + "tarpc does not support `derive_serde` and `derive` at the same time" + ) + ); + } + if derive_serde.len() > 1 { for (i, derive_serde) in derive_serde.iter().enumerate() { extend_errors!( @@ -217,8 +306,20 @@ impl Parse for DeriveSerde { ); } } - let derive_serde = result?.unwrap_or(cfg!(feature = "serde1")); - Ok(Self(derive_serde)) + + if derives.len() > 1 { + for (i, derive) in derives.iter().enumerate() { + extend_errors!( + result, + syn::Error::new( + derive.span(), + format!("`derive` appears more than once (occurrence #{})", i + 1) + ) + ); + } + } + + result } } @@ -232,6 +333,7 @@ impl Parse for DeriveSerde { /// # struct Foo; /// ``` #[proc_macro_attribute] +#[cfg(feature = "serde1")] pub fn derive_serde(_attr: TokenStream, item: TokenStream) -> TokenStream { let mut gen: proc_macro2::TokenStream = quote! { #[derive(::tarpc::serde::Serialize, ::tarpc::serde::Deserialize)] @@ -260,16 +362,54 @@ fn collect_cfg_attrs(rpcs: &[RpcMethod]) -> Vec> { .collect::>() } -/// Generates: -/// - service trait -/// - serve fn -/// - client stub struct -/// - new_stub client factory fn -/// - Request and Response enums -/// - ResponseFut Future +/// This macro generates the machinery used by both the client and server. +/// +/// Namely, it produces: +/// - a serve fn inside the trait +/// - client stub struct +/// - Request and Response enums +/// +/// # Example +/// +/// ```no_run +/// use tarpc::{client, transport, service, server::{self, Channel}, context::Context}; +/// +/// #[service] +/// pub trait Calculator { +/// async fn add(a: i32, b: i32) -> i32; +/// } +/// +/// // The request type looks like the following. +/// // Note, you don't have to interact with this type directly outside +/// // of testing, it is used by the client and server implementation +/// let req = CalculatorRequest::Add {a: 5, b: 7}; +/// +/// // This would be the associated response, again you don't ofent use this, +/// // it is only shown for educational purposes. +/// let resp = CalculatorResponse::Add(12); +/// +/// // This could be any transport. +/// let (client_side, server_side) = transport::channel::unbounded(); +/// +/// // A client can be made like so: +/// let client = CalculatorClient::new(client::Config::default(), client_side); +/// +/// // And a server like so: +/// #[derive(Clone)] +/// struct CalculatorServer; +/// impl Calculator for CalculatorServer { +/// async fn add(self, context: Context, a: i32, b: i32) -> i32 { +/// a + b +/// } +/// } +/// +/// // You would usually spawn on an async runtime. +/// let server = server::BaseChannel::with_defaults(server_side); +/// let _ = server.execute(CalculatorServer.serve()); +/// ``` #[proc_macro_attribute] pub fn service(attr: TokenStream, input: TokenStream) -> TokenStream { - let derive_serde = parse_macro_input!(attr as DeriveSerde); + let derive_meta = parse_macro_input!(attr as DeriveMeta); let unit_type: &Type = &parse_quote!(()); let Service { ref attrs, @@ -283,13 +423,41 @@ pub fn service(attr: TokenStream, input: TokenStream) -> TokenStream { .map(|rpc| snake_to_camel(&rpc.ident.unraw().to_string())) .collect(); let args: &[&[PatType]] = &rpcs.iter().map(|rpc| &*rpc.args).collect::>(); - let derive_serialize = if derive_serde.0 { - Some( - quote! {#[derive(::tarpc::serde::Serialize, ::tarpc::serde::Deserialize)] - #[serde(crate = "::tarpc::serde")]}, - ) - } else { - None + + let derives = match derive_meta.derive.as_ref() { + Some(Derive::Explicit(paths)) => { + if !paths.is_empty() { + Some(quote! { + #[derive( + #( + #paths + ),* + )] + }) + } else { + None + } + } + Some(Derive::Serde(serde)) => { + if *serde { + Some(quote! { + #[derive(::tarpc::serde::Serialize, ::tarpc::serde::Deserialize)] + #[serde(crate = "::tarpc::serde")] + }) + } else { + None + } + } + None => { + if cfg!(feature = "serde1") { + Some(quote! { + #[derive(::tarpc::serde::Serialize, ::tarpc::serde::Deserialize)] + #[serde(crate = "::tarpc::serde")] + }) + } else { + None + } + } }; let methods = rpcs.iter().map(|rpc| &rpc.ident).collect::>(); @@ -316,7 +484,7 @@ pub fn service(attr: TokenStream, input: TokenStream) -> TokenStream { return_types: &rpcs .iter() .map(|rpc| match rpc.output { - ReturnType::Type(_, ref ty) => ty, + ReturnType::Type(_, ref ty) => ty.as_ref(), ReturnType::Default => unit_type, }) .collect::>(), @@ -329,7 +497,8 @@ pub fn service(attr: TokenStream, input: TokenStream) -> TokenStream { .zip(camel_case_fn_names.iter()) .map(|(rpc, name)| Ident::new(name, rpc.ident.span())) .collect::>(), - derive_serialize: derive_serialize.as_ref(), + derives: derives.as_ref(), + warnings: &derive_meta.warnings, } .into_token_stream() .into() @@ -355,7 +524,8 @@ struct ServiceGenerator<'a> { args: &'a [&'a [PatType]], return_types: &'a [&'a Type], arg_pats: &'a [Vec<&'a Pat>], - derive_serialize: Option<&'a TokenStream2>, + derives: Option<&'a TokenStream2>, + warnings: &'a [Warning], } impl<'a> ServiceGenerator<'a> { @@ -378,11 +548,11 @@ impl<'a> ServiceGenerator<'a> { .zip(return_types.iter()) .map( |( - RpcMethod { - attrs, ident, args, .. - }, - output, - )| { + RpcMethod { + attrs, ident, args, .. + }, + output, + )| { quote! { #( #attrs )* async fn #ident(self, context: ::tarpc::context::Context, #( #args ),*) -> #output; @@ -470,7 +640,7 @@ impl<'a> ServiceGenerator<'a> { fn enum_request(&self) -> TokenStream2 { let &Self { - derive_serialize, + derives, vis, request_ident, camel_case_idents, @@ -484,7 +654,7 @@ impl<'a> ServiceGenerator<'a> { /// The request sent over the wire from the client to the server. #[allow(missing_docs)] #[derive(Debug)] - #derive_serialize + #derives #vis enum #request_ident { #( #( #method_cfgs )* @@ -508,7 +678,7 @@ impl<'a> ServiceGenerator<'a> { fn enum_response(&self) -> TokenStream2 { let &Self { - derive_serialize, + derives, vis, response_ident, camel_case_idents, @@ -520,7 +690,7 @@ impl<'a> ServiceGenerator<'a> { /// The response sent over the wire from the server to the client. #[allow(missing_docs)] #[derive(Debug)] - #derive_serialize + #derives #vis enum #response_ident { #( #camel_case_idents(#return_types) ),* } @@ -628,6 +798,10 @@ impl<'a> ServiceGenerator<'a> { } } } + + fn emit_warnings(&self) -> TokenStream2 { + self.warnings.iter().map(|w| w.to_token_stream()).collect() + } } impl<'a> ToTokens for ServiceGenerator<'a> { @@ -641,7 +815,8 @@ impl<'a> ToTokens for ServiceGenerator<'a> { self.struct_client(), self.impl_client_new(), self.impl_client_rpc_methods(), - ]) + self.emit_warnings(), + ]); } } diff --git a/plugins/tests/service.rs b/plugins/tests/service.rs index 2af2b1d1..fc4c2f26 100644 --- a/plugins/tests/service.rs +++ b/plugins/tests/service.rs @@ -1,3 +1,5 @@ +use serde::{Deserialize, Serialize}; +use std::hash::Hash; use tarpc::context; #[test] @@ -72,7 +74,6 @@ fn service_with_cfg_rpc() { #[test] fn syntax() { - #[tarpc::service] trait Syntax { #[deny(warnings)] #[allow(non_snake_case)] @@ -92,3 +93,53 @@ fn syntax() { async fn one_arg_implicit_return_error(one: String); } } + +#[test] +fn custom_derives() { + #[tarpc::service(derive = [Clone, Hash])] + trait Foo { + async fn foo(); + } + + fn requires_clone(_: impl Clone) {} + fn requires_hash(_: impl Hash) {} + + let x = FooRequest::Foo {}; + requires_clone(x.clone()); + requires_hash(x); +} + +#[test] +fn implicit_serde() { + #[tarpc::service] + trait Foo { + async fn foo(); + } + + fn requires_serde(_: T) + where + for<'de> T: Serialize + Deserialize<'de>, + { + } + + let x = FooRequest::Foo {}; + requires_serde(x); +} + +#[allow(deprecated)] +#[test] +fn explicit_serde() { + #[tarpc::service(derive_serde = true)] + trait Foo { + async fn foo(); + } + + fn requires_serde(_: T) + where + for<'de> T: Serialize + Deserialize<'de>, + { + } + + let x = FooRequest::Foo {}; + requires_serde(x); +} diff --git a/tests-build/Cargo.toml b/tests-build/Cargo.toml new file mode 100644 index 00000000..66589334 --- /dev/null +++ b/tests-build/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "tests-build" +version = "0.1.0" +edition = "2021" + +[features] +serde1 = ["tarpc/serde1"] + +[dependencies] +tarpc = { path = "../tarpc" } + +[dev-dependencies] +trybuild = "1" \ No newline at end of file diff --git a/tests-build/src/lib.rs b/tests-build/src/lib.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/tests-build/src/lib.rs @@ -0,0 +1 @@ + diff --git a/tests-build/tests/service.rs b/tests-build/tests/service.rs new file mode 100644 index 00000000..0002ffe9 --- /dev/null +++ b/tests-build/tests/service.rs @@ -0,0 +1,13 @@ +#[test] +#[cfg(feature = "serde1")] +fn service() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/trybuild/service/*.rs"); +} + +#[test] +#[cfg(not(feature = "serde1"))] +fn service_no_serde1() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/trybuild/service_no_serde1/*.rs"); +} diff --git a/tests-build/tests/trybuild/service/deprecated.rs b/tests-build/tests/trybuild/service/deprecated.rs new file mode 100644 index 00000000..813cbfee --- /dev/null +++ b/tests-build/tests/trybuild/service/deprecated.rs @@ -0,0 +1,8 @@ +#![deny(warnings)] + +#[tarpc::service(derive_serde = true)] +trait Foo { + async fn foo(); +} + +fn main() {} diff --git a/tests-build/tests/trybuild/service/deprecated.stderr b/tests-build/tests/trybuild/service/deprecated.stderr new file mode 100644 index 00000000..4f148646 --- /dev/null +++ b/tests-build/tests/trybuild/service/deprecated.stderr @@ -0,0 +1,15 @@ +error: use of deprecated constant `UseDeriveSerde::_w`: + It is deprecated to use tarpc::service(derive_serde = true). + Please instead use tarpc::service(derive = [Serialize, Deserialize]). + --> tests/trybuild/service/deprecated.rs:3:1 + | +3 | #[tarpc::service(derive_serde = true)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> tests/trybuild/service/deprecated.rs:1:9 + | +1 | #![deny(warnings)] + | ^^^^^^^^ + = note: `#[deny(deprecated)]` implied by `#[deny(warnings)]` + = note: this error originates in the attribute macro `tarpc::service` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests-build/tests/trybuild/service/incompatible.rs b/tests-build/tests/trybuild/service/incompatible.rs new file mode 100644 index 00000000..3b1df9f2 --- /dev/null +++ b/tests-build/tests/trybuild/service/incompatible.rs @@ -0,0 +1,7 @@ +#![allow(deprecated)] +#[tarpc::service(derive = [Clone], derive_serde = true)] +trait Foo { + async fn foo(); +} + +fn main() {} diff --git a/tests-build/tests/trybuild/service/incompatible.stderr b/tests-build/tests/trybuild/service/incompatible.stderr new file mode 100644 index 00000000..eebc603c --- /dev/null +++ b/tests-build/tests/trybuild/service/incompatible.stderr @@ -0,0 +1,7 @@ +error: tarpc does not support `derive_serde` and `derive` at the same time + --> tests/trybuild/service/incompatible.rs:2:1 + | +2 | #[tarpc::service(derive = [Clone], derive_serde = true)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `tarpc::service` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests-build/tests/trybuild/service/opt_out_serde.rs b/tests-build/tests/trybuild/service/opt_out_serde.rs new file mode 100644 index 00000000..22089b31 --- /dev/null +++ b/tests-build/tests/trybuild/service/opt_out_serde.rs @@ -0,0 +1,10 @@ +#![allow(deprecated)] +#[tarpc::service(derive_serde = false)] +trait Foo { + async fn foo(); +} + +fn main() { + let x = FooRequest::Foo {}; + x.serialize(); +} diff --git a/tests-build/tests/trybuild/service/opt_out_serde.stderr b/tests-build/tests/trybuild/service/opt_out_serde.stderr new file mode 100644 index 00000000..4aa2d1cb --- /dev/null +++ b/tests-build/tests/trybuild/service/opt_out_serde.stderr @@ -0,0 +1,12 @@ +error[E0599]: no method named `serialize` found for enum `FooRequest` in the current scope + --> tests/trybuild/service/opt_out_serde.rs:9:7 + | +2 | #[tarpc::service(derive_serde = false)] + | --------------------------------------- method `serialize` not found for this enum +... +9 | x.serialize(); + | ^^^^^^^^^ method not found in `FooRequest` + | + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following trait defines an item `serialize`, perhaps you need to implement it: + candidate #1: `Serialize` diff --git a/tests-build/tests/trybuild/service_no_serde1/no_explicit_serde_without_feature.rs b/tests-build/tests/trybuild/service_no_serde1/no_explicit_serde_without_feature.rs new file mode 100644 index 00000000..9b844ba4 --- /dev/null +++ b/tests-build/tests/trybuild/service_no_serde1/no_explicit_serde_without_feature.rs @@ -0,0 +1,9 @@ +#[tarpc::service(derive_serde = true)] +trait Foo { + async fn foo(); +} + +fn main() { + let x = FooRequest::Foo {}; + x.serialize(); +} diff --git a/tests-build/tests/trybuild/service_no_serde1/no_explicit_serde_without_feature.stderr b/tests-build/tests/trybuild/service_no_serde1/no_explicit_serde_without_feature.stderr new file mode 100644 index 00000000..ee27784f --- /dev/null +++ b/tests-build/tests/trybuild/service_no_serde1/no_explicit_serde_without_feature.stderr @@ -0,0 +1,11 @@ +error: To enable serde, first enable the `serde1` feature of tarpc + --> tests/trybuild/service_no_serde1/no_explicit_serde_without_feature.rs:1:18 + | +1 | #[tarpc::service(derive_serde = true)] + | ^^^^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type `FooRequest` + --> tests/trybuild/service_no_serde1/no_explicit_serde_without_feature.rs:7:13 + | +7 | let x = FooRequest::Foo {}; + | ^^^^^^^^^^ use of undeclared type `FooRequest` diff --git a/tests-build/tests/trybuild/service_no_serde1/no_implicit_serde_without_feature.rs b/tests-build/tests/trybuild/service_no_serde1/no_implicit_serde_without_feature.rs new file mode 100644 index 00000000..6a86115d --- /dev/null +++ b/tests-build/tests/trybuild/service_no_serde1/no_implicit_serde_without_feature.rs @@ -0,0 +1,9 @@ +#[tarpc::service] +trait Foo { + async fn foo(); +} + +fn main() { + let x = FooRequest::Foo {}; + x.serialize(); +} diff --git a/tests-build/tests/trybuild/service_no_serde1/no_implicit_serde_without_feature.stderr b/tests-build/tests/trybuild/service_no_serde1/no_implicit_serde_without_feature.stderr new file mode 100644 index 00000000..53d19501 --- /dev/null +++ b/tests-build/tests/trybuild/service_no_serde1/no_implicit_serde_without_feature.stderr @@ -0,0 +1,8 @@ +error[E0599]: no method named `serialize` found for enum `FooRequest` in the current scope + --> tests/trybuild/service_no_serde1/no_implicit_serde_without_feature.rs:8:7 + | +1 | #[tarpc::service] + | ----------------- method `serialize` not found for this enum +... +8 | x.serialize(); + | ^^^^^^^^^ method not found in `FooRequest`