From 85c465dc826bb582d1cf53e9019449e2acb1f286 Mon Sep 17 00:00:00 2001 From: Yilin Chen <1479826151@qq.com> Date: Mon, 13 Apr 2026 21:22:39 +0800 Subject: [PATCH] rustdoc: implement RFC 3842 with safety::requires attribute * Support "#[safety::requires()]" attribute for documentation injection. * Support "{Tag}={description}" format with customized tags. * Prototype implementation of Safety contract insertion. --- Cargo.lock | 1 + library/core/src/lib.rs | 4 + library/core/src/ptr/mod.rs | 7 +- src/bootstrap/src/core/build_steps/doc.rs | 9 + src/librustdoc/Cargo.toml | 1 + src/librustdoc/assets/sp-core.toml | 9 + src/librustdoc/config.rs | 5 + src/librustdoc/core.rs | 13 +- src/librustdoc/lib.rs | 8 + src/librustdoc/passes/inject_safety_docs.rs | 374 ++++++++++++++++++ src/librustdoc/passes/mod.rs | 5 + .../output-default.stdout | 3 + tests/rustdoc-ui/issues/issue-91713.stdout | 2 + 13 files changed, 434 insertions(+), 7 deletions(-) create mode 100644 src/librustdoc/assets/sp-core.toml create mode 100644 src/librustdoc/passes/inject_safety_docs.rs diff --git a/Cargo.lock b/Cargo.lock index 563d99d5475c4..78008cc0219d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4885,6 +4885,7 @@ dependencies = [ "stringdex", "tempfile", "threadpool", + "toml 0.8.23", "tracing", "tracing-subscriber", "tracing-tree", diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index 299aa9f113bf6..fd022a9260752 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -157,6 +157,7 @@ #![feature(optimize_attribute)] #![feature(pattern_types)] #![feature(prelude_import)] +#![feature(register_tool)] #![feature(repr_simd)] #![feature(rustc_attrs)] #![feature(rustdoc_internals)] @@ -192,6 +193,9 @@ #![feature(x86_amx_intrinsics)] // tidy-alphabetical-end +// Inert attributes for rustdoc `inject-safety-docs` (`--safety-spec`); see `safety::requires`. +#![register_tool(safety)] + // allow using `core::` in intra-doc links #[allow(unused_extern_crates)] extern crate self as core; diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs index ddeb1ccc72af7..709b9dc9b864a 100644 --- a/library/core/src/ptr/mod.rs +++ b/library/core/src/ptr/mod.rs @@ -1729,12 +1729,6 @@ pub const unsafe fn read(src: *const T) -> T { /// /// # Safety /// -/// Behavior is undefined if any of the following conditions are violated: -/// -/// * `src` must be [valid] for reads. -/// -/// * `src` must point to a properly initialized value of type `T`. -/// /// Like [`read`], `read_unaligned` creates a bitwise copy of `T`, regardless of /// whether `T` is [`Copy`]. If `T` is not [`Copy`], using both the returned /// value and the value at `*src` can [violate memory safety][read-ownership]. @@ -1797,6 +1791,7 @@ pub const unsafe fn read(src: *const T) -> T { #[rustc_const_stable(feature = "const_ptr_read", since = "1.71.0")] #[track_caller] #[rustc_diagnostic_item = "ptr_read_unaligned"] +#[safety::requires(ValidPtrRead(src, T), Init(src, T))] pub const unsafe fn read_unaligned(src: *const T) -> T { let mut tmp = MaybeUninit::::uninit(); // SAFETY: the caller must guarantee that `src` is valid for reads. diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs index a918ae929d2e0..c1d616ef97147 100644 --- a/src/bootstrap/src/core/build_steps/doc.rs +++ b/src/bootstrap/src/core/build_steps/doc.rs @@ -813,6 +813,15 @@ fn doc_std( .rustdocflag("--extern-html-root-takes-precedence") .rustdocflag("--resource-suffix") .rustdocflag(&builder.version); + let safety_spec_in_rustdocflags = + env::var("RUSTDOCFLAGS").map(|s| s.contains("--safety-spec")).unwrap_or(false); + // If `--safety-spec` is not set in `RUSTDOCFLAGS`, set it to the default spec file. + if !safety_spec_in_rustdocflags { + let safety_spec_path = builder.src.join("src/librustdoc/assets/sp-core.toml"); + if let Some(p) = safety_spec_path.to_str() { + cargo.rustdocflag("--safety-spec").rustdocflag(p); + } + } for arg in extra_args { cargo.rustdocflag(arg); } diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml index 440d54b007a19..447d02ed00b16 100644 --- a/src/librustdoc/Cargo.toml +++ b/src/librustdoc/Cargo.toml @@ -25,6 +25,7 @@ smallvec = "1.8.1" stringdex = "=0.0.6" tempfile = "3" threadpool = "1.8.1" +toml = "0.8" tracing = "0.1" tracing-tree = "0.3.0" unicode-segmentation = "1.9" diff --git a/src/librustdoc/assets/sp-core.toml b/src/librustdoc/assets/sp-core.toml new file mode 100644 index 0000000000000..e19373dad8cd8 --- /dev/null +++ b/src/librustdoc/assets/sp-core.toml @@ -0,0 +1,9 @@ +package.name = "core" + +[tag.ValidPtrRead] +args = [ "p", "T" ] +desc = "pointer `{p}` must be [valid](crate::ptr#safety) for reading `sizeof({T})` bytes." + +[tag.Init] +args = [ "p", "T" ] +desc = "`{p}` must point to a properly initialized value of type `{T}`." \ No newline at end of file diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 3caff6edd504a..d0e66254fbc8f 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -312,6 +312,8 @@ pub(crate) struct RenderOptions { pub(crate) disable_minification: bool, /// If `true`, HTML source pages will generate the possibility to expand macros. pub(crate) generate_macro_expansion: bool, + /// Optional TOML spec for `inject-safety-docs` (`#[safety::requires]`). + pub(crate) safety_spec: Option, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -852,6 +854,8 @@ impl Options { let disable_minification = matches.opt_present("disable-minification"); + let safety_spec = matches.opt_str("safety-spec").map(PathBuf::from); + let options = Options { bin_crate, proc_macro_crate, @@ -930,6 +934,7 @@ impl Options { include_parts_dir, parts_out_dir, disable_minification, + safety_spec, }; Some((input, options, render_options, loaded_paths)) } diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index 3c5b1e55de644..b3d35ffe23800 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -11,7 +11,7 @@ use rustc_errors::emitter::{DynEmitter, HumanReadableErrorType, OutputTheme, std use rustc_errors::json::JsonEmitter; use rustc_feature::UnstableFeatures; use rustc_hir::def::Res; -use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, LocalDefId}; +use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, LOCAL_CRATE, LocalDefId}; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{HirId, Path}; use rustc_lint::{MissingDoc, late_lint_mod}; @@ -68,6 +68,8 @@ pub(crate) struct DocContext<'tcx> { pub(crate) output_format: OutputFormat, /// Used by `strip_private`. pub(crate) show_coverage: bool, + /// Used by `inject-safety-docs` to transform `#[safety::requires]` into documentation text. + pub(crate) safety_spec: Option>, } impl<'tcx> DocContext<'tcx> { @@ -359,6 +361,14 @@ pub(crate) fn run_global_ctxt( let auto_traits = tcx.visible_traits().filter(|&trait_def_id| tcx.trait_is_auto(trait_def_id)).collect(); + let safety_spec = render_options.safety_spec.as_ref().and_then(|path| { + crate::passes::inject_safety_docs::load_safety_spec( + path, + tcx.crate_name(LOCAL_CRATE).as_str(), + tcx.dcx(), + ) + }); + let mut ctxt = DocContext { tcx, param_env: ParamEnv::empty(), @@ -373,6 +383,7 @@ pub(crate) fn run_global_ctxt( inlined: FxHashSet::default(), output_format, show_coverage, + safety_spec, }; for cnum in tcx.crates(()) { diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 4634b24106e4d..36f088e1ef4fa 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -562,6 +562,14 @@ fn opts() -> Vec { "Include the memory layout of types in the docs", "", ), + opt( + Unstable, + Opt, + "", + "safety-spec", + "The path of toml file for expanding safety tags into docs", + "PATH", + ), opt(Unstable, Flag, "", "no-capture", "Don't capture stdout and stderr of tests", ""), opt( Unstable, diff --git a/src/librustdoc/passes/inject_safety_docs.rs b/src/librustdoc/passes/inject_safety_docs.rs new file mode 100644 index 0000000000000..cd7849c6b1d73 --- /dev/null +++ b/src/librustdoc/passes/inject_safety_docs.rs @@ -0,0 +1,374 @@ +//! Injects Markdown (from `#[safety::requires(...)]` + `--safety-spec`) into item docs before +//! intra-doc link resolution. +//! +//! Safety tags support the following shapes: +//! - `Tag(arg1, arg2, …)` — expand using `[tag.Tag]` from the TOML. +//! - `Tag = "…"` — use the string literal as the description for this tag at this site. + +use std::sync::Arc; + +use regex::Regex; +use rustc_ast as ast; +use rustc_ast::token::DocFragmentKind; +use rustc_ast_pretty::pprust::{meta_list_item_to_string, path_to_string}; +use rustc_data_structures::fx::FxHashMap; +use rustc_errors::DiagCtxtHandle; +use rustc_hir as hir; +use rustc_hir::def_id::DefId; +use rustc_resolve::rustdoc::DocFragment; +use rustc_span::DUMMY_SP; +use rustc_span::symbol::{Symbol, sym}; +use tracing::debug; + +use crate::clean::{Attributes, Crate, Item, ItemKind}; +use crate::core::DocContext; +use crate::fold::DocFolder; +use crate::passes::Pass; + +pub(crate) const INJECT_SAFETY_DOCS: Pass = Pass { + name: "inject-safety-docs", + run: Some(inject_safety_docs), + description: "injects `#[safety::requires]` text from a TOML spec into the item docs", +}; + +/// The safety spec. +/// +/// This is a map of tag names to tag definitions. +#[derive(Debug)] +pub(crate) struct SafetySpec { + tags: FxHashMap, +} + +/// A tag definition. +/// +/// The `args` are the arguments of the tag, and the `desc` is the description of the tag. +#[derive(Debug)] +struct TagDef { + args: Vec, + desc: String, +} + +/// Loads the safety spec from the given path. +/// +/// The spec is a TOML file with the following structure: +/// +/// ```toml +/// package.name = "my-package" +/// +/// [tag.*] +/// args = ["arg1", "arg2"] +/// desc = "This is a description of the tag, containing {arg1} and {arg2}." +/// ``` +/// +/// The following conditions will return `None`: +/// +/// * The file could not be read or parsed. +/// * The `package.name` does not match the documented crate. +/// * The `[tag.*]` table is missing. +/// * The `[tag.*]` table has no valid entries. +pub(crate) fn load_safety_spec( + path: &std::path::Path, + crate_name: &str, + dcx: DiagCtxtHandle<'_>, +) -> Option> { + let raw = match std::fs::read_to_string(path) { + Ok(s) => s, + Err(e) => { + dcx.struct_warn(format!("could not read `--safety-spec` file {}: {e}", path.display())) + .emit(); + return None; + } + }; + let value: toml::Value = match toml::from_str(&raw) { + Ok(v) => v, + Err(e) => { + dcx.struct_warn(format!( + "could not parse `--safety-spec` file {}: {e}", + path.display() + )) + .emit(); + return None; + } + }; + let pkg_name = value + .get("package") + .and_then(|v| v.as_table()) + .and_then(|t| t.get("name")) + .and_then(|v| v.as_str())?; + if pkg_name != crate_name { + debug!( + "safety-spec `package.name` ({pkg_name}) does not match documented crate ({crate_name}); skipping" + ); + return None; + } + let Some(tag_root) = value.get("tag").and_then(|v| v.as_table()) else { + dcx.struct_warn(format!("`--safety-spec` {}: missing `[tag.*]` tables", path.display())) + .emit(); + return None; + }; + let mut tags = FxHashMap::default(); + for (tag_name, tag_v) in tag_root { + let Some(t) = tag_v.as_table() else { continue }; + let args = t + .get("args") + .and_then(|v| v.as_array()) + .map(|arr| { + arr.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect::>() + }) + .unwrap_or_default(); + let Some(desc) = t.get("desc").and_then(|v| v.as_str()) else { continue }; + tags.insert(tag_name.clone(), TagDef { args, desc: desc.to_string() }); + } + if tags.is_empty() { + dcx.struct_warn(format!("`--safety-spec` {}: no valid `[tag.*]` entries", path.display())) + .emit(); + return None; + } + Some(Arc::new(SafetySpec { tags })) +} + +fn requires_sym() -> Symbol { + // Common enough that repeated `intern` is fine (Symbol interner dedupes). + Symbol::intern("requires") +} + +/// The type of a tag inside `#[safety::requires(...)]`: either from TOML config or customized tags with inline text. +#[derive(Debug, Clone)] +enum SafetyTagType { + /// `Tag(a, b)` — description comes from TOML `[tag.Tag]` `desc` with arguments filled in. + FromConfig { args: Vec }, + /// `Tag = "…"` — customized tag with inline text as the full description. + Inline { text: String }, +} + +/// Parse a `#[safety::requires(...)]` attribute. +/// +/// Returns `None` if the attribute is not a `#[safety::requires(...)]` attribute. +/// +/// Otherwise, this will parse the attribute, returning a vector of `(tag name, tag type)` pairs. +fn parse_safety_requires(attr: &hir::Attribute) -> Option> { + use rustc_ast::attr::AttributeExt; + + // Check if the attribute is a `#[safety::requires(...)]` attribute. + if !AttributeExt::path_matches(attr, &[sym::safety, requires_sym()]) { + return None; + } + let list = AttributeExt::meta_item_list(attr)?; + let mut out = Vec::new(); + // Parse the arguments to the tag. + for inner in list { + match inner { + ast::MetaItemInner::MetaItem(mi) => { + let tag_name = path_to_string(&mi.path); + match &mi.kind { + ast::MetaItemKind::List(values) => { + let args = values.iter().map(|i| meta_list_item_to_string(i)).collect(); + out.push((tag_name, SafetyTagType::FromConfig { args })); + } + ast::MetaItemKind::Word => { + out.push((tag_name, SafetyTagType::FromConfig { args: vec![] })); + } + ast::MetaItemKind::NameValue(lit) => { + let Some(sym) = lit.value_str() else { + continue; + }; + out.push((tag_name, SafetyTagType::Inline { text: sym.to_string() })); + } + } + } + ast::MetaItemInner::Lit(_) => return None, + } + } + Some(out) +} + +/// Collect all safety properties from the given attributes. +fn collect_safety_properties(attrs: &Attributes) -> Vec<(String, SafetyTagType)> { + let mut props = Vec::new(); + for a in attrs.other_attrs.iter() { + if let Some(p) = parse_safety_requires(a) { + props.extend(p); + } + } + props +} + +/// Renders the description template with the given values. +/// +/// # Arguments +/// +/// * `def` - The tag definition. +/// * `values` - The values to substitute into the template. +/// +/// # Returns +/// +/// The rendered description. +/// +/// # Examples +/// +/// ```text +/// let def = TagDef { args: vec!["p", "T", "len"], desc: "pointer `{p}` must be valid for reading the `sizeof({T})* {len}` memory from it" }; +/// let values = vec!["dst", "i32", "1"]; +/// let rendered = render_desc(&def, &values); +/// assert_eq!(rendered, "pointer `dst` must be valid for reading the `sizeof(i32)* 1` memory from it"); +/// ``` +fn render_desc(def: &TagDef, values: &[String]) -> String { + let mut rendered = def.desc.clone(); + for (i, arg_name) in def.args.iter().enumerate() { + if let Some(value) = values.get(i) { + rendered = rendered.replace(&format!("{{{arg_name}}}"), value); + } + } + rendered +} + +/// Replace all underscores (`_`) with spaces and capitalize the first letter. +fn format_customized_tag(tag: &str) -> String { + let s = tag.replace('_', " "); + let mut chars = s.chars(); + match chars.next() { + None => String::new(), + Some(first) => first.to_uppercase().collect::() + chars.as_str(), + } +} + +/// Build the inject markdown for the given safety properties. +/// +/// Returns the inject markdown. +fn build_inject_markdown(spec: &SafetySpec, props: &[(String, SafetyTagType)]) -> String { + let mut lines = Vec::new(); + for (tag, clause) in props { + match clause { + SafetyTagType::Inline { text } => { + if spec.tags.contains_key(tag) { + lines.push(format!("* {tag}: {text}")); + } else { + let tag = format_customized_tag(tag); + lines.push(format!("* {tag}: {text}")); + } + } + SafetyTagType::FromConfig { args } => { + let Some(def) = spec.tags.get(tag) else { + continue; + }; + let desc = render_desc(def, args); + lines.push(format!("* {tag}: {desc}")); + } + } + } + lines.join("\n") +} + +/// Inject the safety markdown into the given document. +fn inject_safety_markdown(doc: &str, inject: &str) -> String { + if inject.is_empty() { + return doc.to_string(); + } + let doc = doc.trim_end(); + if doc.is_empty() { + return format!("# Safety\n\n{inject}\n"); + } + // Find the `# Safety` and `# Examples` headers. + let safety_header = Regex::new(r"(?m)^#\s+Safety\s*$").unwrap(); + let examples_header = Regex::new(r"(?m)^#\s+Examples\s*$").unwrap(); + // If the `# Safety` header is found, insert the inject markdown after it. + if let Some(m) = safety_header.find(doc) { + let mut pos = m.end(); + if doc.as_bytes().get(pos) == Some(&b'\r') { + pos += 1; + } + if doc.as_bytes().get(pos) == Some(&b'\n') { + pos += 1; + } + // Blank line after the list so following paragraphs are not merged into the list + // (CommonMark list continuation rules). + format!("{}\n{inject}\n\n{}", &doc[..pos], &doc[pos..]) + } else if let Some(m) = examples_header.find(doc) { + // If the `# Examples` header is found, insert the inject markdown before it. + let insert = format!("# Safety\n\n{inject}\n\n"); + format!("{}{}{}", &doc[..m.start()], insert, &doc[m.start()..]) + } else { + // If no `# Safety` or `# Examples` header is found, insert the inject markdown at the end of the document. + format!("{doc}\n\n# Safety\n\n{inject}\n") + } +} + +/// Replaces the item doc with the given new doc. +fn replace_item_doc(attrs: &mut Attributes, new_doc: String, def_id: Option) { + let span = attrs.doc_strings.first().map(|f| f.span).unwrap_or(DUMMY_SP); + let item_id = def_id; + attrs.doc_strings = vec![DocFragment { + span, + item_id, + doc: Symbol::intern(&new_doc), + kind: DocFragmentKind::Raw(DUMMY_SP), + indent: 0, + from_expansion: attrs.doc_strings.first().map(|f| f.from_expansion).unwrap_or(false), + }]; +} + +/// Checks if the given item kind is a target kind for safety documentation injection. +fn target_kind(kind: &ItemKind) -> bool { + matches!( + kind, + ItemKind::FunctionItem(_) + | ItemKind::MethodItem(..) + | ItemKind::StructItem(_) + | ItemKind::EnumItem(_) + | ItemKind::UnionItem(_) + | ItemKind::TypeAliasItem(_) + | ItemKind::TraitItem(_) + ) +} + +/// The safety injector. +/// +/// This is the main struct for injecting safety documentation into the item docs. +struct SafetyInjector { + spec: Arc, +} + +impl SafetyInjector { + /// Injects the safety documentation into the given item. + fn inject(&self, item: &mut Item) { + if item.is_doc_hidden() { + return; + } + let kind = match &item.kind { + ItemKind::StrippedItem(b) => b.as_ref(), + k => k, + }; + if !target_kind(kind) { + return; + } + let props = collect_safety_properties(&item.attrs); + if props.is_empty() { + return; + } + let inject = build_inject_markdown(&self.spec, &props); + if inject.is_empty() { + return; + } + let doc = item.opt_doc_value().unwrap_or_default(); + let new_doc = inject_safety_markdown(&doc, &inject); + let def_id = item.def_id(); + replace_item_doc(&mut item.inner.attrs, new_doc, def_id); + } +} + +impl DocFolder for SafetyInjector { + fn fold_item(&mut self, item: Item) -> Option { + let mut item = self.fold_item_recur(item); + self.inject(&mut item); + Some(item) + } +} + +/// Injects the safety documentation into the given crate. +pub(crate) fn inject_safety_docs(krate: Crate, cx: &mut DocContext<'_>) -> Crate { + let Some(spec) = cx.safety_spec.clone() else { + return krate; + }; + let mut inj = SafetyInjector { spec }; + inj.fold_crate(krate) +} diff --git a/src/librustdoc/passes/mod.rs b/src/librustdoc/passes/mod.rs index 18e1afaf8a242..d996af39a4288 100644 --- a/src/librustdoc/passes/mod.rs +++ b/src/librustdoc/passes/mod.rs @@ -38,6 +38,9 @@ pub(crate) use self::collect_trait_impls::COLLECT_TRAIT_IMPLS; mod calculate_doc_coverage; pub(crate) use self::calculate_doc_coverage::CALCULATE_DOC_COVERAGE; +pub(crate) mod inject_safety_docs; +pub(crate) use self::inject_safety_docs::INJECT_SAFETY_DOCS; + mod lint; pub(crate) use self::lint::RUN_LINTS; @@ -79,6 +82,7 @@ pub(crate) const PASSES: &[Pass] = &[ STRIP_PRIVATE, STRIP_PRIV_IMPORTS, PROPAGATE_STABILITY, + INJECT_SAFETY_DOCS, COLLECT_INTRA_DOC_LINKS, COLLECT_TRAIT_IMPLS, CALCULATE_DOC_COVERAGE, @@ -94,6 +98,7 @@ pub(crate) const DEFAULT_PASSES: &[ConditionalPass] = &[ ConditionalPass::new(STRIP_HIDDEN, WhenNotDocumentHidden), ConditionalPass::new(STRIP_PRIVATE, WhenNotDocumentPrivate), ConditionalPass::new(STRIP_PRIV_IMPORTS, WhenDocumentPrivate), + ConditionalPass::always(INJECT_SAFETY_DOCS), ConditionalPass::always(COLLECT_INTRA_DOC_LINKS), ConditionalPass::always(PROPAGATE_STABILITY), ConditionalPass::always(RUN_LINTS), diff --git a/tests/run-make/rustdoc-default-output/output-default.stdout b/tests/run-make/rustdoc-default-output/output-default.stdout index bc3a67a1ebcdc..65f1d3b17dbf5 100644 --- a/tests/run-make/rustdoc-default-output/output-default.stdout +++ b/tests/run-make/rustdoc-default-output/output-default.stdout @@ -162,6 +162,9 @@ Options: Remap source names in compiler messages --show-type-layout Include the memory layout of types in the docs + --safety-spec PATH + The path of toml file for expanding safety tags into + docs --no-capture Don't capture stdout and stderr of tests --generate-link-to-definition Make the identifiers in the HTML source code pages diff --git a/tests/rustdoc-ui/issues/issue-91713.stdout b/tests/rustdoc-ui/issues/issue-91713.stdout index e7b8a1dccf802..8129aa67dad27 100644 --- a/tests/rustdoc-ui/issues/issue-91713.stdout +++ b/tests/rustdoc-ui/issues/issue-91713.stdout @@ -6,6 +6,7 @@ strip-aliased-non-local - strips all non-local private aliased items from the ou strip-private - strips all private items from a crate which cannot be seen externally, implies strip-priv-imports strip-priv-imports - strips all private import statements (`use`, `extern crate`) from a crate propagate-stability - propagates stability to child items + inject-safety-docs - injects `#[safety::requires]` text from a TOML spec into the item docs collect-intra-doc-links - resolves intra-doc links collect-trait-impls - retrieves trait impls for items in the crate calculate-doc-coverage - counts the number of items with and without documentation @@ -19,6 +20,7 @@ strip-aliased-non-local strip-hidden (when not --document-hidden-items) strip-private (when not --document-private-items) strip-priv-imports (when --document-private-items) + inject-safety-docs collect-intra-doc-links propagate-stability run-lints