From c45a83897fb282f76096ae77060a0f7a3a84fed7 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Wed, 7 Apr 2021 22:49:34 -0700 Subject: [PATCH] Allow several 'field' attributes in all derives. In particular, 'FromFormField' and 'UriDisplayQuery' now allow any number of form 'field' attributes. For the former, multiple 'value's are allowed, all of which are used to match against incoming fields - any match wins. For the latter, multiple 'name' and 'value's are allowed; the first of each is used to render the query value. Additionally, 'UriDisplayQuery' can now be derived for C-like enums. This brings the derive to parity with 'FromFormValue' and allows their unified application on C-like enums. Resolves #843. --- contrib/codegen/Cargo.toml | 2 +- core/codegen/Cargo.toml | 2 +- core/codegen/src/derive/form_field.rs | 83 ++++++- core/codegen/src/derive/from_form.rs | 36 +-- core/codegen/src/derive/from_form_field.rs | 84 ++++--- core/codegen/src/derive/uri_display.rs | 62 +++-- core/codegen/src/lib.rs | 57 ++++- core/codegen/tests/from_form_field.rs | 3 +- .../tests/ui-fail-nightly/from_form.stderr | 36 +-- .../ui-fail-nightly/from_form_field.stderr | 217 +++++++++++++----- .../tests/ui-fail-nightly/uri_display.stderr | 17 +- .../tests/ui-fail-stable/from_form.stderr | 38 +-- .../ui-fail-stable/from_form_field.stderr | 208 ++++++++++++----- .../tests/ui-fail-stable/uri_display.stderr | 18 +- core/codegen/tests/ui-fail/from_form_field.rs | 46 ++++ core/codegen/tests/uri_display.rs | 49 ++++ 16 files changed, 668 insertions(+), 290 deletions(-) diff --git a/contrib/codegen/Cargo.toml b/contrib/codegen/Cargo.toml index a1f75212..100420e1 100644 --- a/contrib/codegen/Cargo.toml +++ b/contrib/codegen/Cargo.toml @@ -19,7 +19,7 @@ proc-macro = true [dependencies] quote = "1.0" -devise = { git = "https://github.com/SergioBenitez/Devise.git", rev = "3ebe83" } +devise = { git = "https://github.com/SergioBenitez/Devise.git", rev = "df00b5" } [dev-dependencies] rocket = { version = "0.5.0-dev", path = "../../core/lib" } diff --git a/core/codegen/Cargo.toml b/core/codegen/Cargo.toml index 3c337f59..55e9a29a 100644 --- a/core/codegen/Cargo.toml +++ b/core/codegen/Cargo.toml @@ -18,7 +18,7 @@ proc-macro = true indexmap = "1.0" quote = "1.0" rocket_http = { version = "0.5.0-dev", path = "../http/" } -devise = { git = "https://github.com/SergioBenitez/Devise.git", rev = "3ebe83" } +devise = { git = "https://github.com/SergioBenitez/Devise.git", rev = "df00b5" } unicode-xid = "0.2" glob = "0.3" diff --git a/core/codegen/src/derive/form_field.rs b/core/codegen/src/derive/form_field.rs index e72eb90c..3a701427 100644 --- a/core/codegen/src/derive/form_field.rs +++ b/core/codegen/src/derive/form_field.rs @@ -3,7 +3,7 @@ use devise::{*, ext::{TypeExt, SpanDiagnosticExt}}; use syn::visit_mut::VisitMut; use syn::visit::Visit; -use crate::proc_macro2::{TokenStream, TokenTree}; +use crate::proc_macro2::{TokenStream, TokenTree, Span}; use crate::syn_ext::IdentExt; use crate::name::Name; @@ -26,11 +26,50 @@ impl FieldAttr { pub(crate) trait FieldExt { fn ident(&self) -> &syn::Ident; fn field_names(&self) -> Result>; - fn one_field_name(&self) -> Result; + fn first_field_name(&self) -> Result; fn stripped_ty(&self) -> syn::Type; fn name_view(&self) -> Result; } +#[derive(FromMeta)] +pub struct VariantAttr { + pub value: Name, +} + +impl VariantAttr { + const NAME: &'static str = "field"; +} + +pub(crate) trait VariantExt { + fn first_form_field_value(&self) -> Result; + fn form_field_values(&self) -> Result>; +} + +impl VariantExt for Variant<'_> { + fn first_form_field_value(&self) -> Result { + let first = VariantAttr::from_attrs(VariantAttr::NAME, &self.attrs)? + .into_iter() + .next(); + + Ok(first.map_or_else( + || FieldName::Uncased(Name::from(&self.ident)), + |attr| FieldName::Uncased(attr.value))) + } + + fn form_field_values(&self) -> Result> { + let attr_values = VariantAttr::from_attrs(VariantAttr::NAME, &self.attrs)? + .into_iter() + .map(|attr| FieldName::Uncased(attr.value)) + .collect::>(); + + if attr_values.is_empty() { + return Ok(vec![FieldName::Uncased(Name::from(&self.ident))]); + } + + Ok(attr_values) + } +} + impl FromMeta for FieldName { fn from_meta(meta: &MetaItem) -> Result { // These are used during parsing. @@ -124,17 +163,9 @@ impl FieldExt for Field<'_> { Ok(attr_names) } - fn one_field_name(&self) -> Result { + fn first_field_name(&self) -> Result { let mut names = self.field_names()?.into_iter(); - let first = names.next().expect("always have >= 1 name"); - - if let Some(name) = names.next() { - return Err(name.span() - .error("unexpected second field name") - .note("only one field rename is allowed in this context")); - } - - Ok(first) + Ok(names.next().expect("always have >= 1 name")) } fn stripped_ty(&self) -> syn::Type { @@ -293,3 +324,31 @@ pub fn validators<'v>( Ok(exprs) } + +pub fn first_duplicate( + keys: impl Iterator + Clone, + values: impl Fn(&K) -> Result>, +) -> Result> { + let (mut all_values, mut key_map) = (vec![], vec![]); + for key in keys { + all_values.append(&mut values(&key)?); + key_map.push((all_values.len(), key)); + } + + // get the key corresponding to all_value index `k`. + let key = |k| key_map.iter().find(|(i, _)| k < *i).expect("k < *i"); + + for (i, a) in all_values.iter().enumerate() { + let rest = all_values.iter().enumerate().skip(i + 1); + if let Some((j, b)) = rest.filter(|(_, b)| *b == a).next() { + let (a_i, key_a) = key(i); + let (b_i, key_b) = key(j); + + let a = (*a_i, key_a.span(), a.span()); + let b = (*b_i, key_b.span(), b.span()); + return Ok(Some((a, b))); + } + } + + Ok(None) +} diff --git a/core/codegen/src/derive/from_form.rs b/core/codegen/src/derive/from_form.rs index 548facf0..0834a38a 100644 --- a/core/codegen/src/derive/from_form.rs +++ b/core/codegen/src/derive/from_form.rs @@ -70,33 +70,19 @@ pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream { return Err(fields.span().error("at least one field is required")); } - let mut names = vec![]; - let mut field_map = vec![]; - for field in fields.iter() { - names.append(&mut field.field_names()?); - field_map.push((names.len(), field)); - } + if let Some(d) = first_duplicate(fields.iter(), |f| f.field_names())? { + let (field_a_i, field_a, name_a) = d.0; + let (field_b_i, field_b, name_b) = d.1; - // get the field corresponding to name index `k`. - let field = |k| field_map.iter().find(|(i, _)| k < *i).expect("k < *i"); - - for (i, a) in names.iter().enumerate() { - let rest = names.iter().enumerate().skip(i + 1); - if let Some((j, b)) = rest.filter(|(_, b)| b == &a).next() { - let ((fa_i, field_a), (fb_i, field_b)) = (field(i), field(j)); - - if fa_i == fb_i { - return Err(field_a.span() - .error("field has conflicting names") - .span_note(a.span(), "this field name...") - .span_note(b.span(), "...conflicts with this name")); - } else { - return Err(b.span() - .error("field name conflicts with previous name") - .span_note(field_a.span(), "previous field with conflicting name") - .span_help(field_b.span(), "field name is part of this field")); - } + if field_a_i == field_b_i { + return Err(field_a.error("field has conflicting names") + .span_note(name_a, "this field name...") + .span_note(name_b, "...conflicts with this field name")); } + + return Err(name_b.error("field name conflicts with previous name") + .span_help(field_b, "declared in this field") + .span_note(field_a, "previous field with conflicting name")); } Ok(()) diff --git a/core/codegen/src/derive/from_form_field.rs b/core/codegen/src/derive/from_form_field.rs index f18e58d1..5fc5e4d6 100644 --- a/core/codegen/src/derive/from_form_field.rs +++ b/core/codegen/src/derive/from_form_field.rs @@ -2,12 +2,7 @@ use devise::{*, ext::SpanDiagnosticExt}; use crate::exports::*; use crate::proc_macro2::TokenStream; -use crate::name::Name; - -#[derive(FromMeta)] -pub struct FieldAttr { - value: Name, -} +use crate::derive::form_field::{VariantExt, first_duplicate}; pub fn derive_from_form_field(input: proc_macro::TokenStream) -> TokenStream { DeriveGenerator::build_for(input, quote!(impl<'__v> #_form::FromFormField<'__v>)) @@ -26,47 +21,68 @@ pub fn derive_from_form_field(input: proc_macro::TokenStream) -> TokenStream { return Err(data.span().error("enum must have at least one variant")); } + if let Some(d) = first_duplicate(data.variants(), |v| v.form_field_values())? { + let (variant_a_i, variant_a, value_a) = d.0; + let (variant_b_i, variant_b, value_b) = d.1; + + if variant_a_i == variant_b_i { + return Err(variant_a.error("variant has conflicting values") + .span_note(value_a, "this value...") + .span_note(value_b, "...conflicts with this value")); + } + + return Err(value_b.error("field value conflicts with previous value") + .span_help(variant_b, "...declared in this variant") + .span_note(variant_a, "previous field with conflicting name")); + } + Ok(()) }) ) - // TODO: Devise should have a try_variant_map. + .outer_mapper(quote! { + #[allow(unused_imports)] + use #_http::uncased::AsUncased; + }) .inner_mapper(MapperBuild::new() - .try_enum_map(|_, data| { - let variant_name_sources = data.variants() - .map(|v| FieldAttr::one_from_attrs("field", &v.attrs).map(|o| { - o.map(|f| f.value).unwrap_or_else(|| Name::from(&v.ident)) - })) - .collect::>>()?; + .with_output(|_, output| quote! { + fn from_value( + __f: #_form::ValueField<'__v> + ) -> Result> { - let variant_name = variant_name_sources.iter() - .map(|n| n.as_str()) - .collect::>(); + #output + } + }) + .try_enum_map(|mapper, data| { + let mut variant_value = vec![]; + for v in data.variants().map(|v| v.form_field_values()) { + variant_value.append(&mut v?); + } - let builder = data.variants() - .map(|v| v.builder(|_| unreachable!("fieldless"))); + let variant_condition = data.variants() + .map(|v| mapper.map_variant(v)) + .collect::>>()?; let (_ok, _cow) = (std::iter::repeat(_Ok), std::iter::repeat(_Cow)); Ok(quote! { - fn from_value( - __f: #_form::ValueField<'__v> - ) -> Result> { - #[allow(unused_imports)] - use #_http::uncased::AsUncased; + #(#variant_condition)* - #( - if __f.value.as_uncased() == #variant_name { - return #_ok(#builder); - } - )* + const OPTS: &'static [#_Cow<'static, str>] = + &[#(#_cow::Borrowed(#variant_value)),*]; - const OPTS: &'static [#_Cow<'static, str>] = - &[#(#_cow::Borrowed(#variant_name)),*]; + let _error = #_form::Error::from(OPTS) + .with_name(__f.name) + .with_value(__f.value); - let _error = #_form::Error::from(OPTS) - .with_name(__f.name) - .with_value(__f.value); + #_Err(_error)? + }) + }) + .try_variant_map(|_, variant| { + let builder = variant.builder(|_| unreachable!("fieldless")); + let value = variant.form_field_values()?; - #_Err(_error)? + Ok(quote_spanned! { variant.span() => + if #(__f.value.as_uncased() == #value)||* { + return #_Ok(#builder); } }) }) diff --git a/core/codegen/src/derive/uri_display.rs b/core/codegen/src/derive/uri_display.rs index 34649749..19227d0c 100644 --- a/core/codegen/src/derive/uri_display.rs +++ b/core/codegen/src/derive/uri_display.rs @@ -3,35 +3,15 @@ use devise::{*, ext::SpanDiagnosticExt}; use rocket_http::uri; use crate::exports::*; -use crate::derive::form_field::FieldExt; +use crate::derive::form_field::{FieldExt, VariantExt}; use crate::proc_macro2::TokenStream; -const NO_EMPTY_FIELDS: &str = "fieldless structs or variants are not supported"; +const NO_EMPTY_FIELDS: &str = "fieldless structs are not supported"; const NO_NULLARY: &str = "nullary items are not supported"; const NO_EMPTY_ENUMS: &str = "empty enums are not supported"; const ONLY_ONE_UNNAMED: &str = "tuple structs or variants must have exactly one field"; const EXACTLY_ONE_FIELD: &str = "struct must have exactly one field"; -fn validate_fields(fields: Fields<'_>) -> Result<()> { - if fields.count() == 0 { - return Err(fields.parent.span().error(NO_EMPTY_FIELDS)) - } else if fields.are_unnamed() && fields.count() > 1 { - return Err(fields.span().error(ONLY_ONE_UNNAMED)); - } else if fields.are_unit() { - return Err(fields.span().error(NO_NULLARY)); - } - - Ok(()) -} - -fn validate_enum(data: Enum<'_>) -> Result<()> { - if data.variants().count() == 0 { - return Err(data.brace_token.span.error(NO_EMPTY_ENUMS)); - } - - Ok(()) -} - pub fn derive_uri_display_query(input: proc_macro::TokenStream) -> TokenStream { use crate::http::uri::Query; @@ -41,8 +21,30 @@ pub fn derive_uri_display_query(input: proc_macro::TokenStream) -> TokenStream { let uri_display = DeriveGenerator::build_for(input.clone(), quote!(impl #URI_DISPLAY)) .support(Support::Struct | Support::Enum | Support::Type | Support::Lifetime) .validator(ValidatorBuild::new() - .enum_validate(|_, v| validate_enum(v)) - .fields_validate(|_, v| validate_fields(v)) + .enum_validate(|_, data| { + if data.variants().count() == 0 { + return Err(data.brace_token.span.error(NO_EMPTY_ENUMS)); + } else { + Ok(()) + } + }) + .struct_validate(|_, data| { + let fields = data.fields(); + if fields.is_empty() { + Err(data.span().error(NO_EMPTY_FIELDS)) + } else if fields.are_unit() { + Err(data.span().error(NO_NULLARY)) + } else { + Ok(()) + } + }) + .fields_validate(|_, fields| { + if fields.are_unnamed() && fields.count() > 1 { + Err(fields.span().error(ONLY_ONE_UNNAMED)) + } else { + Ok(()) + } + }) ) .type_bound(URI_DISPLAY) .inner_mapper(MapperBuild::new() @@ -52,11 +54,21 @@ pub fn derive_uri_display_query(input: proc_macro::TokenStream) -> TokenStream { Ok(()) } }) + .try_variant_map(|mapper, variant| { + if !variant.fields().is_empty() { + return mapper::variant_default(mapper, variant); + } + + let value = variant.first_form_field_value()?; + Ok(quote_spanned! { variant.span() => + f.write_value(#value)?; + }) + }) .try_field_map(|_, field| { let span = field.span().into(); let accessor = field.accessor(); let tokens = if field.ident.is_some() { - let name = field.one_field_name()?; + let name = field.first_field_name()?; quote_spanned!(span => f.write_named_value(#name, &#accessor)?;) } else { quote_spanned!(span => f.write_value(&#accessor)?;) diff --git a/core/codegen/src/lib.rs b/core/codegen/src/lib.rs index 3ec28d74..b438d6ad 100644 --- a/core/codegen/src/lib.rs +++ b/core/codegen/src/lib.rs @@ -394,8 +394,8 @@ pub fn launch(args: TokenStream, input: TokenStream) -> TokenStream { /// /// As an example, for the `enum` above, the form values `"first"`, `"FIRST"`, /// `"fiRSt"`, and so on would parse as `MyValue::First`, while `"second"` and -/// `"third"` would parse as `MyValue::Second` and `MyValue::Third`, -/// respectively. +/// `"third"` (in any casing) would parse as `MyValue::Second` and +/// `MyValue::Third`, respectively. /// /// The `field` field attribute can be used to change the string value that is /// compared against for a given variant: @@ -408,10 +408,16 @@ pub fn launch(args: TokenStream, input: TokenStream) -> TokenStream { /// First, /// Second, /// #[field(value = "fourth")] +/// #[field(value = "fifth")] /// Third, /// } /// ``` /// +/// When more than one `value` is specified, matching _any_ value will result in +/// parsing the decorated variant. Declaring any two values that are +/// case-insensitively equal to any other value or variant name is a +/// compile-time error. +/// /// The `#[field]` attribute's grammar is: /// /// ```text @@ -422,8 +428,8 @@ pub fn launch(args: TokenStream, input: TokenStream) -> TokenStream { /// /// The attribute accepts a single string parameter of name `value` /// corresponding to the string to use to match against for the decorated -/// variant. In the example above, the the strings `"fourth"`, `"FOUrth"` and so -/// on would parse as `MyValue::Third`. +/// variant. In the example above, the the strings `"fourth"`, `"FOUrth"`, +/// `"fiFTH"` and so on would parse as `MyValue::Third`. /// /// [`FromFormField`]: rocket::form::FromFormField /// [`FromFormField::Error`]: rocket::form::FromFormField::Error @@ -648,8 +654,8 @@ pub fn derive_responder(input: TokenStream) -> TokenStream { /// Derive for the [`UriDisplay`] trait. /// /// The [`UriDisplay`] derive can be applied to enums and structs. When -/// applied to enums, variants must have at least one field. When applied to -/// structs, the struct must have at least one field. +/// applied to an enum, the enum must have at least one variant. When applied to +/// a struct, the struct must have at least one field. /// /// ```rust /// # #[macro_use] extern crate rocket; @@ -678,12 +684,15 @@ pub fn derive_responder(input: TokenStream) -> TokenStream { /// The derive accepts one field attribute: `field`, with the following syntax: /// /// ```text -/// field := 'name' '=' '"' IDENT '"' +/// field := 'name' '=' '"' FIELD_NAME '"' +/// | 'value' '=' '"' FIELD_VALUE '"' /// -/// IDENT := valid identifier, as defined by Rust +/// FIELD_NAME := valid HTTP field name +/// FIELD_VALUE := valid HTTP field value /// ``` /// -/// When applied, the attribute looks as follows: +/// When applied to a struct, the attribute can only contain `name` and looks +/// as follows: /// /// ```rust /// # #[macro_use] extern crate rocket; @@ -694,15 +703,39 @@ pub fn derive_responder(input: TokenStream) -> TokenStream { /// name: String, /// id: usize, /// #[field(name = "type")] +/// #[field(name = "kind")] /// kind: Kind, /// } /// ``` /// /// The field attribute directs that a different field name be used when calling /// [`Formatter::write_named_value()`] for the given field. The value of the -/// `name` attribute is used instead of the structure's actual field name. In -/// the example above, the field `MyStruct::kind` is rendered with a name of -/// `type`. +/// `name` attribute is used instead of the structure's actual field name. If +/// more than one `field` attribute is applied to a field, the _first_ name is +/// used. In the example above, the field `MyStruct::kind` is rendered with a +/// name of `type`. +/// +/// The attribute can slso be applied to variants of C-like enums; it may only +/// contain `value` and looks as follows: +/// +/// ```rust +/// # #[macro_use] extern crate rocket; +/// #[derive(UriDisplayQuery)] +/// enum Kind { +/// File, +/// #[field(value = "str")] +/// #[field(value = "string")] +/// String, +/// Other +/// } +/// ``` +/// +/// The field attribute directs that a different value be used when calling +/// [`Formatter::write_named_value()`] for the given variant. The value of the +/// `value` attribute is used instead of the variant's actual name. If more than +/// one `field` attribute is applied to a variant, the _first_ value is used. In +/// the example above, the variant `Kind::String` will render with a value of +/// `str`. /// /// [`UriDisplay`]: ../rocket/http/uri/trait.UriDisplay.html /// [`Formatter::write_named_value()`]: ../rocket/http/uri/struct.Formatter.html#method.write_named_value diff --git a/core/codegen/tests/from_form_field.rs b/core/codegen/tests/from_form_field.rs index e5e14456..52ba03ba 100644 --- a/core/codegen/tests/from_form_field.rs +++ b/core/codegen/tests/from_form_field.rs @@ -62,12 +62,13 @@ fn from_form_value_renames() { #[derive(Debug, FromFormField)] enum Foo { #[field(value = "foo")] + #[field(value = "bark")] Bar, #[field(value = ":book")] Book } - assert_parse!("foo", "FOO", "FoO" => Foo::Bar); + assert_parse!("foo", "FOO", "FoO", "bark", "BARK", "BaRk" => Foo::Bar); assert_parse!(":book", ":BOOK", ":bOOk", ":booK" => Foo::Book); assert_no_parse!("book", "bar" => Foo); } diff --git a/core/codegen/tests/ui-fail-nightly/from_form.stderr b/core/codegen/tests/ui-fail-nightly/from_form.stderr index abc9c793..bba2bc71 100644 --- a/core/codegen/tests/ui-fail-nightly/from_form.stderr +++ b/core/codegen/tests/ui-fail-nightly/from_form.stderr @@ -83,17 +83,17 @@ error: field name conflicts with previous name 33 | foo: usize, | ^^^ | +help: declared in this field + --> $DIR/from_form.rs:33:5 + | +33 | foo: usize, + | ^^^^^^^^^^ note: previous field with conflicting name --> $DIR/from_form.rs:31:5 | 31 | / #[field(name = "foo")] 32 | | field: String, | |_________________^ -help: field name is part of this field - --> $DIR/from_form.rs:33:5 - | -33 | foo: usize, - | ^^^^^^^^^^ note: error occurred while deriving `FromForm` --> $DIR/from_form.rs:29:10 | @@ -107,18 +107,18 @@ error: field name conflicts with previous name 40 | #[field(name = "hello")] | ^^^^^^^ | +help: declared in this field + --> $DIR/from_form.rs:40:5 + | +40 | / #[field(name = "hello")] +41 | | other: String, + | |_________________^ note: previous field with conflicting name --> $DIR/from_form.rs:38:5 | 38 | / #[field(name = "hello")] 39 | | first: String, | |_________________^ -help: field name is part of this field - --> $DIR/from_form.rs:40:5 - | -40 | / #[field(name = "hello")] -41 | | other: String, - | |_________________^ note: error occurred while deriving `FromForm` --> $DIR/from_form.rs:36:10 | @@ -132,17 +132,17 @@ error: field name conflicts with previous name 47 | #[field(name = "first")] | ^^^^^^^ | -note: previous field with conflicting name - --> $DIR/from_form.rs:46:5 - | -46 | first: String, - | ^^^^^^^^^^^^^ -help: field name is part of this field +help: declared in this field --> $DIR/from_form.rs:47:5 | 47 | / #[field(name = "first")] 48 | | other: String, | |_________________^ +note: previous field with conflicting name + --> $DIR/from_form.rs:46:5 + | +46 | first: String, + | ^^^^^^^^^^^^^ note: error occurred while deriving `FromForm` --> $DIR/from_form.rs:44:10 | @@ -228,7 +228,7 @@ note: this field name... | 83 | #[field(name = "blah")] | ^^^^^^ -note: ...conflicts with this name +note: ...conflicts with this field name --> $DIR/from_form.rs:84:20 | 84 | #[field(name = "blah")] diff --git a/core/codegen/tests/ui-fail-nightly/from_form_field.stderr b/core/codegen/tests/ui-fail-nightly/from_form_field.stderr index 133f3b32..e3b4f7dc 100644 --- a/core/codegen/tests/ui-fail-nightly/from_form_field.stderr +++ b/core/codegen/tests/ui-fail-nightly/from_form_field.stderr @@ -104,100 +104,199 @@ note: error occurred while deriving `FromFormField` | ^^^^^^^^^^^^^ = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) -error: field has conflicting names +error: variant has conflicting values --> $DIR/from_form_field.rs:41:5 | -41 | / #[field(name = "foo")] -42 | | #[field(name = uncased("FOO"))] -43 | | single: usize, - | |_________________^ +41 | / #[field(value = "bar")] +42 | | #[field(value = "bar")] +43 | | A, + | |_____^ | -note: this field name... - --> $DIR/from_form_field.rs:41:20 +note: this value... + --> $DIR/from_form_field.rs:41:21 | -41 | #[field(name = "foo")] - | ^^^^^ -note: ...conflicts with this name - --> $DIR/from_form_field.rs:42:28 +41 | #[field(value = "bar")] + | ^^^^^ +note: ...conflicts with this value + --> $DIR/from_form_field.rs:42:21 | -42 | #[field(name = uncased("FOO"))] - | ^^^^^ -note: error occurred while deriving `FromForm` +42 | #[field(value = "bar")] + | ^^^^^ +note: error occurred while deriving `FromFormField` --> $DIR/from_form_field.rs:39:10 | -39 | #[derive(FromForm)] - | ^^^^^^^^ +39 | #[derive(FromFormField)] + | ^^^^^^^^^^^^^ = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) -error: field name conflicts with previous name - --> $DIR/from_form_field.rs:50:20 +error: field value conflicts with previous value + --> $DIR/from_form_field.rs:50:21 | -50 | #[field(name = "foo")] - | ^^^^^ +50 | #[field(value = "BAr")] + | ^^^^^ | +help: ...declared in this variant + --> $DIR/from_form_field.rs:50:5 + | +50 | / #[field(value = "BAr")] +51 | | B, + | |_____^ note: previous field with conflicting name --> $DIR/from_form_field.rs:48:5 | -48 | / #[field(name = "foo")] -49 | | single: usize, - | |_________________^ -help: field name is part of this field - --> $DIR/from_form_field.rs:50:5 - | -50 | / #[field(name = "foo")] -51 | | other: usize, - | |________________^ -note: error occurred while deriving `FromForm` +48 | / #[field(value = "bar")] +49 | | A, + | |_____^ +note: error occurred while deriving `FromFormField` --> $DIR/from_form_field.rs:46:10 | -46 | #[derive(FromForm)] - | ^^^^^^^^ +46 | #[derive(FromFormField)] + | ^^^^^^^^^^^^^ = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) -error: field name conflicts with previous name - --> $DIR/from_form_field.rs:58:5 +error: field value conflicts with previous value + --> $DIR/from_form_field.rs:57:21 | -58 | hello_there: usize, - | ^^^^^^^^^^^ +57 | #[field(value = "a")] + | ^^^ | +help: ...declared in this variant + --> $DIR/from_form_field.rs:57:5 + | +57 | / #[field(value = "a")] +58 | | B, + | |_____^ note: previous field with conflicting name --> $DIR/from_form_field.rs:56:5 | -56 | / #[field(name = uncased("HELLO_THERE"))] -57 | | single: usize, - | |_________________^ -help: field name is part of this field - --> $DIR/from_form_field.rs:58:5 - | -58 | hello_there: usize, - | ^^^^^^^^^^^^^^^^^^ -note: error occurred while deriving `FromForm` +56 | A, + | ^ +note: error occurred while deriving `FromFormField` --> $DIR/from_form_field.rs:54:10 | -54 | #[derive(FromForm)] +54 | #[derive(FromFormField)] + | ^^^^^^^^^^^^^ + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: variant has conflicting values + --> $DIR/from_form_field.rs:80:5 + | +80 | / #[field(value = "FoO")] +81 | | #[field(value = "foo")] +82 | | A, + | |_____^ + | +note: this value... + --> $DIR/from_form_field.rs:80:21 + | +80 | #[field(value = "FoO")] + | ^^^^^ +note: ...conflicts with this value + --> $DIR/from_form_field.rs:81:21 + | +81 | #[field(value = "foo")] + | ^^^^^ +note: error occurred while deriving `FromFormField` + --> $DIR/from_form_field.rs:78:10 + | +78 | #[derive(FromFormField)] + | ^^^^^^^^^^^^^ + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: field has conflicting names + --> $DIR/from_form_field.rs:87:5 + | +87 | / #[field(name = "foo")] +88 | | #[field(name = uncased("FOO"))] +89 | | single: usize, + | |_________________^ + | +note: this field name... + --> $DIR/from_form_field.rs:87:20 + | +87 | #[field(name = "foo")] + | ^^^^^ +note: ...conflicts with this field name + --> $DIR/from_form_field.rs:88:28 + | +88 | #[field(name = uncased("FOO"))] + | ^^^^^ +note: error occurred while deriving `FromForm` + --> $DIR/from_form_field.rs:85:10 + | +85 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) error: field name conflicts with previous name - --> $DIR/from_form_field.rs:65:5 + --> $DIR/from_form_field.rs:96:20 | -65 | hello_there: usize, - | ^^^^^^^^^^^ +96 | #[field(name = "foo")] + | ^^^^^ | +help: declared in this field + --> $DIR/from_form_field.rs:96:5 + | +96 | / #[field(name = "foo")] +97 | | other: usize, + | |________________^ note: previous field with conflicting name - --> $DIR/from_form_field.rs:63:5 + --> $DIR/from_form_field.rs:94:5 | -63 | / #[field(name = "hello_there")] -64 | | single: usize, +94 | / #[field(name = "foo")] +95 | | single: usize, | |_________________^ -help: field name is part of this field - --> $DIR/from_form_field.rs:65:5 - | -65 | hello_there: usize, - | ^^^^^^^^^^^^^^^^^^ note: error occurred while deriving `FromForm` - --> $DIR/from_form_field.rs:61:10 + --> $DIR/from_form_field.rs:92:10 | -61 | #[derive(FromForm)] +92 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: field name conflicts with previous name + --> $DIR/from_form_field.rs:104:5 + | +104 | hello_there: usize, + | ^^^^^^^^^^^ + | +help: declared in this field + --> $DIR/from_form_field.rs:104:5 + | +104 | hello_there: usize, + | ^^^^^^^^^^^^^^^^^^ +note: previous field with conflicting name + --> $DIR/from_form_field.rs:102:5 + | +102 | / #[field(name = uncased("HELLO_THERE"))] +103 | | single: usize, + | |_________________^ +note: error occurred while deriving `FromForm` + --> $DIR/from_form_field.rs:100:10 + | +100 | #[derive(FromForm)] + | ^^^^^^^^ + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: field name conflicts with previous name + --> $DIR/from_form_field.rs:111:5 + | +111 | hello_there: usize, + | ^^^^^^^^^^^ + | +help: declared in this field + --> $DIR/from_form_field.rs:111:5 + | +111 | hello_there: usize, + | ^^^^^^^^^^^^^^^^^^ +note: previous field with conflicting name + --> $DIR/from_form_field.rs:109:5 + | +109 | / #[field(name = "hello_there")] +110 | | single: usize, + | |_________________^ +note: error occurred while deriving `FromForm` + --> $DIR/from_form_field.rs:107:10 + | +107 | #[derive(FromForm)] + | ^^^^^^^^ + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/core/codegen/tests/ui-fail-nightly/uri_display.stderr b/core/codegen/tests/ui-fail-nightly/uri_display.stderr index bf504999..6e7dd2a7 100644 --- a/core/codegen/tests/ui-fail-nightly/uri_display.stderr +++ b/core/codegen/tests/ui-fail-nightly/uri_display.stderr @@ -1,4 +1,4 @@ -error: fieldless structs or variants are not supported +error: fieldless structs are not supported --> $DIR/uri_display.rs:4:1 | 4 | struct Foo1; @@ -11,7 +11,7 @@ note: error occurred while deriving `UriDisplay` | ^^^^^^^^^^^^^^^ = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) -error: fieldless structs or variants are not supported +error: fieldless structs are not supported --> $DIR/uri_display.rs:7:1 | 7 | struct Foo2(); @@ -37,19 +37,6 @@ note: error occurred while deriving `UriDisplay` | ^^^^^^^^^^^^^^^ = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) -error: fieldless structs or variants are not supported - --> $DIR/uri_display.rs:14:5 - | -14 | Variant, - | ^^^^^^^ - | -note: error occurred while deriving `UriDisplay` - --> $DIR/uri_display.rs:12:10 - | -12 | #[derive(UriDisplayQuery)] - | ^^^^^^^^^^^^^^^ - = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) - error: tuple structs or variants must have exactly one field --> $DIR/uri_display.rs:18:12 | diff --git a/core/codegen/tests/ui-fail-stable/from_form.stderr b/core/codegen/tests/ui-fail-stable/from_form.stderr index daf6ccb4..d4187f17 100644 --- a/core/codegen/tests/ui-fail-stable/from_form.stderr +++ b/core/codegen/tests/ui-fail-stable/from_form.stderr @@ -89,18 +89,18 @@ error: field name conflicts with previous name 33 | foo: usize, | ^^^ +error: [help] declared in this field + --> $DIR/from_form.rs:33:5 + | +33 | foo: usize, + | ^^^ + error: [note] previous field with conflicting name --> $DIR/from_form.rs:31:5 | 31 | #[field(name = "foo")] | ^ -error: [help] field name is part of this field - --> $DIR/from_form.rs:33:5 - | -33 | foo: usize, - | ^^^ - error: [note] error occurred while deriving `FromForm` --> $DIR/from_form.rs:29:10 | @@ -115,18 +115,18 @@ error: field name conflicts with previous name 40 | #[field(name = "hello")] | ^^^^^^^ +error: [help] declared in this field + --> $DIR/from_form.rs:40:5 + | +40 | #[field(name = "hello")] + | ^ + error: [note] previous field with conflicting name --> $DIR/from_form.rs:38:5 | 38 | #[field(name = "hello")] | ^ -error: [help] field name is part of this field - --> $DIR/from_form.rs:40:5 - | -40 | #[field(name = "hello")] - | ^ - error: [note] error occurred while deriving `FromForm` --> $DIR/from_form.rs:36:10 | @@ -141,18 +141,18 @@ error: field name conflicts with previous name 47 | #[field(name = "first")] | ^^^^^^^ +error: [help] declared in this field + --> $DIR/from_form.rs:47:5 + | +47 | #[field(name = "first")] + | ^ + error: [note] previous field with conflicting name --> $DIR/from_form.rs:46:5 | 46 | first: String, | ^^^^^ -error: [help] field name is part of this field - --> $DIR/from_form.rs:47:5 - | -47 | #[field(name = "first")] - | ^ - error: [note] error occurred while deriving `FromForm` --> $DIR/from_form.rs:44:10 | @@ -243,7 +243,7 @@ error: [note] this field name... 83 | #[field(name = "blah")] | ^^^^^^ -error: [note] ...conflicts with this name +error: [note] ...conflicts with this field name --> $DIR/from_form.rs:84:20 | 84 | #[field(name = "blah")] diff --git a/core/codegen/tests/ui-fail-stable/from_form_field.stderr b/core/codegen/tests/ui-fail-stable/from_form_field.stderr index 8c0f98d0..fc2cc08e 100644 --- a/core/codegen/tests/ui-fail-stable/from_form_field.stderr +++ b/core/codegen/tests/ui-fail-stable/from_form_field.stderr @@ -110,106 +110,210 @@ error: [note] error occurred while deriving `FromFormField` | = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) -error: field has conflicting names +error: variant has conflicting values --> $DIR/from_form_field.rs:41:5 | -41 | #[field(name = "foo")] +41 | #[field(value = "bar")] | ^ -error: [note] this field name... - --> $DIR/from_form_field.rs:41:20 +error: [note] this value... + --> $DIR/from_form_field.rs:41:21 | -41 | #[field(name = "foo")] - | ^^^^^ +41 | #[field(value = "bar")] + | ^^^^^ -error: [note] ...conflicts with this name - --> $DIR/from_form_field.rs:42:28 +error: [note] ...conflicts with this value + --> $DIR/from_form_field.rs:42:21 | -42 | #[field(name = uncased("FOO"))] - | ^^^^^ +42 | #[field(value = "bar")] + | ^^^^^ -error: [note] error occurred while deriving `FromForm` +error: [note] error occurred while deriving `FromFormField` --> $DIR/from_form_field.rs:39:10 | -39 | #[derive(FromForm)] - | ^^^^^^^^ +39 | #[derive(FromFormField)] + | ^^^^^^^^^^^^^ | = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) -error: field name conflicts with previous name - --> $DIR/from_form_field.rs:50:20 +error: field value conflicts with previous value + --> $DIR/from_form_field.rs:50:21 | -50 | #[field(name = "foo")] - | ^^^^^ +50 | #[field(value = "BAr")] + | ^^^^^ + +error: [help] ...declared in this variant + --> $DIR/from_form_field.rs:50:5 + | +50 | #[field(value = "BAr")] + | ^ error: [note] previous field with conflicting name --> $DIR/from_form_field.rs:48:5 | -48 | #[field(name = "foo")] +48 | #[field(value = "bar")] | ^ -error: [help] field name is part of this field - --> $DIR/from_form_field.rs:50:5 - | -50 | #[field(name = "foo")] - | ^ - -error: [note] error occurred while deriving `FromForm` +error: [note] error occurred while deriving `FromFormField` --> $DIR/from_form_field.rs:46:10 | -46 | #[derive(FromForm)] - | ^^^^^^^^ +46 | #[derive(FromFormField)] + | ^^^^^^^^^^^^^ | = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) -error: field name conflicts with previous name - --> $DIR/from_form_field.rs:58:5 +error: field value conflicts with previous value + --> $DIR/from_form_field.rs:57:21 | -58 | hello_there: usize, - | ^^^^^^^^^^^ +57 | #[field(value = "a")] + | ^^^ + +error: [help] ...declared in this variant + --> $DIR/from_form_field.rs:57:5 + | +57 | #[field(value = "a")] + | ^ error: [note] previous field with conflicting name --> $DIR/from_form_field.rs:56:5 | -56 | #[field(name = uncased("HELLO_THERE"))] +56 | A, | ^ -error: [help] field name is part of this field - --> $DIR/from_form_field.rs:58:5 - | -58 | hello_there: usize, - | ^^^^^^^^^^^ - -error: [note] error occurred while deriving `FromForm` +error: [note] error occurred while deriving `FromFormField` --> $DIR/from_form_field.rs:54:10 | -54 | #[derive(FromForm)] +54 | #[derive(FromFormField)] + | ^^^^^^^^^^^^^ + | + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: variant has conflicting values + --> $DIR/from_form_field.rs:80:5 + | +80 | #[field(value = "FoO")] + | ^ + +error: [note] this value... + --> $DIR/from_form_field.rs:80:21 + | +80 | #[field(value = "FoO")] + | ^^^^^ + +error: [note] ...conflicts with this value + --> $DIR/from_form_field.rs:81:21 + | +81 | #[field(value = "foo")] + | ^^^^^ + +error: [note] error occurred while deriving `FromFormField` + --> $DIR/from_form_field.rs:78:10 + | +78 | #[derive(FromFormField)] + | ^^^^^^^^^^^^^ + | + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: field has conflicting names + --> $DIR/from_form_field.rs:87:5 + | +87 | #[field(name = "foo")] + | ^ + +error: [note] this field name... + --> $DIR/from_form_field.rs:87:20 + | +87 | #[field(name = "foo")] + | ^^^^^ + +error: [note] ...conflicts with this field name + --> $DIR/from_form_field.rs:88:28 + | +88 | #[field(name = uncased("FOO"))] + | ^^^^^ + +error: [note] error occurred while deriving `FromForm` + --> $DIR/from_form_field.rs:85:10 + | +85 | #[derive(FromForm)] | ^^^^^^^^ | = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) error: field name conflicts with previous name - --> $DIR/from_form_field.rs:65:5 + --> $DIR/from_form_field.rs:96:20 | -65 | hello_there: usize, - | ^^^^^^^^^^^ +96 | #[field(name = "foo")] + | ^^^^^ -error: [note] previous field with conflicting name - --> $DIR/from_form_field.rs:63:5 +error: [help] declared in this field + --> $DIR/from_form_field.rs:96:5 | -63 | #[field(name = "hello_there")] +96 | #[field(name = "foo")] | ^ -error: [help] field name is part of this field - --> $DIR/from_form_field.rs:65:5 +error: [note] previous field with conflicting name + --> $DIR/from_form_field.rs:94:5 | -65 | hello_there: usize, - | ^^^^^^^^^^^ +94 | #[field(name = "foo")] + | ^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form_field.rs:61:10 + --> $DIR/from_form_field.rs:92:10 | -61 | #[derive(FromForm)] +92 | #[derive(FromForm)] | ^^^^^^^^ | = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: field name conflicts with previous name + --> $DIR/from_form_field.rs:104:5 + | +104 | hello_there: usize, + | ^^^^^^^^^^^ + +error: [help] declared in this field + --> $DIR/from_form_field.rs:104:5 + | +104 | hello_there: usize, + | ^^^^^^^^^^^ + +error: [note] previous field with conflicting name + --> $DIR/from_form_field.rs:102:5 + | +102 | #[field(name = uncased("HELLO_THERE"))] + | ^ + +error: [note] error occurred while deriving `FromForm` + --> $DIR/from_form_field.rs:100:10 + | +100 | #[derive(FromForm)] + | ^^^^^^^^ + | + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: field name conflicts with previous name + --> $DIR/from_form_field.rs:111:5 + | +111 | hello_there: usize, + | ^^^^^^^^^^^ + +error: [help] declared in this field + --> $DIR/from_form_field.rs:111:5 + | +111 | hello_there: usize, + | ^^^^^^^^^^^ + +error: [note] previous field with conflicting name + --> $DIR/from_form_field.rs:109:5 + | +109 | #[field(name = "hello_there")] + | ^ + +error: [note] error occurred while deriving `FromForm` + --> $DIR/from_form_field.rs:107:10 + | +107 | #[derive(FromForm)] + | ^^^^^^^^ + | + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/core/codegen/tests/ui-fail-stable/uri_display.stderr b/core/codegen/tests/ui-fail-stable/uri_display.stderr index 954fcd49..8705f14b 100644 --- a/core/codegen/tests/ui-fail-stable/uri_display.stderr +++ b/core/codegen/tests/ui-fail-stable/uri_display.stderr @@ -1,4 +1,4 @@ -error: fieldless structs or variants are not supported +error: fieldless structs are not supported --> $DIR/uri_display.rs:4:1 | 4 | struct Foo1; @@ -12,7 +12,7 @@ error: [note] error occurred while deriving `UriDisplay` | = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) -error: fieldless structs or variants are not supported +error: fieldless structs are not supported --> $DIR/uri_display.rs:7:1 | 7 | struct Foo2(); @@ -40,20 +40,6 @@ error: [note] error occurred while deriving `UriDisplay` | = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) -error: fieldless structs or variants are not supported - --> $DIR/uri_display.rs:14:5 - | -14 | Variant, - | ^^^^^^^ - -error: [note] error occurred while deriving `UriDisplay` - --> $DIR/uri_display.rs:12:10 - | -12 | #[derive(UriDisplayQuery)] - | ^^^^^^^^^^^^^^^ - | - = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) - error: tuple structs or variants must have exactly one field --> $DIR/uri_display.rs:18:12 | diff --git a/core/codegen/tests/ui-fail/from_form_field.rs b/core/codegen/tests/ui-fail/from_form_field.rs index bedf84df..85d49ca4 100644 --- a/core/codegen/tests/ui-fail/from_form_field.rs +++ b/core/codegen/tests/ui-fail/from_form_field.rs @@ -36,6 +36,52 @@ enum Bar2 { A, } +#[derive(FromFormField)] +enum Dup1 { + #[field(value = "bar")] + #[field(value = "bar")] + A, +} + +#[derive(FromFormField)] +enum Dup2 { + #[field(value = "bar")] + A, + #[field(value = "BAr")] + B, +} + +#[derive(FromFormField)] +enum Dup3 { + A, + #[field(value = "a")] + B, +} + +#[derive(FromFormField)] +enum Dup4 { + A, + #[field(value = "c")] // this shouldn't error + B, + #[field(value = "b")] // this shouldn't error + C, +} + +#[derive(FromFormField)] +enum Dup5 { + #[field(value = "a")] // this shouldn't error + A, + B, + C, +} + +#[derive(FromFormField)] +enum Dup6 { + #[field(value = "FoO")] + #[field(value = "foo")] + A, +} + #[derive(FromForm)] struct Renamed0 { #[field(name = "foo")] diff --git a/core/codegen/tests/uri_display.rs b/core/codegen/tests/uri_display.rs index 5cd5aa47..e3748a5e 100644 --- a/core/codegen/tests/uri_display.rs +++ b/core/codegen/tests/uri_display.rs @@ -112,6 +112,55 @@ fn uri_display_bam() { assert_uri_display_query!(bam, "foo=hi%20hi&baz=tony"); } +#[test] +fn uri_display_c_like() { + #[derive(UriDisplayQuery)] + enum CLike { A, B, C } + + assert_uri_display_query!(CLike::A, "A"); + assert_uri_display_query!(CLike::B, "B"); + assert_uri_display_query!(CLike::C, "C"); + + #[derive(UriDisplayQuery)] + enum CLikeV { + #[field(value = "a")] + A, + #[field(value = "tomato")] + #[field(value = "juice")] + B, + #[field(value = "carrot")] + C + } + + assert_uri_display_query!(CLikeV::A, "a"); + assert_uri_display_query!(CLikeV::B, "tomato"); + assert_uri_display_query!(CLikeV::C, "carrot"); + + #[derive(UriDisplayQuery)] + #[allow(non_camel_case_types)] + enum CLikeR { r#for, r#type, r#async, #[field(value = "stop")] r#yield } + + assert_uri_display_query!(CLikeR::r#for, "for"); + assert_uri_display_query!(CLikeR::r#type, "type"); + assert_uri_display_query!(CLikeR::r#async, "async"); + assert_uri_display_query!(CLikeR::r#yield, "stop"); + + #[derive(UriDisplayQuery)] + struct Nested { + foo: CLike, + bar: CLikeV, + last: CLikeR + } + + let nested = Nested { + foo: CLike::B, + bar: CLikeV::B, + last: CLikeR::r#type, + }; + + assert_uri_display_query!(nested, "foo=B&bar=tomato&last=type"); +} + macro_rules! assert_uri_display_path { ($v:expr, $s:expr) => ( let uri_string = format!("{}", &$v as &dyn UriDisplay);