3

I am creating an attribute macro that can be invoked with (a path to) some type amongst its attributes:

#[my_macro(path::to::SomeType)]

This macro generates a module, within which the specified type is to be used:

mod generated {
    use path::to::SomeType;
}

However, from within that generated module, the path as provided may no longer be correct:

  • if the path is prefixed with ::, it remains correct—I can determine this within the proc macro;
  • otherwise, if the path starts with an identifier that is in a prelude (such as that of an external crate), it remains correct—I can't see any way to determine this within the proc macro;
  • otherwise it must now be prefixed with super:: for resolution to succeed.

Of course, I could require users of the macro to provide a path that will be correct from within the generated module, but that would not be very ergonomic.

Instead, I'm currently generating a type alias within the parent module:

type __generated_SomeType = path::to::SomeType;
mod generated {
    use super::__generated_SomeType as SomeType;
}

However this is not only a tad clunky, but it furthermore pollutes the parent namespace.

Is there a better way?

eggyal
  • 122,705
  • 18
  • 212
  • 237
  • Have you tried `use super::*;` at the top of the generated module? – PitaJ Apr 12 '22 at 22:54
  • @PitaJ: Yes, sorry should have mentioned that... I discounted it because it could cause name collisions within the generated module (avoidance of which is the reason that a generated module is being used in the first place). – eggyal Apr 12 '22 at 23:24
  • A type alias isn't really appropriate here (for example, it doesn't allow generics). Better to use `use path::to::SomeType as __generated_SomeType;`. – Chayim Friedman Apr 13 '22 at 00:17
  • I think your best bet is `#[doc(hidden)] use path::to::SomeType as __generated_SomeType;` unless you want to require that all external paths are prefixed with `::` and put `super::` in front of everything else. – PitaJ Apr 13 '22 at 16:36

1 Answers1

1

I don't think there's a satisfying solution. But there's an alternative, and a way to improve your current approach.

The alternative is to use a const item:

const _: () = {
    // ... your generated code here
};

It can still cause name collisions with items from the parent scope, but at least the generated items aren't visible outside of the const. This is what most macro authors do.

If you don't want to do that, you can go with your current approach, but you should add #[doc(hidden)] to the __generated_SomeType type, so it doesn't show up in documentation. Also, you should use an import instead of a type alias to support generic types, as suggested by @ChayimFriedman:

#[doc(hidden)]
use path::to::SomeType as __generated_SomeType;

I was just about to suggest combining these to approaches by putting a module inside a const item, but it doesn't work:

const _: () = {
    use path::to::SomeType as __generated_SomeType;

    mod generated {
        // doesn't work :(
        use super::__generated_SomeType as SomeType;
    }
};

Rust complains that __generated_SomeType doesn't exist in the parent module.

Aloso
  • 5,123
  • 4
  • 24
  • 41
  • "Rust complains `that __generated_SomeType` doesn't exist in the parent module." because that's the entire point of `const _: () = { ... };`, to not pollute the namespace. – Chayim Friedman Apr 13 '22 at 00:56
  • I assumed that, since the module is within the `const` item, it can access other items within the same `const` item, but that's apparently not the case. This could actually be considered a bug. It is entirely reasonable to expect that `use super::...` can access items in the scope where the module is defined. I'm _not_ asking that items in the `const` item should be visible outside of it. – Aloso Apr 13 '22 at 00:59
  • But from Rust POV, `const` is not like a module and does not participate in the module hierarchy. It is a way to isolate names, but the parent of `generated` is the module, not the `const`. – Chayim Friedman Apr 13 '22 at 01:23
  • The generated module itself needs to be exposed, so a `const` item here is not of any use (and, as you mention, does not in any case address the issue at hand). Thanks for the suggestion, though! – eggyal Apr 13 '22 at 09:53