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);