rust-lang / rust

Empowering everyone to build reliable and efficient software.

Home Page:https://www.rust-lang.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Tracking issue for `#[doc(cfg(…))]`, `#[doc(cfg_hide(…))]` and `doc_auto_cfg`

kennytm opened this issue · comments

This is a tracking issue for the #[doc(cfg(…))] attribute (feature: doc_cfg) introduced in #43348 and #[doc(cfg_hide(…))] (feature: doc_cfg_hide) attribute introduced in #89596, along with the doc_auto_cfg feature introduced in #90502.

Steps:

(cc #1998)

#[cfg(rustdoc)] is also gated on this issue but seems distinct (and less risky). Could we FCP that portion in particular?

I think that #[cfg(rustdoc)], when applied to structs, should automatically skip any non-public members. That would greatly reduce the amount of extra typing required by crates like Nix.

I was just trying this out for documenting crate features, it "works" but if this is a potential usecase it would be nice to special case the rendering for it:


Screenshot 2019-07-04 at 12 35 06


Screenshot 2019-07-04 at 12 35 27

There is also the issue that it repeats every feature on every item in a page:

Screenshot 2019-07-04 at 14 17 45

When I last attempted to do something about rendering features I found it much more useful to separately keep track of "all required features" to render at the top of the items page and "newly introduced features" to render on the sub-items on the page, so you don't get this distracting repetition on every item.

We tried out this feature in Syn (dtolnay/syn#734) and decided against using it yet.


What I am happy with

I like how the message turns out at the top of the doc page of a single type or function.

We had previously displayed this information using an italicized note, which was less noticeable.


What I am not happy with

Our index page becomes extremely noisy. I wish there were a way to not show all of these in our case. It is enough to have this information on the type's individual page. Cfg combinations are not among the most important information to show on the index page.

Also inheriting the same note onto every public field seems unnecessary in our use case.

The links in the opening post have gone dead.

What's the status of this? I've been using it on the time crate for as long as I can remember, and a number of other crates have been doing so as well.

For searchability: this is feature doc_cfg.

I changed the rendering of feature="foo" cfgs in #75330, which vastly simplifies the display on module index pages like shown in the syn screenshot above. I have also just opened #77672 to address the other point @dtolnay had, that there is no need to repeat the exact same cfg rendering over and over when it is already implied by context.

Other than those changes, there are bugs around trait implementation handling such as #68100, I want to try and create an exhaustive test covering trait implementations once #77672 is done and fix their handling.

After that, I feel like this would be ready for stabilization, it's had quite thorough usage on docs.rs already, so when rendering is all fixed we can rebuild the documentation for some large crates like tokio that use it and check whether there's other edgecase bugs remaining.

I'm wondering: Would it not make sense to generate doc_cfg hints automatically for any #[cfg(...)] items, without also needing to type #[doc(cfg(...))]?

So the following

#[cfg(all(target_feature = "avx", target_feature = "avx2"))]
#[doc(cfg(all(target_feature = "avx", target_feature = "avx2")))]
pub mod avx;

could just be

#[cfg(all(target_feature = "avx", target_feature = "avx2"))]
pub mod avx;

Otherwise, we're duplicating information unnecessarily, right?

...pretty much

In the general case yes, but there are quite a few crates using things like internal cfg's generated by their build.rs for compiler version detection that would not want to show those cfg to their users.

commented

What about the opposite - having #[doc(cfg(x))] also imply #[cfg(x)]? I can't imagine a scenario where you wouldn't want to have that.

commented

Not sure how hard that would be to implement, though - maybe @petrochenkov would know?

commented

I'm wondering: Would it not make sense to generate doc_cfg hints automatically for any #[cfg(...)] items, without also needing to type #[doc(cfg(...))]?

Issue with that is if you want to implement the same function on different platforms in different ways. It shouldn't show up as gated on the current platform, but as not gated at all... or if it's only implemented on a set of platforms, then as that list.

So if there is such an implication, it should be opt in, eg via an empty #[doc(cfg)] tag. Maybe one can have a feature to make it opt out for a region of code, or an entire crate, but that requires manual review of the code, so can't be the default.

having #[doc(cfg(x))] also imply #[cfg(x)]?

IMO it's strange if #[doc(...)] influences normal code.

This is mostly looking good for syn. Filed one bug regarding the rendering of doc cfg on impls of empty traits: #79279.

One situation where it would be nice to have doc_cfg but doesn't currently seem to be feasible is on derive-generated implementations. Even if the provider of the derive wanted to, they'd have to manually accept an equivalent attribute to doc_cfg, only to pass it on.

Not something worth holding up stabilization over, but just something I noticed when adding in more attributes in some of my code.

commented

Can't we have something like a feature-selector, a ui for selecting which feature to show?

commented

I think #84437 needs to be fixed before stabilizing this.

Can't we have something like a feature-selector, a ui for selecting which feature to show?

@aobatact the more features we add to doc(cfg), the longer it will be before it's stabilized. I would rather stabilize an MVP and then we can add features later.

There are some issues around how the #[doc(cfg)] annotations propagate from module to the items contained within, IIRC. It does only work in some circumstances (though I don't remember which ones, so I can't file a bug…)

That said, I don't think it or any other presentation concerns need to block the stabilization of the attribute itself – I don't believe the output of the rustdoc falls under the stability guarantees, so we can always fix that later.

commented

One situation where it would be nice to have doc_cfg but doesn't currently seem to be feasible is on derive-generated implementations. Even if the provider of the derive wanted to, they'd have to manually accept an equivalent attribute to doc_cfg, only to pass it on.

Do you want to show the cfg message on both the type and the impl? I'm having trouble understanding the use-case - when would that be necessary?

So you can put #[doc(cfg)] on a module and it will show up on the types within (or at least used to), but only in some cases – again I don't recall exactly what the circumstances were. This helps when you have #[doc(cfg(...))] #[cfg(any(docs, ...)] mod a_ton_of_optional_functionality; and want to avoid having to annotate with #[doc(cfg)] everything within the module, but you ultimately end up having to annotate everything anyway, because propagation is not working quite right.

commented

So you can put #[doc(cfg)] on a module and it will show up on the types within (or at least used to), but only in some cases – again I don't recall exactly what the circumstances were

This is unfortunately not super actionable, but please do open an issue if you can figure out how to replicate it!

I can only speak anecdotally, but I haven't run into any issues with this feature aside from the aforementioned derived traits behind a cfg gate (which shouldn't be a blocker imo).

@jyn514 #83428 is the same issue that I was hitting. As I said originally, though:

That said, I don't think it or any other presentation concerns need to block the stabilization of the attribute itself – I don't believe the output of the rustdoc falls under the stability guarantees, so we can always fix that later.

I actually recently saw a case of the doc-cfg hint being rendered on some items when no doc(cfg()) attribute is applied to those items or any of their parents.

The items in question are all of the methods on this type: https://docs.rs/ruma/0.0.3/ruma/serde/struct.Raw.html
The doc(cfg()) attribute being rendered does exist in the ruma crate, but only in one place: https://docs.rs/ruma/0.0.3/src/ruma/lib.rs.html#103

One thing that may be nice would be the ability to provide user-defined text for the "x". I use a number of special features like thread_impl="c11". While these typically don't appear in the public api, having "This is only supported on thread implementation c11" in internal documentation, rather than "This is only supported on thread_impl="c11"" might be nice.
Not necessarily a blocking feature, though. The way I'd imagine this would be an optional second argument that gives a user-defined representation for the cfg key. In the above case, you'd get #[doc(cfg(thread_impl="c11","thread implementation"))] or something similar.

Do you want to show the cfg message on both the type and the impl? I'm having trouble understanding the use-case - when would that be necessary?

I just hit a case where I'd like the cfg message on the derived impl but not the type (the derive attribute itself is applied in a cfg_attr attribute). In particular, it's a no_std library that optionally derives some std-only traits if the "std" feature is enabled. With doc_cfg, all my explicit impls of std-only traits are correctly documented as such; but I can't similarly annotate derived ones.

commented

I can't similarly annotate derived ones.

I don't think this can be fixed on rustdoc's end; rust doesn't allow users to apply attributes to derived traits. We're planning to make doc(cfg) enabled by default for any #[cfg] attribute, would that solve your issue? #89596

We're planning to make doc(cfg) enabled by default for any #[cfg] attribute, would that solve your issue?

It certainly would!

Though, because of how #[cfg_attr(foo, derive(Bar))] works that won't be detected by #89596, it might be possible to extend it to do so.

To summarise on current status:

Bugs

UI Improvements

So, the decision now that needs to be taken is, when do we want to stabilise this? Now? or after the above mentioned bugs have been fixed?

#89596 made #[doc(cfg)] implicit when #[feature(doc_cfg)] is enabled. It also added #[doc(cfg_hide(...))]. So by "stabilizing" I guess you should specify which feature(s) to be stabilized:

  • the render #[cfg] as #[doc(cfg)] behavior?
  • the #[doc(cfg)] attribute?
  • the #[doc(cfg_hide)] attribute?

I found the following bug:

#![feature(doc_cfg)]

pub struct A;

#[cfg(feature = "test")]
impl Clone for A {
    fn clone(&self) -> Self {
        Self {}
    }
}

#[cfg(feature = "test")]
mod test {
    use crate::A;

    impl Copy for A {}

    #[cfg(feature = "test")]
    impl PartialEq for A {
        fn eq(&self, _: &Self) -> bool {
            true
        }
    }
}

image
As you can see, Copy doesn't show a note, but PartialEq does because it is marked explicitly.
Is this somehow related to #83428 or should I file a separate bug report?

It's the same thing as #83428, private modules don't properly participate in the tree of cfg that rustdoc sees.

Currently #[cfg(any(feature = "danger", test))] shows up as:
grafik

I know doc_cfg allows to overwrite, but whats the purpose of showing the test tag in documentation?
Is there already an opinion on this matter that I missed?

Seems like that should be excluded by default.

commented

Happy to take a PR for that :) look in src/librustdoc for where it checks whether cfg_hide is active and add test to the default set.

I looked into it, it seems to me that it's a bit more complicated. cfg_hide only filters out exact matches, so filtering test won't filter test out of any(feature = "x", test).

After contemplating a couple of solutions, I was wondering if we shouldn't fix this behavior on a different level. Currently #![doc(cfg_hide(feature = "x"))] won't hide x in #[cfg(any(feature = "x", feature = "y"))] or #[cfg(all(feature = "x", feature = "y"))]. cfg_hide only works on exact matches, does that make sense?

The most straightforward way to me to implement this is to transform Attributes after parsing them, removing anything that should be hidden. This can include test of course. But if we don't want to change cfg_hide, we could also do this for test only.

Any guidance would be appreciated.

@daxpedda since it looks nontrivial, could you open a separate issue to discuss it on?

#89596 made #[doc(cfg)] implicit when #[feature(doc_cfg)] is enabled.

I'm confused. I thought that it worked before, but now it somehow doesn't? Was this behaviour reversed?

// lib.rs
#![feature(doc_cfg)]

#[cfg(feature = "a")]
pub struct What;
# Cargo.toml
[package]
name = "rustdoc_no_doccfg"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

[features]
a = []
; cargo +nightly doc --all-features --open

screenshot of rustdoc output showing that feature is not shown
another screenshot of rustdoc output showing that feature is not shown

; cargo +nightly --version
cargo 1.59.0-nightly (fcef61230 2021-12-17)
; rustdoc +nightly --version
rustdoc 1.59.0-nightly (cfa3fe5af 2021-12-31)

(adding #[doc(cfg(...))] fixes this, but it seemed like it's not required anymore)

Ah, so since #90502 the implication of #[doc(cfg(...))] by #[cfg(...)] needs a different feature, doc_auto_cfg. It's weird that this wasn't mentioned in this tracked issue at all.

#90502 split that feature out into a separate doc_auto_cfg feature, which is as of yet undocumented (I opened #92484 for that).

This feature currently doesn't document feature gated function arguments:

fn frobnify(#[cfg(feature = "bar")] _bar: ()) {}

Whats blocking stabilization here? It seems like the feature is complete, and people are using it. If there's tweaks to how documentation is rendered, surely that could happen after stabilization? Devs can already opt out of this for their crates by not adding #[doc(...)] attributes in their code.

Are we just waiting on a write-up being merged into the core rust documentation?

I use stable rust, and I find issues like this very frustrating - where there's functionally complete, working compiler features sitting for years behind a flag thats unavailable in stable because ??. It makes stable feel like rust's forgotten stepchild, rather than the main & recommended version of the compiler.

I agree with @josephg, this functionality feels relatively mature and is used all over the place on docs.rs. At this point doing any kind of backwards-incompatible change would break a ton of crate documentation on docs.rs so it feels almost de-facto stable. And since this is still unstable, locally-built documentation feels incomplete, to the extent that I end up running RUSTDOCFLAGS='--cfg docsrs' cargo +nightly doc to build local documentation just for #[doc(cfg(…))].

Perhaps this raises a wider issue re stabilisation: if docs.rs runs on nightly/applies unstable features, then those features are at risk of becoming "de facto stabilised". Perhaps this was already (or should be) raised/discussed elsewhere, but it seems that perhaps this is a Bad Thing.

docs.rs generates documentation once and stores it as is. If it looks good at the time of generation, it will remain that way indefinitely. While changing the way this functionality works today would affect the documentation output if a new version of the crate was published after such a change, it is also pretty clear cut that the crate opts into nightly functionality knowingly by asking docs.rs to use an unstable feature with the specific version of the Rust that docs.rs happens to be using at the time.

I don't see the risk as significant here. With that context I'm also opposed to freezing the current functionality or rushing the stabilization in case we have in mind any improvements we'd like to make.


Are we just waiting on a write-up being merged into the core rust documentation? I use stable rust, and I find issues like this very frustrating...

A great way to have this particular itch scratched is to contribute some work towards scratching it. One thing that's missing for the stabilization is a stabilization report which could among other things give an objective overview of the known issues and why they don't block the stabilization of this feature. It should be posted as a comment on this issue.

#89596 was merged a while ago. Is there anything else remaining to be done in here? Otherwise I can open a stabilization PR like #79263.

I'm not aware of any blockers. I have been using this for many months without issue, as have many other large crates.

Same on my side but I was thinking that maybe I was missing something.

Do you think we can stabilize it @rust-lang/rustdoc ?

commented

I'm wondering about this and here.
Screenshot_20220523_173414

It's a bit ambiguous what and entails. It can be interpreted by users that enabling either of the two features is enough for getting that function.

Maybe "Requires crate features http and cache" would make it clearer?

Requires windows seems a bit weird but I'm fine with it.

Stabilization of this would be a massive quality of life improvement.

For clarity, the stabilization is regarding the attribute itself. How it's displayed, like everything else in docs, can change to be improved. So wording isn't a blocker.

Is the plan to only stabilize doc(cfg), or all three features? If the latter, feature(doc_auto_cfg) probably needs to have an explicit opt-in like #![doc(auto_cfg)] added so that we don't start just showing cfgs on crates that aren't prepared for it (and maybe a plan to make that default active in a future edition); and some usage reports for cfg_hide would be nice.

Only doc(cfg) iiuc.

However I think most people in here are very likely talking about doc_auto_cfg. Might be better to clarify beforehand.

commented

Does cfg_hide currently not support hide not() feature?

#[cfg(any(all(windows, feature = "winapi"), all(doc, not(doctest))))]
pub use win_debug_sink::*;

I need not(doctest) here because without it cargo test will run doctests in the win_debug_sink mod on a platform other than Windows.

In the head of my lib.rs:

#![cfg_attr(
    all(doc, CHANNEL_NIGHTLY), // `CHANNEL_NIGHTLY` is passed by `build.rs`
    feature(doc_auto_cfg, doc_cfg_hide),
    doc(cfg_hide(doc))
)]

It generates badge: Available on Windows and crate feature winapi, or non-doctest only.

It's good that the doc is properly hidden. But if I add a doc(cfg_hide(doctest)), the badge just disappeared, instead if I add a doc(cfg_hide(not(doctest))), the badge is back, but it doesn't seem to be doing anything.


Or consider another solution, maybe we should remove doc and doctest in the same way as we did in issue #91740?


BTW, the implementation in #91740 also only removes test but not not(test), the latter makes the badge disappear. Looks like a bug.

MRE:

#![feature(doc_auto_cfg)]

// #[cfg(any(doc, test))] // OK
#[cfg(any(doc, not(test)))] // badge disappear
pub fn foo() {}

Please open a separate issue and link to this one so it can be fixed.

Is the way with defining a doc_auto_cfg feature still supported currently? And in the future?

What do you mean? If we will keep supporting #[doc(cfg(...))]? If so yes, the syntax will be kept.

There used to be a feature the the docs.rs renderer enabled, like play.rust-lang.org, so as a crate author I could enable specific features for the respective builds.

Also, I found this example that could solve the same problem: https://github.com/rust-lang/docs.rs/blob/369e1bb0ed5f0b4c14f6ecaa01013094c7d234dd/templates/core/Cargo.toml.example

[package.metadata.docs.rs]

# Features to pass to Cargo (default: [])
features = ["feature1", "feature2"]

# Whether to pass `--all-features` to Cargo (default: false)
all-features = true

I am very lost. This is not linked to that (at least not directly). doc(cfg(...)) is used to show the cfgs independently or the current cfg.

Could we get a small warning triangle or a similar marker with hover at declaration site? Consider this example:

Screenshot 2022-12-06 at 13-57-54 Foo in foo - Rust

The conditionality is not immediately visible, the user has to scroll down. For small enums this is OK but large enums that have each variant extensively documented are a problem. If one doesn't scroll down to check if it requires a feature it could result in annoying "why it says it doesn't exist?" confusion.

Adding a small warning sign beside the variant (and nothing else) could be enough I suppose. We still need to write an RFC for all this feature though.

Is anyone already working on creating an RFC?

Not that I know. I have it in my TODO list.

commented

As a reminder, this is a tracking issue for stabilizing doc_cfg. Bug reports should go in separate issues to avoid pinging the 30+ people watching this issue, unless you think the bug is so severe it should block stabilization. Suggestions for enhancements should also go in separate issues.


As far as I can tell, the current status is that the stabilization PR (#100883) is blocked on deciding whether doc_auto_cfg should be on by default or not (and whether we should let crate authors control whether it's on or just force it on). The discussion for that is in https://rust-lang.zulipchat.com/#narrow/stream/266220-rustdoc/topic/Discussion.20about.20.60doc_cfg*.60.20features.20stabilization/.

Thanks for the sum up. We actually decided to go through an RFC that started to be written because too many elements need to be discussed. More information in the next weeks.

Since I see an RFC is being written, I take the opportunity to share my case, since I wanted to use doc_cfg to show that a particular function is only available on the absence of a feature. But it seems that's not currently possible.

This does result in: "Available on feature safe only".

#[cfg(feature = "safe")]
#[doc(cfg(feature = "safe"))]
pub fn safe_fn() {}

But this results in: "Available on non-crate feature safe only". Instead of the expected: "Not available on feature safe".

#[cfg(not(feature = "safe"))]
#[doc(cfg(not(feature = "safe")))]
pub fn unsafe_fn() {}

I wonder if this is by design.

I wonder if this is by design.

Somewhat, yes. Cargo itself doesn't support negative features, so no effort was put into making them render well.

Should the original post link to #100883 instead?

I'm running into a rather annoying problem with documenting features.

I am writing currently a serde crate with multiple specifications for a specific format. I have a binary module that is documented with the following doc_cfg: any(feature = "be", feature = "le", feature = "varint"). Now, if I document one of the functions as present with just the be feature and inline their re-export I get this annoying display:

image

Of course, I tried with doc_cfg_hide, wrestling with the minimal documentation, but nothing seems to work for this. I can not inline the re-exports, but purposefully sacrificing usability is presumably a bug or at least something worth mentioning here. When I don't inline it, it looks like so:

image

My one gripe with the first selection is that even the order it's appended to is non-uniform, which bothers me, but that's not the core issue here.

Is there some hack I'm unaware of besides not inlining items in documentation? I haven't seen this case discussed so far. Even if I simplified it to an internal feature (binary) it still displays, even if assumed to be enabled in context; either way, extraneous information is applied, and I have no say in omitting it.

#[doc(no_inline)]

I believe #[doc(no_inline)] (as described here) is specifically the behavior I'm trying to avoid. As you can see, binary is a public module.

image

I understand this is stylistic, but every serde_* crate uses #[doc(inline)] on their re-exported functions. This is the behavior I want.

The behavior I don't want is my inability to omit the automatically appended features. It introduces a ton of noise. Even if I make some "internal" feature named binary, it still adds itself (non-uniformly in terms of order) to the re-exported functions. I've made this an issue here because it seems like not too rare of a use-case that makes documenting features so noisy that it becomes unhelpful at-a-glance.

In summary, I can either not inline my re-exports, or deal with unhelpful documented features; it seems like a bug that I can't simply do both, even if it means manually overriding the item at its declaration to say exactly what I want to document.

It should certainly be possible to simplify be and std and (be or le or varint) to be and std, IIRC there are already some simplifications applied to the cfgs so this would just require extending those. It'd be good to open a separate issue with a small testcase.

We'll need #103300 to be fixed beforehand as well.

I noticed in gfx-rs/wgpu#4935 (comment) that it would be very nice if we could add custom names to cfgs. This is currently done by Rustdoc for known cfgs here:

Cfg::Cfg(name, value) => {
let human_readable = match (name, value) {
(sym::unix, None) => "Unix",
(sym::windows, None) => "Windows",
(sym::debug_assertions, None) => "debug-assertions enabled",
(sym::target_os, Some(os)) => match os.as_str() {
"android" => "Android",
"dragonfly" => "DragonFly BSD",
"emscripten" => "Emscripten",
"freebsd" => "FreeBSD",
"fuchsia" => "Fuchsia",
"haiku" => "Haiku",
"hermit" => "HermitCore",
"illumos" => "illumos",
"ios" => "iOS",
"l4re" => "L4Re",
"linux" => "Linux",
"macos" => "macOS",
"netbsd" => "NetBSD",
"openbsd" => "OpenBSD",
"redox" => "Redox",
"solaris" => "Solaris",
"tvos" => "tvOS",
"wasi" => "WASI",
"watchos" => "watchOS",
"windows" => "Windows",
_ => "",
},
(sym::target_arch, Some(arch)) => match arch.as_str() {
"aarch64" => "AArch64",
"arm" => "ARM",
"loongarch64" => "LoongArch LA64",
"m68k" => "M68k",
"csky" => "CSKY",
"mips" => "MIPS",
"mips32r6" => "MIPS Release 6",
"mips64" => "MIPS-64",
"mips64r6" => "MIPS-64 Release 6",
"msp430" => "MSP430",
"powerpc" => "PowerPC",
"powerpc64" => "PowerPC-64",
"riscv32" => "RISC-V RV32",
"riscv64" => "RISC-V RV64",
"s390x" => "s390x",
"sparc64" => "SPARC64",
"wasm32" | "wasm64" => "WebAssembly",
"x86" => "x86",
"x86_64" => "x86-64",
_ => "",
},
(sym::target_vendor, Some(vendor)) => match vendor.as_str() {
"apple" => "Apple",
"pc" => "PC",
"sun" => "Sun",
"fortanix" => "Fortanix",
_ => "",
},
(sym::target_env, Some(env)) => match env.as_str() {
"gnu" => "GNU",
"msvc" => "MSVC",
"musl" => "musl",
"newlib" => "Newlib",
"uclibc" => "uClibc",
"sgx" => "SGX",
_ => "",
},
(sym::target_endian, Some(endian)) => return write!(fmt, "{endian}-endian"),
(sym::target_pointer_width, Some(bits)) => return write!(fmt, "{bits}-bit"),
(sym::target_feature, Some(feat)) => match self.1 {
Format::LongHtml => {
return write!(fmt, "target feature <code>{feat}</code>");
}
Format::LongPlain => return write!(fmt, "target feature `{feat}`"),
Format::ShortHtml => return write!(fmt, "<code>{feat}</code>"),
},
(sym::feature, Some(feat)) => match self.1 {
Format::LongHtml => {
return write!(fmt, "crate feature <code>{feat}</code>");
}
Format::LongPlain => return write!(fmt, "crate feature `{feat}`"),
Format::ShortHtml => return write!(fmt, "<code>{feat}</code>"),
},
_ => "",
};
if !human_readable.is_empty() {
fmt.write_str(human_readable)
} else if let Some(v) = value {
if self.1.is_html() {
write!(
fmt,
r#"<code>{}="{}"</code>"#,
Escape(name.as_str()),
Escape(v.as_str())
)
} else {
write!(fmt, r#"`{name}="{v}"`"#)
}
} else if self.1.is_html() {
write!(fmt, "<code>{}</code>", Escape(name.as_str()))
} else {
write!(fmt, "`{name}`")
}
}

Maybe something like doc(cfg_rename(foo = "Foo"))?