Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add macro support in jump to definition feature #91264

Merged
merged 6 commits into from
Jun 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions compiler/rustc_metadata/src/creader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ impl CStore {
CrateNum::new(self.metas.len() - 1)
}

pub fn has_crate_data(&self, cnum: CrateNum) -> bool {
self.metas[cnum].is_some()
}

pub(crate) fn get_crate_data(&self, cnum: CrateNum) -> CrateMetadataRef<'_> {
let cdata = self.metas[cnum]
.as_ref()
Expand Down
80 changes: 79 additions & 1 deletion src/librustdoc/html/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@
use std::borrow::Cow;
use std::cell::Cell;
use std::fmt;
use std::iter;
use std::iter::{self, once};

use rustc_ast as ast;
use rustc_attr::{ConstStability, StabilityLevel};
use rustc_data_structures::captures::Captures;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir as hir;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId;
use rustc_metadata::creader::{CStore, LoadedMacro};
use rustc_middle::ty;
use rustc_middle::ty::DefIdTree;
use rustc_middle::ty::TyCtxt;
Expand Down Expand Up @@ -519,6 +521,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.
///
Expand Down Expand Up @@ -556,6 +559,79 @@ pub(crate) fn join_with_double_colon(syms: &[Symbol]) -> String {
s
}

/// This function is to get the external macro path because they are not in the cache used in
/// `href_with_root_path`.
fn generate_macro_def_id_path(
def_id: DefId,
cx: &Context<'_>,
root_path: Option<&str>,
) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
let tcx = cx.shared.tcx;
let crate_name = tcx.crate_name(def_id.krate).to_string();
let cache = cx.cache();

let fqp: Vec<Symbol> = tcx
.def_path(def_id)
.data
.into_iter()
.filter_map(|elem| {
// extern blocks (and a few others things) have an empty name.
match elem.data.get_opt_name() {
Some(s) if !s.is_empty() => Some(s),
_ => None,
}
})
.collect();
let relative = fqp.iter().map(|elem| elem.to_string());
let cstore = CStore::from_tcx(tcx);
// We need this to prevent a `panic` when this function is used from intra doc links...
if !cstore.has_crate_data(def_id.krate) {
debug!("No data for crate {}", crate_name);
return Err(HrefError::NotInExternalCache);
}
// Check to see if it is a macro 2.0 or built-in macro.
// More information in <https://rust-lang.github.io/rfcs/1584-macros.html>.
let is_macro_2 = match cstore.load_macro_untracked(def_id, tcx.sess) {
LoadedMacro::MacroDef(def, _) => {
// If `ast_def.macro_rules` is `true`, then it's not a macro 2.0.
matches!(&def.kind, ast::ItemKind::MacroDef(ast_def) if !ast_def.macro_rules)
}
_ => false,
};

let mut path = if is_macro_2 {
once(crate_name.clone()).chain(relative).collect()
} else {
vec![crate_name.clone(), relative.last().unwrap()]
};
if path.len() < 2 {
// The minimum we can have is the crate name followed by the macro name. If shorter, then
// it means that that `relative` was empty, which is an error.
debug!("macro path cannot be empty!");
return Err(HrefError::NotInExternalCache);
}

if let Some(last) = path.last_mut() {
*last = format!("macro.{}.html", last);
}

let url = match cache.extern_locations[&def_id.krate] {
ExternalLocation::Remote(ref s) => {
// `ExternalLocation::Remote` always end with a `/`.
format!("{}{}", s, path.join("/"))
}
ExternalLocation::Local => {
// `root_path` always end with a `/`.
format!("{}{}/{}", root_path.unwrap_or(""), crate_name, path.join("/"))
}
ExternalLocation::Unknown => {
debug!("crate {} not in cache when linkifying macros", crate_name);
return Err(HrefError::NotInExternalCache);
}
};
Ok((url, ItemType::Macro, fqp))
}

pub(crate) fn href_with_root_path(
did: DefId,
cx: &Context<'_>,
Expand Down Expand Up @@ -611,6 +687,8 @@ pub(crate) fn href_with_root_path(
ExternalLocation::Unknown => return Err(HrefError::DocumentationNotBuilt),
},
)
} else if matches!(def_kind, DefKind::Macro(_)) {
return generate_macro_def_id_path(did, cx, root_path);
} else {
return Err(HrefError::NotInExternalCache);
}
Expand Down
115 changes: 83 additions & 32 deletions src/librustdoc/html/highlight.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use super::format::{self, Buffer};
use super::render::LinkFromSrc;

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

write_header(out, class, extra_content);
write_code(out, src, edition, context_info, decoration_info);
write_code(out, src, edition, href_context, decoration_info);
write_footer(out, playground_button);
}

Expand All @@ -85,31 +85,36 @@ fn write_header(out: &mut Buffer, class: Option<&str>, extra_content: Option<Buf
///
/// Some explanations on the last arguments:
///
/// In case we are rendering a code block and not a source code file, `context_info` will be `None`.
/// To put it more simply: if `context_info` is `None`, the code won't try to generate links to an
/// In case we are rendering a code block and not a source code file, `href_context` will be `None`.
/// To put it more simply: if `href_context` is `None`, the code won't try to generate links to an
/// item definition.
///
/// More explanations about spans and how we use them here are provided in the
fn write_code(
out: &mut Buffer,
src: &str,
edition: Edition,
context_info: Option<ContextInfo<'_, '_, '_>>,
href_context: Option<HrefContext<'_, '_, '_>>,
decoration_info: Option<DecorationInfo>,
) {
// 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_tags: Vec<&'static str> = Vec::new();
Classifier::new(
&src,
edition,
context_info.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP),
href_context.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP),
decoration_info,
)
.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::Token { text, class } => string(out, Escape(text), class, &href_context),
Highlight::EnterSpan { class } => {
closing_tags.push(enter_span(out, class, &href_context))
}
Highlight::ExitSpan => {
exit_span(out, closing_tags.pop().expect("ExitSpan without EnterSpan"))
}
};
});
}
Expand All @@ -129,7 +134,7 @@ enum Class {
RefKeyWord,
Self_(Span),
Op,
Macro,
Macro(Span),
MacroNonTerminal,
String,
Number,
Expand All @@ -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",
Expand All @@ -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,
}
}
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -658,13 +677,20 @@ 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,
href_context: &Option<HrefContext<'_, '_, '_>>,
) -> &'static str {
string_without_closing_tag(out, "", Some(klass), href_context).expect(
"internal error: enter_span was called with Some(klass) but did not return a \
closing HTML tag",
)
}

/// 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
Expand All @@ -687,15 +713,39 @@ fn string<T: Display>(
out: &mut Buffer,
text: T,
klass: Option<Class>,
context_info: &Option<ContextInfo<'_, '_, '_>>,
href_context: &Option<HrefContext<'_, '_, '_>>,
) {
if let Some(closing_tag) = string_without_closing_tag(out, text, klass, href_context) {
out.write_str(closing_tag);
}
}

/// This function writes `text` into `out` with some modifications depending on `klass`:
///
/// * If `klass` is `None`, `text` is written into `out` with no modification.
/// * If `klass` is `Some` but `klass.get_span()` is `None`, it writes the text wrapped in a
/// `<span>` with the provided `klass`.
/// * If `klass` is `Some` and has a [`rustc_span::Span`], it then tries to generate a link (`<a>`
/// element) by retrieving the link information from the `span_correspondance_map` that was filled
/// in `span_map.rs::collect_spans_and_sources`. If it cannot retrieve the information, then it's
/// the same as the second point (`klass` is `Some` but doesn't have a [`rustc_span::Span`]).
fn string_without_closing_tag<T: Display>(
out: &mut Buffer,
text: T,
klass: Option<Class>,
href_context: &Option<HrefContext<'_, '_, '_>>,
) -> 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| {
Expand All @@ -715,10 +765,10 @@ fn string<T: Display>(
path
});
}
if let Some(context_info) = context_info {
if let Some(href_context) = href_context {
if let Some(href) =
context_info.context.shared.span_correspondance_map.get(&def_span).and_then(|href| {
let context = context_info.context;
href_context.context.shared.span_correspondance_map.get(&def_span).and_then(|href| {
let context = href_context.context;
// FIXME: later on, it'd be nice to provide two links (if possible) for all items:
// one to the documentation page and one to the source definition.
// FIXME: currently, external items only generate a link to their documentation,
Expand All @@ -727,27 +777,28 @@ fn string<T: Display>(
match href {
LinkFromSrc::Local(span) => context
.href_from_span(*span, true)
.map(|s| format!("{}{}", context_info.root_path, s)),
.map(|s| format!("{}{}", href_context.root_path, s)),
LinkFromSrc::External(def_id) => {
format::href_with_root_path(*def_id, context, Some(context_info.root_path))
format::href_with_root_path(*def_id, context, Some(href_context.root_path))
.ok()
.map(|(url, _, _)| url)
}
LinkFromSrc::Primitive(prim) => format::href_with_root_path(
PrimitiveType::primitive_locations(context.tcx())[prim],
context,
Some(context_info.root_path),
Some(href_context.root_path),
)
.ok()
.map(|(url, _, _)| url),
}
})
{
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>")
}

#[cfg(test)]
Expand Down
Loading