Skip to content

Commit 3f12fa7

Browse files
Add support for macro in "jump to def" feature
1 parent a5c039c commit 3f12fa7

File tree

3 files changed

+162
-36
lines changed

3 files changed

+162
-36
lines changed

src/librustdoc/html/format.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,7 @@ impl clean::GenericArgs {
519519
}
520520

521521
// Possible errors when computing href link source for a `DefId`
522+
#[derive(PartialEq, Eq)]
522523
pub(crate) enum HrefError {
523524
/// This item is known to rustdoc, but from a crate that does not have documentation generated.
524525
///

src/librustdoc/html/highlight.rs

Lines changed: 107 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@
55
//!
66
//! Use the `render_with_highlighting` to highlight some rust code.
77
8-
use crate::clean::PrimitiveType;
8+
use crate::clean::{ExternalLocation, PrimitiveType};
99
use crate::html::escape::Escape;
1010
use crate::html::render::Context;
1111

1212
use std::collections::VecDeque;
1313
use std::fmt::{Display, Write};
14+
use std::iter::once;
1415

16+
use rustc_ast as ast;
1517
use rustc_data_structures::fx::FxHashMap;
18+
use rustc_hir::def_id::DefId;
1619
use rustc_lexer::{LiteralKind, TokenKind};
20+
use rustc_metadata::creader::{CStore, LoadedMacro};
1721
use rustc_span::edition::Edition;
1822
use rustc_span::symbol::Symbol;
1923
use rustc_span::{BytePos, Span, DUMMY_SP};
@@ -99,6 +103,7 @@ fn write_code(
99103
) {
100104
// This replace allows to fix how the code source with DOS backline characters is displayed.
101105
let src = src.replace("\r\n", "\n");
106+
let mut closing_tag = "";
102107
Classifier::new(
103108
&src,
104109
edition,
@@ -108,8 +113,8 @@ fn write_code(
108113
.highlight(&mut |highlight| {
109114
match highlight {
110115
Highlight::Token { text, class } => string(out, Escape(text), class, &context_info),
111-
Highlight::EnterSpan { class } => enter_span(out, class),
112-
Highlight::ExitSpan => exit_span(out),
116+
Highlight::EnterSpan { class } => closing_tag = enter_span(out, class, &context_info),
117+
Highlight::ExitSpan => exit_span(out, &closing_tag),
113118
};
114119
});
115120
}
@@ -129,7 +134,7 @@ enum Class {
129134
RefKeyWord,
130135
Self_(Span),
131136
Op,
132-
Macro,
137+
Macro(Span),
133138
MacroNonTerminal,
134139
String,
135140
Number,
@@ -153,7 +158,7 @@ impl Class {
153158
Class::RefKeyWord => "kw-2",
154159
Class::Self_(_) => "self",
155160
Class::Op => "op",
156-
Class::Macro => "macro",
161+
Class::Macro(_) => "macro",
157162
Class::MacroNonTerminal => "macro-nonterminal",
158163
Class::String => "string",
159164
Class::Number => "number",
@@ -171,8 +176,22 @@ impl Class {
171176
/// a "span" (a tuple representing `(lo, hi)` equivalent of `Span`).
172177
fn get_span(self) -> Option<Span> {
173178
match self {
174-
Self::Ident(sp) | Self::Self_(sp) => Some(sp),
175-
_ => None,
179+
Self::Ident(sp) | Self::Self_(sp) | Self::Macro(sp) => Some(sp),
180+
Self::Comment
181+
| Self::DocComment
182+
| Self::Attribute
183+
| Self::KeyWord
184+
| Self::RefKeyWord
185+
| Self::Op
186+
| Self::MacroNonTerminal
187+
| Self::String
188+
| Self::Number
189+
| Self::Bool
190+
| Self::Lifetime
191+
| Self::PreludeTy
192+
| Self::PreludeVal
193+
| Self::QuestionMark
194+
| Self::Decoration(_) => None,
176195
}
177196
}
178197
}
@@ -611,7 +630,7 @@ impl<'a> Classifier<'a> {
611630
},
612631
TokenKind::Ident | TokenKind::RawIdent if lookahead == Some(TokenKind::Bang) => {
613632
self.in_macro = true;
614-
sink(Highlight::EnterSpan { class: Class::Macro });
633+
sink(Highlight::EnterSpan { class: Class::Macro(self.new_span(before, text)) });
615634
sink(Highlight::Token { text, class: None });
616635
return;
617636
}
@@ -658,13 +677,18 @@ impl<'a> Classifier<'a> {
658677

659678
/// Called when we start processing a span of text that should be highlighted.
660679
/// The `Class` argument specifies how it should be highlighted.
661-
fn enter_span(out: &mut Buffer, klass: Class) {
662-
write!(out, "<span class=\"{}\">", klass.as_html());
680+
fn enter_span(
681+
out: &mut Buffer,
682+
klass: Class,
683+
context_info: &Option<ContextInfo<'_, '_, '_>>,
684+
) -> &'static str {
685+
string_without_closing_tag(out, "", Some(klass), context_info)
686+
.expect("no closing tag to close wrapper...")
663687
}
664688

665689
/// Called at the end of a span of highlighted text.
666-
fn exit_span(out: &mut Buffer) {
667-
out.write_str("</span>");
690+
fn exit_span(out: &mut Buffer, closing_tag: &str) {
691+
out.write_str(closing_tag);
668692
}
669693

670694
/// Called for a span of text. If the text should be highlighted differently
@@ -689,13 +713,28 @@ fn string<T: Display>(
689713
klass: Option<Class>,
690714
context_info: &Option<ContextInfo<'_, '_, '_>>,
691715
) {
716+
if let Some(closing_tag) = string_without_closing_tag(out, text, klass, context_info) {
717+
out.write_str(closing_tag);
718+
}
719+
}
720+
721+
fn string_without_closing_tag<T: Display>(
722+
out: &mut Buffer,
723+
text: T,
724+
klass: Option<Class>,
725+
context_info: &Option<ContextInfo<'_, '_, '_>>,
726+
) -> Option<&'static str> {
692727
let Some(klass) = klass
693-
else { return write!(out, "{}", text) };
728+
else {
729+
write!(out, "{}", text);
730+
return None;
731+
};
694732
let Some(def_span) = klass.get_span()
695733
else {
696-
write!(out, "<span class=\"{}\">{}</span>", klass.as_html(), text);
697-
return;
734+
write!(out, "<span class=\"{}\">{}", klass.as_html(), text);
735+
return Some("</span>");
698736
};
737+
699738
let mut text_s = text.to_string();
700739
if text_s.contains("::") {
701740
text_s = text_s.split("::").intersperse("::").fold(String::new(), |mut path, t| {
@@ -730,8 +769,17 @@ fn string<T: Display>(
730769
.map(|s| format!("{}{}", context_info.root_path, s)),
731770
LinkFromSrc::External(def_id) => {
732771
format::href_with_root_path(*def_id, context, Some(context_info.root_path))
733-
.ok()
734772
.map(|(url, _, _)| url)
773+
.or_else(|e| {
774+
if e == format::HrefError::NotInExternalCache
775+
&& matches!(klass, Class::Macro(_))
776+
{
777+
Ok(generate_macro_def_id_path(context_info, *def_id))
778+
} else {
779+
Err(e)
780+
}
781+
})
782+
.ok()
735783
}
736784
LinkFromSrc::Primitive(prim) => format::href_with_root_path(
737785
PrimitiveType::primitive_locations(context.tcx())[prim],
@@ -743,11 +791,51 @@ fn string<T: Display>(
743791
}
744792
})
745793
{
746-
write!(out, "<a class=\"{}\" href=\"{}\">{}</a>", klass.as_html(), href, text_s);
747-
return;
794+
write!(out, "<a class=\"{}\" href=\"{}\">{}", klass.as_html(), href, text_s);
795+
return Some("</a>");
748796
}
749797
}
750-
write!(out, "<span class=\"{}\">{}</span>", klass.as_html(), text_s);
798+
write!(out, "<span class=\"{}\">{}", klass.as_html(), text_s);
799+
Some("</span>")
800+
}
801+
802+
/// This function is to get the external macro path because they are not in the cache used n
803+
/// `href_with_root_path`.
804+
fn generate_macro_def_id_path(context_info: &ContextInfo<'_, '_, '_>, def_id: DefId) -> String {
805+
let tcx = context_info.context.shared.tcx;
806+
let crate_name = tcx.crate_name(def_id.krate).to_string();
807+
let cache = &context_info.context.cache();
808+
809+
let relative = tcx.def_path(def_id).data.into_iter().filter_map(|elem| {
810+
// extern blocks have an empty name
811+
let s = elem.data.to_string();
812+
if !s.is_empty() { Some(s) } else { None }
813+
});
814+
// Check to see if it is a macro 2.0 or built-in macro
815+
let mut path = if matches!(
816+
CStore::from_tcx(tcx).load_macro_untracked(def_id, tcx.sess),
817+
LoadedMacro::MacroDef(def, _)
818+
if matches!(&def.kind, ast::ItemKind::MacroDef(ast_def)
819+
if !ast_def.macro_rules)
820+
) {
821+
once(crate_name.clone()).chain(relative).collect()
822+
} else {
823+
vec![crate_name.clone(), relative.last().expect("relative was empty")]
824+
};
825+
826+
let url_parts = match cache.extern_locations[&def_id.krate] {
827+
ExternalLocation::Remote(ref s) => vec![s.trim_end_matches('/')],
828+
ExternalLocation::Local => vec![context_info.root_path.trim_end_matches('/'), &crate_name],
829+
ExternalLocation::Unknown => panic!("unknown crate"),
830+
};
831+
832+
let last = path.pop().unwrap();
833+
let last = format!("macro.{}.html", last);
834+
if path.is_empty() {
835+
format!("{}/{}", url_parts.join("/"), last)
836+
} else {
837+
format!("{}/{}/{}", url_parts.join("/"), path.join("/"), last)
838+
}
751839
}
752840

753841
#[cfg(test)]

src/librustdoc/html/render/span_map.rs

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ use rustc_hir::intravisit::{self, Visitor};
88
use rustc_hir::{ExprKind, HirId, Mod, Node};
99
use rustc_middle::hir::nested_filter;
1010
use rustc_middle::ty::TyCtxt;
11-
use rustc_span::Span;
11+
use rustc_span::hygiene::MacroKind;
12+
use rustc_span::{BytePos, ExpnKind, Span};
1213

1314
use std::path::{Path, PathBuf};
1415

@@ -63,32 +64,59 @@ struct SpanMapVisitor<'tcx> {
6364

6465
impl<'tcx> SpanMapVisitor<'tcx> {
6566
/// This function is where we handle `hir::Path` elements and add them into the "span map".
66-
fn handle_path(&mut self, path: &rustc_hir::Path<'_>, path_span: Option<Span>) {
67+
fn handle_path(&mut self, path: &rustc_hir::Path<'_>) {
6768
let info = match path.res {
68-
// FIXME: For now, we only handle `DefKind` if it's not `DefKind::TyParam` or
69-
// `DefKind::Macro`. Would be nice to support them too alongside the other `DefKind`
69+
// FIXME: For now, we handle `DefKind` if it's not a `DefKind::TyParam`.
70+
// Would be nice to support them too alongside the other `DefKind`
7071
// (such as primitive types!).
71-
Res::Def(kind, def_id) if kind != DefKind::TyParam => {
72-
if matches!(kind, DefKind::Macro(_)) {
73-
return;
74-
}
75-
Some(def_id)
76-
}
72+
Res::Def(kind, def_id) if kind != DefKind::TyParam => Some(def_id),
7773
Res::Local(_) => None,
7874
Res::PrimTy(p) => {
7975
// FIXME: Doesn't handle "path-like" primitives like arrays or tuples.
80-
let span = path_span.unwrap_or(path.span);
81-
self.matches.insert(span, LinkFromSrc::Primitive(PrimitiveType::from(p)));
76+
self.matches.insert(path.span, LinkFromSrc::Primitive(PrimitiveType::from(p)));
8277
return;
8378
}
8479
Res::Err => return,
8580
_ => return,
8681
};
8782
if let Some(span) = self.tcx.hir().res_span(path.res) {
88-
self.matches
89-
.insert(path_span.unwrap_or(path.span), LinkFromSrc::Local(clean::Span::new(span)));
83+
self.matches.insert(path.span, LinkFromSrc::Local(clean::Span::new(span)));
9084
} else if let Some(def_id) = info {
91-
self.matches.insert(path_span.unwrap_or(path.span), LinkFromSrc::External(def_id));
85+
self.matches.insert(path.span, LinkFromSrc::External(def_id));
86+
}
87+
}
88+
89+
/// Adds the macro call into the span map. Returns `true` if the `span` was inside a macro
90+
/// expansion, whether or not it was added to the span map.
91+
fn handle_macro(&mut self, span: Span) -> bool {
92+
if span.from_expansion() {
93+
let mut data = span.ctxt().outer_expn_data();
94+
let mut call_site = data.call_site;
95+
while call_site.from_expansion() {
96+
data = call_site.ctxt().outer_expn_data();
97+
call_site = data.call_site;
98+
}
99+
100+
if let ExpnKind::Macro(MacroKind::Bang, macro_name) = data.kind {
101+
let link_from_src = if let Some(macro_def_id) = data.macro_def_id {
102+
if macro_def_id.is_local() {
103+
LinkFromSrc::Local(clean::Span::new(data.def_site))
104+
} else {
105+
LinkFromSrc::External(macro_def_id)
106+
}
107+
} else {
108+
return true;
109+
};
110+
let new_span = data.call_site;
111+
let macro_name = macro_name.as_str();
112+
// The "call_site" includes the whole macro with its "arguments". We only want
113+
// the macro name.
114+
let new_span = new_span.with_hi(new_span.lo() + BytePos(macro_name.len() as u32));
115+
self.matches.insert(new_span, link_from_src);
116+
}
117+
true
118+
} else {
119+
false
92120
}
93121
}
94122
}
@@ -101,7 +129,10 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
101129
}
102130

103131
fn visit_path(&mut self, path: &'tcx rustc_hir::Path<'tcx>, _id: HirId) {
104-
self.handle_path(path, None);
132+
if self.handle_macro(path.span) {
133+
return;
134+
}
135+
self.handle_path(path);
105136
intravisit::walk_path(self, path);
106137
}
107138

@@ -143,12 +174,18 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
143174
);
144175
}
145176
}
177+
} else if self.handle_macro(expr.span) {
178+
// We don't want to deeper into the macro.
179+
return;
146180
}
147181
intravisit::walk_expr(self, expr);
148182
}
149183

150184
fn visit_use(&mut self, path: &'tcx rustc_hir::Path<'tcx>, id: HirId) {
151-
self.handle_path(path, None);
185+
if self.handle_macro(path.span) {
186+
return;
187+
}
188+
self.handle_path(path);
152189
intravisit::walk_use(self, path, id);
153190
}
154191
}

0 commit comments

Comments
 (0)