From ef282ff86b7cb244b027761768f8a0fb33940812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Thu, 23 Apr 2026 10:57:57 +0200 Subject: [PATCH] rustdoc: Reify emission types --- src/librustdoc/config.rs | 108 +++++++++++------- src/librustdoc/formats/renderer.rs | 20 ++-- src/librustdoc/html/render/context.rs | 13 +-- src/librustdoc/html/render/write_shared.rs | 6 +- src/librustdoc/json/mod.rs | 9 +- src/librustdoc/lib.rs | 4 +- ...output-format-doctest-emit.dep-info.stderr | 2 + ...rmat-doctest-emit.html-static-files.stderr | 2 + .../rustdoc-ui/output-format-doctest-emit.rs | 8 ++ .../output-format-json-emit-html.rs | 9 +- 10 files changed, 109 insertions(+), 72 deletions(-) create mode 100644 tests/rustdoc-ui/output-format-doctest-emit.dep-info.stderr create mode 100644 tests/rustdoc-ui/output-format-doctest-emit.html-static-files.stderr create mode 100644 tests/rustdoc-ui/output-format-doctest-emit.rs diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index d726c612acf6..3cd34f24c6e3 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -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, + 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), } +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 { } 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> { - 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::>(); - 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::() 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 => {} } } diff --git a/src/librustdoc/formats/renderer.rs b/src/librustdoc/formats/renderer.rs index 305c8c39ba7f..5c458232f8f9 100644 --- a/src/librustdoc/formats/renderer.rs +++ b/src/librustdoc/formats/renderer.rs @@ -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()) } diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 4a7b1d1d6c56..6c02eecbd06e 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -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 { diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index 8de899ea0eef..26894a64c6c1 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -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::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); diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs index 7c8e7b7669cd..e33d2d44cffd 100644 --- a/src/librustdoc/json/mod.rs +++ b/src/librustdoc/json/mod.rs @@ -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 { diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 87de4244b5c8..d51405d35254 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -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 { diff --git a/tests/rustdoc-ui/output-format-doctest-emit.dep-info.stderr b/tests/rustdoc-ui/output-format-doctest-emit.dep-info.stderr new file mode 100644 index 000000000000..e09774d9a03b --- /dev/null +++ b/tests/rustdoc-ui/output-format-doctest-emit.dep-info.stderr @@ -0,0 +1,2 @@ +error: the `--emit` flag is not supported with `--output-format=doctest` + diff --git a/tests/rustdoc-ui/output-format-doctest-emit.html-static-files.stderr b/tests/rustdoc-ui/output-format-doctest-emit.html-static-files.stderr new file mode 100644 index 000000000000..e09774d9a03b --- /dev/null +++ b/tests/rustdoc-ui/output-format-doctest-emit.html-static-files.stderr @@ -0,0 +1,2 @@ +error: the `--emit` flag is not supported with `--output-format=doctest` + diff --git a/tests/rustdoc-ui/output-format-doctest-emit.rs b/tests/rustdoc-ui/output-format-doctest-emit.rs new file mode 100644 index 000000000000..b590d9057bb6 --- /dev/null +++ b/tests/rustdoc-ui/output-format-doctest-emit.rs @@ -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` diff --git a/tests/rustdoc-ui/output-format-json-emit-html.rs b/tests/rustdoc-ui/output-format-json-emit-html.rs index 7a99cbd91ba6..7ce3bf756ec9 100644 --- a/tests/rustdoc-ui/output-format-json-emit-html.rs +++ b/tests/rustdoc-ui/output-format-json-emit-html.rs @@ -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;