Issue resolving module-imports after applying proc macro attribute


#1

I’m using a procedural macro attribute which analyzes the contents of the module it’s declared on.
As far as i understood from The Unstable Book; the compiler removes the actual tokens of the module and passes them into the macro function call.
My macro analyses the module contents and builds an enumeration with variants matching each struct identifier. The macro also implements a custom FromType<X> trait for the generated enum for each struct. This is similar to the std-prelude From<X> and allows me to produce runtime-equivalent values when (abusing?) generic arguments.

So far my implementation works, except i cannot access the generated module (and contents) within the same crate (= crate X)! This makes it impossible to write unit-tests for the macro within the same crate, but there seems to be no issue accessing the auto-generated content from crates depending on crate X.
Can anyone point me towards a solution?

My code is located here: https://github.com/Bert-Proesmans/medici
The crate of the offending macro is value_from_type_macros (located: ‘/value_from_type_derive/value_from_type_macros’) and can be called like the following snippet:

// Attribute macro must be imported through a use statement
use value_from_type_macros::value_from_type;
// The module is made public by the proc macro
mod default {
    // This macro will build enum EnumerationTiming into the this module.
    #![value_from_type(EnumerationTiming)]

    #[derive(Debug)]
    pub struct Pre();
    impl Timing for Pre {}

    [...]
}

Calling the macro attribute returns the module named default back, which now contains all original defined code + the enum EnumerationTiming and corresponding implementations.

The macro is called twice within the repo:

  1. file: /medici_derive/medici_traits/src/timing_traits.rs
    This file also contains a test method default_timing_into() that tests the presence of the auto-generated module contents -> This test fails.
    The top-level crate also contains a test method value_from_type() for the same purpose -> This test succeeds.

  2. file: /src/hs_automaton/states/action_states.rs
    The code in the top-level crate makes use of the auto-generated code, but compiling this crate fails due to unresolved imports.

Example error code when trying to importing auto-generated code:

error[E0432]: unresolved import `self::custom::EnumerationTrigger`
 --> src\hs_automaton\states\action_states.rs:4:33
  |
4 | pub use self::custom::{EndTurn, EnumerationTrigger};
  |                                 ^^^^^^^^^^^^^^^^^^ no `EnumerationTrigger` in `hs_automaton::states::action_states::custom`. Did you mean to use `EnumerationTrigger`?

Of course, when i follow the compiler advice it still throws an error.
Running cargo build or cargo test --all on the top-level crate will reveal the same error.

This looks to me that the use statement refers to the original module, which is removed by the compiler and re-inserted by the macro attribute. It seems i can’t explicitly refer to the re-inserted code.
So far i tried using different combinations of Spans for inserting the module again to no avail. I’m out of ideas to fix this…


#2

I reflected on my first post and concluded that i was unreasonable to ask anything to this community in such a manner. This short-sightedness can be attributed to my frustration about the issue and tiredness. My apologies.

I built a minimal reproduction, which can be found here.
The reproduction contains the procedural macro attribute in a separate crate.
The top-level crate source makes use of the macro and contains a test for it (see /src/lib.rs).
The issue is still the same, the compiler reasons about original contents of the module while trying to resolve imports from inside the test module.

I suppose this has to do with hygiene and is consequently a feature; if so, the question becomes if this works as intended or not?
I re-read the documentation material which seem to declare that the observed behaviour is in fact an error. Going further on that note i suspect this issue might be the culprit, even though accessing externally defined types is not a problem.


#3

So far i tried using different combinations of Spans for inserting the module again to no avail.

I believe you identified the problem correctly. All of your generated code is spanned in a way that is not visible to any imports at the call site. In the generated enum EnumerationTrigger { EndTurn } both the EnumerationTrigger token coming from here and the EndTurn token coming from here have a span that resolves at the def site. Changing both to resolve at call site made the tests pass.

Check out rust-lang/rust#45934 for some background on def site and call site as it pertains to generated code.


#4

Thanks man, you saved me (again)! :slight_smile:
So i read the issue you linked and i finally understand how to properly use Span. (3rd time was the charm :stuck_out_tongue:)

I used to think that a Span would only influence the way you import types from elsewhere. Although this is technically not incorrect, it’s actually an effect of what Spans actually do (-> the bigger picture).
The mistake I made was thinking that procedural macros always replace the macro-call with emitted tokens, like declarative macros.

Even though I understand it now, it’s still tough to properly put it into words. I guess linking to that issue, like you did in your answer, and make it stand out is the best way to convey what hygiene is and Spans do.

Thanks again for all the effort you put into making meta-programming easier!
This issue is solved.