Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Allow #[serde(rename=str|bool|int)] for enum variants. #973

Closed
wants to merge 7 commits into from

Conversation

Phaiax
Copy link

@Phaiax Phaiax commented Jul 1, 2017

(This commit contains untested code and a dirty hacks so that the rename="str" still works)

#745 Issue for this PR

I got started and it was not too hard, serde derive is written very well. (I'm still confused by the deserializiation part, but I have not looked into it to much)

Some questions:

  1. Non string literals are not stabilized yet. I think we should support a fallback with #[serde(rename="1", rename_as="int")]. This is necessary since serde supports old rustc versions anyway, so even with stabilization this will not go away.
    error: non-string literals in attributes, or string literals in top-level positions, are experimental (see issue #34981)
       --> tests/test_macros.rs:713:9
        |
    713 |         #[serde(rename=true)]
        |         ^^^^^^^^^^^^^^^^^^^^^
        |
        = help: add #![feature(attr_literals)] to the crate attributes to enable
    
  2. Currently I implemented a type StrBoolInt, but I had to define it twice, once in serde_derive_internals and once again in serde_derive, so that I can implement ToTokens.
  3. The second StrBoolInt is stuffed into fragment.rs. Should I use a new file or is that ok?
  4. I decided to only allow non-string renames for internally and adjacently tagged enums. Correct?
  5. As far as I understood it, Serializers have the choice to serialize either with the name of the variant or with the id. What happens if rename=2 is stated for attribute with field number 1? Should I just replace the whole the derserialize_u32 part as soon as an int-rename is encountered?
  6. What is that [#serde(field_identifier/variant_identifier)] setting? It is not mentioned in the docs.

Thx, Daniel

@Phaiax
Copy link
Author

Phaiax commented Jul 5, 2017

Hey @dtolnay .

I think this is ready for a first review.

As for my Questions.

  1. The implementation treats #[serde(rename=3)] the same as #[serde(rename="3", rename_as="int")] and gives errors if the parsing fails or the rename_as is used with a non string literal for rename.
  2. still same question as above.
  3. still same question as above.
  4. still same question as above.
  5. I implemented it so that deserializing by index is deactivated as soon as one rename has an int type.
  6. still same question as above.
  7. This PR will collide with Fix identifier deserialization from non-u32 #963 (same lines edited). I use u64 for the int type.
  8. Naming of structs. See my own review.

After review I will merge the commits (except for the last) into one.

Copy link
Author

@Phaiax Phaiax left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Questions / Notes

}

#[cfg(not(any(feature = "std", feature = "alloc")))]
pub fn from_int(i: u64) -> [u8; 20] {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the int/bool to string is needed for fn deserialize_identifier(..., fallthrough_arm : Option<Tokens>) where the fallthrough arm expects a local '&str' value, so writing to stdout is not possible.
It feels a bit too hacky :D

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems fine. Nicely done.

@@ -19,25 +19,63 @@ pub fn constrain<T: ?Sized>(t: &T) -> &T {
t
}

pub enum VariantName {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: This enum is defined three times. The other two times it is named StrBoolInt.
Should it be named after its purpose or after its content?

In the serde_derive_internals crate, the name VariantName is already occupied by the struct VariantName { deserialize: StrBoolInt, serialize: StrBoolInt }. Maybe rename the VariantName into SerDeVariantName and StrBoolInt into VariantName?

@@ -602,7 +602,7 @@ fn deserialize_externally_tagged_enum(
.iter()
.enumerate()
.filter(|&(_, variant)| !variant.attrs.skip_deserializing())
.map(|(i, variant)| (variant.attrs.name().deserialize_name(), field_i(i)),)
.map(|(i, variant)| (StrBoolInt::from(variant.attrs.name().deserialize_name()), field_i(i)))
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: These StrBoolInt::from() calls convert from serde_derive_internals::StrBoolInt into serde_derive::StrBoolInt (at least in enum/variant related functions, not in struct/field related functions).

@@ -1366,14 +1366,29 @@ fn deserialize_custom_identifier(

fn deserialize_identifier(
this: Tokens,
fields: &[(String, Ident)],
fields: &[(StrBoolInt, Ident)],
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe renaming StrBoolInt into VariantName is not so good because it can also represent a FieldName, (in this case it is always a string).


let constructors: &Vec<_> = &fields
let field_strs = fields.iter().filter_map(|&(ref n, _)| n.as_str());
let field_bytes = fields.iter().filter_map(|&(ref n, _)| n.as_str().map(|n| quote::ByteStr(n)));
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe rename into matcharms_str. (field_strs was the original name)

///
/// It will be used as second argument in
/// `_serde::Serializer::serialize_struct(__serializer, name, len)`
/// while serializing the inner struct in adjacently tagged enums.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be a problem, depending on how a serializer serializes a struct name. But I guess that formats that require a tagged enum do not serialize the variant/struct name. (This case is part of the tests and has a comment there also)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am okay with the behavior as you implemented it.


/// An extended name, used for the type tag for enums.
#[derive(Debug, Clone)]
pub enum StrBoolInt {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct file for this struct?

let variant_name = variant.attrs.name().serialize_name();
let variant_name = match variant.attrs.name().serialize_name() {
attr::StrBoolInt::Str(s) => s,
_ => unreachable!(),
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check.rs prevents this case

Token::Bool(false)
],
"unknown variant `false`, expected one of `abc`, `true`, `3`, `4`, `5`, `6`",
);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the error message doesn't differentiate between string literals and non string literals ('3' vs 3). Should be ok anyway I hope.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fine.

Token::Bool(true),

Token::Str("c"),
// TODO: Is this the correct way to name the struct? stringify the rename?
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that is the comment I referred to earlier in the review.

Copy link
Member

@dtolnay dtolnay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer not to implement this with rename_as because we will need to support that for long after the better attribute way is stable.

How would you feel about implementing just the attr_literals way and putting some pressure on the lang team to stabilize rust-lang/rust#34981? As far as I can tell there haven't been objections or changes to it over the past 11 months so it should be just about ready.

Alternatively I would be okay with a temporary fork of serde_derive under a different name like serde_derive_experimental that provides the rename_as workaround.

@dtolnay
Copy link
Member

dtolnay commented Jul 30, 2017

Currently I implemented a type StrBoolInt, but I had to define it twice, once in serde_derive_internals and once again in serde_derive, so that I can implement ToTokens.

serde_derive depends on serde_derive_internals so it should have access to the StrBoolInt in serde_derive_internals. We can add a dependency of serde_derive_internals on quote if necessary and provide the impl there.

The second StrBoolInt is stuffed into fragment.rs. Should I use a new file or is that ok?

It should be in serde_derive_internals.

I decided to only allow non-string renames for internally and adjacently tagged enums. Correct?

Yes I agree with this choice.

What is that [#serde(field_identifier/variant_identifier)] setting? It is not mentioned in the docs.

They generate a Deserialize impl for reading field names in a struct or variant names in an enum in a way that is compatible with how derive(Deserialize) behaves on structs and externally tagged enums. See the example of field_identifier in Manually implementing Deserialize for a struct. This is helpful when you want custom Deserialize logic but the field names / variant names do not need to be custom.

@Ten0
Copy link

Ten0 commented Apr 2, 2020

What happened to this ? Oh nvm it's replaced by #1392 right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants