I have an attribute macro that is invoked as an inner attribute of a module. The problem is that all the macro hygiene points not to the module actual content, but to the module declaration in the parent module. This applies even to individual statements, function declarations etc., if you parse the module content and inspect the spans.
This obviously becomes a problem if there's an error in the module content, b/c as soon as it is processed by my macro all spans are lost essentially. It happens even if you just
#[proc_macro_attribute]
pub fn my_macro(attr: TokenStream, item: TokenStream) -> TokenStream {
return item;
}
Thing is, what I need to do is to parse the module content and do stuff with it, with module as a whole. If I use this as an inner attribute, I get module.content, but the spans are all just pointing to the module declaration, and if I try to apply it as an outer attribute, then module.content is None.
Is this an expected behavior or a bug? Is there anything I can do?
Okay, my apologies for posting essentially a duplicate topic. I'll be following the helpful suggestion from a similar thread, courtesy of Yandros.
By the way, is this issue being addressed anywhere in the Nightly Rust? Is it on the roadmap?
So I tried to actually infer the path to the module file, load and parse it. Here's what I have so far, but it doesn't solve the problem at all:
fn load_module_content(module: &syn::ItemMod) -> Vec<syn::Item> {
let mod_filename = format!(
"{}/{}.rs",
Span::call_site()
.local_file()
.expect("No local file")
.as_path()
.parent()
.expect("No parent").to_str()
.expect("Couldn't convert path to string"),
module.ident.to_string(),
);
println!("{}", mod_filename);
let file_content = std::fs::read_to_string(&mod_filename).unwrap();
let file_parsed = syn::parse_file(&file_content).unwrap();
println!("{:#?}", file_parsed.items[0].span());
file_parsed.items
}
#[proc_macro_attribute]
pub fn precept(attr: TokenStream, item: TokenStream) -> TokenStream {
let mut module = parse_macro_input!(item as syn::ItemMod);
let _ = parse_module_content(&module);
...
I don't see any way to proceed. The issues I encountered:
Span::local_file is None for this span, and if I try to use Span::file() instead, it says "<token stream>". I don't see a way to actually find where the module is coming from.
- Even if I succeeded here,
syn::parse_file() only accepts a string (slice), and doesn't appear to take any file path nor span information whatsoever, so it would have no way of knowing where does the code come from.
- Alternatively, I considered using a
proc_macro[2]::TokenStream, but it does not appear to have any way of explicitly setting the span either.
- Even if either
syn::parse_file() or TokenStream could be explicitly told where the code is coming from, there's no way to create a Span that would actually point to the file. It would appear that spans are always created implicitly by the compiler.
Note that I enabled the features proc_macro_span and proc_macro_hygiene, to no avail (although as far as the latter goes, I have found no way to figure out how does it actually affect attribute macros on modules and/or inner attributes, it vaguely mentions merging several features together, but there are no links to the features themselves and I couldn't look them up; I did not try to dive headfirst into the code just yet).
So, I'm stuck on multiple accounts here. The only workaround for my use case might be to give up on the inner attribute macro and just wrap the entire module content in a regular proc macro, like my_macro! { ... } on an entire file. This will probably work and preserve spans, but will 1) add an indentation level which looks ugly and 2) means that slapping a #[cfg] on the entire module won't be possible to do implicitly, though it can be slapped on everything inside via cfg_block. This is no big deal I suppose, but I'm still frustrated.
Please let me know if there's any other way.
(Update: it works, but I lose about a half my syntax highlighting...)