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

How to treat inert attributes on macro invocations? #63221

Open
petrochenkov opened this issue Aug 2, 2019 · 6 comments

Comments

@petrochenkov
Copy link
Contributor

@petrochenkov petrochenkov commented Aug 2, 2019

Examples of inert attributes on macro invocations:

#[inert]
bang_macro!();

#[inert]
#[attr_macro]
struct S;

#[inert]
#[derive(DeriveMacro)]
struct S;

// Doc comments are also attributes (attribute literals if you wish).

/// Doc.
bang_macro!();

/// Doc.
#[attr_macro]
struct S;

/// Doc.
#[derive(DeriveMacro)]
struct S;

How these attributes are treated currently (ad hoc, there's no RFC or anything):

  • For bang macros the attributes are thrown away (sometimes with a warning).
  • For attribute macros and derive macros the attributes become a part of the macro input, the attribute tokens are prepended to the tokens of the "primary" input.
    Effectively, #[inert] #[macro_attr] struct S; -> macro_attr! { #[inert] struct S; }.

TODO: Possible alternatives and interpretations.

Related issues: #61733 (comment).

@petrochenkov

This comment has been minimized.

Copy link
Contributor Author

@petrochenkov petrochenkov commented Aug 2, 2019

Ideally I'd want a common scheme for all macro kinds + a scheme that's more intuitive and/or resistant to mistakes.
As recent improvements to the unused_doc_comments (#57882) showed, people somewhat often expect

/// Doc.
mac! {
    struct S;
}

to translate into

/// Doc.
struct S;

, so the current approach of throwing the attributes away may be not the best one.

@petrochenkov

This comment has been minimized.

Copy link
Contributor Author

@petrochenkov petrochenkov commented Aug 2, 2019

Alternative 1: Throwing the attributes away.

  • #[inert] bang_macro!() -> bang_macro!()
  • #[inert] #[attr_macro] struct S; -> #[attr_macro] struct S;
  • #[inert] #[derive(DeriveMacro)] struct S; -> #[derive(DeriveMacro)] struct S;

This alternative seems non-viable for attributes/derives.
It's very idiomatic to write doc comments (which are inert attributes) before any other attributes

/// Doc.
#[some_attrs]
#[derive(SomeDerives)]
struct S;

and the doc comments must not be lost during expansion in this case.

For bang macros this is probably undesirable as well (#63221 (comment)), this leads to users writing code that most likely doesn't do what they expect.

@petrochenkov

This comment has been minimized.

Copy link
Contributor Author

@petrochenkov petrochenkov commented Aug 2, 2019

Alternative 2: Prepending the attributes to the macro output.

This alternative basically follows the token-based expansion model described in #61733 (comment).

Suppose our macro is derive-like and emit the input item (after perhaps tweaking it slightly) + some additional items like impls.

mac! { struct S; }

=>

pub struct S;
impl S { ... }

In this case the behavior is pretty reasonable, #[inert] mac! { struct S; } becomes #[inert] pub struct S;.

It's less reasonable if the macro doesn't have a "primary" item, e.g.

#[inert]
mac!();

=>

#[inert]
struct U;

struct V;

struct W;

, in this case the macro may want to "spread" the attribute across all the produced items.

Note that for post-fix semicolons appending them in accordance with the token-based model is almost always the desired behavior that turns the final expression produced by the macro into a non-trailing statement or has no effect.

Migrating to this model would be a breaking change for attribute and derive macros, we can estimate the scale of the breakage with crater.
It's expected to be somewhat sizeable because inert attributes are commonly used before macro attributes and derives.

@petrochenkov

This comment has been minimized.

Copy link
Contributor Author

@petrochenkov petrochenkov commented Aug 2, 2019

Alternative 3: Prepending the attributes to the macro input.

This is the most flexible alternative - let the macro itself to decide what to do with the attributes.
Attribute and derive macros use this approach currently.

In case of derive-like macros it works somewhat automatically, similarly to the alternative 2.

#[inert]
mac! { struct S; }

=>

mac! { #[inert] struct S; }

=>

#[inert]
pub struct S;
impl S { ... }

In other cases the macro may implement the spreading semantics or some other semantics itself.

Migrating to this model would be a breaking change for fn-like macros, we can estimate the scale of the breakage with crater.
I'd expect the breakage in this case to be more limited compared to the alternative 2.
The first reason for this is that inert attributes on fn-like macro calls are currently thrown away and sometimes warned about, so they are expected to be rare and written accidentally.
The second reason is that if something like

/// Doc.
mac! {
    struct S;
}

is written accidentally, then it's likely that the macro will expect an item and accept

mac! {
    /// Doc.
    struct S;
}

as well, so in some cases the change may fix the code rather than break it.

We can analyze the breakage and perhaps add some compatibility hacks if necessary (until the next edition or something).

@Centril

This comment has been minimized.

Copy link
Member

@Centril Centril commented Aug 2, 2019

I tend to agree that alternative 3 seems best here.

@Centril

This comment has been minimized.

Copy link
Member

@Centril Centril commented Aug 2, 2019

Plausibly relevant: It would be neat if when given #[derive(Foo, Bar)] the derive macros would know of each other.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.