Skip to content

Writing testable documentation examples for proc macros is not possible #58700

Closed
@LukasKalbertodt

Description

@LukasKalbertodt

Suppose our main crate foo defines a trait Foo. Now we want to write a derive(Foo) proc macro (or any other kind of proc macro which emits code using Foo). We define that proc macro in the foo-macro crate. Our main crate foo wants to rexport the proc macro so that users only have to depend on foo.

The code for this example:

Cargo.toml

[package]
name = "foo"

[dependencies]
foo-macro = { path = "foo-macro" }

src/lib.rs

pub use foo_macro::Foo;
trait Foo {}

foo-macro/Cargo.toml

[package]
name = "foo-macro"

foo-macro/src/lib.rs

#[proc_macro_derive(Foo)]
fn derive_foo(_: TokenStream) -> TokenStream {
    // ...
    quote! { impl foo::Foo for #name {} }
}

The question is now: how to document the custom derive in a way such that cargo test can test included example code?. I don't think it's currently possible.

If we document the function derive_foo in the proc macro crate, we have to make all examples ignore because they cannot be compiled, because compiling them would require using the foo crate. But this leads to a cyclic dependency (not even dev-dependencies works). foo-macros cannot depend on foo.

But we also can't document the reexport (that documentation is ignored/not shown). So it's not possible to write the documentation in the foo crate either. As a consequence, we don't have checked documentation tests -- which probably leads to stale and incorrect example code.

Now I can think of three workarounds:

  • Add #[cfg(not(rustdoc))] to the reexport, and add some other item with #[cfg(rustdoc)] and the actual documentation to your foo crate. That way, the reexport doesn't happen when rustdoc generates the documentation and instead renders the other dummy item with the correct documentation. For function like proc macros, it might be fine to have a macro_rules macro as dummy item, but for other kinds of proc macros, this... absolutely not nice.
  • Add a feature to your main foo crate (like no_macro_reexport). When that feature is activated, your main crate does not reexport macros and does not depend on the foo-macro crate. Now the foo-macro crate can depend on foo via dev-dependencies and activate that no_macro_reexport feature. That prevents the cyclic dependency. But now your main crate has a feature that should be implementation detail and you often have to build your main crate twice.
  • Just don't bother and document your proc macros somewhere else (e.g. how serde does it). I don't think that's a good solution at all: rustdoc should be able to also properly document proc macros.

So neither of these workarounds is particularly nice. I guess it's clear that there should be some kind of solution to this.


Potentially related:

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-doctestsArea: Documentation tests, run by rustdocA-macrosArea: All kinds of macros (custom derive, macro_rules!, proc macros, ..)T-rustdocRelevant to the rustdoc team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions