Skip to content

Commit e6b88c1

Browse files
GuillaumeGomezfmeasenotriddle
committed
Start RFC for doc cfg feature
Co-authored-by: León Orell Valerian Liehr <[email protected]> Co-authored-by: Michael Howell <[email protected]>
1 parent 9653bae commit e6b88c1

File tree

1 file changed

+283
-0
lines changed

1 file changed

+283
-0
lines changed

text/000-rustdoc-cfgs-handling.md

+283
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
Rustdoc: stabilization of the `doc(cfg*)` attributes
2+
3+
- Features Name: `doc_cfg`
4+
- Start Date: 2022-12-07
5+
- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000)
6+
- Rust Issue: [rust-lang/rust#43781](https://github.com/rust-lang/rust/issues/43781)
7+
8+
9+
# Summary
10+
[summary]: #summary
11+
12+
This RFC aims at providing rustdoc users the possibility to add visual markers to the rendered documentation to know under which conditions an item is available (currently possible through the following unstable features: `doc_cfg`, `doc_auto_cfg` and `doc_cfg_hide`).
13+
14+
It does not aim to allow having a same item with different `cfg`s to appear more than once in the generated documentation.
15+
16+
It does not aim to document items which are *inactive* under the current configuration (i.e., “`cfg`'ed out”).
17+
18+
# Motivation
19+
[motivation]: #motivation
20+
21+
The goal of this RFC is to stabilize the possibility to add visual markers to the rendered documentation to know under which conditions an item is available.
22+
23+
Providing this information to users will solve a common issue: “Why can I see this item in the documentation and yet can't use it in my code?”.
24+
The end goal being to provide this information automatically so that the documentation maintenance cost won't increase.
25+
26+
27+
# Guide-level explanation
28+
[guide-level-explanation]: #guide-level-explanation
29+
30+
This RFC proposes to add the following attributes:
31+
32+
* `#[doc(cfg(...))]`
33+
34+
This attribute is used to document the operating systems, feature flags, and build profiles where an item is available. For example, `#[doc(cfg(unix))` will add a tag that says "this is supported on **unix** only" to the item.
35+
36+
The syntax of this attribute is the same as the syntax of the [`#[cfg(unix)]` attribute][cfg attribute] used for conditional compilation.
37+
38+
* `#[doc(auto_cfg)]`/`#[doc(no_auto_cfg)]`
39+
40+
When this is turned on, `#[cfg]` attributes are shown in documentation just like `#[doc(cfg)]` attributes are.
41+
42+
* `#[doc(cfg_hide(...))]` / `#[doc(cfg_show(...))]`
43+
44+
These attributes suppress or un-suppress the `auto_cfg` behavior for a particular configuration predicate.
45+
46+
For example, `#[doc(cfg_hide(windows))]` shall be used in newer versions of the [`windows` crate] to prevent the "this is supported on **windows** only" tag from being shown on every single item.
47+
48+
[cfg attribute]: https://doc.rust-lang.org/reference/conditional-compilation.html
49+
[`windows` crate]: https://docs.rs/windows/latest/windows/
50+
51+
All of these attributes can be added to a module or to the crate root, and they will be inherited by the child items unless another attribute overrides it (except that `doc(cfg)` cannot be added to the crate root). This is why "opposite" attributes like `cfg_hide` and `cfg_show` are provided: they allow a child item to override its parent.
52+
53+
54+
# Reference-level explanation
55+
[reference-level-explanation]: #reference-level-explanation
56+
57+
## The attributes
58+
59+
### `#[doc(cfg(...))]`
60+
61+
This attribute provides a standardized format to document conditionally available items. Example:
62+
63+
```rust
64+
// the "real" cfg condition
65+
#[cfg(feature = "futures-io")]
66+
// the `doc(cfg())` so it's displayed to the readers
67+
#[doc(cfg(feature = "futures-io"))]
68+
pub mod futures {}
69+
```
70+
71+
It will display in the documentation for this module:
72+
73+
![This is supported on feature="futures-io" only.](https://user-images.githubusercontent.com/81079/89731116-d7b7ce00-da44-11ea-87c6-022d192d6eca.png)
74+
75+
This attribute has the same syntax as conditional compilation, but it only causes documentation to be added. This means `#[doc(cfg(false))` will not cause your docs to be hidden, even though `#[cfg(false)]` does do that.
76+
77+
This attribute works on modules and on items but cannot be used at the crate root level.
78+
79+
### `#[doc(auto_cfg)]`/`#[doc(no_auto_cfg)]`
80+
81+
By default, `#[doc(auto_cfg)]` is enabled at the crate-level. When it's enabled, Rustdoc will automatically display `cfg(...)` compatibility information as-if the same `#[doc(cfg(...))]` had been specified.
82+
83+
So if we take back the previous example:
84+
85+
```rust
86+
#[cfg(feature = "futures-io")]
87+
pub mod futures {}
88+
```
89+
90+
There's no need to "duplicate" the `cfg` into a `doc(cfg())` to make Rustdoc display it.
91+
92+
In some situations, the detailed conditional compilation rules used to implement the feature might not serve as good documentation (for example, the list of supported platforms might be very long, and it might be better to document them in one place). To turn it off, add the `#[doc(no_auto_cfg)]` attribute.
93+
94+
Both `#[doc(auto_cfg)]` and `#[doc(no_auto_cfg)]` attributes impact all there descendants. You can then enable/disable them by using the opposite attribute on a given item. They can be used as follows:
95+
96+
```rust
97+
// As an inner attribute, all this module's descendants will have this feature
98+
// enabled.
99+
#![doc(auto_cfg)]
100+
101+
// As an outer attribute. So in this case, `foo` and all its
102+
// descendants won't have the `auto_cfg` feature enabled.
103+
#[doc(no_auto_cfg)]
104+
pub mod foo {
105+
// We re-enable the feature on `Bar` and on all its descendants.
106+
#[doc(auto_cfg)]
107+
pub struct Bar {
108+
pub f: u32,
109+
}
110+
}
111+
```
112+
113+
As mentioned, both attributes can be used on modules, items and crate root level.
114+
115+
### `#[doc(cfg_hide(...))]`
116+
117+
This attribute is used to prevent some `cfg` to be generated in the visual markers. So in the previous example:
118+
119+
```rust
120+
#[cfg(any(doc, feature = "futures-io"))]
121+
pub mod futures {}
122+
```
123+
124+
It currently displays both `doc` and `feature = "futures-io"` into the documentation, which is not great. To prevent the `doc` cfg to ever be displayed, you can use this attribute at the crate root level:
125+
126+
```rust
127+
#![doc(cfg_hide(doc))]
128+
```
129+
130+
Or directly on a given item/module as it covers any of the item's descendants:
131+
132+
```rust
133+
#[doc(cfg_hide(doc))]
134+
#[cfg(any(doc, feature = "futures-io"))]
135+
pub mod futures {
136+
// `futures` and all its descendants won't display "doc" in their cfgs.
137+
}
138+
```
139+
140+
Then, the `doc` cfg will never be displayed into the documentation.
141+
142+
Rustdoc currently hides `doc` and `doctest` attributes by default and reserves the right to change the list of "hidden by default" attributes.
143+
144+
The attribute accepts only a list of identifiers or key/value items. So you can write:
145+
146+
```rust
147+
#[doc(cfg_hide(doc, doctest, feature = "something"))]
148+
#[doc(cfg_hide())]
149+
```
150+
151+
But you cannot write:
152+
153+
```rust
154+
#[doc(cfg_hide(not(doc)))]
155+
```
156+
157+
### `#[doc(cfg_show(...))]`
158+
159+
This attribute does the opposite of `#[doc(cfg_hide(...))]`: if you used `#[doc(cfg_hide(...))]` and want to revert its effect on an item and its descendants, you can use `#[doc(cfg_show(...))]`:
160+
161+
```rust
162+
#[doc(cfg_hide(doc))]
163+
#[cfg(any(doc, feature = "futures-io"))]
164+
pub mod futures {
165+
// `futures` and all its descendants won't display "doc" in their cfgs.
166+
#[doc(cfg_show(doc))]
167+
pub mod child {
168+
// `child` and all its descendants will display "doc" in their cfgs.
169+
}
170+
}
171+
```
172+
173+
The attribute accepts only a list of identifiers or key/value items. So you can write:
174+
175+
```rust
176+
#[doc(cfg_show(doc, doctest, feature = "something"))]
177+
#[doc(cfg_show())]
178+
```
179+
180+
But you cannot write:
181+
182+
```rust
183+
#[doc(cfg_show(not(doc)))]
184+
```
185+
186+
## Inheritance
187+
188+
Rustdoc merges `cfg` attributes from parent modules to its children. For example, in this case, the module `non_unix` will describe the entire compatibility matrix for the module, and not just its directly attached information:
189+
190+
```rust
191+
#[doc(cfg(any(windows, unix)))]
192+
pub mod desktop {
193+
#[doc(cfg(not(unix)))]
194+
pub mod non_unix {
195+
//
196+
}
197+
}
198+
```
199+
200+
> ![Available on (Windows or Unix) and non-Unix only.](https://hackmd.io/_uploads/SJrmwYeF2.png)
201+
202+
[Future versions of rustdoc][boolean simplification] may simplify this display down to "available on **Windows** only."
203+
204+
### Re-exports and inlining
205+
206+
`cfg` attributes of a re-export are never merged the re-exported item(s). If `#[doc(inline)]` attribute is used on a re-export, the `cfg` of the re-exported item will be merged with the re-export's.
207+
208+
```rust
209+
#[doc(cfg(any(windows, unix)))]
210+
pub mod desktop {
211+
#[doc(cfg(not(unix)))]
212+
pub mod non_unix {
213+
//
214+
}
215+
}
216+
217+
#[doc(cfg(target_os = "freebsd"))]
218+
pub use desktop::non_unix as non_unix_desktop;
219+
#[doc(cfg(target_os = "macos"))]
220+
#[doc(inline)]
221+
pub use desktop::non_unix as inlined_non_unix_desktop;
222+
```
223+
224+
In this example, `non_unix_desktop` will only display `cfg(target_os = "freeebsd")` and not display any `cfg` from `desktop::non_unix`.
225+
226+
On the contrary, `inlined_non_unix_desktop` will have cfgs from both the re-export and the re-exported item.
227+
228+
# Drawbacks
229+
[drawbacks]: #drawbacks
230+
231+
A potential drawback is that it adds more attributes, making documentation more complex.
232+
233+
234+
# Rationale and alternatives
235+
[rationale-and-alternatives]: #rationale-and-alternatives
236+
237+
## Why not merging cfg and doc(cfg) attributes by default?
238+
239+
It was debated and implemented in [rust-lang/rust#113091](https://github.com/rust-lang/rust/pull/113091).
240+
241+
When re-exporting items with different cfgs there are two things that can happen:
242+
243+
1. The re-export uses a subset of cfgs, this subset is sufficient so that the item will appear exactly with the subset
244+
2. The re-export uses a non-subset of cfgs like in this code:
245+
```rust
246+
#![feature(doc_auto_cfg)]
247+
248+
#[cfg(target_os = "linux")]
249+
mod impl_ {
250+
pub fn foo() { /* impl for linux */ }
251+
}
252+
253+
#[cfg(target_os = "macos")]
254+
mod impl_ {
255+
pub fn foo() { /* impl for darwin */ }
256+
}
257+
258+
pub use impl_::foo;
259+
```
260+
If the non-subset cfgs are active (e.g. compiling this example on windows), then this will be a compile error as the item doesn't exist to re-export. If the subset cfgs are active it behaves like described in 1.
261+
262+
263+
# Unresolved questions
264+
[unresolved-questions]: #unresolved-questions
265+
266+
267+
# Future possibilities
268+
[future possibilities]: #future-possibilities
269+
270+
## Boolean simplification
271+
[boolean simplification]: #boolean-simplification
272+
273+
> ![Available on (Windows or Unix) and non-Unix only.](https://hackmd.io/_uploads/SJrmwYeF2.png)
274+
275+
Of course, the above example is equivalent to "available on **Windows** only."
276+
277+
Making this actually work all the time is equivalent to a [boolean satisfiability] check, coliquially called a "SAT problem," and can take exponential time.
278+
279+
[boolean satisfiability]: https://en.wikipedia.org/wiki/Boolean_satisfiability_problem
280+
281+
We probably don't want to make promises one way or the other about whether rustdoc does this, but for compatibility's sake, Rustdoc does promise that `#[doc(cfg(false))` will not hide the documentation. This means simplification can be added, and it won't cause docs to mysteriously vanish.
282+
283+
This is tracked in issue [rust-lang/rust#104991](https://github.com/rust-lang/rust/issues/104991).

0 commit comments

Comments
 (0)