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

Procedural macro attributes can't be applied to struct fields #53012

Open
mjbshaw opened this issue Aug 3, 2018 · 8 comments
Open

Procedural macro attributes can't be applied to struct fields #53012

mjbshaw opened this issue Aug 3, 2018 · 8 comments
Labels
A-macros Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..) T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@mjbshaw
Copy link
Contributor

mjbshaw commented Aug 3, 2018

Given the simple program:

#![feature(rust_2018_preview)]
extern crate demo_macros;
use demo_macros::nop;

#[nop]
pub struct Foo {
  #[nop]
  pub foo_field: u32,
}

#[nop]
impl Foo {
  #[nop]
  pub fn foo_fn() {}
}

And the corresponding definition of the #[nop] attribute:

#![feature(rust_2018_preview)]
extern crate proc_macro;
use proc_macro::TokenStream;

#[proc_macro_attribute]
pub fn nop(_args: TokenStream, input: TokenStream) -> TokenStream {
  return input;
}

Attempting to compile this results in the error:

error[E0658]: The attribute `nop` is currently unknown to the compiler and may have meaning added to it in the future (see issue #29642)
 --> src/lib.rs:7:3
  |
7 |   #[nop]
  |   ^^^^^^
  |
  = help: add #![feature(custom_attribute)] to the crate attributes to enable

error: aborting due to previous error

The problem is the #[nop] attribute on foo_field. If it's removed, the library compiles fine. I believe this is incorrect and that the code should compile as-is. The #[nop] macro can be applied to foo_fn just fine; it seems wrong that it doesn't work with foo_field.

Rust version: rustc --version: rustc 1.29.0-nightly (97085f9fb 2018-08-01)

Please let me know if there's anything I can do to be of assistance.

Here's a bash script that fully reproduces the issue:

#!/bin/bash

cargo +nightly new --lib demo_macros

cat >> demo_macros/Cargo.toml << EOF
[lib]
proc-macro = true
EOF

cat >| demo_macros/src/lib.rs << EOF
#![feature(rust_2018_preview)]
extern crate proc_macro;
use proc_macro::TokenStream;

#[proc_macro_attribute]
pub fn nop(_args: TokenStream, input: TokenStream) -> TokenStream {
  return input;
}
EOF

cargo +nightly init --lib .

echo 'demo_macros = { path = "demo_macros" }' >> Cargo.toml

cat >| src/lib.rs << EOF
#![feature(rust_2018_preview)]
extern crate demo_macros;
use demo_macros::nop;

#[nop]
pub struct Foo {
  #[nop]
  pub foo_field: u32,
}

#[nop]
impl Foo {
  #[nop]
  pub fn foo_fn() {}
}
EOF

cargo +nightly build
@eddyb
Copy link
Member

eddyb commented Aug 20, 2018

@dtolnay
Copy link
Member

dtolnay commented Aug 20, 2018

I would not want proc macro attributes to be allowed on struct fields, just as a decl macro cannot expand to one field of a struct. Proc macro attributes should remain conceptually as a way to pass a fragment of syntax as the input to an eagerly expanded proc macro and the following is not allowed.

pub struct Foo {
    // do not allow
    nop! {
        pub foo_field: u32,
    }
}

Instead I would want proc_macro_attribute macros to declare names of inert helper attributes the way that proc_macro_derives do. This is the same request as #38356 (comment).

#[proc_macro_derive(Serialize, attributes(serde))]
pub fn derive_serialize(input: TokenStream) -> TokenStream;

#[proc_macro_attribute(attributes(serde))]
pub fn derive_serialize_as_an_attribute(args: TokenStream, input: TokenStream) -> TokenStream;
#[derive(Serialize)]
pub struct Foo {
    #[serde(...)]
    pub foo_field: u32,
}

#[derive_serialize_as_an_attribute]
pub struct Foo {
    #[serde(...)]
    pub foo_field: u32,
}

@mjbshaw
Copy link
Contributor Author

mjbshaw commented Aug 20, 2018

Instead I would want proc_macro_attribute macros to declare names of inert helper attributes the way that proc_macro_derives do.

That would work for me, but would there be inconsistency between how struct items and impl items allow uses of attribute macros (in their inner sub-items/fields)?

@dtolnay
Copy link
Member

dtolnay commented Aug 20, 2018

@mjbshaw it is intentional. Inserting invisible items into an impl block is not at all disruptive while inserting invisible fields into a struct affects its use in struct literals. We may allow it at some point, possibly with restrictions like we have for macros in expression position (they must expand to exactly one expression), but we should not allow proc macro attributes on struct fields before we allow bang macros around struct fields.

pub struct Foo {
    // If this expands to more than one field, I can no longer write
    // the struct literal `Foo { foo_field: 0 }`.
    not_noop! {
        pub foo_field: u32,
    }
}

impl Foo {
    // If this expands to more than one impl item there is no disruption.
    not_noop! {
        fn method(&self) {}
    }
}

@mjbshaw
Copy link
Contributor Author

mjbshaw commented Sep 2, 2018

Thanks for the explanation, @dtolnay. Respectfully, I have to disagree with the justification for disallowing this. My counterarguments are:

  1. An item-level macro could do the exact same "disruptive" manipulation, yet it is allowed:
not_noop! {
    pub struct Foo {
        pub foo_field: u32,
    }
}
  1. A macro around an impl item can be just as disruptive as a macro around a struct item, yet it is allowed:
macro_rules! not_noop {
    ( pub fn $ident:ident ( $($args:tt)* ) {} ) => {
        pub fn $ident(_: usize, $($args)*) {}
    }
}

pub struct Foo;

impl Foo {
    not_noop! {
        pub fn method() {}
    }
}

pub fn call_foo_method() {
    // I can no longer call Foo::method(). I need to pass in a usize.
    Foo::method(0usize);
}

I don't believe the disallowance of a (attribute) macro within a struct item provides any benefit. It just unnecessarily hinders users.

@estebank estebank added A-macros Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..) T-lang Relevant to the language team, which will review and decide on the PR/issue. labels Jan 8, 2019
@steveklabnik
Copy link
Member

Triage: don't think anything has changed here, not sure if it's going to.

@petrochenkov
Copy link
Contributor

Current error:

struct S {
    #[global_allocator]
    field: u8
}

fn main() {}
error: expected an inert attribute, found an attribute macro
 --> src/main.rs:2:5
  |
2 |     #[global_allocator]
  |     ^^^^^^^^^^^^^^^^^^^

This is a language extension, so it should go through RFC or similar process.
Not sure this issue needs to be open on the rust-lang/rust bug tracker.

@lamafab
Copy link

lamafab commented Jul 19, 2020

I would really appreciate this feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-macros Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..) T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

7 participants