What I expected to happen was that the warning would be silenced.
Instead, this code still triggers the same warning.
Is this a bug, or is this intended behavior?
(Though for the life of me I can't imagine why this would be an intended behavior. Perhaps it's an unintended side-effect of otherwise intended behavior?)
For completeness' sake, this does succeed in making rustc omit the error:
Hmm I can see that what you're saying is true. And yet the situation described still persists in my real code.
The real enum is a bit more complex, as it has a derive attribute, a tuple struct variant and a bunch of unit struct variants. So now I'm guessing something about that cocktail is confusing the compiler, because what else could it possibly be?
From first principles I wonder how the derive could even influence that. Derive macro's are limited to generating new code, and have read-only access to the type's tokens, correct? So what could possibly be generated that generates a warning of this kind on the input enum variant itself?
For reference, these are the traits being derived: Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize and one I've written called Delta that appropriately enough calculates deltas for 2 values of the same type as well as apply those deltas. But like I said, above, AFAIK derives have read-only access to their input, so it would have to be some generated code somehow that is triggering the warning on the input enum.
Also a note on cargo-expand: I actually tried to use it during the development of my derive macro. My takeaway was that its usability is greatly hampered by the fact that it does an expand-all in lisp terms, while what I need is an expand-1 i.e. just 1 level of macro expansion. This is because I don't care about generated code that uses macros in the output. In fact its presence is detrimental because it means that the information I care about (i.e. the signal) gets buried between bits and pieces of information I don't care about (i.e. the noise).
A derive could be creating a type with a name based on your type, and the warning is emitted due to the macro-created-type. I don't think Serialize and Deserialize do this, none of the std derives do this, so maybe Delta?
The Delta derive macro does in fact do this.
Okay so given that I also have the fix (which is easier said than done though): copy over the tokens for the enum variant's attribute macro to the corresponding enum variant of the generated type.
You could unconditionally #[allow(nonstandard_style)] on your derived code. If the user has something weird, they'll get those warnings on their own part, or mask it themselves, but I don't think you should worry about that on your part.
That's a pretty excellent solution that I didn't know existed. Thanks for the tip, that will make it a lot easier to fix this, at least for the short term.
The long term solution will still require passing of attribute macro tokens from input to generated output, because attributes other than this one may be applied to enum variants.
This isn't correct. In fact, it's quite common for derives to delete stuff that's there; this is how custom attributes to control the derive usually work.
This takes ownership of the TokenStream, and returns an owned one. Nothing prevents you from doing whatever you want, other than it likely confusing users and causing them to not use your derive.
The input TokenStream is the token stream of the item that has the derive attribute on it. The output TokenStream must be a set of items that are then appended to the module or block that the item from the input TokenStream is in.
(emphasis mine)
only attribute/function like macros replace their inputs
I have defined a #[deltoid(ignore_field)] attribute as part of the attribute macro that, when applied to a field, ignores that field when computing and applying a delta. (so no, luckily it's not removed from the token stream). Basically it's just a part of the input which can be parsed using syn, at which point you have an enum and struct based AST. So it's possible to just scan the AST for the information required, and generate code accordingly.
serde just passes in the new field name to serialize_field instead of extracting to actual field name. If serde changed the actual type, you couldn't use the names you declared in your code.
Derive macros can declare a number of “helper” attributes that they will parse during the expansion. These helpers are then treated as noop attribute macros after the expansion has completed. One key for this is that Serialize and Deserialize share the same attributes, if one of them were to remove the attribute during expansion then the second to run would not get to see it.
Rustc knows that these attributes belong to the derive macros, and ignores them for all other purposes. When declaring a derive proc macro, the macro programmer also declares all the attributes it takes in, like:
As I understand it, then, the attributes declared by a proc macro are treated as valid, and ignored for all purposes except being sent to proc macros. I think they're also scoped per-derive: a derive macro won't see #[serde(...)] unless it declares the serde attribute.
That is roughly what I do now, albeit with a feature flag provided to the derive macro. In fact there are 2, one that dumps to stdout and one that dumps each type's derive expansion to its own file.