Rust: Tracking issue for stmt_expr_attributes: Add attributes to expressions, etc.

Created on 16 Jul 2014  ·  98Comments  ·  Source: rust-lang/rust

A-grammar B-RFC-implemented B-unstable C-tracking-issue T-lang

Most helpful comment

It's worth noting that in many cases you can work around the lack of expression attribute support by simply wrapping the expression in a block, turning it into a statement.

All 98 comments

cc @huonw

ping. will this be implemented before 1.0?

The RFC suggests

The sort of things one could do with other arbitrary annotations are

#[allowed_unsafe_actions(ffi)]
#[audited="2014-04-22"]
unsafe { ... }

and then have an external tool that checks that that unsafe block's only unsafe actions are FFI, or a tool that lists blocks that have been changed since the last audit or haven't been audited ever.

which inspired me to make a plugin for cryptographically signing unsafe blocks. It would be great to put those signatures on the blocks themselves rather than the enclosing function.

The RFC says:

Attributes bind tighter than any operator, that is #[attr] x op y is always parsed as (#[attr] x) op y.

Does this mean that #[attr] x.f() should be parsed as (#[attr] x).f()? And should #[a] f() be the same as (#[a] f)()?

And what happens if an attribute is applied to a syntax sugar construct that is later expanded, for example #[attr] in <place> { <expr> }? There isn't really an obvious place for the attribute in the desugared form, so not allowing them seems more sensible.

Just a heads up that I'm currently in the process of implementing this RFC.

I think the obvious choice is to give attributes the same operator precedence as unary operators.
So #[attr] x.f() would be parsed as #[attr] (x.f()), but #[attr] x + f will be (#[attr] x) + f.

Yeah, thats how I'm doing it in my WIP branch right now.

@Kimundi, is there a reason that https://github.com/rust-lang/rust/pull/29850 doesn't close this?

Not as far as I can rember, probably just forgot about it. :)

FYI, the stmt_expr_attributes feature gate still mentions this issue number.

Tracking issues are open until the feature is stable, aren't they?

By what Rust version will this feature gate be allowed on the stable channel? I couldn't find any chart / matrix with the list of them.

Nominating for discussion at lang-team meeting. I'd like to propose we stabilise on statements, remove from expressions. I suppose that requires an amendment RFC?

Do we have examples of uses where an attribute is on an expression, not a statement (and not an expression in statement position)?

@nrc Yes, here is one (which I have used as well): https://github.com/rust-lang/rust/issues/32796#issuecomment-216884218

Hear ye, hear ye! This unstable issue is now entering final comment period. We wanted to move to stabilize attributes at the statement, but we also wished to seek feedback on attributes on the expression and sub-expression level. This FCP lasts for roughly the current release cycle, which began on Aug 18.

@rust-lang/lang members, please check off your name to signal agreement. Leave a comment with concerns or objections. Others, please leave comments. Thanks!

  • [x] @nikomatsakis
  • [x] @nrc
  • [x] @aturon
  • [x] @eddyb
  • [x] @pnkfelix (on vacation)

My take on the expression-level attributes: I remain somewhat uncomfortable with the precedence and so forth of said attributes, but on the other hand I don't see any fundamental difficulties. The use-case described by @comex seems pretty compelling. I also can't help but think of Java's experience with @Attribute, which were initially very limited in where they could be used, but which have been gradually being expanded more and more. Finally, it seems like since Rust is such an expression-oriented language, limiting to statements might be a bit surprising.

@nrc, I'm curious what your specific concerns about regarding expression positions.

Does Java allow attributes on expressions.

My objection is basically that it is confusing and feels weird, and that there have not been enough use cases to persuade me that it is worth the confusing weirdness. In particular since any expression can be made into a statement by extracting a variable, there is always a work around. I believe, that doing that refactoring where an attribute is required results in nicer code in nearly all cases.

Concretely, a large part of the weirdness to me comes from precedence difficulties. There is basically no 'good' precedence - e.g., #[foo] a + b, I feel like there is no reason to apply it to the whole expression or the sub-expression. There is no intuition for which expression the attribute should apply to.

I think of attributes as a way to indicate meta-data about an AST node to the compiler or a tool or macro. I have a hard time thinking of such meta-data that should be indicated at an expression-level granularity. Anything I can think of would change the semantics of the expression in such a way that I would expect there to be first-class syntax for it.

Finally there is the exact meaning of attributes, especially cfg on experssions, e.g., what does (#[cfg(foo)] a) + b mean? + b is not a well-formed expression, but by the same reasoning (#[cfg(foo)] a + ) b should not parse.

Looking at the example provided as a use case:

let backends = [
    #[cfg(feature = "foo-backend")] foo_backend,
    #[cfg(feature = "bar-backend")] bar_backend,
];

If neither of the features hold, then we should have let backends = [,,]. This works in practice because of the way the list node is defined in the AST. I.e., the feature is kind of weird in that it is applying not just to an expression node in the AST, but also some decoration, depending on the exact implementation of the parent AST node.

@nrc Keep in mind that you can't work around by extracting a variable if the goal is to conditionally remove an element from some sort of list, including arrays as in the 'backends' case, and potentially function arguments and fields in struct definitions/literals. Though for the latter, it's probably better to just use a typedef which evaluates to () in the omitted case.

Incidentally, a data point relevant to attributes on _patterns_: GCC and Clang have a number of function attributes which require specifying one or more parameters by number; for example, __attribute__(alloc_size(2)) is a hint that the function will return a newly allocated buffer whose size is the value passed as the second argument. If such an attribute were added to Rust, it would be nicer if it could just be placed directly on the relevant parameter declaration rather than requiring counting.

For that matter, any future attribute for variables or name bindings in general would ideally be writable wherever variables can be declared, and only to individual variables within a multi-variable declaration, as opposed to adorning a let statement or whatnot. I don't have any good examples, though, since out of GCC's variable attributes, I didn't notice any that weren't either (a) only applicable to statics (e.g. 'section'), (b) better expressed as part of the type (e.g. alignment), or (c) otherwise already handled by Rust ('unused').

@comex

...otherwise already handled by Rust ('unused')

FWIW, I am occassionally annoyed at my inability to label unused things with more precision (e.g., an individual binding). I can't come up with any good examples just now though. =)

@nrc

Concretely, a large part of the weirdness to me comes from precedence difficulties. There is basically no 'good' precedence - e.g., #[foo] a + b, I feel like there is no reason to apply it to the whole expression or the sub-expression. There is no intuition for which expression the attribute should apply to.

To address precedence concerns, one could also imagine limiting the attribute to parenthesized expressions (I think we talked about this at one point?). e.g., #[foo] (a+b). Or even (#![foo] a + b).

This works in practice because of the way the list node is defined in the AST. I.e., the feature is kind of weird in that it is applying not just to an expression node in the AST, but also some decoration, depending on the exact implementation of the parent AST node.

That is weird, but also cool. =) The feature applies at the AST level, basically, where things like , are not really present at all. It's worth thinking though if this will cause difficulties as we move towards macros 2.0 etc... I guess not, since this is all done post-parsing, right?

I recently ran into a similar situation that made me want attributes on _more_ places, not less. In particular, I have a field:

struct DepGraph {
   ...
   forbidden: RefCell<Vec<...>>
}

but this field is only used for some debugging code:

impl DepGraph {
    ...
    fn foo() {
        debug_assert!(self.forbidden.blah);
    }
}

Because of our tight lints, this can result in the field forbidden being considered "dead code", I believe. It'd be nice to make the forbidden field #[cfg(debug_assertions)], but that is annoying because of the constructor:

    pub fn new(enabled: bool) -> DepGraph {
        DepGraph {
            data: Rc::new(DepGraphData {
                thread: DepGraphThreadData::new(enabled),
                previous_work_products: RefCell::new(FnvHashMap()),
                work_products: RefCell::new(FnvHashMap()),
                #[cfg(debug_assertions)] // what I want to write, but can't
                forbidden: RefCell::new(Vec::new()),
            })
        }
    }

This seems analogous to the vec! situation.

An use case for attributes on (at least block) expressions is to supply compiler with branch likeliness metadata.

I could imagine allowing attributes on blocks, perhaps only inner attributes.

Would it be possible to include something like “attribute scopes”, where we have a block to which the attribute is applied, but which is distinct from a scope? Right now, if I wanted to, say, declare several variables inside a scope they would be destroyed at the end of the scope. On the other hand, writing the attribute for every single variable is a bit excessive... Compare:

#[cfg(debug_assertions)] let foo_1 = 1;
#[cfg(debug_assertions)] let foo_2 = 2;
#[cfg(debug_assertions)] let foo_3 = 3;

My proposal:

#[cfg(debug_assertions)]
[
    let foo_1 = 1;
    let foo_2 = 2;
    let foo_3 = 3;
]

Classical scopes don't work:

#[cfg(debug_assertions)]
{
    let foo_1 = 1;
    let foo_2 = 2;
    let foo_3 = 3;
} // all variables get destroyed here

I think statement attributes should be stabilized, but I also want to clarify their relationship with lints. Currently #[allow(...)] stmt; doesn't seem to do anything, e.g. the following seems reasonable:

#[allow(unused_must_use)] something_that_returns_a_result();

but you still get a warning. Is this feasible to fix? If not, we'll be in a confusing situation where some statement attributes silently do nothing.

@durka I think this is "just a bug". We should file an issue. I would expect the lint to be scoped to the statement.

Though @durka's comment is interesting, in that it has bearing on the precedence of attributes on arbitrary expressions. Consider #[allow(foo)] a + b; --- does that apply to just a, or the entire statement (a + b)? Presumably this commits us to either limiting expression attributes to be within parens (#![foo] foo) or else giving them a very low priority. I'm ok with either of those. =)

Looks like FCP on this is over? It doesn't seem to have been stabilized yet. We're getting stuck on this in Servo where a macro is generating a large volume of warnings and we can't silence them easily. (https://github.com/servo/servo/pull/13435)

I'd like to (informally) propose that as a next step we stabilise attributes on block expressions, and deprecate attributes on other expressions. Thoughts?

Seems unnecessarily piecemeal. Why don't we want attributes on all arbitrary expressions?

I also echo @Manishearth's question... looking at the checkboxes it seems like statement attributes should be stable. Is a PR to stabilize statement attributes welcome, and is it too late to have that in 1.14?

Stabilised by #36995, which will be in 1.14.

Seems unnecessarily piecemeal. Why don't we want attributes on all arbitrary expressions?

Blocks seem like a good compromise to me between on all expressions (where the precedence issues are off-putting to me). Plus some weirdness over exactly how expressions in lists, etc. are handled. I realise these are not hard reasons to oppose, but I don't see enough use cases to outweigh the downsides.

Blocks don't suffer from the precedence weirdness and feel like a big enough chunk of code that attributes are worthwhile. From a practical point of view, It covers nearly all of the use cases on this thread and all of the really strong ones I've heard.

@nrc My only concern about _block_ expressions is that they can affect rvalue lifetimes, prohibit coercions, or have other subtle effects. What if we included also parenthesized expressions?

@nikomatsakis yeah, that seems fine to me. It makes sense from a perspective of allowing attributes on all expressions, but in a disciplined way (I was thinking more of where it makes some kind of logical sense to draw a line, but your earlier point about tending towards attributes in more places was persuasive).

Following that line of thought further, could they be allowed on everything _except_ binary operator expressions?

Following that line of thought further, could they be allowed on everything except binary operator expressions?

This feels like it would be confusing. I like that blocks and parens give a really obvious scope for what an attribute applies to. Composition of other expressions makes this difficult, e.g.,

foo(#[bar] x.f);

does bar apply to x or x.f? It also weakens the nice-ness of the parens rule:

foo(#[bar] (a + b).f);

With just the parens rule it is obvious that bar applies to a+b, without it, the scope is no longer obvious.

The rule should have to do more with prefixes, which makes _a lot_ of expression kinds ambiguous.
That is, x is a prefix of x.f just like a is a prefix of a + b.

Based on the current set of expressions, the following are prefix-unambiguous:

  • literal expressions
  • a::b::c (path expressions)
  • {...} (block expression) and (expr) (paren expression)
  • macro invocations
  • [a, b, c], [expr; n] (arrays), (a, b, c) (tuples), Foo { ... } (structs)
  • box expr
  • -expr, !expr, *expr (unary expressions)
  • &expr, &mut expr (address-of)
  • .., ..expr, ...expr (ranges w/o a lower bound)
  • break, continue, return
  • while, while let and for (no else branch, although that might change)
  • loop {...}
  • match {...}
  • |...| {...} (closure expressions)

While these are prefix-ambiguous:

  • expr op rhs (binary expressions)
  • expr = rhs (assignment), expr op= rhs (augmented assignment)
  • expr.., expr..rhs, expr...rhs (ranges w/ a lower bound)
  • expr <- rhs (emplace)
  • expr as type (cast expressions), expr : type (type ascription)
  • expr(...) (calls), expr.name(...) (method calls)
  • expr.f (field access), expr[i] (indexing)
  • expr?
  • expr else {...} (where expr starts with if or if let)

Looking at @nrc's examples:

foo(#[bar] x.f);
foo(#[bar] (a + b).f);

The second one is as ambiguous as the first, to my eyes. Even with the parens, #[bar] can be read as applying to the entire (a + b).f expression. This is explained by @eddyb's comment: it's unclear because (a + b) is a prefix of the (a + b).f expression.

In both examples I would only consider it completely unambiguous if written like this:

foo((#[bar] x).f); // inner
foo(#[bar] (x.f)); // outer

foo((#[bar] (a + b)).f); // inner
foo(#[bar] ((a + b).f)); // outer

Now, we can technically resolve the ambiguity any way we prefer, but I find that unsatisfying because the reader needs to know what we chose to be able to read correctly. The problem of needing to know that extra information is the same reason people on the style RFCs are discussing the addition of technically unnecessary parens around operators, because they let you avoid the problem altogether. I would expect people to write something like my examples for clarity.

Another way to look at it is that we've chosen an attribute syntax that has precisely the same problem as C casting syntax. Would (int) (a + b).f apply to the parenthesized expression, or the field expression?

Block expressions are equally susceptible to this ambiguity, since { a + b }.f is a valid expression, and it's an arbitrary choice whether #[bar] { a + b }.f applies to the { a + b } prefix or the field expression.

This ambiguity is why I preferred inner attributes. In that case, the example here:

foo(#[bar] (a + b).f);

would be written like so:

foo((#![bar] a + b).f)

which seems completely unambiguous. Similarly:

foo({#![bar] a + b}.f)

The point about allowing attributes only on parens or blocks is that there would be no ambiguity. Though it might look like it at first glance, I think we'd soon get used to it. I also agree with @nikomatsakis that inner attributes are better for parens or inline blocks. We could easily lint or rustfmt this.

@nrc The point I was trying to make is that there's literally no ambiguity in a + b * c & 3 | 1 >> z _either_, but people don't necessarily get used to it. But, there are ways to write things so that they can't be read more than one way.

For the particular choice of making #[bar] (a + b).f mean (#[bar] (a + b)).f, I think this has a high potential for confusion next to existing unary operators, because it binds tighter than any of them.

One argument I could see in favour is treating the expression like an "argument" to the attribute, written as #[bar](a + b).f to mirror bar(a + b).f, but this isn't entirely clear, either.

Question btw: What would happen if you did #[cfg(foo)] (a+b).f? I guess that's an error?

I think it depends which model we use. IMO, under a braces and parens only model, it would be equivalent to (#[cfg(foo)] (a+b)).f

An alternate model we could choose that is relatively easy to explain is to say "attributes on expressions associate just like closure arg lists".

Then #[foo] (a+b).f === #[foo] ((a+b).f)

because |x| (a+b).f === |x| ((a+b).f).

Likewise #[foo] a + b === #[foo] (a + b)

because |x| a + b === |x| (a + b).

This makes the case where you need outer parens cleaner, as (#[foo] a + b).f instead of @nikomatsakis's (#![foo] a + b).f with the inner attribute !.

Closure have weird precedence:

a * #[foo] b + c
parsed using the closure rules becomes:
a * (#[foo] (b + c))

That is, closures can cause addition to bind tighter than multiplication, going against against the normal operator precedence. This doesn't matter much for closures because it's unusual to apply binary operators to closures, but it's more problematic with attributes.

One possible solution would be to make the precedence of attributes work like closures, but additionally make it a compiler error when an attributed expression appears as operand of a binary or unary operator. All of @solson's examples would work, but a * #[foo] b + c would require outer parens.

We might even want to make a similar (breaking) change to closures...

@dgrunwald That's not a bad idea. If not an error, at least a warning. We could introduce a warning for closures, too, and warnings aren't breaking changes.

I like both ideas. It's going to be exceedingly rare for an expression with a closure as binary operand to compile, except maybe as a range, so it'd be interesting to see crater for that.

Did #36995 accidentally stabilize attributes on list elements? Or was that on purpose? The following code now compiles on stable:

const THINGS: &'static [i32] = &[
    #[cfg(a)] 2,
    #[cfg(not(a))] 3,
    #[cfg(not(a))] 4,
];

Also works for arguments of function calls and fields of struct declarations (including tuple-like), but not arguments of function declarations or fields of tuple types. Seems awfully inconsistent.

Re: Last discussion about expression attribute precedence: One thing I didn't see mentioned recently is that we could just make them have the same precedence as the existing unary operators.

People already sometimes get confused about the precedence in expressions like &x.f + y, we could just make expr attributes ride on the exact same rules:

   &x.f + y       => (&(x.f)) + y
   #[foo] x.f + y => (#[foo] (x.f)) + y

I still prefer using a precedence like closures, since attributes are separated from their expression by whitespace like a closure argument list.

FCP since August. Removing tag.

So... I think our consensus was to stabilize a limited form of these statements/expressions? Have we made the implementation match that intention? I honestly can't recall, but @nrc might.

We have stabilised attributes on statements, but not on other expressions.

I tried to use #[allow(…)] an an expression (to make its scope as small as possible) and was lead here.

I don’t really see a downside in stabilizing this as well, at least on kinds of expressions that introduce a {} pair.

@SimonSapin I think the main concerns arise around precedence, and whether we want to permit the inherent ambiguity of #[foo] a + b (does #[foo] apply to a? a+b?).

I still think I favor using (#![foo] a + b), which makes the scope explicit, but I suppose we could also "just say" what the answer will be (I actually don't know what would be most intuitive -- probably giving attributes very low precedence, so that they cover a + b?)

Right, this is why I was suggesting limiting this, at least at first, to expressions that introduce {} blocks. if, match, loop, unsafe, actual blocks, etc.

I still think that worrying about the precedence of expression attributes is overthinking it somewhat. 😄

#[foo] <expr> syntactically looks like a prefix operator, so we can just make it one precedence-wise as well, which as a bonus point means we don't need to add additional explanation for it.

@Kimundi It looks more like |foo| <expr> than &expr, so I would expect closure-like precedence as I mentioned earlier.

@Kimundi that seems pretty incompatible with statements:

#[foo]
x = y + z;

Is that an expression attribute on the LHS of the = (that is, x), or a statement attribute on the whole line?

Ah, that's an interesting problem, and I think it affects nearly all suggested syntaxes.

Consider:

#[cfg(foo)]
bar(baz);

This needs to apply to the entire statement or it is invalid, since cfg cannot apply to expressions.

I think in general, when the parser encounters #[foo] in a statement-start position, it should be expecting an entire statement to follow, not an expression.

This doesn't seem like a big deal, unless we have common attributes that fail on stmts but work on exprs, which would mean that the following potentially won't work as expected, and would require extra parentheses:

#[expr_attribute] bar(baz);

Ah true, I hadn't really considered that case.

Then I guess I would be more in favour of handling it like the argumetn list of a closure, eg covering the entire trailing expression:

// Attributes cover the same amount of code in both cases:

#[attr]
x = y + z;

let _ = #[attr] x = y + z;

@solson: I think that, however the system ends up being defined in the end, any expression attribute should behave identical for expression statements, and if possible that should be enforced or at least be implied per default.

// Attributes cover the same amount of code in both cases:

#[attr]
x = y + z;

let _ = #[attr] x = y + z;

@Kimundi These can't cover the same amount of code. The former parses as applying to the entire x = y + z; statement (not just the contained x = y + z expression), so that it is valid for a #[cfg(foo)] to delete a statement, while the latter would parse like let _ = (#[attr] x = y + z).

However...

@solson: I think that, however the system ends up being defined in the end, any expression attribute should behave identical for expression statements, and if possible that should be enforced or at least be implied per default.

@Kimundi I totally agree with this restriction. If #[attr] is an attribute that can be used on an expression, then even though #[attr] x = y + z; parses as if it applies to the entire statement, it doesn't make a difference to how it acts.

This restriction would make my entire pedantic distinction above never make a difference, which seems good...

@solson Ah right, I was imprecise with that comparison, since lambda syntax behaves like this:

        |_| x = y + z; =>         |_| (x = y + z);
let _ = |_| x = y + z; => let _ = |_| (x = y + z);

While attributes would instead behave like this:

        #[attr] x = y + z; =>         #[attr] (x = y + z;)
let _ = #[attr] x = y + z; => let _ = #[attr] (x = y + z);

I meant to refer to just the general notion of "covers everything to the right", since _this_ would certainly be unexpected:

        #[attr] x = y + z; =>         #[attr] (x = y + z;)
let _ = #[attr] x = y + z; => let _ = #[attr] (x) = y + z;

(This is all assuming that #[attr] is a expression-only attribute to begin with and behaves the same on expression statements)

@solson

Ah, that's an interesting problem, and I think it affects nearly all suggested syntaxes.

This is why I was suggesting that the only way to attribute an expression is using parens: (#![foo] a). Everything else would be considered to apply to the statement.

@nikomatsakis: I'd be fine with that as a initial "punting on it" restriction, but I have the feeling that with a sufficiently advanced macro and attribute system we could have a few nice syntactic opportunities in the future.

I'm mainly thinking of what I'd call "attribute-based macros" - syntatic transformations that start off as an attribute on a pice of normal code.

As an example, I could imagine an attribute to turn a lambda expression into an AST similar to what is possible in C# with lambdas to create LINQ queries:

let x: Expression = #[linq] |x, y| x + y;

While I don't see this becoming possible or stable anytime soon, I would at least like to hold open the door for possibilities like this.


(Similarly, I still hold hope for getting ride of the [] in attributes in at least some circumstances)

@Kimundi What would the advantage be of that over linq!(|x, y| x + y)? They both require about the same amount of typing, although I guess yours looks a little less 'intrusive'.

@comex:

One advantage I can see is that in the attribute case you would still get Rust syntax checking and potentially highlighting for the expression, while in the latter macro case you explicitly left Rust standard grammar, and thus the compiler or editor won't be able to check it as such.

The other advantage is indeed just mainly that it (subjectively) would look nicer.

I realize that these are weak reasons though, and that this might spawn a heated debate of what macros and syntax extensions should or should not be able to do or be used for. ;)

@comex Also people dislike using linq!(...), since rustfmt for example just doesn't format macros at all.

@Kimundi

One advantage I can see is that in the attribute case you would still get Rust syntax checking and potentially highlighting for the expression, while in the latter macro case you explicitly left Rust standard grammar, and thus the compiler or editor won't be able to check it as such.

Interesting. I think we do anticipate, at least when decorating functions, that we'd accept a superset of Rust grammar within decorated functions, so that you can do things like add an await keyword and the like, but I guess that there are advantages to enforcing Rust grammar more strictly as well. But that seems a bit like a false comfort -- in particular, until you see the expansion, you can't e.g. do name resolution etc necessarily.

An idea of attributes pair like xml?
#[foo..] expr/stmt/blocks #[/foo]

Relevant issue https://github.com/rust-lang/rust/issues/43494

I still think it is a good idea to work on stabilising different groups of expressions, starting with the most trivial and useful of all -- blocks, so e.g. { #![cfg(...)] ... } could be used.

We can then think how and if we want to stabilise the other sort of statement/expression attributes.

@liigo that’s just a

#[foo] {
    exprs/stmts/blocks
}
// or
{ #![foo] 
    exprs/stmts/blocks
}

I still think it is a good idea to work on stabilising different groups of expressions, starting with the most trivial and useful of all -- blocks, so e.g. { #![cfg(...)] ... } could be used.

This form is already stable:

We can also already use #[] directly on statements. Only expression attributes remain unstable and undecided.

@solson It is only stable for blocks in statement position.

Ah, fair point. In that case, I would think it an odd decision to stabilize just blocks in expression position next, instead of all expressions. I think we already came up with reasonable rules for parsing above, and we should have an FCP about implementing and stabilizing for all expressions if there are no major objections.

To make this easier to find for others, this issue (afaict) is why we can't have cfg attributes on if statements:

#[cfg(test)]
if true {
    println!("testing");
}

fails to compile with

attributes are not yet allowed on `if` expressions

Perhaps it'd be useful to make that error include a reference to this issue?

This seems to be the proper place to raise this: (if not, let me know)

Why is the {} block necessary for cfg attributes on macro calls ?

and I'm generalizing that based on only this one example using the println! macro and this clippy lint print_stdout so, I might be wrong in my loaded question assumptions. (in which case, please correct me)

#![feature(plugin)]
#![plugin(clippy)]

#![deny(print_stdout)]

fn main() {

    #[allow(print_stdout)] {
        println!("Hello, world!"); // all good now
    }

    {
        #![allow(print_stdout)]
        println!("Hello, world!"); // all good
    }

    #[allow(print_stdout)]
    println!("Hello, world!"); //error: use of `println!`
}

It feels like there's a multi-line behind-the-scenes expansion of println!. Or is there something clippy does wrong? (I tried asking on IRC...)

What is actually happening and will this be supported when this issue is closed?

  1. $ cargo new --bin proj1
  2. Add line in Cargo.toml for clippy
[dependencies]
clippy = "*"
  1. replace src/main.rs contents with the above example
  2. $ cargo build
error: use of `println!`
  --> /tmp/proj1/src/main.rs:18:5
   |
18 |     println!("Hello, world!"); //error: use of `println!`
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
note: lint level defined here
  --> /tmp/proj1/src/main.rs:4:9
   |
4  | #![deny(print_stdout)]
   |         ^^^^^^^^^^^^
   = help: for further information visit https://rust-lang-nursery.github.io/rust-clippy/v0.0.187/index.html#print_stdout

error: aborting due to previous error

error: Could not compile `proj1`.

To learn more, run the command again with --verbose.

Your first two examples are identical, the second one uses #! so it applies to the enclosing block instead of the macro invocation. Maybe you know this already but it tripped me up a bit since I didn't initially notice.

@eddyb sorry :D I didn't mean to include that one initially, but the first I've seen of it was here in this thread, in these two comments from above:
https://github.com/rust-lang/rust/issues/15701#issuecomment-320337881
https://github.com/rust-lang/rust/issues/15701#issuecomment-320356418

and I thought it was kinda clever and wanted to have it added, for completion(so to have all known ways to add attrs to macro calls - in my case), though I initially wanted to include only first and third examples only

Well, neither of the first two examples attach an attribute to the macro invocation, but to a block surrounding it - maybe macro expansion just discards the attribute in the third example?
cc @jseyfried

Is it expected that attributes on function arguments are allowed?

fn main() {
    drop(#[cfg(never)] 1, #[cfg(not(never))] 2); // compiles fine on stable
}

edit: never mind, I see it was mentioned in https://github.com/rust-lang/rust/issues/15701#issuecomment-264561141

@goffrie my theory (which I documented on #32796) is that such attributes were accidentally stabilized.

Same with #[inline] on closures, compiles fine on Rust 1.28 - 1.31 when the closure expression is a function parameter.

I would like a rust conditional compile block, seems like:

// mod.rs
#[cfg(any(unix))]
{
    pub const MAIN_CMD: &str = "my-app";
    pub const SHELL_CMD: &str = "sh";
}

#[cfg(any(windows))]
{
    pub const MAIN_CMD: &str = "my-app.exe";
    pub const SHELL_CMD: &str = "cmd.exe";
}

say, one "cfg" multiple lines, is this possiable?

There's a separate issue for applying attributes to multiple items.

It's worth noting that in many cases you can work around the lack of expression attribute support by simply wrapping the expression in a block, turning it into a statement.

Currently, in the following example, rustc parses the attribute as it belongs to foo, not to the whole assignment:

#![feature(stmt_expr_attributes)]

fn main() {
    let foo;
    #[must_use]
    foo = false;
}

Is this expected? I think the behavior is inconsistent with how rustc parsed the attribute on let.

This is currently illegal grammar:

fn main() {
    #[inline(always)]
    let inlined = || {
        let x = 12;
        x
    };

    return x;
}

I propose that it should be interpreted as equivalent to the following:

fn main() {
    let inlined = #[inline(always)] || {
        let x = 12;
        x
    };

    return x;
}

as the former is considerably more ergonomic. When there is no direct binding to a variable (e.g. foo(#[inline(always)] || "foo") it makes sense to require it directly preceding the ||, but when binding to a plain variable, I believe there is not much room for confusion.

The primary motivation is that it much more nicely parallels the syntax for the inlining of functions, where we write

#[inline(always)]
fn foo() {
    ...
}

and not

fn foo #[inline(always)] () {
    ...
}

or any other such abomination, making it much more discoverable and ergonomic.

What's the status of this issue?

What's the status of this issue?

The parsing priorities need to be reworked, IMO (needs an RFC).

Expansion needs a rewrite (especially treatment of trailing expressions, https://github.com/rust-lang/rust/issues/61733). This is in my plans for this summer.

Overall, the demand for this doesn't seem too high given the expr -> stmt workaround, so it's not a top priority.

The following code redirects to this issue on Rust 1.45:

vec![0].iter().filter(|_| {
    //!comment
    true
});

with error and warning message:

   Compiling playground v0.0.1 (/playground)
error[E0658]: attributes on expressions are experimental
 --> src/main.rs:3:9
  |
3 |         //!comment
  |         ^^^^^^^^^^
  |
  = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
  = help: `///` is for documentation comments. For a plain comment, use `//`.

warning: unused doc comment
 --> src/main.rs:3:9
  |
2 |       vec![0].iter().filter(|_| {
  |  _______________________________-
3 | |         //!comment
  | |         ^^^^^^^^^^
4 | |         true
5 | |     });
  | |_____- rustdoc does not generate documentation for expressions
  |
  = note: `#[warn(unused_doc_comments)]` on by default

error: aborting due to previous error; 1 warning emitted

Just as a note, I was trying to fix up some code using #[cfg] and cfg_if recently and kept slamming into this, even with the workaround to annotate blocks. So consider some interest expressed, at least.

There's an issue with how this applies to item statements.

Currently, the following code compiles on stable:

fn main() {
    #[my_attr] struct Foo {}
    #[my_attr] struct Bar {};

Both of these invocations are interpreted as an item attribute, not a statement attribute. #[my_attr] does not see the semicolon in struct Bar {};, since it's not part of the item.

This means that it's impossible to write an attribute that transforms struct Foo{}; into let a = 1;, since the macro expander will end up trying to parse let a = 1; as an item.

Supporting this would require us to treat #[my_attr] struct Bar {}; as applying to the entire statement, which would probably break many proc maros that aren't expecting a trailing semicolon. This could be done in a new edition (proc-macro crates defined in older editions would not receive the trailing semicolon), but I'm not sure if the increase in complexity is worth supporting this rather obscure usecase.

the macro expander will end up trying to parse let a = 1; as an item.

Does it have to? As I understand it, we already have a way to parse "either item or statement", since that is what is parsed inside of blocks. Can that be made to work on proc macro output?

Supporting this would require us to treat #[my_attr] struct Bar {}; as applying to the entire statement, which would probably break many proc macros that aren't expecting a trailing semicolon.

This seems less good; I would be opposed to this. struct Bar {}; is not an item nor a statement. It is an item followed by an empty statement. Rustc should not pass it to a macro as if it were an item or a statement.

macro_rules! m {
    ($i:item) => {"item"};
    ($i:stmt) => {"stmt"};
    ($i:item $s:stmt) => {"item+stmt"};
}

fn main() {
    println!(m!{struct S {};}); // item+stmt
}

Does it have to? As I understand it, we already have a way to parse "either item or statement", since that is what is parsed inside of blocks. Can that be made to work on proc macro output?

Currently, this is implemented by making items a kind of statement: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir/enum.StmtKind.html

However, your example shows that we dont consider the trailing semicolon to be part of the first statement. It looks like I misunderstood how we parse statements.

It looks like we could support #[my_attr] struct Foo{}(;)? -> let a = 1; just by handling attributes on statements properly within the compiler - we will never need to pass a trailing semicolon to the proc macro.

@Aaron1011

There's an issue with how this applies to item statements.

Yes, that's

Expansion needs a rewrite (especially treatment of trailing expressions, #61733).

from https://github.com/rust-lang/rust/issues/15701#issuecomment-627818180.
The treatment of semicolons is a big mess in particular.

Attributes on item statements should be treated as attributed on statements, and produce statements.
Attributes on expression statements, including trailing ones, should also be treated as attributed on statements, and produce statements.
(Basically, ast::Stmt should has an attrs field and those attrs need to be processed as any other attrs, with zero additional hackery.)

Right now such attributes are kind of passed through from statements to items and expanded as item attributes.
I suspect this, like many other hacks, has something to deal with #[derive] and stabilization of macros 1.1 (derive only works on items, but it had to work on item statements).

Was this page helpful?
0 / 5 - 0 ratings