I'm trying to write some macros to clean up boilerplate around my intros/headers, to enforce that I'm organizing things in the same way, and to have a one-spot place to mandate any changes to such. I wanted to write one for organizing tests.
The structure I've settled on is for unit tests to go in a "test" sub-directory within a module's parent folder. So, for a leaf module general.rs, the header looks like:
#[cfg(test)]
#[path = "test/general_test.rs"]
mod general_test;
I'm trying to generate this via macro call:
#[macro_export]
macro_rules! declare_tests {
($mod_name : ident) => {
#[cfg(test)]
#[path = concat!("test/", stringify!($mod_name), ".rs")]
mod $mod_name;
};
}
But this fails:
crate::declare_tests!(general_test);
unresolved module, can't find module file: general/general_test.rs, or general/general_test/mod.rsrust-analyzerE0583
But if my IDE inlines it to inspect the problem, the problem goes away:
crate::declare_tests!(general_test);
inlines as
#[cfg(test)]
#[path = concat!("test/",stringify!(general_test),".rs")]
mod general_test;
which inlines as
#[cfg(test)]
#[path = "test/general_test.rs"]
mod general_test;
which works.
I think maybe my IDE (vscode) is doing these in the wrong order, but I can't tell what the right order would be?
Curious what's up with the inlining, but of course a better solution overall would also be welcome. (I plan to integrate this with std::module_path! to further reduce boilerplate/standardize, once I get this step working.)
EDIT:
With some experimentation:
#[macro_export]
macro_rules! declare_tests_helper {
($mod_path : expr, $mod_name : tt) => {
#[cfg(test)]
#[path = $mod_path]
mod $mod_name;
};
}
...
crate::declare_tests_helper!("test/general_test.rs", general_test);
works, tho doesn't help me as much as I'd like - I still can't pass another macro's result as an argument, because the outer macro is expanded first.
I'm wondering if it's possible to do this with a procedural macro, and (hopefully) greater control over order of execution?
You cannot use
concat!in a#[path]attribute.Telling your IDE to inline the
concat!macro is not what the compiler does when evaluating the#[path]attribute. In general, macros are evaluated by the outside-inward; so theconcat!is not evaluated before the#[path]starts trying to use it.Running function-like macros eagerly inside attributes macros only happen in limited circumstances. See this PR for more info on allowing
#[doc = include!(...)]but it mentions "the attributespath, ... do not support this". And see this issue for#[path]to support it.