diff --git a/core/codegen/src/derive/responder.rs b/core/codegen/src/derive/responder.rs index 203f4248..5d1bbfb4 100644 --- a/core/codegen/src/derive/responder.rs +++ b/core/codegen/src/derive/responder.rs @@ -1,17 +1,13 @@ use quote::ToTokens; -use devise::{*, ext::{TypeExt, SpanDiagnosticExt, GenericsExt}}; +use devise::{*, ext::{TypeExt, SpanDiagnosticExt}}; use proc_macro2::TokenStream; -use syn::punctuated::Punctuated; -use syn::parse::Parser; use crate::exports::*; +use crate::syn_ext::{TypeExt as _, GenericsExt as _}; use crate::http_codegen::{ContentType, Status}; -type WherePredicates = Punctuated; - #[derive(Debug, Default, FromMeta)] struct ItemAttr { - bound: Option>, content_type: Option>, status: Option>, } @@ -21,28 +17,45 @@ struct FieldAttr { ignore: bool, } +fn generic_bounds_tokens(input: Input<'_>) -> Result { + MapperBuild::new() + .try_enum_map(|m, e| mapper::enum_null(m, e)) + .try_fields_map(|_, fields| { + let generic_idents = fields.parent.input().generics().type_idents(); + let lifetime = |ty: &syn::Type| syn::Lifetime::new("'o", ty.span()); + let mut types = fields.iter() + .map(|f| (f, &f.field.inner.ty)) + .map(|(f, ty)| (f, ty.with_replaced_lifetimes(lifetime(ty)))); + + let mut bounds = vec![]; + if let Some((_, ty)) = types.next() { + if !ty.is_concrete(&generic_idents) { + bounds.push(quote_spanned!(ty.span() => #ty: #_response::Responder<'r, 'o>)); + } + } + + for (f, ty) in types { + let attr = FieldAttr::one_from_attrs("response", &f.attrs)?.unwrap_or_default(); + if ty.is_concrete(&generic_idents) || attr.ignore { + continue; + } + + bounds.push(quote_spanned! { ty.span() => + #ty: ::std::convert::Into<#_http::Header<'o>> + }); + } + + Ok(quote!(#(#bounds,)*)) + }) + .map_input(input) +} + pub fn derive_responder(input: proc_macro::TokenStream) -> TokenStream { - let impl_tokens = quote!(impl<'r, 'o: 'r> ::rocket::response::Responder<'r, 'o>); + let impl_tokens = quote!(impl<'r, 'o: 'r> #_response::Responder<'r, 'o>); DeriveGenerator::build_for(input, impl_tokens) .support(Support::Struct | Support::Enum | Support::Lifetime | Support::Type) .replace_generic(1, 0) - .type_bound_mapper(MapperBuild::new() - .try_input_map(|_, input| { - ItemAttr::one_from_attrs("response", input.attrs())? - .and_then(|attr| attr.bound) - .map(|bound| { - let span = bound.span; - let bounds = WherePredicates::parse_terminated.parse_str(&bound) - .map_err(|e| span.error(format!("invalid bound syntax: {}", e)))?; - Ok(quote_respanned!(span => #bounds)) - }) - .unwrap_or_else(|| { - let bound = quote!(::rocket::response::Responder<'r, 'o>); - let preds = input.generics().parsed_bounded_types(bound)?; - Ok(quote!(#preds)) - }) - }) - ) + .type_bound_mapper(MapperBuild::new().try_input_map(|_, i| generic_bounds_tokens(i))) .validator(ValidatorBuild::new() .input_validate(|_, i| match i.generics().lifetimes().count() > 1 { true => Err(i.generics().span().error("only one lifetime is supported")), @@ -70,7 +83,7 @@ pub fn derive_responder(input: proc_macro::TokenStream) -> TokenStream { let responder = fields.iter().next().map(|f| { let (accessor, ty) = (f.accessor(), f.ty.with_stripped_lifetimes()); quote_spanned! { f.span().into() => - let mut __res = <#ty as ::rocket::response::Responder>::respond_to( + let mut __res = <#ty as #_response::Responder>::respond_to( #accessor, __req )?; } diff --git a/core/codegen/src/lib.rs b/core/codegen/src/lib.rs index 56358dd3..99b8ca99 100644 --- a/core/codegen/src/lib.rs +++ b/core/codegen/src/lib.rs @@ -796,9 +796,15 @@ pub fn derive_from_form(input: TokenStream) -> TokenStream { /// # Generics /// /// The derive accepts any number of type generics and at most one lifetime -/// generic. If a type generic is present, the generated implementation will -/// require a bound of `Responder<'r, 'o>` for each generic unless a -/// `#[response(bound = ...)]` attribute as used: +/// generic. If a type generic is present and the generic is used in the first +/// field of a structure, the generated implementation will require a bound of +/// `Responder<'r, 'o>` for the field type containing the generic. In all other +/// fields, unless ignores, a bound of `Into` is added. +/// +/// For example, for a struct `struct Foo(Json, H)`, the derive adds: +/// +/// * `Json: Responder<'r, 'o>` +/// * `H: Into>` /// /// ```rust /// # #[macro_use] extern crate rocket; @@ -807,21 +813,17 @@ pub fn derive_from_form(input: TokenStream) -> TokenStream { /// use rocket::http::ContentType; /// use rocket::response::Responder; /// -/// // The bound `T: Responder` will be added to the generated implementation. +/// // The bound `T: Responder` will be added. /// #[derive(Responder)] /// #[response(status = 404, content_type = "html")] /// struct NotFoundHtml(T); /// -/// // The bound `T: Serialize` will be added to the generated implementation. -/// // This would fail to compile otherwise. +/// // The bound `Json: Responder` will be added. /// #[derive(Responder)] -/// #[response(bound = "T: Serialize", status = 404)] /// struct NotFoundJson(Json); /// -/// // The bounds `T: Serialize, E: Responder` will be added to the generated -/// // implementation. This would fail to compile otherwise. +/// // The bounds `Json: Responder, E: Responder` will be added. /// #[derive(Responder)] -/// #[response(bound = "T: Serialize, E: Responder<'r, 'o>")] /// enum MyResult { /// Ok(Json), /// #[response(status = 404)] @@ -829,7 +831,7 @@ pub fn derive_from_form(input: TokenStream) -> TokenStream { /// } /// ``` /// -/// If a lifetime generic is present, it will be replace with `'o` in the +/// If a lifetime generic is present, it will be replaced with `'o` in the /// generated implementation `impl Responder<'r, 'o>`: /// /// ```rust diff --git a/core/codegen/src/syn_ext.rs b/core/codegen/src/syn_ext.rs index 5e5b6f5f..276a4ae4 100644 --- a/core/codegen/src/syn_ext.rs +++ b/core/codegen/src/syn_ext.rs @@ -30,6 +30,16 @@ pub trait FnArgExt { fn wild(&self) -> Option<&syn::PatWild>; } +pub trait TypeExt { + fn unfold(&self) -> Vec>; + fn unfold_with_ty_macros(&self, names: &[&str], mapper: MacTyMapFn) -> Vec>; + fn is_concrete(&self, generic_ident: &[&Ident]) -> bool; +} + +pub trait GenericsExt { + fn type_idents(&self) -> Vec<&Ident>; +} + #[derive(Debug)] pub struct Child<'a> { pub parent: Option>, @@ -57,12 +67,6 @@ impl IntoOwned for Child<'_> { type MacTyMapFn = fn(&TokenStream) -> Option; -pub trait TypeExt { - fn unfold(&self) -> Vec>; - fn unfold_with_ty_macros(&self, names: &[&str], mapper: MacTyMapFn) -> Vec>; - fn is_concrete(&self, generic_ident: &[&Ident]) -> bool; -} - impl IdentExt for syn::Ident { fn prepend(&self, string: &str) -> syn::Ident { syn::Ident::new(&format!("{}{}", string, self.unraw()), self.span()) @@ -229,6 +233,12 @@ impl TypeExt for syn::Type { } } +impl GenericsExt for syn::Generics { + fn type_idents(&self) -> Vec<&Ident> { + self.type_params().map(|p| &p.ident).collect() + } +} + #[cfg(test)] mod tests { #[test] diff --git a/core/codegen/tests/responder.rs b/core/codegen/tests/responder.rs index fbe84bb6..65af9062 100644 --- a/core/codegen/tests/responder.rs +++ b/core/codegen/tests/responder.rs @@ -110,14 +110,13 @@ async fn responder_baz() { use rocket::serde::json::Json; -// The bounds `T: Serialize, E: Responder` will be added to the generated +// The bounds `Json: Responder, E: Responder` will be added to the generated // implementation. This would fail to compile otherwise. #[derive(Responder)] -#[response(bound = "T: rocket::serde::Serialize, E: Responder<'r, 'o>")] -enum MyResult<'a, T, E> { +enum MyResult<'a, T, E, H> { Ok(Json), #[response(status = 404)] - Err(E, ContentType), + Err(E, H), #[response(status = 500)] Other(&'a str), } @@ -128,19 +127,19 @@ async fn generic_responder() { let local_req = client.get("/"); let req = local_req.inner(); - let v: MyResult<_, ()> = MyResult::Ok(Json("hi")); + let v: MyResult<_, (), ContentType> = MyResult::Ok(Json("hi")); let mut r = v.respond_to(req).unwrap(); assert_eq!(r.status(), Status::Ok); assert_eq!(r.content_type().unwrap(), ContentType::JSON); assert_eq!(r.body_mut().to_string().await.unwrap(), "\"hi\""); - let v: MyResult<(), &[u8]> = MyResult::Err(&[7, 13, 23], ContentType::JPEG); + let v: MyResult<(), &[u8], _> = MyResult::Err(&[7, 13, 23], ContentType::JPEG); let mut r = v.respond_to(req).unwrap(); assert_eq!(r.status(), Status::NotFound); assert_eq!(r.content_type().unwrap(), ContentType::JPEG); assert_eq!(r.body_mut().to_bytes().await.unwrap(), vec![7, 13, 23]); - let v: MyResult<(), &[u8]> = MyResult::Other("beep beep"); + let v: MyResult<(), &[u8], ContentType> = MyResult::Other("beep beep"); let mut r = v.respond_to(req).unwrap(); assert_eq!(r.status(), Status::InternalServerError); assert_eq!(r.content_type().unwrap(), ContentType::Text); diff --git a/core/codegen/tests/ui-fail-nightly/responder.stderr b/core/codegen/tests/ui-fail-nightly/responder.stderr index d2837381..1a0ec920 100644 --- a/core/codegen/tests/ui-fail-nightly/responder.stderr +++ b/core/codegen/tests/ui-fail-nightly/responder.stderr @@ -140,78 +140,3 @@ note: error occurred while deriving `Responder` 48 | #[derive(Responder)] | ^^^^^^^^^ = note: this error originates in the derive macro `Responder` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: invalid value: expected string literal - --> $DIR/responder.rs:53:20 - | -53 | #[response(bound = 10)] - | ^^ - | -note: error occurred while deriving `Responder` - --> $DIR/responder.rs:52:10 - | -52 | #[derive(Responder)] - | ^^^^^^^^^ - = note: this error originates in the derive macro `Responder` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: invalid bound syntax: expected `:` - --> $DIR/responder.rs:65:20 - | -65 | #[response(bound = "ponies are cool")] - | ^^^^^^^^^^^^^^^^^ - | -note: error occurred while deriving `Responder` - --> $DIR/responder.rs:64:10 - | -64 | #[derive(Responder)] - | ^^^^^^^^^ - = note: this error originates in the derive macro `Responder` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: invalid bound syntax: expected `,` - --> $DIR/responder.rs:69:20 - | -69 | #[response(bound = "T: ROCKETS + ARE COOLER")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - | -note: error occurred while deriving `Responder` - --> $DIR/responder.rs:68:10 - | -68 | #[derive(Responder)] - | ^^^^^^^^^ - = note: this error originates in the derive macro `Responder` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `Header<'_>: From` is not satisfied - --> $DIR/responder.rs:22:24 - | -22 | struct Thing6(T, E); // NO ERROR - | ^ the trait `From` is not implemented for `Header<'_>` - | - = note: required because of the requirements on the impl of `Into>` for `E` -help: consider extending the `where` bound, but there might be an alternative better way to express this requirement - | -21 | #[derive(Responder, Header<'_>: From)] - | ^^^^^^^^^^^^^^^^^^^^^ - -error[E0277]: the trait bound `T: Responder<'_, '_>` is not satisfied - --> $DIR/responder.rs:58:19 - | -58 | struct Thing15(T); - | ^ the trait `Responder<'_, '_>` is not implemented for `T` - | - = note: required by `respond_to` -help: consider further restricting this bound - | -57 | #[response(bound = "T: std::fmt::Display" + rocket::response::Responder<'_, '_>)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error[E0277]: the trait bound `T: Responder<'_, '_>` is not satisfied - --> $DIR/responder.rs:62:19 - | -62 | struct Thing16(T); - | ^ the trait `Responder<'_, '_>` is not implemented for `T` - | - = note: required by `respond_to` -help: consider further restricting this bound - | -61 | #[response(bound = "T: std::fmt::Display" + rocket::response::Responder<'_, '_>)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/core/codegen/tests/ui-fail-stable/responder.stderr b/core/codegen/tests/ui-fail-stable/responder.stderr index 27e389da..9f35b79c 100644 --- a/core/codegen/tests/ui-fail-stable/responder.stderr +++ b/core/codegen/tests/ui-fail-stable/responder.stderr @@ -151,81 +151,3 @@ error: [note] error occurred while deriving `Responder` | ^^^^^^^^^ | = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) - -error: invalid value: expected string literal - --> $DIR/responder.rs:53:20 - | -53 | #[response(bound = 10)] - | ^^ - -error: [note] error occurred while deriving `Responder` - --> $DIR/responder.rs:52:10 - | -52 | #[derive(Responder)] - | ^^^^^^^^^ - | - = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) - -error: invalid bound syntax: expected `:` - --> $DIR/responder.rs:65:20 - | -65 | #[response(bound = "ponies are cool")] - | ^^^^^^^^^^^^^^^^^ - -error: [note] error occurred while deriving `Responder` - --> $DIR/responder.rs:64:10 - | -64 | #[derive(Responder)] - | ^^^^^^^^^ - | - = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) - -error: invalid bound syntax: expected `,` - --> $DIR/responder.rs:69:20 - | -69 | #[response(bound = "T: ROCKETS + ARE COOLER")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: [note] error occurred while deriving `Responder` - --> $DIR/responder.rs:68:10 - | -68 | #[derive(Responder)] - | ^^^^^^^^^ - | - = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `Header<'_>: From` is not satisfied - --> $DIR/responder.rs:22:24 - | -22 | struct Thing6(T, E); // NO ERROR - | ^ the trait `From` is not implemented for `Header<'_>` - | - = note: required because of the requirements on the impl of `Into>` for `E` -help: consider extending the `where` bound, but there might be an alternative better way to express this requirement - | -21 | #[derive(Responder, Header<'_>: From)] - | ^^^^^^^^^^^^^^^^^^^^^ - -error[E0277]: the trait bound `T: Responder<'_, '_>` is not satisfied - --> $DIR/responder.rs:58:19 - | -58 | struct Thing15(T); - | ^ the trait `Responder<'_, '_>` is not implemented for `T` - | - = note: required by `respond_to` -help: consider further restricting this bound - | -57 | #[response(bound = "T: std::fmt::Display" + rocket::response::Responder<'_, '_>)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error[E0277]: the trait bound `T: Responder<'_, '_>` is not satisfied - --> $DIR/responder.rs:62:19 - | -62 | struct Thing16(T); - | ^ the trait `Responder<'_, '_>` is not implemented for `T` - | - = note: required by `respond_to` -help: consider further restricting this bound - | -61 | #[response(bound = "T: std::fmt::Display" + rocket::response::Responder<'_, '_>)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/core/codegen/tests/ui-fail/responder.rs b/core/codegen/tests/ui-fail/responder.rs index ec1271e7..f7b5cc62 100644 --- a/core/codegen/tests/ui-fail/responder.rs +++ b/core/codegen/tests/ui-fail/responder.rs @@ -19,7 +19,7 @@ struct Thing4<'a, 'b>(&'a str, &'b str); struct Thing5(T); // NO ERROR #[derive(Responder)] -struct Thing6(T, E); // NO ERROR +struct Thing6(T, E); #[derive(Responder)] #[response(content_type = "")] @@ -49,24 +49,4 @@ struct Thing12(()); #[response(status = 404, content_type = 120)] struct Thing13(()); -#[derive(Responder)] -#[response(bound = 10)] -struct Thing14(()); - -#[derive(Responder)] -#[response(bound = "T: std::fmt::Display")] -struct Thing15(T); - -#[derive(Responder)] -#[response(bound = "T: std::fmt::Display")] -struct Thing16(T); - -#[derive(Responder)] -#[response(bound = "ponies are cool")] -struct Thing17(T); - -#[derive(Responder)] -#[response(bound = "T: ROCKETS + ARE COOLER")] -struct Thing18(T); - fn main() {} diff --git a/core/lib/src/response/responder.rs b/core/lib/src/response/responder.rs index 8041bcad..e60ba627 100644 --- a/core/lib/src/response/responder.rs +++ b/core/lib/src/response/responder.rs @@ -35,7 +35,6 @@ use crate::request::Request; /// use rocket::serde::{Serialize, json::Json}; /// /// #[derive(Responder)] -/// #[response(bound = "T: Serialize")] /// enum Error { /// #[response(status = 400)] /// Unauthorized(Json),