use std::sync::atomic::AtomicBool;
use std::sync::{Arc, LazyLock};
use std::{io, mem};
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
use rustc_data_structures::sync::Lrc;
use rustc_data_structures::unord::UnordSet;
use rustc_errors::codes::*;
use rustc_errors::emitter::{
DynEmitter, HumanEmitter, HumanReadableErrorType, OutputTheme, stderr_destination,
};
use rustc_errors::json::JsonEmitter;
use rustc_errors::{ErrorGuaranteed, TerminalUrl};
use rustc_feature::UnstableFeatures;
use rustc_hir::def::Res;
use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, LocalDefId};
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{HirId, Path};
use rustc_interface::interface;
use rustc_lint::{MissingDoc, late_lint_mod};
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::{self, ParamEnv, Ty, TyCtxt};
use rustc_session::config::{self, CrateType, ErrorOutputType, Input, ResolveDocLinks};
pub(crate) use rustc_session::config::{Options, UnstableOptions};
use rustc_session::{Session, lint};
use rustc_span::source_map;
use rustc_span::symbol::sym;
use tracing::{debug, info};
use crate::clean::inline::build_external_trait;
use crate::clean::{self, ItemId};
use crate::config::{Options as RustdocOptions, OutputFormat, RenderOptions};
use crate::formats::cache::Cache;
use crate::passes;
use crate::passes::Condition::*;
use crate::passes::collect_intra_doc_links::LinkCollector;
pub(crate) struct DocContext<'tcx> {
pub(crate) tcx: TyCtxt<'tcx>,
pub(crate) param_env: ParamEnv<'tcx>,
pub(crate) external_traits: FxIndexMap<DefId, clean::Trait>,
pub(crate) active_extern_traits: DefIdSet,
pub(crate) args: DefIdMap<clean::GenericArg>,
pub(crate) current_type_aliases: DefIdMap<usize>,
pub(crate) impl_trait_bounds: FxHashMap<ImplTraitParam, Vec<clean::GenericBound>>,
pub(crate) generated_synthetics: FxHashSet<(Ty<'tcx>, DefId)>,
pub(crate) auto_traits: Vec<DefId>,
pub(crate) render_options: RenderOptions,
pub(crate) cache: Cache,
pub(crate) inlined: FxHashSet<ItemId>,
pub(crate) output_format: OutputFormat,
pub(crate) show_coverage: bool,
}
impl<'tcx> DocContext<'tcx> {
pub(crate) fn sess(&self) -> &'tcx Session {
self.tcx.sess
}
pub(crate) fn with_param_env<T, F: FnOnce(&mut Self) -> T>(
&mut self,
def_id: DefId,
f: F,
) -> T {
let old_param_env = mem::replace(&mut self.param_env, self.tcx.param_env(def_id));
let ret = f(self);
self.param_env = old_param_env;
ret
}
pub(crate) fn typing_env(&self) -> ty::TypingEnv<'tcx> {
ty::TypingEnv {
typing_mode: ty::TypingMode::non_body_analysis(),
param_env: self.param_env,
}
}
pub(crate) fn enter_alias<F, R>(
&mut self,
args: DefIdMap<clean::GenericArg>,
def_id: DefId,
f: F,
) -> R
where
F: FnOnce(&mut Self) -> R,
{
let old_args = mem::replace(&mut self.args, args);
*self.current_type_aliases.entry(def_id).or_insert(0) += 1;
let r = f(self);
self.args = old_args;
if let Some(count) = self.current_type_aliases.get_mut(&def_id) {
*count -= 1;
if *count == 0 {
self.current_type_aliases.remove(&def_id);
}
}
r
}
pub(crate) fn as_local_hir_id(tcx: TyCtxt<'_>, item_id: ItemId) -> Option<HirId> {
match item_id {
ItemId::DefId(real_id) => {
real_id.as_local().map(|def_id| tcx.local_def_id_to_hir_id(def_id))
}
_ => None,
}
}
pub(crate) fn is_json_output(&self) -> bool {
self.output_format.is_json() && !self.show_coverage
}
}
pub(crate) fn new_dcx(
error_format: ErrorOutputType,
source_map: Option<Lrc<source_map::SourceMap>>,
diagnostic_width: Option<usize>,
unstable_opts: &UnstableOptions,
) -> rustc_errors::DiagCtxt {
let fallback_bundle = rustc_errors::fallback_fluent_bundle(
rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(),
false,
);
let emitter: Box<DynEmitter> = match error_format {
ErrorOutputType::HumanReadable(kind, color_config) => {
let short = kind.short();
Box::new(
HumanEmitter::new(stderr_destination(color_config), fallback_bundle)
.sm(source_map.map(|sm| sm as _))
.short_message(short)
.teach(unstable_opts.teach)
.diagnostic_width(diagnostic_width)
.track_diagnostics(unstable_opts.track_diagnostics)
.theme(if let HumanReadableErrorType::Unicode = kind {
OutputTheme::Unicode
} else {
OutputTheme::Ascii
})
.ui_testing(unstable_opts.ui_testing),
)
}
ErrorOutputType::Json { pretty, json_rendered, color_config } => {
let source_map = source_map.unwrap_or_else(|| {
Lrc::new(source_map::SourceMap::new(source_map::FilePathMapping::empty()))
});
Box::new(
JsonEmitter::new(
Box::new(io::BufWriter::new(io::stderr())),
source_map,
fallback_bundle,
pretty,
json_rendered,
color_config,
)
.ui_testing(unstable_opts.ui_testing)
.diagnostic_width(diagnostic_width)
.track_diagnostics(unstable_opts.track_diagnostics)
.terminal_url(TerminalUrl::No),
)
}
};
rustc_errors::DiagCtxt::new(emitter).with_flags(unstable_opts.dcx_flags(true))
}
pub(crate) fn create_config(
input: Input,
RustdocOptions {
crate_name,
proc_macro_crate,
error_format,
diagnostic_width,
libs,
externs,
mut cfgs,
check_cfgs,
codegen_options,
unstable_opts,
target,
edition,
maybe_sysroot,
lint_opts,
describe_lints,
lint_cap,
scrape_examples_options,
expanded_args,
remap_path_prefix,
..
}: RustdocOptions,
RenderOptions { document_private, .. }: &RenderOptions,
using_internal_features: Arc<AtomicBool>,
) -> rustc_interface::Config {
cfgs.push("doc".to_string());
let mut lints_to_show = vec![
rustc_lint::builtin::MISSING_DOCS.name.to_string(),
rustc_lint::builtin::INVALID_DOC_ATTRIBUTES.name.to_string(),
rustc_lint::builtin::RENAMED_AND_REMOVED_LINTS.name.to_string(),
rustc_lint::builtin::UNKNOWN_LINTS.name.to_string(),
rustc_lint::builtin::UNEXPECTED_CFGS.name.to_string(),
rustc_lint::builtin::UNFULFILLED_LINT_EXPECTATIONS.name.to_string(),
];
lints_to_show.extend(crate::lint::RUSTDOC_LINTS.iter().map(|lint| lint.name.to_string()));
let (lint_opts, lint_caps) = crate::lint::init_lints(lints_to_show, lint_opts, |lint| {
Some((lint.name_lower(), lint::Allow))
});
let crate_types =
if proc_macro_crate { vec![CrateType::ProcMacro] } else { vec![CrateType::Rlib] };
let resolve_doc_links =
if *document_private { ResolveDocLinks::All } else { ResolveDocLinks::Exported };
let test = scrape_examples_options.map(|opts| opts.scrape_tests).unwrap_or(false);
let sessopts = config::Options {
maybe_sysroot,
search_paths: libs,
crate_types,
lint_opts,
lint_cap,
cg: codegen_options,
externs,
target_triple: target,
unstable_features: UnstableFeatures::from_environment(crate_name.as_deref()),
actually_rustdoc: true,
resolve_doc_links,
unstable_opts,
error_format,
diagnostic_width,
edition,
describe_lints,
crate_name,
test,
remap_path_prefix,
..Options::default()
};
interface::Config {
opts: sessopts,
crate_cfg: cfgs,
crate_check_cfg: check_cfgs,
input,
output_file: None,
output_dir: None,
file_loader: None,
locale_resources: rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(),
lint_caps,
psess_created: None,
hash_untracked_state: None,
register_lints: Some(Box::new(crate::lint::register_lints)),
override_queries: Some(|_sess, providers| {
providers.lint_mod = |tcx, module_def_id| late_lint_mod(tcx, module_def_id, MissingDoc);
providers.used_trait_imports = |_, _| {
static EMPTY_SET: LazyLock<UnordSet<LocalDefId>> = LazyLock::new(UnordSet::default);
&EMPTY_SET
};
providers.typeck = move |tcx, def_id| {
let typeck_root_def_id = tcx.typeck_root_def_id(def_id.to_def_id()).expect_local();
if typeck_root_def_id != def_id {
return tcx.typeck(typeck_root_def_id);
}
let hir = tcx.hir();
let body = hir.body_owned_by(def_id);
debug!("visiting body for {def_id:?}");
EmitIgnoredResolutionErrors::new(tcx).visit_body(body);
(rustc_interface::DEFAULT_QUERY_PROVIDERS.typeck)(tcx, def_id)
};
}),
make_codegen_backend: None,
registry: rustc_driver::diagnostics_registry(),
ice_file: None,
using_internal_features,
expanded_args,
}
}
pub(crate) fn run_global_ctxt(
tcx: TyCtxt<'_>,
show_coverage: bool,
render_options: RenderOptions,
output_format: OutputFormat,
) -> Result<(clean::Crate, RenderOptions, Cache), ErrorGuaranteed> {
let _ = tcx.sess.time("wf_checking", || {
tcx.hir().try_par_for_each_module(|module| tcx.ensure().check_mod_type_wf(module))
});
if let Some(guar) = tcx.dcx().has_errors() {
return Err(guar);
}
tcx.sess.time("missing_docs", || rustc_lint::check_crate(tcx));
tcx.sess.time("check_mod_attrs", || {
tcx.hir().for_each_module(|module| tcx.ensure().check_mod_attrs(module))
});
rustc_passes::stability::check_unused_or_stable_features(tcx);
let auto_traits =
tcx.all_traits().filter(|&trait_def_id| tcx.trait_is_auto(trait_def_id)).collect();
let mut ctxt = DocContext {
tcx,
param_env: ParamEnv::empty(),
external_traits: Default::default(),
active_extern_traits: Default::default(),
args: Default::default(),
current_type_aliases: Default::default(),
impl_trait_bounds: Default::default(),
generated_synthetics: Default::default(),
auto_traits,
cache: Cache::new(render_options.document_private, render_options.document_hidden),
inlined: FxHashSet::default(),
output_format,
render_options,
show_coverage,
};
for cnum in tcx.crates(()) {
crate::visit_lib::lib_embargo_visit_item(&mut ctxt, cnum.as_def_id());
}
if let Some(sized_trait_did) = ctxt.tcx.lang_items().sized_trait() {
let sized_trait = build_external_trait(&mut ctxt, sized_trait_did);
ctxt.external_traits.insert(sized_trait_did, sized_trait);
}
debug!("crate: {:?}", tcx.hir().krate());
let mut krate = tcx.sess.time("clean_crate", || clean::krate(&mut ctxt));
if krate.module.doc_value().is_empty() {
let help = format!(
"The following guide may be of use:\n\
{}/rustdoc/how-to-write-documentation.html",
crate::DOC_RUST_LANG_ORG_CHANNEL
);
tcx.node_lint(
crate::lint::MISSING_CRATE_LEVEL_DOCS,
DocContext::as_local_hir_id(tcx, krate.module.item_id).unwrap(),
|lint| {
lint.primary_message("no documentation found for this crate's top-level module");
lint.help(help);
},
);
}
for attr in krate.module.attrs.lists(sym::doc) {
let name = attr.name_or_empty();
if attr.is_word() && name == sym::document_private_items {
ctxt.render_options.document_private = true;
}
}
info!("Executing passes");
let mut visited = FxHashMap::default();
let mut ambiguous = FxIndexMap::default();
for p in passes::defaults(show_coverage) {
let run = match p.condition {
Always => true,
WhenDocumentPrivate => ctxt.render_options.document_private,
WhenNotDocumentPrivate => !ctxt.render_options.document_private,
WhenNotDocumentHidden => !ctxt.render_options.document_hidden,
};
if run {
debug!("running pass {}", p.pass.name);
if let Some(run_fn) = p.pass.run {
krate = tcx.sess.time(p.pass.name, || run_fn(krate, &mut ctxt));
} else {
let (k, LinkCollector { visited_links, ambiguous_links, .. }) =
passes::collect_intra_doc_links::collect_intra_doc_links(krate, &mut ctxt);
krate = k;
visited = visited_links;
ambiguous = ambiguous_links;
}
}
}
tcx.sess.time("check_lint_expectations", || tcx.check_expectations(Some(sym::rustdoc)));
krate = tcx.sess.time("create_format_cache", || Cache::populate(&mut ctxt, krate));
let mut collector =
LinkCollector { cx: &mut ctxt, visited_links: visited, ambiguous_links: ambiguous };
collector.resolve_ambiguities();
if let Some(guar) = tcx.dcx().has_errors() {
return Err(guar);
}
Ok((krate, ctxt.render_options, ctxt.cache))
}
struct EmitIgnoredResolutionErrors<'tcx> {
tcx: TyCtxt<'tcx>,
}
impl<'tcx> EmitIgnoredResolutionErrors<'tcx> {
fn new(tcx: TyCtxt<'tcx>) -> Self {
Self { tcx }
}
}
impl<'tcx> Visitor<'tcx> for EmitIgnoredResolutionErrors<'tcx> {
type NestedFilter = nested_filter::OnlyBodies;
fn nested_visit_map(&mut self) -> Self::Map {
self.tcx.hir()
}
fn visit_path(&mut self, path: &Path<'tcx>, _id: HirId) {
debug!("visiting path {path:?}");
if path.res == Res::Err {
let label = format!(
"could not resolve path `{}`",
path.segments
.iter()
.map(|segment| segment.ident.as_str())
.intersperse("::")
.collect::<String>()
);
rustc_errors::struct_span_code_err!(
self.tcx.dcx(),
path.span,
E0433,
"failed to resolve: {label}",
)
.with_span_label(path.span, label)
.with_note("this error was originally ignored because you are running `rustdoc`")
.with_note("try running again with `rustc` or `cargo check` and you may get a more detailed error")
.emit();
}
intravisit::walk_path(self, path);
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum ImplTraitParam {
DefId(DefId),
ParamIndex(u32),
}
impl From<DefId> for ImplTraitParam {
fn from(did: DefId) -> Self {
ImplTraitParam::DefId(did)
}
}
impl From<u32> for ImplTraitParam {
fn from(idx: u32) -> Self {
ImplTraitParam::ParamIndex(idx)
}
}