From 3f12fa7fda96de6687cdd281affcee4a61c35b80 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 26 Nov 2021 21:20:24 +0100 Subject: [PATCH] Add support for macro in "jump to def" feature --- src/librustdoc/html/format.rs | 1 + src/librustdoc/html/highlight.rs | 126 +++++++++++++++++++++---- src/librustdoc/html/render/span_map.rs | 71 ++++++++++---- 3 files changed, 162 insertions(+), 36 deletions(-) diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 5baa53d5554..29a58810036 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -519,6 +519,7 @@ fn print<'a, 'tcx: 'a>( } // Possible errors when computing href link source for a `DefId` +#[derive(PartialEq, Eq)] pub(crate) enum HrefError { /// This item is known to rustdoc, but from a crate that does not have documentation generated. /// diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index 480728b1797..209172bb98e 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -5,15 +5,19 @@ //! //! Use the `render_with_highlighting` to highlight some rust code. -use crate::clean::PrimitiveType; +use crate::clean::{ExternalLocation, PrimitiveType}; use crate::html::escape::Escape; use crate::html::render::Context; use std::collections::VecDeque; use std::fmt::{Display, Write}; +use std::iter::once; +use rustc_ast as ast; use rustc_data_structures::fx::FxHashMap; +use rustc_hir::def_id::DefId; use rustc_lexer::{LiteralKind, TokenKind}; +use rustc_metadata::creader::{CStore, LoadedMacro}; use rustc_span::edition::Edition; use rustc_span::symbol::Symbol; use rustc_span::{BytePos, Span, DUMMY_SP}; @@ -99,6 +103,7 @@ fn write_code( ) { // This replace allows to fix how the code source with DOS backline characters is displayed. let src = src.replace("\r\n", "\n"); + let mut closing_tag = ""; Classifier::new( &src, edition, @@ -108,8 +113,8 @@ fn write_code( .highlight(&mut |highlight| { match highlight { Highlight::Token { text, class } => string(out, Escape(text), class, &context_info), - Highlight::EnterSpan { class } => enter_span(out, class), - Highlight::ExitSpan => exit_span(out), + Highlight::EnterSpan { class } => closing_tag = enter_span(out, class, &context_info), + Highlight::ExitSpan => exit_span(out, &closing_tag), }; }); } @@ -129,7 +134,7 @@ enum Class { RefKeyWord, Self_(Span), Op, - Macro, + Macro(Span), MacroNonTerminal, String, Number, @@ -153,7 +158,7 @@ fn as_html(self) -> &'static str { Class::RefKeyWord => "kw-2", Class::Self_(_) => "self", Class::Op => "op", - Class::Macro => "macro", + Class::Macro(_) => "macro", Class::MacroNonTerminal => "macro-nonterminal", Class::String => "string", Class::Number => "number", @@ -171,8 +176,22 @@ fn as_html(self) -> &'static str { /// a "span" (a tuple representing `(lo, hi)` equivalent of `Span`). fn get_span(self) -> Option { match self { - Self::Ident(sp) | Self::Self_(sp) => Some(sp), - _ => None, + Self::Ident(sp) | Self::Self_(sp) | Self::Macro(sp) => Some(sp), + Self::Comment + | Self::DocComment + | Self::Attribute + | Self::KeyWord + | Self::RefKeyWord + | Self::Op + | Self::MacroNonTerminal + | Self::String + | Self::Number + | Self::Bool + | Self::Lifetime + | Self::PreludeTy + | Self::PreludeVal + | Self::QuestionMark + | Self::Decoration(_) => None, } } } @@ -611,7 +630,7 @@ fn advance( }, TokenKind::Ident | TokenKind::RawIdent if lookahead == Some(TokenKind::Bang) => { self.in_macro = true; - sink(Highlight::EnterSpan { class: Class::Macro }); + sink(Highlight::EnterSpan { class: Class::Macro(self.new_span(before, text)) }); sink(Highlight::Token { text, class: None }); return; } @@ -658,13 +677,18 @@ fn check_if_is_union_keyword(&mut self) -> bool { /// Called when we start processing a span of text that should be highlighted. /// The `Class` argument specifies how it should be highlighted. -fn enter_span(out: &mut Buffer, klass: Class) { - write!(out, "", klass.as_html()); +fn enter_span( + out: &mut Buffer, + klass: Class, + context_info: &Option>, +) -> &'static str { + string_without_closing_tag(out, "", Some(klass), context_info) + .expect("no closing tag to close wrapper...") } /// Called at the end of a span of highlighted text. -fn exit_span(out: &mut Buffer) { - out.write_str(""); +fn exit_span(out: &mut Buffer, closing_tag: &str) { + out.write_str(closing_tag); } /// Called for a span of text. If the text should be highlighted differently @@ -689,13 +713,28 @@ fn string( klass: Option, context_info: &Option>, ) { + if let Some(closing_tag) = string_without_closing_tag(out, text, klass, context_info) { + out.write_str(closing_tag); + } +} + +fn string_without_closing_tag( + out: &mut Buffer, + text: T, + klass: Option, + context_info: &Option>, +) -> Option<&'static str> { let Some(klass) = klass - else { return write!(out, "{}", text) }; + else { + write!(out, "{}", text); + return None; + }; let Some(def_span) = klass.get_span() else { - write!(out, "{}", klass.as_html(), text); - return; + write!(out, "{}", klass.as_html(), text); + return Some(""); }; + let mut text_s = text.to_string(); if text_s.contains("::") { text_s = text_s.split("::").intersperse("::").fold(String::new(), |mut path, t| { @@ -730,8 +769,17 @@ fn string( .map(|s| format!("{}{}", context_info.root_path, s)), LinkFromSrc::External(def_id) => { format::href_with_root_path(*def_id, context, Some(context_info.root_path)) - .ok() .map(|(url, _, _)| url) + .or_else(|e| { + if e == format::HrefError::NotInExternalCache + && matches!(klass, Class::Macro(_)) + { + Ok(generate_macro_def_id_path(context_info, *def_id)) + } else { + Err(e) + } + }) + .ok() } LinkFromSrc::Primitive(prim) => format::href_with_root_path( PrimitiveType::primitive_locations(context.tcx())[prim], @@ -743,11 +791,51 @@ fn string( } }) { - write!(out, "{}", klass.as_html(), href, text_s); - return; + write!(out, "{}", klass.as_html(), href, text_s); + return Some(""); } } - write!(out, "{}", klass.as_html(), text_s); + write!(out, "{}", klass.as_html(), text_s); + Some("") +} + +/// This function is to get the external macro path because they are not in the cache used n +/// `href_with_root_path`. +fn generate_macro_def_id_path(context_info: &ContextInfo<'_, '_, '_>, def_id: DefId) -> String { + let tcx = context_info.context.shared.tcx; + let crate_name = tcx.crate_name(def_id.krate).to_string(); + let cache = &context_info.context.cache(); + + let relative = tcx.def_path(def_id).data.into_iter().filter_map(|elem| { + // extern blocks have an empty name + let s = elem.data.to_string(); + if !s.is_empty() { Some(s) } else { None } + }); + // Check to see if it is a macro 2.0 or built-in macro + let mut path = if matches!( + CStore::from_tcx(tcx).load_macro_untracked(def_id, tcx.sess), + LoadedMacro::MacroDef(def, _) + if matches!(&def.kind, ast::ItemKind::MacroDef(ast_def) + if !ast_def.macro_rules) + ) { + once(crate_name.clone()).chain(relative).collect() + } else { + vec![crate_name.clone(), relative.last().expect("relative was empty")] + }; + + let url_parts = match cache.extern_locations[&def_id.krate] { + ExternalLocation::Remote(ref s) => vec![s.trim_end_matches('/')], + ExternalLocation::Local => vec![context_info.root_path.trim_end_matches('/'), &crate_name], + ExternalLocation::Unknown => panic!("unknown crate"), + }; + + let last = path.pop().unwrap(); + let last = format!("macro.{}.html", last); + if path.is_empty() { + format!("{}/{}", url_parts.join("/"), last) + } else { + format!("{}/{}/{}", url_parts.join("/"), path.join("/"), last) + } } #[cfg(test)] diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index 86961dc3bf1..0c60278a82d 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -8,7 +8,8 @@ use rustc_hir::{ExprKind, HirId, Mod, Node}; use rustc_middle::hir::nested_filter; use rustc_middle::ty::TyCtxt; -use rustc_span::Span; +use rustc_span::hygiene::MacroKind; +use rustc_span::{BytePos, ExpnKind, Span}; use std::path::{Path, PathBuf}; @@ -63,32 +64,59 @@ struct SpanMapVisitor<'tcx> { 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) { + fn handle_path(&mut self, path: &rustc_hir::Path<'_>) { 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` + // FIXME: For now, we handle `DefKind` if it's not a `DefKind::TyParam`. + // 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::Def(kind, def_id) if kind != DefKind::TyParam => 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))); + self.matches.insert(path.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))); + self.matches.insert(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)); + self.matches.insert(path.span, LinkFromSrc::External(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. + fn handle_macro(&mut self, span: Span) -> bool { + if span.from_expansion() { + let mut data = span.ctxt().outer_expn_data(); + let mut call_site = data.call_site; + while call_site.from_expansion() { + data = call_site.ctxt().outer_expn_data(); + call_site = data.call_site; + } + + if let ExpnKind::Macro(MacroKind::Bang, macro_name) = data.kind { + let link_from_src = if let Some(macro_def_id) = data.macro_def_id { + if macro_def_id.is_local() { + LinkFromSrc::Local(clean::Span::new(data.def_site)) + } else { + LinkFromSrc::External(macro_def_id) + } + } else { + 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, link_from_src); + } + true + } else { + false } } } @@ -101,7 +129,10 @@ fn nested_visit_map(&mut self) -> Self::Map { } fn visit_path(&mut self, path: &'tcx rustc_hir::Path<'tcx>, _id: HirId) { - self.handle_path(path, None); + if self.handle_macro(path.span) { + return; + } + self.handle_path(path); intravisit::walk_path(self, path); } @@ -143,12 +174,18 @@ fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'tcx>) { ); } } + } else if self.handle_macro(expr.span) { + // We don't want to deeper into the macro. + return; } intravisit::walk_expr(self, expr); } fn visit_use(&mut self, path: &'tcx rustc_hir::Path<'tcx>, id: HirId) { - self.handle_path(path, None); + if self.handle_macro(path.span) { + return; + } + self.handle_path(path); intravisit::walk_use(self, path, id); } }