blob: 54d9b6905c53d56f6829f9380f73590335937370 [file] [log] [blame]
use crate::clean::{self, PrimitiveType};
use crate::html::sources;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
use rustc_hir::{ExprKind, GenericParam, GenericParamKind, HirId, Mod, Node};
use rustc_middle::ty::TyCtxt;
use rustc_span::Span;
use std::path::{Path, PathBuf};
/// This enum allows us to store two different kinds of information:
///
/// In case the `span` definition comes from the same crate, we can simply get the `span` and use
/// it as is.
///
/// Otherwise, we store the definition `DefId` and will generate a link to the documentation page
/// instead of the source code directly.
#[derive(Debug)]
crate enum LinkFromSrc {
Local(clean::Span),
External(DefId),
Primitive(PrimitiveType),
}
/// This function will do at most two things:
///
/// 1. Generate a `span` correspondance map which links an item `span` to its definition `span`.
/// 2. Collect the source code files.
///
/// It returns the `krate`, the source code files and the `span` correspondance map.
///
/// Note about the `span` correspondance map: the keys are actually `(lo, hi)` of `span`s. We don't
/// need the `span` context later on, only their position, so instead of keep a whole `Span`, we
/// only keep the `lo` and `hi`.
crate fn collect_spans_and_sources(
tcx: TyCtxt<'_>,
krate: &clean::Crate,
src_root: &Path,
include_sources: bool,
generate_link_to_definition: bool,
) -> (FxHashMap<PathBuf, String>, FxHashMap<Span, LinkFromSrc>) {
let mut visitor = SpanMapVisitor { tcx, matches: FxHashMap::default() };
if include_sources {
if generate_link_to_definition {
tcx.hir().walk_toplevel_module(&mut visitor);
}
let sources = sources::collect_local_sources(tcx, src_root, &krate);
(sources, visitor.matches)
} else {
(Default::default(), Default::default())
}
}
struct SpanMapVisitor<'tcx> {
crate tcx: TyCtxt<'tcx>,
crate matches: FxHashMap<Span, LinkFromSrc>,
}
impl<'tcx> SpanMapVisitor<'tcx> {
/// This function is where we handle `hir::Path` elements and add them into the "span map".
fn handle_path(&mut self, path: &rustc_hir::Path<'_>, path_span: Option<Span>) {
let info = match path.res {
// FIXME: For now, we only handle `DefKind` if it's not `DefKind::TyParam` or
// `DefKind::Macro`. Would be nice to support them too alongside the other `DefKind`
// (such as primitive types!).
Res::Def(kind, def_id) if kind != DefKind::TyParam => {
if matches!(kind, DefKind::Macro(_)) {
return;
}
Some(def_id)
}
Res::Local(_) => None,
Res::PrimTy(p) => {
// FIXME: Doesn't handle "path-like" primitives like arrays or tuples.
let span = path_span.unwrap_or(path.span);
self.matches.insert(span, LinkFromSrc::Primitive(PrimitiveType::from(p)));
return;
}
Res::Err => return,
_ => return,
};
if let Some(span) = self.tcx.hir().res_span(path.res) {
self.matches
.insert(path_span.unwrap_or(path.span), LinkFromSrc::Local(clean::Span::new(span)));
} else if let Some(def_id) = info {
self.matches.insert(path_span.unwrap_or(path.span), LinkFromSrc::External(def_id));
}
}
}
impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
type Map = rustc_middle::hir::map::Map<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::All(self.tcx.hir())
}
fn visit_generic_param(&mut self, p: &'tcx GenericParam<'tcx>) {
if !matches!(p.kind, GenericParamKind::Type { .. }) {
return;
}
for bound in p.bounds {
if let Some(trait_ref) = bound.trait_ref() {
self.handle_path(trait_ref.path, None);
}
}
}
fn visit_path(&mut self, path: &'tcx rustc_hir::Path<'tcx>, _id: HirId) {
self.handle_path(path, None);
intravisit::walk_path(self, path);
}
fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: Span, id: HirId) {
// To make the difference between "mod foo {}" and "mod foo;". In case we "import" another
// file, we want to link to it. Otherwise no need to create a link.
if !span.overlaps(m.inner) {
// Now that we confirmed it's a file import, we want to get the span for the module
// name only and not all the "mod foo;".
if let Some(Node::Item(item)) = self.tcx.hir().find(id) {
self.matches.insert(item.ident.span, LinkFromSrc::Local(clean::Span::new(m.inner)));
}
}
intravisit::walk_mod(self, m, id);
}
fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'tcx>) {
if let ExprKind::MethodCall(segment, method_span, _, _) = expr.kind {
if let Some(hir_id) = segment.hir_id {
let hir = self.tcx.hir();
let body_id = hir.enclosing_body_owner(hir_id);
let typeck_results = self.tcx.sess.with_disabled_diagnostic(|| {
self.tcx.typeck_body(
hir.maybe_body_owned_by(body_id).expect("a body which isn't a body"),
)
});
if let Some(def_id) = typeck_results.type_dependent_def_id(expr.hir_id) {
self.matches.insert(
method_span,
match hir.span_if_local(def_id) {
Some(span) => LinkFromSrc::Local(clean::Span::new(span)),
None => LinkFromSrc::External(def_id),
},
);
}
}
}
intravisit::walk_expr(self, expr);
}
fn visit_use(&mut self, path: &'tcx rustc_hir::Path<'tcx>, id: HirId) {
self.handle_path(path, None);
intravisit::walk_use(self, path, id);
}
}