rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
96.63k stars 12.48k forks source link

Tracking issue for custom inner attributes #54726

Open alexcrichton opened 5 years ago

alexcrichton commented 5 years ago

Added in https://github.com/rust-lang/rust/pull/54093 inner attributes are not allowed to be procedural macros right now, and this issue is tracking that feature of procedural macros!

Assistance in filling in this issue would be greatly appreciated! UPDATE: https://github.com/rust-lang/rust/issues/54726#issuecomment-431931166 below contains the detailed description.

dhardy commented 5 years ago

Summary:

error[E0658]: non-builtin inner attributes are unstable (see issue #54726)
 --> src/lib.rs:3:1
  |
3 | #![foo]
  | ^^^^^^^
  |
  = help: add #![feature(custom_inner_attributes)] to the crate attributes to enable

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

(I'll fill in details about why this is unstable soon.)

petrochenkov commented 5 years ago

So, the primary open question about inner attributes is what scope they are resolved in.

For example:

use xxx::attr;

mod m {
    #![attr]

    use yyy::attr;
}

Does #![attr] refer to xxx::attr or yyy::attr?

Right now, #![attr] is resolved from outside of the module in the same way as outer attributes (with exception of the crate attributes maybe, not sure).

However, if something is inside a module (or block, etc) it's normally reasonable to expect that it's resolved from inside of that module as well. IIRC, @nrc argued for applying this logic to attributes too (UPDATE: this happened in https://github.com/rust-lang/rust/issues/41430).

With attributes such resolution from the inside causes problems though.


First of all, there's no "inside" before we expand all the macro attributes.

mod m {
    #![attr]

    /* items */
}

is just a token stream, even if it looks like module and items, it effectively doesn't exist in the module structure of the crate yet.

#![attr] can transform that token stream in any way, for example turn module into a block, or struct, or even into nothing, possibly invalidating our "inner" resolution for attr even if we somehow obtained it speculatively from the unexpanded mod m.


Possible solutions to this problem:

petrochenkov commented 5 years ago

Use the outer resolution for expanding #![attr], but then validate that the inner resolution after expansion gives the same result.

This variant is the way to go right now, I think.

dhardy commented 5 years ago

The caveat is that adding use yyy::attr; into the inner module may cause breakage. Overall though it seems a reasonable compromise.

mehcode commented 5 years ago

My use case for this feature would be to have a custom crate level attribute in a main.rs.

#![custom_attr_here]

fn main() {
 // [...]
}

There is no outer scope here to resolve that custom attribute.

petrochenkov commented 5 years ago

@mehcode There's some outer scope even in this case

For locally defined/imported attributes that's probably the hardest case though, it certainly requires eager expansion.

macpp commented 5 years ago

Sorry if I'm missing something important, but it seems to me that current behaviour of custom inner attributes is rather inconsistent. For example, if i have file named foo.rs with following content :

#![my_macro]

mod bar {
    #![my_macro]
    fn some_fn () {

    }
}

then first invocation of #![my_macro] receives token stream equivalent to mod foo; (without content of module) but second invocation receives tokent stream with module and full content :

mod bar {
    fn some_fn() { }
}

So custom inner attribute receives no module content if that module is placed in it's own file, but what's even stranger is that this is not always the case. If i add #![my_macro_crate::my_macro] at the top of main.rs then my_macro receives tokenStream with module and it's content, together with prelude import and fn main().

Is there any chance to make this behavior more consistent? Current behavior could result in unwanted suprises when somebody decides for example to do refactoring and split modules to dedicated files.

nhynes commented 5 years ago

I tried adding a custom attribute using syn (after a small patch), but got the error

error: macro-expanded `extern crate` items cannot shadow names passed with `--extern`
thread 'rustc' panicked at 'internal error: entered unreachable code', src/libsyntax/ext/expand.rs:294:18

using rustc 1.35.0-nightly (f22dca0a1 2019-03-05) running on x86_64-unknown-linux-gnu.

lib.rs

#![feature(custom_inner_attributes, proc_macro_hygiene)]
#![my_attr]

and in the proc_macro crate

#[proc_macro_attribute]
pub fn my_attr(_args: TokenStream, input: TokenStream) -> TokenStream {
    let module = parse_macro_input!(input as syn::ItemMod);
    let content = module.content.unwrap().1; // workaround for mod having no name
    proc_macro::TokenStream::from(quote! { #(#content)* })
}

The error is coming from the extern crate std emitted during the addition of the prelude.

petrochenkov commented 5 years ago

@nhynes This specific error about extern crate shadowing is too conservative in this case and can be fixed, but crate-level attribute macros are still generally unsupported.

The recommendation is to make a module and apply the attribute to it instead.

gnzlbg commented 5 years ago

This broke stdsimd, which uses #![rustfmt::skip] as an inner attribute. I thought inner tool attributes were ok, but since recently they fail (playground)[https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=e0cee9e1b9a9c8a7aba84b7cd700e184]:

error[E0658]: non-builtin inner attributes are unstable
 --> src/main.rs:1:1
  |
1 | #![rustfmt::skip]
  | ^^^^^^^^^^^^^^^^^
  |
  = note: for more information, see https://github.com/rust-lang/rust/issues/54726
  = help: add #![feature(custom_inner_attributes)] to the crate attributes to enable
petrochenkov commented 5 years ago

@gnzlbg "Since recently" here is 1.29, this feature gate was introduced before stabilization of tool attributes. So, inner tool attributes were never available on stable.

gnzlbg commented 5 years ago

Weird, this started erroring this week.

petrochenkov commented 5 years ago

@gnzlbg I've just realized this is caused by https://github.com/rust-lang/rust/pull/62133.

Previously #![feature(custom_inner_attributes)] was implicitly enabled by #![feature(custom_attributes)] or #![feature(rustc_attrs)], but the linked PR removed that because custom_attributes is being retired and rustc_attrs changed its meaning. So, #![feature(custom_inner_attributes)] needs to be written explicitly now.

coolreader18 commented 4 years ago

RustPython has a use case for this in allowing the contents of a module to basically be collected into a map; RustPython/RustPython#1832. The current implementation would basically have an inline module with an attribute on it in every module file, but that's not great as it creates unnecessary rightward drift/indentation.

djc commented 3 years ago

It appears that this has been fixed, potentially via https://github.com/rust-lang/rust/pull/69838. See also https://github.com/rust-lang/rust/issues/78488.

jyn514 commented 3 years ago

From https://github.com/rust-lang/rust/issues/78488: It looks like the rustfmt issue was fixed in 1.44.0, in case you're trying to find your MSRV like me.

Fishrock123 commented 3 years ago

I'd like this so I can expand a whole bunch of lint rules org-wide from one library attribute line. That'd be quite nicer than the current mess it gives us.

afetisov commented 2 years ago

So, the primary open question about inner attributes is what scope they are resolved in.

I don't know whether this was already implicitly finalized, but I haven't seen two other possibilities suggested;

  1. Never resolve inner attributes within inner modules. All inner attributes must always be used with a fully qualified path. Perhaps the inner attributes wouldn't be common enough to make it onerous?
  2. Introduce a separate "import inner attribute at crate level" syntax. For example, it could be a crate-level inner attribute similar to #[register_tool], e.g. #[register_attr(::path::to::attr)].
joshlf commented 1 year ago

Is there any way to work around this limitation at the crate root?

sam0x17 commented 1 year ago

My use case for this is I am the author of a crate that allows for annotating a module with a cryptographic signature indicating that it has been audited (based on a hash of the underlying AST nodes of the module). Ideally we would only allow using this for top-level whole-file inner attributes, as for these you can safely assume that no unaudited items are being sideloaded into the module. Right now we have to do complex filtering of the module to ensure that every item referenced in the module is part of the audited footprint. With top-of-file inner attributes, this filtering is a lot simpler.

So would love to see this stabilized, with the ability to tell whether a particular attribute is "top-level" in a file or not. That would help us out a lot.

Qix- commented 1 year ago

Not sure if this should be a separate issue or not, or where it should live (let me know and I can create it elsewhere), but there seems to be a weird quirk with #![no_std] being emitted from a custom inner proc macro that is itself used otherwise legally.

Repro case: https://github.com/Qix-/repro-no_std-custom-inner-attr

Results in this error message:

   Compiling test-exe v0.1.0 (/src/qix-/repro-no_std-custom-inner-attr/test-exe)
error[E0658]: `#[prelude_import]` is for use by rustc only
 --> test-exe/src/main.rs:1:1
  |
1 | / #![feature(custom_inner_attributes)]
2 | | #![::custom_attr::add_no_std]
3 | |
4 | | fn main() {}
  | |____________^
  |
  = help: add `#![feature(prelude_import)]` to the crate attributes to enable

warning: unused import: `#![feature(custom_inner_attributes)]
         #![::custom_attr::add_no_std]

         fn main() {}`
 --> test-exe/src/main.rs:1:1
  |
1 | / #![feature(custom_inner_attributes)]
2 | | #![::custom_attr::add_no_std]
3 | |
4 | | fn main() {}
  | |____________^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: unused `#[macro_use]` import
 --> test-exe/src/main.rs:1:1
  |
1 | / #![feature(custom_inner_attributes)]
2 | | #![::custom_attr::add_no_std]
3 | |
4 | | fn main() {}
  | |____________^

For more information about this error, try `rustc --explain E0658`.
warning: `test-exe` (bin "test-exe") generated 2 warnings
error: could not compile `test-exe` due to previous error; 2 warnings emitted
DanielJoyce commented 1 year ago

Yeah this is a rather sizable wart when you want to use an attribute macro in the root crate ( lib.rs ) because there is no way to decorate the crate itself outside of it. :/

lukesneeringer commented 10 months ago

Would it be possible to remove the feature gate if the inner attribute is at the crate root? It seems like the ambiguity doesn't actually apply there, and that's the key spot where there's no alternative / workaround.

jacks0n9 commented 1 month ago

i want to make a crate that uses this feature at the crate root and, honestly, i don't really want my users to have to enable this feature just to use my crate. it seems like there's been no recent discussion on the issue so I'm going to go see if i can remove the feature gate for my users' convenience.