Skip to content

Commit 97f4d7b

Browse files
authored
Rollup merge of #91264 - GuillaumeGomez:macro-jump-to-def, r=jsha
Add macro support in jump to definition feature Fixes #91174. To do so, I check if the span comes from an expansion, and if so, I infer the original macro `DefId` or `Span` depending if it's a defined in the current crate or not. There is one limitation due to macro expansion though: ```rust macro_rules! yolo { () => {}} fn foo() { yolo!(); } ``` In `foo`, `yolo!` won't be linked because after expansion, it is replaced by nothing (which seems logical). So I can't get an item from the `Visitor` from which I could tell if its `Span` comes from an expansion. I added a test for this specific limitation alongside others. Demo: https://rustdoc.crud.net/imperio/macro-jump-to-def/src/foo/check-source-code-urls-to-def-std.rs.html As for the empty macro issue that cannot create a jump to definition, you can see it [here](https://rustdoc.crud.net/imperio/macro-jump-to-def/src/foo/check-source-code-urls-to-def-std.rs.html#35). r? ```@jyn514```
2 parents fc96600 + beb2f36 commit 97f4d7b

File tree

6 files changed

+258
-51
lines changed

6 files changed

+258
-51
lines changed

compiler/rustc_metadata/src/creader.rs

+4
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ impl CStore {
133133
CrateNum::new(self.metas.len() - 1)
134134
}
135135

136+
pub fn has_crate_data(&self, cnum: CrateNum) -> bool {
137+
self.metas[cnum].is_some()
138+
}
139+
136140
pub(crate) fn get_crate_data(&self, cnum: CrateNum) -> CrateMetadataRef<'_> {
137141
let cdata = self.metas[cnum]
138142
.as_ref()

src/librustdoc/html/format.rs

+79-1
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@
88
use std::borrow::Cow;
99
use std::cell::Cell;
1010
use std::fmt;
11-
use std::iter;
11+
use std::iter::{self, once};
1212

13+
use rustc_ast as ast;
1314
use rustc_attr::{ConstStability, StabilityLevel};
1415
use rustc_data_structures::captures::Captures;
1516
use rustc_data_structures::fx::FxHashSet;
1617
use rustc_hir as hir;
1718
use rustc_hir::def::DefKind;
1819
use rustc_hir::def_id::DefId;
20+
use rustc_metadata::creader::{CStore, LoadedMacro};
1921
use rustc_middle::ty;
2022
use rustc_middle::ty::DefIdTree;
2123
use rustc_middle::ty::TyCtxt;
@@ -519,6 +521,7 @@ impl clean::GenericArgs {
519521
}
520522

521523
// Possible errors when computing href link source for a `DefId`
524+
#[derive(PartialEq, Eq)]
522525
pub(crate) enum HrefError {
523526
/// This item is known to rustdoc, but from a crate that does not have documentation generated.
524527
///
@@ -556,6 +559,79 @@ pub(crate) fn join_with_double_colon(syms: &[Symbol]) -> String {
556559
s
557560
}
558561

562+
/// This function is to get the external macro path because they are not in the cache used in
563+
/// `href_with_root_path`.
564+
fn generate_macro_def_id_path(
565+
def_id: DefId,
566+
cx: &Context<'_>,
567+
root_path: Option<&str>,
568+
) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
569+
let tcx = cx.shared.tcx;
570+
let crate_name = tcx.crate_name(def_id.krate).to_string();
571+
let cache = cx.cache();
572+
573+
let fqp: Vec<Symbol> = tcx
574+
.def_path(def_id)
575+
.data
576+
.into_iter()
577+
.filter_map(|elem| {
578+
// extern blocks (and a few others things) have an empty name.
579+
match elem.data.get_opt_name() {
580+
Some(s) if !s.is_empty() => Some(s),
581+
_ => None,
582+
}
583+
})
584+
.collect();
585+
let relative = fqp.iter().map(|elem| elem.to_string());
586+
let cstore = CStore::from_tcx(tcx);
587+
// We need this to prevent a `panic` when this function is used from intra doc links...
588+
if !cstore.has_crate_data(def_id.krate) {
589+
debug!("No data for crate {}", crate_name);
590+
return Err(HrefError::NotInExternalCache);
591+
}
592+
// Check to see if it is a macro 2.0 or built-in macro.
593+
// More information in <https://rust-lang.github.io/rfcs/1584-macros.html>.
594+
let is_macro_2 = match cstore.load_macro_untracked(def_id, tcx.sess) {
595+
LoadedMacro::MacroDef(def, _) => {
596+
// If `ast_def.macro_rules` is `true`, then it's not a macro 2.0.
597+
matches!(&def.kind, ast::ItemKind::MacroDef(ast_def) if !ast_def.macro_rules)
598+
}
599+
_ => false,
600+
};
601+
602+
let mut path = if is_macro_2 {
603+
once(crate_name.clone()).chain(relative).collect()
604+
} else {
605+
vec![crate_name.clone(), relative.last().unwrap()]
606+
};
607+
if path.len() < 2 {
608+
// The minimum we can have is the crate name followed by the macro name. If shorter, then
609+
// it means that that `relative` was empty, which is an error.
610+
debug!("macro path cannot be empty!");
611+
return Err(HrefError::NotInExternalCache);
612+
}
613+
614+
if let Some(last) = path.last_mut() {
615+
*last = format!("macro.{}.html", last);
616+
}
617+
618+
let url = match cache.extern_locations[&def_id.krate] {
619+
ExternalLocation::Remote(ref s) => {
620+
// `ExternalLocation::Remote` always end with a `/`.
621+
format!("{}{}", s, path.join("/"))
622+
}
623+
ExternalLocation::Local => {
624+
// `root_path` always end with a `/`.
625+
format!("{}{}/{}", root_path.unwrap_or(""), crate_name, path.join("/"))
626+
}
627+
ExternalLocation::Unknown => {
628+
debug!("crate {} not in cache when linkifying macros", crate_name);
629+
return Err(HrefError::NotInExternalCache);
630+
}
631+
};
632+
Ok((url, ItemType::Macro, fqp))
633+
}
634+
559635
pub(crate) fn href_with_root_path(
560636
did: DefId,
561637
cx: &Context<'_>,
@@ -611,6 +687,8 @@ pub(crate) fn href_with_root_path(
611687
ExternalLocation::Unknown => return Err(HrefError::DocumentationNotBuilt),
612688
},
613689
)
690+
} else if matches!(def_kind, DefKind::Macro(_)) {
691+
return generate_macro_def_id_path(did, cx, root_path);
614692
} else {
615693
return Err(HrefError::NotInExternalCache);
616694
}

src/librustdoc/html/highlight.rs

+83-32
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use super::format::{self, Buffer};
2222
use super::render::LinkFromSrc;
2323

2424
/// This type is needed in case we want to render links on items to allow to go to their definition.
25-
pub(crate) struct ContextInfo<'a, 'b, 'c> {
25+
pub(crate) struct HrefContext<'a, 'b, 'c> {
2626
pub(crate) context: &'a Context<'b>,
2727
/// This span contains the current file we're going through.
2828
pub(crate) file_span: Span,
@@ -44,7 +44,7 @@ pub(crate) fn render_with_highlighting(
4444
tooltip: Option<(Option<Edition>, &str)>,
4545
edition: Edition,
4646
extra_content: Option<Buffer>,
47-
context_info: Option<ContextInfo<'_, '_, '_>>,
47+
href_context: Option<HrefContext<'_, '_, '_>>,
4848
decoration_info: Option<DecorationInfo>,
4949
) {
5050
debug!("highlighting: ================\n{}\n==============", src);
@@ -62,7 +62,7 @@ pub(crate) fn render_with_highlighting(
6262
}
6363

6464
write_header(out, class, extra_content);
65-
write_code(out, src, edition, context_info, decoration_info);
65+
write_code(out, src, edition, href_context, decoration_info);
6666
write_footer(out, playground_button);
6767
}
6868

@@ -85,31 +85,36 @@ fn write_header(out: &mut Buffer, class: Option<&str>, extra_content: Option<Buf
8585
///
8686
/// Some explanations on the last arguments:
8787
///
88-
/// In case we are rendering a code block and not a source code file, `context_info` will be `None`.
89-
/// To put it more simply: if `context_info` is `None`, the code won't try to generate links to an
88+
/// In case we are rendering a code block and not a source code file, `href_context` will be `None`.
89+
/// To put it more simply: if `href_context` is `None`, the code won't try to generate links to an
9090
/// item definition.
9191
///
9292
/// More explanations about spans and how we use them here are provided in the
9393
fn write_code(
9494
out: &mut Buffer,
9595
src: &str,
9696
edition: Edition,
97-
context_info: Option<ContextInfo<'_, '_, '_>>,
97+
href_context: Option<HrefContext<'_, '_, '_>>,
9898
decoration_info: Option<DecorationInfo>,
9999
) {
100100
// This replace allows to fix how the code source with DOS backline characters is displayed.
101101
let src = src.replace("\r\n", "\n");
102+
let mut closing_tags: Vec<&'static str> = Vec::new();
102103
Classifier::new(
103104
&src,
104105
edition,
105-
context_info.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP),
106+
href_context.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP),
106107
decoration_info,
107108
)
108109
.highlight(&mut |highlight| {
109110
match highlight {
110-
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),
111+
Highlight::Token { text, class } => string(out, Escape(text), class, &href_context),
112+
Highlight::EnterSpan { class } => {
113+
closing_tags.push(enter_span(out, class, &href_context))
114+
}
115+
Highlight::ExitSpan => {
116+
exit_span(out, closing_tags.pop().expect("ExitSpan without EnterSpan"))
117+
}
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,20 @@ 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+
href_context: &Option<HrefContext<'_, '_, '_>>,
684+
) -> &'static str {
685+
string_without_closing_tag(out, "", Some(klass), href_context).expect(
686+
"internal error: enter_span was called with Some(klass) but did not return a \
687+
closing HTML tag",
688+
)
663689
}
664690

665691
/// Called at the end of a span of highlighted text.
666-
fn exit_span(out: &mut Buffer) {
667-
out.write_str("</span>");
692+
fn exit_span(out: &mut Buffer, closing_tag: &str) {
693+
out.write_str(closing_tag);
668694
}
669695

670696
/// Called for a span of text. If the text should be highlighted differently
@@ -687,15 +713,39 @@ fn string<T: Display>(
687713
out: &mut Buffer,
688714
text: T,
689715
klass: Option<Class>,
690-
context_info: &Option<ContextInfo<'_, '_, '_>>,
716+
href_context: &Option<HrefContext<'_, '_, '_>>,
691717
) {
718+
if let Some(closing_tag) = string_without_closing_tag(out, text, klass, href_context) {
719+
out.write_str(closing_tag);
720+
}
721+
}
722+
723+
/// This function writes `text` into `out` with some modifications depending on `klass`:
724+
///
725+
/// * If `klass` is `None`, `text` is written into `out` with no modification.
726+
/// * If `klass` is `Some` but `klass.get_span()` is `None`, it writes the text wrapped in a
727+
/// `<span>` with the provided `klass`.
728+
/// * If `klass` is `Some` and has a [`rustc_span::Span`], it then tries to generate a link (`<a>`
729+
/// element) by retrieving the link information from the `span_correspondance_map` that was filled
730+
/// in `span_map.rs::collect_spans_and_sources`. If it cannot retrieve the information, then it's
731+
/// the same as the second point (`klass` is `Some` but doesn't have a [`rustc_span::Span`]).
732+
fn string_without_closing_tag<T: Display>(
733+
out: &mut Buffer,
734+
text: T,
735+
klass: Option<Class>,
736+
href_context: &Option<HrefContext<'_, '_, '_>>,
737+
) -> Option<&'static str> {
692738
let Some(klass) = klass
693-
else { return write!(out, "{}", text) };
739+
else {
740+
write!(out, "{}", text);
741+
return None;
742+
};
694743
let Some(def_span) = klass.get_span()
695744
else {
696-
write!(out, "<span class=\"{}\">{}</span>", klass.as_html(), text);
697-
return;
745+
write!(out, "<span class=\"{}\">{}", klass.as_html(), text);
746+
return Some("</span>");
698747
};
748+
699749
let mut text_s = text.to_string();
700750
if text_s.contains("::") {
701751
text_s = text_s.split("::").intersperse("::").fold(String::new(), |mut path, t| {
@@ -715,10 +765,10 @@ fn string<T: Display>(
715765
path
716766
});
717767
}
718-
if let Some(context_info) = context_info {
768+
if let Some(href_context) = href_context {
719769
if let Some(href) =
720-
context_info.context.shared.span_correspondance_map.get(&def_span).and_then(|href| {
721-
let context = context_info.context;
770+
href_context.context.shared.span_correspondance_map.get(&def_span).and_then(|href| {
771+
let context = href_context.context;
722772
// FIXME: later on, it'd be nice to provide two links (if possible) for all items:
723773
// one to the documentation page and one to the source definition.
724774
// FIXME: currently, external items only generate a link to their documentation,
@@ -727,27 +777,28 @@ fn string<T: Display>(
727777
match href {
728778
LinkFromSrc::Local(span) => context
729779
.href_from_span(*span, true)
730-
.map(|s| format!("{}{}", context_info.root_path, s)),
780+
.map(|s| format!("{}{}", href_context.root_path, s)),
731781
LinkFromSrc::External(def_id) => {
732-
format::href_with_root_path(*def_id, context, Some(context_info.root_path))
782+
format::href_with_root_path(*def_id, context, Some(href_context.root_path))
733783
.ok()
734784
.map(|(url, _, _)| url)
735785
}
736786
LinkFromSrc::Primitive(prim) => format::href_with_root_path(
737787
PrimitiveType::primitive_locations(context.tcx())[prim],
738788
context,
739-
Some(context_info.root_path),
789+
Some(href_context.root_path),
740790
)
741791
.ok()
742792
.map(|(url, _, _)| url),
743793
}
744794
})
745795
{
746-
write!(out, "<a class=\"{}\" href=\"{}\">{}</a>", klass.as_html(), href, text_s);
747-
return;
796+
write!(out, "<a class=\"{}\" href=\"{}\">{}", klass.as_html(), href, text_s);
797+
return Some("</a>");
748798
}
749799
}
750-
write!(out, "<span class=\"{}\">{}</span>", klass.as_html(), text_s);
800+
write!(out, "<span class=\"{}\">{}", klass.as_html(), text_s);
801+
Some("</span>")
751802
}
752803

753804
#[cfg(test)]

0 commit comments

Comments
 (0)