Files
rust/src/librustdoc/core.rs
T
Simonas Kazlauskas 4c8d00a3ec rustdoc: generate implementors for all auto traits
Previously we would only generate a list of synthetic implementations
for two well known traits – Send and Sync. With this patch all the auto
traits known to rustc are considered. This includes such traits like
Unpin and user’s own traits.

Sadly the implementation still iterates through the list of crate items
and checks them against the traits, which for non-std crates containing
their own auto-traits will still not include types defined in std/core.

It is an improvement nontheless.
2019-06-20 17:36:43 +03:00

475 lines
17 KiB
Rust

use rustc_lint;
use rustc::session::{self, config};
use rustc::hir::def_id::{DefId, DefIndex, CrateNum, LOCAL_CRATE};
use rustc::hir::HirId;
use rustc::middle::cstore::CrateStore;
use rustc::middle::privacy::AccessLevels;
use rustc::ty::{Ty, TyCtxt};
use rustc::lint::{self, LintPass};
use rustc::session::config::ErrorOutputType;
use rustc::session::DiagnosticOutput;
use rustc::util::nodemap::{FxHashMap, FxHashSet};
use rustc_interface::interface;
use rustc_driver::abort_on_err;
use rustc_resolve as resolve;
use rustc_metadata::cstore::CStore;
use rustc_target::spec::TargetTriple;
use syntax::source_map;
use syntax::feature_gate::UnstableFeatures;
use syntax::json::JsonEmitter;
use syntax::symbol::sym;
use errors;
use errors::emitter::{Emitter, EmitterWriter};
use parking_lot::ReentrantMutex;
use std::cell::RefCell;
use std::mem;
use rustc_data_structures::sync::{self, Lrc};
use std::sync::Arc;
use std::rc::Rc;
use crate::visit_ast::RustdocVisitor;
use crate::config::{Options as RustdocOptions, RenderOptions};
use crate::clean;
use crate::clean::{Clean, MAX_DEF_ID, AttributesExt};
use crate::html::render::RenderInfo;
use crate::passes;
pub use rustc::session::config::{Input, Options, CodegenOptions};
pub use rustc::session::search_paths::SearchPath;
pub type ExternalPaths = FxHashMap<DefId, (Vec<String>, clean::TypeKind)>;
pub struct DocContext<'tcx> {
pub tcx: TyCtxt<'tcx>,
pub resolver: Rc<Option<RefCell<interface::BoxedResolver>>>,
/// The stack of module NodeIds up till this point
pub crate_name: Option<String>,
pub cstore: Lrc<CStore>,
/// Later on moved into `html::render::CACHE_KEY`
pub renderinfo: RefCell<RenderInfo>,
/// Later on moved through `clean::Crate` into `html::render::CACHE_KEY`
pub external_traits: Arc<ReentrantMutex<RefCell<FxHashMap<DefId, clean::Trait>>>>,
/// Used while populating `external_traits` to ensure we don't process the same trait twice at
/// the same time.
pub active_extern_traits: RefCell<Vec<DefId>>,
// The current set of type and lifetime substitutions,
// for expanding type aliases at the HIR level:
/// Table `DefId` of type parameter -> substituted type
pub ty_substs: RefCell<FxHashMap<DefId, clean::Type>>,
/// Table `DefId` of lifetime parameter -> substituted lifetime
pub lt_substs: RefCell<FxHashMap<DefId, clean::Lifetime>>,
/// Table `DefId` of const parameter -> substituted const
pub ct_substs: RefCell<FxHashMap<DefId, clean::Constant>>,
/// Table DefId of `impl Trait` in argument position -> bounds
pub impl_trait_bounds: RefCell<FxHashMap<DefId, Vec<clean::GenericBound>>>,
pub fake_def_ids: RefCell<FxHashMap<CrateNum, DefId>>,
pub all_fake_def_ids: RefCell<FxHashSet<DefId>>,
/// Auto-trait or blanket impls processed so far, as `(self_ty, trait_def_id)`.
// FIXME(eddyb) make this a `ty::TraitRef<'tcx>` set.
pub generated_synthetics: RefCell<FxHashSet<(Ty<'tcx>, DefId)>>,
pub all_traits: Vec<DefId>,
pub auto_traits: Vec<DefId>,
}
impl<'tcx> DocContext<'tcx> {
pub fn sess(&self) -> &session::Session {
&self.tcx.sess
}
pub fn enter_resolver<F, R>(&self, f: F) -> R
where F: FnOnce(&mut resolve::Resolver<'_>) -> R {
let resolver = &*self.resolver;
let resolver = resolver.as_ref().unwrap();
resolver.borrow_mut().access(f)
}
/// Call the closure with the given parameters set as
/// the substitutions for a type alias' RHS.
pub fn enter_alias<F, R>(&self,
ty_substs: FxHashMap<DefId, clean::Type>,
lt_substs: FxHashMap<DefId, clean::Lifetime>,
ct_substs: FxHashMap<DefId, clean::Constant>,
f: F) -> R
where F: FnOnce() -> R {
let (old_tys, old_lts, old_cts) = (
mem::replace(&mut *self.ty_substs.borrow_mut(), ty_substs),
mem::replace(&mut *self.lt_substs.borrow_mut(), lt_substs),
mem::replace(&mut *self.ct_substs.borrow_mut(), ct_substs),
);
let r = f();
*self.ty_substs.borrow_mut() = old_tys;
*self.lt_substs.borrow_mut() = old_lts;
*self.ct_substs.borrow_mut() = old_cts;
r
}
// This is an ugly hack, but it's the simplest way to handle synthetic impls without greatly
// refactoring either librustdoc or librustc. In particular, allowing new DefIds to be
// registered after the AST is constructed would require storing the defid mapping in a
// RefCell, decreasing the performance for normal compilation for very little gain.
//
// Instead, we construct 'fake' def ids, which start immediately after the last DefId.
// In the Debug impl for clean::Item, we explicitly check for fake
// def ids, as we'll end up with a panic if we use the DefId Debug impl for fake DefIds
pub fn next_def_id(&self, crate_num: CrateNum) -> DefId {
let start_def_id = {
let next_id = if crate_num == LOCAL_CRATE {
self.tcx
.hir()
.definitions()
.def_path_table()
.next_id()
} else {
self.cstore
.def_path_table(crate_num)
.next_id()
};
DefId {
krate: crate_num,
index: next_id,
}
};
let mut fake_ids = self.fake_def_ids.borrow_mut();
let def_id = fake_ids.entry(crate_num).or_insert(start_def_id).clone();
fake_ids.insert(
crate_num,
DefId {
krate: crate_num,
index: DefIndex::from(def_id.index.index() + 1),
},
);
MAX_DEF_ID.with(|m| {
m.borrow_mut()
.entry(def_id.krate.clone())
.or_insert(start_def_id);
});
self.all_fake_def_ids.borrow_mut().insert(def_id);
def_id.clone()
}
/// Like the function of the same name on the HIR map, but skips calling it on fake DefIds.
/// (This avoids a slice-index-out-of-bounds panic.)
pub fn as_local_hir_id(&self, def_id: DefId) -> Option<HirId> {
if self.all_fake_def_ids.borrow().contains(&def_id) {
None
} else {
self.tcx.hir().as_local_hir_id(def_id)
}
}
}
pub trait DocAccessLevels {
fn is_doc_reachable(&self, did: DefId) -> bool;
}
impl DocAccessLevels for AccessLevels<DefId> {
fn is_doc_reachable(&self, did: DefId) -> bool {
self.is_public(did)
}
}
/// Creates a new diagnostic `Handler` that can be used to emit warnings and errors.
///
/// If the given `error_format` is `ErrorOutputType::Json` and no `SourceMap` is given, a new one
/// will be created for the handler.
pub fn new_handler(error_format: ErrorOutputType,
source_map: Option<Lrc<source_map::SourceMap>>,
treat_err_as_bug: Option<usize>,
ui_testing: bool,
) -> errors::Handler {
// rustdoc doesn't override (or allow to override) anything from this that is relevant here, so
// stick to the defaults
let sessopts = Options::default();
let emitter: Box<dyn Emitter + sync::Send> = match error_format {
ErrorOutputType::HumanReadable(kind) => {
let (short, color_config) = kind.unzip();
Box::new(
EmitterWriter::stderr(
color_config,
source_map.map(|cm| cm as _),
short,
sessopts.debugging_opts.teach,
).ui_testing(ui_testing)
)
},
ErrorOutputType::Json { pretty, json_rendered } => {
let source_map = source_map.unwrap_or_else(
|| Lrc::new(source_map::SourceMap::new(sessopts.file_path_mapping())));
Box::new(
JsonEmitter::stderr(
None,
source_map,
pretty,
json_rendered,
).ui_testing(ui_testing)
)
},
};
errors::Handler::with_emitter_and_flags(
emitter,
errors::HandlerFlags {
can_emit_warnings: true,
treat_err_as_bug,
report_delayed_bugs: false,
external_macro_backtrace: false,
..Default::default()
},
)
}
pub fn run_core(options: RustdocOptions) -> (clean::Crate, RenderInfo, RenderOptions, Vec<String>) {
// Parse, resolve, and typecheck the given crate.
let RustdocOptions {
input,
crate_name,
error_format,
libs,
externs,
cfgs,
codegen_options,
debugging_options,
target,
edition,
maybe_sysroot,
lint_opts,
describe_lints,
lint_cap,
mut default_passes,
mut manual_passes,
display_warnings,
render_options,
..
} = options;
let cpath = Some(input.clone());
let input = Input::File(input);
let intra_link_resolution_failure_name = lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE.name;
let warnings_lint_name = lint::builtin::WARNINGS.name;
let missing_docs = rustc_lint::builtin::MISSING_DOCS.name;
let missing_doc_example = rustc_lint::builtin::MISSING_DOC_CODE_EXAMPLES.name;
let private_doc_tests = rustc_lint::builtin::PRIVATE_DOC_TESTS.name;
// In addition to those specific lints, we also need to whitelist those given through
// command line, otherwise they'll get ignored and we don't want that.
let mut whitelisted_lints = vec![warnings_lint_name.to_owned(),
intra_link_resolution_failure_name.to_owned(),
missing_docs.to_owned(),
missing_doc_example.to_owned(),
private_doc_tests.to_owned()];
whitelisted_lints.extend(lint_opts.iter().map(|(lint, _)| lint).cloned());
let lints = || {
lint::builtin::HardwiredLints
.get_lints()
.into_iter()
.chain(rustc_lint::SoftLints.get_lints().into_iter())
};
let lint_opts = lints().filter_map(|lint| {
if lint.name == warnings_lint_name ||
lint.name == intra_link_resolution_failure_name {
None
} else {
Some((lint.name_lower(), lint::Allow))
}
}).chain(lint_opts.into_iter()).collect::<Vec<_>>();
let lint_caps = lints().filter_map(|lint| {
// We don't want to whitelist *all* lints so let's
// ignore those ones.
if whitelisted_lints.iter().any(|l| &lint.name == l) {
None
} else {
Some((lint::LintId::of(lint), lint::Allow))
}
}).collect();
let host_triple = TargetTriple::from_triple(config::host_triple());
// plays with error output here!
let sessopts = config::Options {
maybe_sysroot,
search_paths: libs,
crate_types: vec![config::CrateType::Rlib],
lint_opts: if !display_warnings {
lint_opts
} else {
vec![]
},
lint_cap: Some(lint_cap.unwrap_or_else(|| lint::Forbid)),
cg: codegen_options,
externs,
target_triple: target.unwrap_or(host_triple),
// Ensure that rustdoc works even if rustc is feature-staged
unstable_features: UnstableFeatures::Allow,
actually_rustdoc: true,
debugging_opts: debugging_options,
error_format,
edition,
describe_lints,
..Options::default()
};
let config = interface::Config {
opts: sessopts,
crate_cfg: config::parse_cfgspecs(cfgs),
input,
input_path: cpath,
output_file: None,
output_dir: None,
file_loader: None,
diagnostic_output: DiagnosticOutput::Default,
stderr: None,
crate_name: crate_name.clone(),
lint_caps,
};
interface::run_compiler_in_existing_thread_pool(config, |compiler| {
let sess = compiler.session();
// We need to hold on to the complete resolver, so we cause everything to be
// cloned for the analysis passes to use. Suboptimal, but necessary in the
// current architecture.
let resolver = abort_on_err(compiler.expansion(), sess).peek().1.clone();
if sess.err_count() > 0 {
sess.fatal("Compilation failed, aborting rustdoc");
}
let mut global_ctxt = abort_on_err(compiler.global_ctxt(), sess).take();
global_ctxt.enter(|tcx| {
tcx.analysis(LOCAL_CRATE).ok();
// Abort if there were any errors so far
sess.abort_if_errors();
let access_levels = tcx.privacy_access_levels(LOCAL_CRATE);
// Convert from a HirId set to a DefId set since we don't always have easy access
// to the map from defid -> hirid
let access_levels = AccessLevels {
map: access_levels.map.iter()
.map(|(&k, &v)| (tcx.hir().local_def_id_from_hir_id(k), v))
.collect()
};
let mut renderinfo = RenderInfo::default();
renderinfo.access_levels = access_levels;
let all_traits = tcx.all_traits(LOCAL_CRATE).to_vec();
let ctxt = DocContext {
tcx,
resolver,
crate_name,
cstore: compiler.cstore().clone(),
external_traits: Default::default(),
active_extern_traits: Default::default(),
renderinfo: RefCell::new(renderinfo),
ty_substs: Default::default(),
lt_substs: Default::default(),
ct_substs: Default::default(),
impl_trait_bounds: Default::default(),
fake_def_ids: Default::default(),
all_fake_def_ids: Default::default(),
generated_synthetics: Default::default(),
auto_traits: all_traits.iter().cloned().filter(|trait_def_id| {
tcx.trait_is_auto(*trait_def_id)
}).collect(),
all_traits,
};
debug!("crate: {:?}", tcx.hir().krate());
let mut krate = {
let mut v = RustdocVisitor::new(&ctxt);
v.visit(tcx.hir().krate());
v.clean(&ctxt)
};
fn report_deprecated_attr(name: &str, diag: &errors::Handler) {
let mut msg = diag.struct_warn(&format!("the `#![doc({})]` attribute is \
considered deprecated", name));
msg.warn("please see https://github.com/rust-lang/rust/issues/44136");
if name == "no_default_passes" {
msg.help("you may want to use `#![doc(document_private_items)]`");
}
msg.emit();
}
// Process all of the crate attributes, extracting plugin metadata along
// with the passes which we are supposed to run.
for attr in krate.module.as_ref().unwrap().attrs.lists(sym::doc) {
let diag = ctxt.sess().diagnostic();
let name = attr.name_or_empty();
if attr.is_word() {
if name == sym::no_default_passes {
report_deprecated_attr("no_default_passes", diag);
if default_passes == passes::DefaultPassOption::Default {
default_passes = passes::DefaultPassOption::None;
}
}
} else if let Some(value) = attr.value_str() {
let sink = match name {
sym::passes => {
report_deprecated_attr("passes = \"...\"", diag);
&mut manual_passes
},
sym::plugins => {
report_deprecated_attr("plugins = \"...\"", diag);
eprintln!("WARNING: #![doc(plugins = \"...\")] no longer functions; \
see CVE-2018-1000622");
continue
},
_ => continue,
};
for p in value.as_str().split_whitespace() {
sink.push(p.to_string());
}
}
if attr.is_word() && name == sym::document_private_items {
if default_passes == passes::DefaultPassOption::Default {
default_passes = passes::DefaultPassOption::Private;
}
}
}
let mut passes: Vec<String> =
passes::defaults(default_passes).iter().map(|p| p.to_string()).collect();
passes.extend(manual_passes);
info!("Executing passes");
for pass_name in &passes {
match passes::find_pass(pass_name).map(|p| p.pass) {
Some(pass) => {
debug!("running pass {}", pass_name);
krate = pass(krate, &ctxt);
}
None => error!("unknown pass {}, skipping", *pass_name),
}
}
ctxt.sess().abort_if_errors();
(krate, ctxt.renderinfo.into_inner(), render_options, passes)
})
})
}