rustdoc: Reify emission types

This commit is contained in:
León Orell Valerian Liehr
2026-04-23 10:57:57 +02:00
parent cb40c25f6a
commit ef282ff86b
10 changed files with 109 additions and 72 deletions
+68 -40
View File
@@ -18,6 +18,7 @@
use rustc_span::edition::Edition;
use rustc_span::{FileName, RemapPathScopeComponents};
use rustc_target::spec::TargetTuple;
use smallvec::SmallVec;
use crate::core::new_dcx;
use crate::externalfiles::ExternalHtml;
@@ -293,7 +294,7 @@ pub(crate) struct RenderOptions {
/// Note: this field is duplicated in `Options` because it's useful to have
/// it in both places.
pub(crate) unstable_features: rustc_feature::UnstableFeatures,
pub(crate) emit: Vec<EmitType>,
pub(crate) emit: SmallVec<[EmitType; 2]>,
/// If `true`, HTML source pages will generate links for items to their definition.
pub(crate) generate_link_to_definition: bool,
/// Set of function-call locations to include as examples
@@ -327,9 +328,22 @@ pub(crate) enum ModuleSorting {
pub(crate) enum EmitType {
HtmlStaticFiles,
HtmlNonStaticFiles,
// not explicitly nameable by the user for now
JsonFiles,
DepInfo(Option<OutFileName>),
}
impl fmt::Display for EmitType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::HtmlStaticFiles => "html-static-files",
Self::HtmlNonStaticFiles => "html-non-static-files",
Self::JsonFiles => "json-files",
Self::DepInfo(_) => "dep-info",
})
}
}
impl FromStr for EmitType {
type Err = ();
@@ -352,17 +366,11 @@ fn from_str(s: &str) -> Result<Self, Self::Err> {
}
impl RenderOptions {
pub(crate) fn should_emit_crate(&self) -> bool {
self.emit.is_empty() || self.emit.contains(&EmitType::HtmlNonStaticFiles)
}
pub(crate) fn dep_info(&self) -> Option<Option<&OutFileName>> {
for emit in &self.emit {
if let EmitType::DepInfo(file) = emit {
return Some(file.as_ref());
}
}
None
self.emit.iter().find_map(|emit| match emit {
EmitType::DepInfo(file) => Some(file.as_ref()),
_ => None,
})
}
}
@@ -469,26 +477,6 @@ fn println_condition(condition: Condition) {
let should_test = matches.opt_present("test");
let mut emit = FxIndexMap::<_, EmitType>::default();
for list in matches.opt_strs("emit") {
if should_test {
dcx.fatal("the `--test` flag and the `--emit` flag are not supported together");
}
for kind in list.split(',') {
match kind.parse() {
Ok(kind) => {
// De-duplicate emit types and the last wins.
// Only one instance for each type is allowed
// regardless the actual data it carries.
// This matches rustc's `--emit` behavior.
emit.insert(std::mem::discriminant(&kind), kind);
}
Err(()) => dcx.fatal(format!("unrecognized emission type: {kind}")),
}
}
}
let emit = emit.into_values().collect::<Vec<_>>();
let show_coverage = matches.opt_present("show-coverage");
let output_format_s = matches.opt_str("output-format");
let output_format = match output_format_s {
@@ -527,15 +515,55 @@ fn println_condition(condition: Condition) {
}
}
if output_format == OutputFormat::Json {
if let Some(emit_flag) = emit.iter().find_map(|emit| match emit {
EmitType::HtmlStaticFiles => Some("html-static-files"),
EmitType::HtmlNonStaticFiles => Some("html-non-static-files"),
EmitType::DepInfo(_) => None,
}) {
dcx.fatal(format!(
"the `--emit={emit_flag}` flag is not supported with `--output-format=json`",
));
let mut emit = FxIndexMap::default();
for list in matches.opt_strs("emit") {
if should_test {
dcx.fatal("the `--test` flag and the `--emit` flag are not supported together");
}
if let OutputFormat::Doctest = output_format {
dcx.fatal("the `--emit` flag is not supported with `--output-format=doctest`");
}
for typ in list.split(',') {
let Ok(typ) = typ.parse::<EmitType>() else {
dcx.fatal(format!("unrecognized emission type: {typ}"))
};
match typ {
EmitType::DepInfo(_) => match output_format {
OutputFormat::Json | OutputFormat::Html => {}
OutputFormat::Doctest => unreachable!(),
},
EmitType::HtmlStaticFiles | EmitType::HtmlNonStaticFiles => match output_format
{
OutputFormat::Html => {}
OutputFormat::Json => dcx.fatal(format!(
"the `--emit={typ}` flag is not supported with `--output-format=json`",
)),
OutputFormat::Doctest => unreachable!(),
},
EmitType::JsonFiles => unreachable!(),
}
// De-duplicate emit types and the last wins.
// Only one instance for each type is allowed
// regardless the actual data it carries.
// This matches rustc's `--emit` behavior.
emit.insert(std::mem::discriminant(&typ), typ);
}
}
let mut emit: SmallVec<[_; 2]> = emit.into_values().collect();
// If `--emit` is absent we'll register default emission types depending on the requested
// output format. We can safely use `is_empty` for this since `--emit=` ("truly empty")
// will have already been rejected above.
if emit.is_empty() {
match output_format {
OutputFormat::Json => emit.push(EmitType::JsonFiles),
OutputFormat::Html => {
emit.push(EmitType::HtmlStaticFiles);
emit.push(EmitType::HtmlNonStaticFiles);
}
OutputFormat::Doctest => {}
}
}
+11 -9
View File
@@ -2,7 +2,7 @@
use rustc_middle::ty::TyCtxt;
use crate::clean;
use crate::config::RenderOptions;
use crate::config::{EmitType, RenderOptions};
use crate::error::Error;
use crate::formats::cache::Cache;
@@ -10,14 +10,16 @@
/// backend renderer has hooks for initialization, documenting an item, entering and exiting a
/// module, and cleanup/finalizing output.
pub(crate) trait FormatRenderer<'tcx>: Sized {
/// Gives a description of the renderer. Used for performance profiling.
fn descr() -> &'static str;
/// A description of the renderer. Used for performance profiling.
const DESCR: &'static str;
/// Whether to call `item` recursively for modules
/// Whether to call `item` recursively for modules.
///
/// This is true for html, and false for json. See #80664
/// See [#80664](https://github.com/rust-lang/rust/issues/80664).
const RUN_ON_MODULE: bool;
const NON_STATIC_FILE_EMIT_TYPE: EmitType;
/// This associated type is the type where the current module information is stored.
///
/// For each module, we go through their items by calling for each item:
@@ -109,18 +111,18 @@ pub(crate) fn run_format<
) -> Result<(), Error> {
let prof = &tcx.sess.prof;
let emit_crate = options.should_emit_crate();
let emit_non_static_files = options.emit.contains(&T::NON_STATIC_FILE_EMIT_TYPE);
let (mut format_renderer, krate) = prof
.verbose_generic_activity_with_arg("create_renderer", T::descr())
.verbose_generic_activity_with_arg("create_renderer", T::DESCR)
.run(|| init(krate, options, cache, tcx))?;
if !emit_crate {
if !emit_non_static_files {
return Ok(());
}
// Render the crate documentation
run_format_inner(&mut format_renderer, &krate.module, prof)?;
prof.verbose_generic_activity_with_arg("renderer_after_krate", T::descr())
prof.verbose_generic_activity_with_arg("renderer_after_krate", T::DESCR)
.run(|| format_renderer.after_krate())
}
+6 -7
View File
@@ -23,7 +23,7 @@
use crate::clean::types::ExternalLocation;
use crate::clean::utils::has_doc_flag;
use crate::clean::{self, ExternalCrate};
use crate::config::{ModuleSorting, RenderOptions, ShouldMerge};
use crate::config::{EmitType, ModuleSorting, RenderOptions, ShouldMerge};
use crate::docfs::{DocFS, PathError};
use crate::error::Error;
use crate::formats::FormatRenderer;
@@ -481,7 +481,6 @@ pub(crate) fn init(
) -> Result<(Self, clean::Crate), Error> {
// need to save a copy of the options for rendering the index page
let md_opts = options.clone();
let emit_crate = options.should_emit_crate();
let RenderOptions {
output,
external_html,
@@ -495,6 +494,7 @@ pub(crate) fn init(
static_root_path,
generate_redirect_map,
show_type_layout,
emit,
generate_link_to_definition,
call_locations,
no_emit_shared,
@@ -605,7 +605,7 @@ pub(crate) fn init(
info: ContextInfo::new(include_sources),
};
if emit_crate {
if emit.contains(&EmitType::HtmlNonStaticFiles) {
sources::render(&mut cx, &krate)?;
}
@@ -619,11 +619,10 @@ pub(crate) fn init(
/// Generates the documentation for `crate` into the directory `dst`
impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
fn descr() -> &'static str {
"html"
}
const DESCR: &'static str = "html";
const RUN_ON_MODULE: bool = true;
const NON_STATIC_FILE_EMIT_TYPE: EmitType = EmitType::HtmlNonStaticFiles;
type ModuleData = ContextInfo;
fn save_module_data(&mut self) -> Self::ModuleData {
+3 -3
View File
@@ -165,7 +165,7 @@ fn write_rendered_cross_crate_info(
resource_suffix: &str,
) -> Result<(), Error> {
let m = &opt.should_merge;
if opt.should_emit_crate() {
if opt.emit.contains(&EmitType::HtmlNonStaticFiles) {
if include_sources {
write_rendered_cci::<SourcesPart, _>(SourcesPart::blank, dst, crates, m)?;
}
@@ -190,7 +190,7 @@ fn write_resources(
css_file_extension: Option<&Path>,
resource_suffix: &str,
) -> Result<(), Error> {
if opt.emit.is_empty() || opt.emit.contains(&EmitType::HtmlNonStaticFiles) {
if opt.emit.contains(&EmitType::HtmlNonStaticFiles) {
// Handle added third-party themes
for entry in style_files {
let theme = entry.basename()?;
@@ -218,7 +218,7 @@ fn write_resources(
}
}
if opt.emit.is_empty() || opt.emit.contains(&EmitType::HtmlStaticFiles) {
if opt.emit.contains(&EmitType::HtmlStaticFiles) {
let static_dir = dst.join("static.files");
try_err!(fs::create_dir_all(&static_dir), &static_dir);
+4 -5
View File
@@ -27,7 +27,7 @@
use crate::clean::ItemKind;
use crate::clean::types::{ExternalCrate, ExternalLocation};
use crate::config::RenderOptions;
use crate::config::{EmitType, RenderOptions};
use crate::docfs::PathError;
use crate::error::Error;
use crate::formats::FormatRenderer;
@@ -132,11 +132,10 @@ pub(crate) fn init(
}
impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
fn descr() -> &'static str {
"json"
}
const DESCR: &'static str = "json";
const RUN_ON_MODULE: bool = false;
const NON_STATIC_FILE_EMIT_TYPE: EmitType = EmitType::JsonFiles;
type ModuleData = ();
fn save_module_data(&mut self) -> Self::ModuleData {
+1 -3
View File
@@ -869,9 +869,7 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) {
};
rustc_interface::create_and_enter_global_ctxt(compiler, krate, |tcx| {
let has_dep_info = render_options.dep_info().is_some();
if render_options.emit.contains(&EmitType::HtmlNonStaticFiles)
|| render_options.emit.is_empty()
{
if render_options.emit.contains(&EmitType::HtmlNonStaticFiles) {
markdown::render_and_write(file, render_options, edition)?;
}
if has_dep_info {
@@ -0,0 +1,2 @@
error: the `--emit` flag is not supported with `--output-format=doctest`
@@ -0,0 +1,2 @@
error: the `--emit` flag is not supported with `--output-format=doctest`
@@ -0,0 +1,8 @@
// Ensure that `--output-format=doctest` is incompatible with the `--emit` flag (for now at least).
//@ revisions: html-static-files dep-info
//@ compile-flags: -Zunstable-options --output-format doctest
//@[html-static-files] compile-flags: --emit html-static-files
//@[dep-info] compile-flags: --emit dep-info
//~? ERROR the `--emit` flag is not supported with `--output-format=doctest`
@@ -1,8 +1,7 @@
//@ revisions: html_static html_non_static
//@ check-fail
//@[html_static] compile-flags: -Z unstable-options --output-format=json --emit=html-static-files
//@[html_non_static] compile-flags: -Z unstable-options --output-format=json --emit=html-non-static-files
//@ compile-flags: -Zunstable-options --output-format json
//@[html_static] compile-flags: --emit html-static-files
//@[html_non_static] compile-flags: --emit html-non-static-files
//[html_static]~? ERROR the `--emit=html-static-files` flag is not supported with `--output-format=json`
//[html_non_static]~? ERROR the `--emit=html-non-static-files` flag is not supported with `--output-format=json`
pub struct Foo;