about summary refs log tree commit diff
path: root/src/librustdoc/html
diff options
context:
space:
mode:
authorGuillaume Gomez <guillaume.gomez@huawei.com>2021-11-26 21:20:24 +0100
committerGuillaume Gomez <guillaume.gomez@huawei.com>2022-06-20 17:00:48 +0200
commit3f12fa7fda96de6687cdd281affcee4a61c35b80 (patch)
treebab9532c53d1f3111f87aedf23669d62636a0e6f /src/librustdoc/html
parenta5c039cdb7431ddf3653c582b98ab6eb9af0701b (diff)
downloadrust-3f12fa7fda96de6687cdd281affcee4a61c35b80.tar.gz
rust-3f12fa7fda96de6687cdd281affcee4a61c35b80.zip
Add support for macro in "jump to def" feature
Diffstat (limited to 'src/librustdoc/html')
-rw-r--r--src/librustdoc/html/format.rs1
-rw-r--r--src/librustdoc/html/highlight.rs126
-rw-r--r--src/librustdoc/html/render/span_map.rs71
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 @@ impl clean::GenericArgs {
 }
 
 // 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 @@ impl Class {
             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 @@ impl Class {
     /// a "span" (a tuple representing `(lo, hi)` equivalent of `Span`).
     fn get_span(self) -> Option<Span> {
         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 @@ impl<'a> Classifier<'a> {
             },
             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 @@ impl<'a> Classifier<'a> {
 
 /// 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, "<span class=\"{}\">", klass.as_html());
+fn enter_span(
+    out: &mut Buffer,
+    klass: Class,
+    context_info: &Option<ContextInfo<'_, '_, '_>>,
+) -> &'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("</span>");
+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<T: Display>(
     klass: Option<Class>,
     context_info: &Option<ContextInfo<'_, '_, '_>>,
 ) {
+    if let Some(closing_tag) = string_without_closing_tag(out, text, klass, context_info) {
+        out.write_str(closing_tag);
+    }
+}
+
+fn string_without_closing_tag<T: Display>(
+    out: &mut Buffer,
+    text: T,
+    klass: Option<Class>,
+    context_info: &Option<ContextInfo<'_, '_, '_>>,
+) -> 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, "<span class=\"{}\">{}</span>", klass.as_html(), text);
-        return;
+        write!(out, "<span class=\"{}\">{}", klass.as_html(), text);
+        return Some("</span>");
     };
+
     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<T: Display>(
                         .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<T: Display>(
                 }
             })
         {
-            write!(out, "<a class=\"{}\" href=\"{}\">{}</a>", klass.as_html(), href, text_s);
-            return;
+            write!(out, "<a class=\"{}\" href=\"{}\">{}", klass.as_html(), href, text_s);
+            return Some("</a>");
         }
     }
-    write!(out, "<span class=\"{}\">{}</span>", klass.as_html(), text_s);
+    write!(out, "<span class=\"{}\">{}", klass.as_html(), text_s);
+    Some("</span>")
+}
+
+/// 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::intravisit::{self, Visitor};
 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<Span>) {
+    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 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
     }
 
     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 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'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);
     }
 }