| use std::path::{Path, PathBuf}; |
| |
| use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; |
| use rustc_hir as hir; |
| use rustc_hir::def::{DefKind, Res}; |
| use rustc_hir::def_id::{DefId, LOCAL_CRATE}; |
| use rustc_hir::intravisit::{self, Visitor, VisitorExt}; |
| use rustc_hir::{ExprKind, HirId, Item, ItemKind, Mod, Node, QPath}; |
| use rustc_middle::hir::nested_filter; |
| use rustc_middle::ty::{self, TyCtxt}; |
| use rustc_span::{BytePos, ExpnKind}; |
| |
| use crate::clean::{self, PrimitiveType, rustc_span}; |
| use crate::html::sources; |
| |
| /// This is a stripped down version of [`rustc_span::Span`] that only contains the start and end byte positions of the span. |
| /// |
| /// Profiling showed that the `Span` interner was taking up a lot of the run-time when highlighting, and since we |
| /// never actually use the context and parent that are stored in a normal `Span`, we can replace its usages with this |
| /// one, which is much cheaper to construct. |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
| pub(crate) struct Span { |
| lo: BytePos, |
| hi: BytePos, |
| } |
| |
| impl From<rustc_span::Span> for Span { |
| fn from(value: rustc_span::Span) -> Self { |
| Self { lo: value.lo(), hi: value.hi() } |
| } |
| } |
| |
| impl Span { |
| pub(crate) fn lo(self) -> BytePos { |
| self.lo |
| } |
| |
| pub(crate) fn hi(self) -> BytePos { |
| self.hi |
| } |
| |
| pub(crate) fn with_lo(self, lo: BytePos) -> Self { |
| Self { lo, hi: self.hi() } |
| } |
| |
| pub(crate) fn with_hi(self, hi: BytePos) -> Self { |
| Self { lo: self.lo(), hi } |
| } |
| } |
| |
| pub(crate) const DUMMY_SP: Span = Span { lo: BytePos(0), hi: BytePos(0) }; |
| |
| /// 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)] |
| pub(crate) enum LinkFromSrc { |
| Local(clean::Span), |
| External(DefId), |
| Primitive(PrimitiveType), |
| Doc(DefId), |
| } |
| |
| /// This function will do at most two things: |
| /// |
| /// 1. Generate a `span` correspondence map which links an item `span` to its definition `span`. |
| /// 2. Collect the source code files. |
| /// |
| /// It returns the source code files and the `span` correspondence map. |
| /// |
| /// Note about the `span` correspondence 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 keeping a whole `Span`, we |
| /// only keep the `lo` and `hi`. |
| pub(crate) fn collect_spans_and_sources( |
| tcx: TyCtxt<'_>, |
| krate: &clean::Crate, |
| src_root: &Path, |
| include_sources: bool, |
| generate_link_to_definition: bool, |
| ) -> (FxIndexMap<PathBuf, String>, FxHashMap<Span, LinkFromSrc>) { |
| if include_sources { |
| let mut visitor = |
| SpanMapVisitor { tcx, maybe_typeck_results: None, matches: FxHashMap::default() }; |
| |
| 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> { |
| pub(crate) tcx: TyCtxt<'tcx>, |
| pub(crate) maybe_typeck_results: Option<LazyTypeckResults<'tcx>>, |
| pub(crate) matches: FxHashMap<Span, LinkFromSrc>, |
| } |
| |
| impl<'tcx> SpanMapVisitor<'tcx> { |
| /// Returns the typeck results of the current body if we're in one. |
| /// |
| /// This will typeck the body if it hasn't been already. Since rustdoc intentionally doesn't run |
| /// all semantic analysis passes on function bodies at the time of writing, this can lead to us |
| /// "suddenly" rejecting the user's code under `--generate-link-to-definition` while accepting |
| /// it if that flag isn't passed! So use this method sparingly and think about the consequences |
| /// including performance! |
| /// |
| /// This behavior is documented in the rustdoc book. Ideally, it wouldn't be that way but no |
| /// good solution has been found so far. Don't think about adding some sort of flag to rustc to |
| /// suppress diagnostic emission that would be unsound wrt. `ErrorGuaranteed`[^1] and generally |
| /// be quite hacky! |
| /// |
| /// [^1]: Historical context: |
| /// <https://github.com/rust-lang/rust/issues/69426#issuecomment-1019412352>. |
| fn maybe_typeck_results(&mut self) -> Option<&'tcx ty::TypeckResults<'tcx>> { |
| let results = self.maybe_typeck_results.as_mut()?; |
| let results = results.cache.get_or_insert_with(|| self.tcx.typeck_body(results.body_id)); |
| Some(results) |
| } |
| |
| fn link_for_def(&self, def_id: DefId) -> LinkFromSrc { |
| if def_id.is_local() { |
| LinkFromSrc::Local(rustc_span(def_id, self.tcx)) |
| } else { |
| LinkFromSrc::External(def_id) |
| } |
| } |
| |
| /// This function is where we handle `hir::Path` elements and add them into the "span map". |
| fn handle_path(&mut self, path: &hir::Path<'_>, only_use_last_segment: bool) { |
| match path.res { |
| // FIXME: Properly support type parameters. Note they resolve just fine. The issue is |
| // that our highlighter would then also linkify their *definition site* for some reason |
| // linking them to themselves. Const parameters don't exhibit this issue. |
| Res::Def(DefKind::TyParam, _) => {} |
| Res::Def(_, def_id) => { |
| // The segments can be empty for `use *;` in a non-crate-root scope in Rust 2015. |
| let span = path.segments.last().map_or(path.span, |seg| seg.ident.span); |
| // In case the path ends with generics, we remove them from the span. |
| let span = if only_use_last_segment { |
| span |
| } else { |
| // In `use` statements, the included item is not in the path segments. However, |
| // it doesn't matter because you can't have generics on `use` statements. |
| if path.span.contains(span) { path.span.with_hi(span.hi()) } else { path.span } |
| }; |
| self.matches.insert(span.into(), self.link_for_def(def_id)); |
| } |
| Res::Local(_) if let Some(span) = self.tcx.hir_res_span(path.res) => { |
| let path_span = if only_use_last_segment { |
| path.segments.last().unwrap().ident.span |
| } else { |
| path.span |
| }; |
| self.matches.insert(path_span.into(), LinkFromSrc::Local(clean::Span::new(span))); |
| } |
| Res::PrimTy(p) => { |
| // FIXME: Doesn't handle "path-like" primitives like arrays or tuples. |
| self.matches |
| .insert(path.span.into(), LinkFromSrc::Primitive(PrimitiveType::from(p))); |
| } |
| _ => {} |
| } |
| } |
| |
| /// Used to generate links on items' definition to go to their documentation page. |
| pub(crate) fn extract_info_from_hir_id(&mut self, hir_id: HirId) { |
| if let Node::Item(item) = self.tcx.hir_node(hir_id) |
| && let Some(span) = self.tcx.def_ident_span(item.owner_id) |
| { |
| let cspan = clean::Span::new(span); |
| // If the span isn't from the current crate, we ignore it. |
| if cspan.inner().is_dummy() || cspan.cnum(self.tcx.sess) != LOCAL_CRATE { |
| return; |
| } |
| self.matches.insert(span.into(), LinkFromSrc::Doc(item.owner_id.to_def_id())); |
| } |
| } |
| |
| /// Adds the macro call into the span map. Returns `true` if the `span` was inside a macro |
| /// expansion, whether or not it was added to the span map. |
| /// |
| /// The idea for the macro support is to check if the current `Span` comes from expansion. If |
| /// so, we loop until we find the macro definition by using `outer_expn_data` in a loop. |
| /// Finally, we get the information about the macro itself (`span` if "local", `DefId` |
| /// otherwise) and store it inside the span map. |
| fn handle_macro(&mut self, span: rustc_span::Span) -> bool { |
| if !span.from_expansion() { |
| return false; |
| } |
| // So if the `span` comes from a macro expansion, we need to get the original |
| // macro's `DefId`. |
| let mut data = span.ctxt().outer_expn_data(); |
| let mut call_site = data.call_site; |
| // Macros can expand to code containing macros, which will in turn be expanded, etc. |
| // So the idea here is to "go up" until we're back to code that was generated from |
| // macro expansion so that we can get the `DefId` of the original macro that was at the |
| // origin of this expansion. |
| while call_site.from_expansion() { |
| data = call_site.ctxt().outer_expn_data(); |
| call_site = data.call_site; |
| } |
| |
| let macro_name = match data.kind { |
| ExpnKind::Macro(_, macro_name) => macro_name, |
| // Even though we don't handle this kind of macro, this `data` still comes from |
| // expansion so we return `true` so we don't go any deeper in this code. |
| _ => return true, |
| }; |
| let link_from_src = match data.macro_def_id { |
| Some(macro_def_id) => { |
| if macro_def_id.is_local() { |
| LinkFromSrc::Local(clean::Span::new(data.def_site)) |
| } else { |
| LinkFromSrc::External(macro_def_id) |
| } |
| } |
| None => return true, |
| }; |
| let new_span = data.call_site; |
| let macro_name = macro_name.as_str(); |
| // The "call_site" includes the whole macro with its "arguments". We only want |
| // the macro name. |
| let new_span = new_span.with_hi(new_span.lo() + BytePos(macro_name.len() as u32)); |
| self.matches.insert(new_span.into(), link_from_src); |
| true |
| } |
| } |
| |
| impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { |
| type NestedFilter = nested_filter::All; |
| |
| fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { |
| self.tcx |
| } |
| |
| fn visit_nested_body(&mut self, body_id: hir::BodyId) -> Self::Result { |
| let maybe_typeck_results = |
| self.maybe_typeck_results.replace(LazyTypeckResults { body_id, cache: None }); |
| self.visit_body(self.tcx.hir_body(body_id)); |
| self.maybe_typeck_results = maybe_typeck_results; |
| } |
| |
| fn visit_anon_const(&mut self, ct: &'tcx hir::AnonConst) { |
| // FIXME: Typeck'ing anon consts leads to ICEs in rustc if the parent body wasn't typeck'ed |
| // yet. See #156418. Figure out what the best and proper solution for this is. Until |
| // then, let's prevent `typeck` from being called on anon consts by not setting |
| // `maybe_typeck_results` to `Some(_)`. |
| let maybe_typeck_results = self.maybe_typeck_results.take(); |
| self.visit_body(self.tcx.hir_body(ct.body)); |
| self.maybe_typeck_results = maybe_typeck_results; |
| } |
| |
| fn visit_path(&mut self, path: &hir::Path<'tcx>, _id: HirId) { |
| if self.handle_macro(path.span) { |
| return; |
| } |
| self.handle_path(path, false); |
| intravisit::walk_path(self, path); |
| } |
| |
| fn visit_qpath(&mut self, qpath: &QPath<'tcx>, id: HirId, _span: rustc_span::Span) { |
| match *qpath { |
| QPath::TypeRelative(qself, segment) => { |
| // FIXME: This doesn't work for paths in *types* since HIR ty lowering currently |
| // doesn't write back the resolution of type-relative paths. Updating it to |
| // do so should be a simple fix. |
| // FIXME: This obviously doesn't support item signatures / non-bodies. Sadly, rustc |
| // currently doesn't keep around that information & thus can't provide an API |
| // for it. |
| // `ItemCtxt`s would need a place to write back the resolution of type- |
| // dependent definitions. Ideally there was some sort of query keyed on the |
| // `LocalDefId` of the owning item that returns some table with which we can |
| // map the `HirId` to a `DefId`. |
| // Of course, we could re-HIR-ty-lower such paths *here* if we were to extend |
| // the public API of HIR analysis. However, I strongly advise against it as |
| // it would be too much of a hack. |
| if let Some(typeck_results) = self.maybe_typeck_results() { |
| let path = hir::Path { |
| // We change the span to not include parens. |
| span: segment.ident.span, |
| res: typeck_results.qpath_res(qpath, id), |
| segments: std::slice::from_ref(segment), |
| }; |
| self.handle_path(&path, false); |
| } |
| |
| rustc_ast::visit::try_visit!(self.visit_ty_unambig(qself)); |
| self.visit_path_segment(segment); |
| } |
| QPath::Resolved(maybe_qself, path) => { |
| self.handle_path(path, true); |
| |
| rustc_ast::visit::visit_opt!(self, visit_ty_unambig, maybe_qself); |
| if !self.handle_macro(path.span) { |
| intravisit::walk_path(self, path); |
| } |
| } |
| } |
| } |
| |
| fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: rustc_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.spans.inner_span) { |
| // 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 Node::Item(item) = self.tcx.hir_node(id) { |
| let (ident, _) = item.expect_mod(); |
| self.matches.insert( |
| ident.span.into(), |
| LinkFromSrc::Local(clean::Span::new(m.spans.inner_span)), |
| ); |
| } |
| } else { |
| // If it's a "mod foo {}", we want to look to its documentation page. |
| self.extract_info_from_hir_id(id); |
| } |
| intravisit::walk_mod(self, m); |
| } |
| |
| fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { |
| match expr.kind { |
| ExprKind::MethodCall(segment, ..) => { |
| if let Some(typeck_results) = self.maybe_typeck_results() |
| && let Some(def_id) = typeck_results.type_dependent_def_id(expr.hir_id) |
| { |
| self.matches.insert(segment.ident.span.into(), self.link_for_def(def_id)); |
| } |
| } |
| // We don't want to go deeper into the macro. |
| _ if self.handle_macro(expr.span) => return, |
| _ => {} |
| } |
| intravisit::walk_expr(self, expr); |
| } |
| |
| fn visit_item(&mut self, item: &'tcx Item<'tcx>) { |
| // We're no longer in a body since we've crossed an item boundary. |
| // Temporarily take away the typeck results which are only valid in bodies. |
| let maybe_typeck_results = self.maybe_typeck_results.take(); |
| |
| match item.kind { |
| ItemKind::Static(..) |
| | ItemKind::Const(..) |
| | ItemKind::Fn { .. } |
| | ItemKind::Macro(..) |
| | ItemKind::TyAlias(..) |
| | ItemKind::Enum(..) |
| | ItemKind::Struct(..) |
| | ItemKind::Union(..) |
| | ItemKind::Trait { .. } |
| | ItemKind::TraitAlias(..) => self.extract_info_from_hir_id(item.hir_id()), |
| ItemKind::Impl(_) |
| | ItemKind::Use(..) |
| | ItemKind::ExternCrate(..) |
| | ItemKind::ForeignMod { .. } |
| | ItemKind::GlobalAsm { .. } |
| // We already have "visit_mod" above so no need to check it here. |
| | ItemKind::Mod(..) => {} |
| } |
| |
| intravisit::walk_item(self, item); |
| |
| self.maybe_typeck_results = maybe_typeck_results; |
| } |
| } |
| |
| /// Lazily computed & cached [`ty::TypeckResults`]. |
| struct LazyTypeckResults<'tcx> { |
| body_id: hir::BodyId, |
| cache: Option<&'tcx ty::TypeckResults<'tcx>>, |
| } |