#missing fields error when using proc_macro_attribute to add fields

21 messages · Page 1 of 1 (latest)

marsh copper
#

I am trying to make a procedural macro that adds a common set of fields to a struct.
When using the macro on a struct, I get the error: missing fields `example1` and `example2` in initializer

macros/src/lib.rs:

extern crate proc_macro;
extern crate quote;
extern crate syn;

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Fields};

#[proc_macro_attribute]
pub fn commonfields(item: TokenStream, input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    let struct_name = &input.ident;
    let data = match input.data {
        syn::Data::Struct(ref data) => data,
        _ => panic!("commonfields macro can only be used on structs"),
    };

    let existing_fields = match &data.fields {
        Fields::Named(fields) => fields.named.iter().map(|f| &f.ident).collect::<Vec<_>>(),
        _ => panic!("commonfields macro currently only supports named fields"),
    };

    let new_fields = vec![
        quote! { example1: String },
        quote! { example2: u32 },
    ];

    let expanded = quote! {
        pub struct #struct_name {
            #(
                #existing_fields: String,
            )*
            #(
                #new_fields,
            )*
        }
    };

TokenStream::from(expanded)
}

src/pagetypes.rs:

use macros::commonfields;
use serde::{Serialize, Deserialize};
extern crate macros;
// [...]
#[derive(Serialize, Deserialize, Debug)]
#[commonfields]
pub struct PageContent {
    content: String,
    icon: String,
    icontitle: String,
    theme: String,
    cat: String,
    startdate: String,
    enddate: String
}

full error:

error[E0063]: missing fields `example1` and `example2` in initializer of `pagetypes::PageContent`
  --> src/pagetypes.rs:48:12
   |
48 | pub struct PageContent {
   |            ^^^^^^^^^^^ missing `example1` and `example2`
marsh copper
#

From my understanding by looking at cargo-expand's output is that the serde derive does not "see" the fields added by #[commonfields]; not added to Serialize/Deserialize impl.

placid radish
#

you need to put the attributes the other way around than you have them

#

in general, macros are always expanded outside-in, and this applies to attribute macros and derive macros too

#

so, if you want derive(Serialize) to see the fields, you have to have commonfields produce the fields before derive(Serialize) executes

#[commonfields]
#[derive(Serialize, Deserialize, Debug)]
pub struct PageContent {
#

(This problem (mostly) doesn't come up when using only derive macros, because derive macros produce new separate code rather than modifying what they're applied to.)

marsh copper
#

I have tried that, where it is used in the enum:

#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type")]
pub enum Page {
    #[serde(alias = "content")]
    Content(PageContent)
}

There are the following errors:

error[E0277]: the trait bound `pagetypes::PageContent: _::_serde::Deserialize<'_>` is not satisfied
  --> src/pagetypes.rs:68:13
   |
68 |     Content(PageContent),
   |             ^^^^^^^^^^^ the trait `_::_serde::Deserialize<'_>` is not implemented for `pagetypes::PageContent`
   |
   = note: for local types consider adding `#[derive(serde::Deserialize)]` to your `pagetypes::PageContent` type
   = note: for types from other crates check whether the crate offers a `serde` feature flag
   = help: the following other types implement trait `_::_serde::Deserialize<'de>`:
             bool
             char
             isize
             i8
             i16
             i32
             i64
             i128
           and 184 others
#
error[E0277]: the trait bound `pagetypes::PageContent: _::_serde::Serialize` is not satisfied
  --> src/pagetypes.rs:64:10
   |
64 | #[derive(Serialize, Deserialize, Debug)]
   |          ^^^^^^^^^ the trait `_::_serde::Serialize` is not implemented for `pagetypes::PageContent`
...
68 |     Content(PageContent),
   |             ----------- required by a bound introduced by this call
   |
   = note: for local types consider adding `#[derive(serde::Serialize)]` to your `pagetypes::PageContent` type
   = note: for types from other crates check whether the crate offers a `serde` feature flag
   = help: the following other types implement trait `_::_serde::Serialize`:
             bool
             char
             isize
             i8
             i16
             i32
             i64
             i128
           and 167 others
note: required by a bound in `_::_serde::__private::ser::serialize_tagged_newtype`
  --> /home/ctcl/.cargo/registry/src/index.crates.io-6f17d22bba15001f/serde-1.0.209/src/private/ser.rs:27:8
   |
17 | pub fn serialize_tagged_newtype<S, T>(
   |        ------------------------ required by a bound in this function
...
27 |     T: Serialize,
   |        ^^^^^^^^^ required by this bound in `serialize_tagged_newtype`
   = note: this error originates in the derive macro `Serialize` (in Nightly builds, run with -Z macro-backtrace for more info)
placid radish
#

the error is telling you that you haven't implemented Serialize or Deserialize for PageContent

#

that is a separate problem from the macro problem

marsh copper
#

Though they have been implemented with #derive(Serialize, Deserialize)]?

dusty cove
#

Maybe check if #[commonfields] is removing the #[derive] in it's emitted code?

placid radish
#

oh, I misread

#

yeah, make sure you are passing through the attributes

marsh copper
#

In the macro, I have added the derive like this:

  let expanded = quote! {
        #[derive(Serialize, Deserialize, Debug)]
        pub struct #struct_name {
            #(
                #new_fields,
            )*
            #(
                #existing_fields: String,
            )*
        }
    };

That seemed to have solved it

Thanks so much to both of you

placid radish
#

it would probably be more useful to copy the attributes from the input instead of hardcoding them

marsh copper
#

Yes, that would make more sense

placid radish
#

show the code you're using for parsing the input

#

but broadly, I expect there's a field in the parse result that has the attributes

#

and you just add #( #attrs )* to your quote! output