diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 09622d721f7..9d5a95a88a2 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -235,7 +235,7 @@ fn build_external_function(cx: &mut DocContext<'_>, did: DefId) -> clean::Functi decl, generics, header: hir::FnHeader { unsafety: sig.unsafety(), abi: sig.abi(), constness, asyncness }, - call_locations: None, + def_id: did, } } diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index e2b1ff4547b..dded5c9934b 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -801,10 +801,8 @@ impl<'a> Clean for (&'a hir::FnSig<'a>, &'a hir::Generics<'a>, hir::Bo fn clean(&self, cx: &mut DocContext<'_>) -> Function { let (generics, decl) = enter_impl_trait(cx, |cx| (self.1.clean(cx), (&*self.0.decl, self.2).clean(cx))); - let mut function = Function { decl, generics, header: self.0.header, call_locations: None }; let def_id = self.2.hir_id.owner.to_def_id(); - function.load_call_locations(def_id, cx); - function + Function { decl, generics, header: self.0.header, def_id } } } @@ -936,14 +934,13 @@ impl Clean for hir::TraitItem<'_> { let (generics, decl) = enter_impl_trait(cx, |cx| { (self.generics.clean(cx), (&*sig.decl, &names[..]).clean(cx)) }); - let mut t = - Function { header: sig.header, decl, generics, call_locations: None }; + let def_id = self.def_id.to_def_id(); + let mut t = Function { header: sig.header, decl, generics, def_id }; if t.header.constness == hir::Constness::Const && is_unstable_const_fn(cx.tcx, local_did).is_some() { t.header.constness = hir::Constness::NotConst; } - t.load_call_locations(self.def_id.to_def_id(), cx); TyMethodItem(t) } hir::TraitItemKind::Type(ref bounds, ref default) => { @@ -1062,7 +1059,7 @@ impl Clean for ty::AssocItem { ty::ImplContainer(_) => Some(self.defaultness), ty::TraitContainer(_) => None, }; - let mut function = Function { + let function = Function { generics, decl, header: hir::FnHeader { @@ -1071,12 +1068,11 @@ impl Clean for ty::AssocItem { constness, asyncness, }, - call_locations: None, + def_id: self.def_id, }; - function.load_call_locations(self.def_id, cx); MethodItem(function, defaultness) } else { - let mut function = Function { + let function = Function { generics, decl, header: hir::FnHeader { @@ -1085,9 +1081,8 @@ impl Clean for ty::AssocItem { constness: hir::Constness::NotConst, asyncness: hir::IsAsync::NotAsync, }, - call_locations: None, + def_id: self.def_id, }; - function.load_call_locations(self.def_id, cx); TyMethodItem(function) } } @@ -2086,7 +2081,8 @@ fn clean_use_statement( impl Clean for (&hir::ForeignItem<'_>, Option) { fn clean(&self, cx: &mut DocContext<'_>) -> Item { let (item, renamed) = self; - cx.with_param_env(item.def_id.to_def_id(), |cx| { + let def_id = item.def_id.to_def_id(); + cx.with_param_env(def_id, |cx| { let kind = match item.kind { hir::ForeignItemKind::Fn(ref decl, ref names, ref generics) => { let abi = cx.tcx.hir().get_foreign_abi(item.hir_id()); @@ -2106,7 +2102,7 @@ impl Clean for (&hir::ForeignItem<'_>, Option) { constness: hir::Constness::NotConst, asyncness: hir::IsAsync::NotAsync, }, - call_locations: None, + def_id, }) } hir::ForeignItemKind::Static(ref ty, mutability) => { diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index eb507e4eeca..043ffbfd187 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -42,7 +42,6 @@ use crate::formats::cache::Cache; use crate::formats::item_type::ItemType; use crate::html::render::cache::ExternalLocation; use crate::html::render::Context; -use crate::scrape_examples::{self, FnCallLocations}; use self::FnRetTy::*; use self::ItemKind::*; @@ -1255,19 +1254,7 @@ crate struct Function { crate decl: FnDecl, crate generics: Generics, crate header: hir::FnHeader, - crate call_locations: Option, -} - -impl Function { - /// If --scrape-examples is used, then this function attempts to find call locations - /// for `self` within `RenderOptions::call_locations` and store them in `Function::call_locations`. - crate fn load_call_locations(&mut self, def_id: hir::def_id::DefId, cx: &DocContext<'_>) { - if let Some(call_locations) = cx.render_options.call_locations.as_ref() { - let key = scrape_examples::def_id_call_key(cx.tcx, def_id); - self.call_locations = call_locations.get(&key).cloned(); - debug!("call_locations: {:?} -- {:?}", key, self.call_locations); - } - } + crate def_id: DefId, } #[derive(Clone, PartialEq, Eq, Debug, Hash)] diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 4e019d4e15d..54b62db7917 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -207,6 +207,7 @@ impl fmt::Debug for Options { .field("run_check", &self.run_check) .field("no_run", &self.no_run) .field("nocapture", &self.nocapture) + .field("scrape_examples", &self.scrape_examples) .finish() } } @@ -285,7 +286,7 @@ crate struct RenderOptions { crate emit: Vec, /// If `true`, HTML source pages will generate links for items to their definition. crate generate_link_to_definition: bool, - crate call_locations: Option, + crate call_locations: AllCallLocations, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index b0e907cb059..a97dd95dcb6 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -31,7 +31,9 @@ crate struct ContextInfo<'a, 'b, 'c> { crate root_path: &'c str, } -crate type DecorationInfo = FxHashMap<&'static str, Vec<(u32, u32)>>; +/// Decorations are represented as a map from CSS class to vector of character ranges. +/// Each range will be wrapped in a span with that class. +crate struct DecorationInfo(crate FxHashMap<&'static str, Vec<(u32, u32)>>); /// Highlights `src`, returning the HTML output. crate fn render_with_highlighting( @@ -273,6 +275,7 @@ struct Decorations { impl Decorations { fn new(info: DecorationInfo) -> Self { let (starts, ends) = info + .0 .into_iter() .map(|(kind, ranges)| ranges.into_iter().map(move |(lo, hi)| ((lo, kind), hi))) .flatten() @@ -305,6 +308,7 @@ impl<'a> Classifier<'a> { decoration_info: Option, ) -> Classifier<'_> { let tokens = PeekIter::new(TokenIter { src }); + let decorations = decoration_info.map(Decorations::new); Classifier { tokens, in_attribute: false, diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index b99d2fe5aa0..fe684fd79a1 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -35,6 +35,7 @@ use crate::html::format::Buffer; use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap}; use crate::html::static_files::PAGE; use crate::html::{layout, sources}; +use crate::scrape_examples::AllCallLocations; /// Major driving force in all rustdoc rendering. This contains information /// about where in the tree-like hierarchy rendering is occurring and controls @@ -124,6 +125,8 @@ crate struct SharedContext<'tcx> { crate span_correspondance_map: FxHashMap, /// The [`Cache`] used during rendering. crate cache: Cache, + + crate call_locations: AllCallLocations, } impl SharedContext<'_> { @@ -389,6 +392,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { generate_redirect_map, show_type_layout, generate_link_to_definition, + call_locations, .. } = options; @@ -480,6 +484,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { templates, span_correspondance_map: matches, cache, + call_locations, }; // Add the default themes to the `Vec` of stylepaths diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 6b30abb8260..cd5316240d0 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -73,8 +73,9 @@ use crate::html::format::{ print_generic_bounds, print_where_clause, Buffer, HrefError, PrintWithSpace, }; use crate::html::markdown::{HeadingOffset, Markdown, MarkdownHtml, MarkdownSummaryLine}; +use crate::html::highlight; use crate::html::sources; -use crate::scrape_examples::{CallData, FnCallLocations}; +use crate::scrape_examples::CallData; /// A pair of name and its optional document. crate type NameDoc = (String, Option); @@ -592,9 +593,13 @@ fn document_full_inner( } } - match &*item.kind { + let kind = match &*item.kind { + clean::ItemKind::StrippedItem(box kind) | kind => kind, + }; + + match kind { clean::ItemKind::FunctionItem(f) | clean::ItemKind::MethodItem(f, _) => { - render_call_locations(w, cx, &f.call_locations, item); + render_call_locations(w, cx, f.def_id, item); } _ => {} } @@ -2458,14 +2463,11 @@ fn collect_paths_for_type(first_ty: clean::Type, cache: &Cache) -> Vec { const MAX_FULL_EXAMPLES: usize = 5; /// Generates the HTML for example call locations generated via the --scrape-examples flag. -fn render_call_locations( - w: &mut Buffer, - cx: &Context<'_>, - call_locations: &Option, - item: &clean::Item, -) { - let call_locations = match call_locations.as_ref() { - Some(call_locations) if call_locations.len() > 0 => call_locations, +fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, def_id: DefId, item: &clean::Item) { + let tcx = cx.tcx(); + let key = crate::scrape_examples::def_id_call_key(tcx, def_id); + let call_locations = match cx.shared.call_locations.get(&key) { + Some(call_locations) => call_locations, _ => { return; } @@ -2483,7 +2485,6 @@ fn render_call_locations( ); // Generate the HTML for a single example, being the title and code block - let tcx = cx.tcx(); let write_example = |w: &mut Buffer, (path, call_data): (&PathBuf, &CallData)| -> bool { let contents = match fs::read_to_string(&path) { Ok(contents) => contents, @@ -2497,40 +2498,51 @@ fn render_call_locations( // To reduce file sizes, we only want to embed the source code needed to understand the example, not // the entire file. So we find the smallest byte range that covers all items enclosing examples. - assert!(call_data.locations.len() > 0); + assert!(!call_data.locations.is_empty()); let min_loc = call_data.locations.iter().min_by_key(|loc| loc.enclosing_item.byte_span.0).unwrap(); - let min_byte = min_loc.enclosing_item.byte_span.0; - let min_line = min_loc.enclosing_item.line_span.0; - let max_byte = + let (byte_offset, _) = min_loc.enclosing_item.byte_span; + let (line_offset, _) = min_loc.enclosing_item.line_span; + let byte_ceiling = call_data.locations.iter().map(|loc| loc.enclosing_item.byte_span.1).max().unwrap(); // The output code is limited to that byte range. - let contents_subset = &contents[(min_byte as usize)..(max_byte as usize)]; + let contents_subset = &contents[(byte_offset as usize)..(byte_ceiling as usize)]; // The call locations need to be updated to reflect that the size of the program has changed. - // Specifically, the ranges are all subtracted by `min_byte` since that's the new zero point. + // Specifically, the ranges are all subtracted by `byte_offset` since that's the new zero point. let (byte_ranges, line_ranges): (Vec<_>, Vec<_>) = call_data .locations .iter() .map(|loc| { let (byte_lo, byte_hi) = loc.call_expr.byte_span; let (line_lo, line_hi) = loc.call_expr.line_span; - ((byte_lo - min_byte, byte_hi - min_byte), (line_lo - min_line, line_hi - min_line)) + ( + (byte_lo - byte_offset, byte_hi - byte_offset), + (line_lo - line_offset, line_hi - line_offset), + ) }) .unzip(); - let edition = cx.shared.edition(); + let (init_min, init_max) = line_ranges[0]; + let line_range = if init_min == init_max { + format!("line {}", init_min + line_offset + 1) + } else { + format!("lines {}-{}", init_min + line_offset + 1, init_max + line_offset + 1) + }; + write!( w, - r#"
+ r#"
- {name} [src] + {name} ({line_range})
"#, root = cx.root_path(), url = call_data.url, name = call_data.display_name, + line_range = line_range, + offset = line_offset, // The locations are encoded as a data attribute, so they can be read // later by the JS for interactions. locations = serde_json::to_string(&line_ranges).unwrap(), @@ -2551,8 +2563,8 @@ fn render_call_locations( _ => false, })?; Some(rustc_span::Span::with_root_ctxt( - file.start_pos + BytePos(min_byte), - file.start_pos + BytePos(max_byte), + file.start_pos + BytePos(byte_offset), + file.start_pos + BytePos(byte_ceiling), )) })() .unwrap_or(rustc_span::DUMMY_SP); @@ -2566,12 +2578,12 @@ fn render_call_locations( sources::print_src( w, contents_subset, - edition, + call_data.edition, file_span, cx, &root_path, - Some(min_line), - Some(decoration_info), + Some(line_offset), + Some(highlight::DecorationInfo(decoration_info)), ); write!(w, "
"); @@ -2590,12 +2602,14 @@ fn render_call_locations( }; let mut locs = call_locations.into_iter().collect::>(); - locs.sort_by_key(|x| sort_criterion(x)); + locs.sort_by_key(sort_criterion); locs }; - // Write just one example that's visible by default in the method's description. let mut it = ordered_locations.into_iter().peekable(); + + // An example may fail to write if its source can't be read for some reason, so this method + // continues iterating until a write suceeds let write_and_skip_failure = |w: &mut Buffer, it: &mut Peekable<_>| { while let Some(example) = it.next() { if write_example(&mut *w, example) { @@ -2604,6 +2618,7 @@ fn render_call_locations( } }; + // Write just one example that's visible by default in the method's description. write_and_skip_failure(w, &mut it); // Then add the remaining examples in a hidden section. @@ -2626,7 +2641,7 @@ fn render_call_locations( write_and_skip_failure(w, &mut it); } - // For the remaining examples, generate a
    containing links to the source files. + // For the remaining examples, generate a
      containing links to the source files. if it.peek().is_some() { write!( w, @@ -2635,7 +2650,7 @@ fn render_call_locations( it.for_each(|(_, call_data)| { write!( w, - r#"
    • {}
    • "#, + r#"
    • {name}
    • "#, root = cx.root_path(), url = call_data.url, name = call_data.display_name diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index b35412bdc2a..b7d9a30beb1 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -1978,10 +1978,6 @@ details.undocumented[open] > summary::before { font-family: 'Fira Sans'; } -.scraped-example-title a { - margin-left: 5px; -} - .scraped-example:not(.expanded) .code-wrapper pre.line-numbers, .scraped-example:not(.expanded) .code-wrapper .example-wrap pre.rust { overflow: hidden; diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 1b924991139..be63e129f3d 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -996,9 +996,11 @@ function hideThemeButtonState() { function updateScrapedExample(example) { var locs = JSON.parse(example.attributes.getNamedItem("data-locs").textContent); + var offset = parseInt(example.attributes.getNamedItem("data-offset").textContent); var locIndex = 0; var highlights = example.querySelectorAll('.highlight'); + var link = example.querySelector('.scraped-example-title a'); addClass(highlights[0], 'focus'); if (locs.length > 1) { // Toggle through list of examples in a given file @@ -1007,13 +1009,36 @@ function hideThemeButtonState() { f(); scrollToLoc(example, locs[locIndex]); addClass(highlights[locIndex], 'focus'); + + var curLoc = locs[locIndex]; + var minLine = curLoc[0] + offset + 1; + var maxLine = curLoc[1] + offset + 1; + + var text; + var anchor; + if (minLine == maxLine) { + text = 'line ' + minLine.toString(); + anchor = minLine.toString(); + } else { + var range = minLine.toString() + '-' + maxLine.toString(); + text = 'lines ' + range; + anchor = range; + } + + var url = new URL(link.href); + url.hash = anchor; + + link.href = url.toString(); + link.innerHTML = text; }; + example.querySelector('.prev') .addEventListener('click', function() { onChangeLoc(function() { locIndex = (locIndex - 1 + locs.length) % locs.length; }); }); + example.querySelector('.next') .addEventListener('click', function() { onChangeLoc(function() { locIndex = (locIndex + 1) % locs.length; }); diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 866514d7c9b..b151f62c1bf 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -289,7 +289,7 @@ crate fn from_fn_header(header: &rustc_hir::FnHeader) -> HashSet { impl FromWithTcx for Function { fn from_tcx(function: clean::Function, tcx: TyCtxt<'_>) -> Self { - let clean::Function { decl, generics, header, call_locations: _ } = function; + let clean::Function { decl, generics, header, def_id: _ } = function; Function { decl: decl.into_tcx(tcx), generics: generics.into_tcx(tcx), @@ -530,7 +530,7 @@ crate fn from_function_method( has_body: bool, tcx: TyCtxt<'_>, ) -> Method { - let clean::Function { header, decl, generics, call_locations: _ } = function; + let clean::Function { header, decl, generics, def_id: _ } = function; Method { decl: decl.into_tcx(tcx), generics: generics.into_tcx(tcx), diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index b7407ee409f..71a62ec5f0d 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -47,11 +47,13 @@ extern crate rustc_interface; extern crate rustc_lexer; extern crate rustc_lint; extern crate rustc_lint_defs; +extern crate rustc_macros; extern crate rustc_metadata; extern crate rustc_middle; extern crate rustc_parse; extern crate rustc_passes; extern crate rustc_resolve; +extern crate rustc_serialize; extern crate rustc_session; extern crate rustc_span; extern crate rustc_target; @@ -619,8 +621,22 @@ fn opts() -> Vec { "Make the identifiers in the HTML source code pages navigable", ) }), - unstable("scrape-examples", |o| o.optopt("", "scrape-examples", "", "")), - unstable("with-examples", |o| o.optmulti("", "with-examples", "", "")), + unstable("scrape-examples", |o| { + o.optopt( + "", + "scrape-examples", + "", + "collect function call information (for use with `--with-examples`)", + ) + }), + unstable("with-examples", |o| { + o.optmulti( + "", + "with-examples", + "", + "path to function call information (for displaying examples in the documentation)", + ) + }), ] } diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs index 3887647ca0a..a3d61fa4d2c 100644 --- a/src/librustdoc/scrape_examples.rs +++ b/src/librustdoc/scrape_examples.rs @@ -13,14 +13,19 @@ use rustc_hir::{ HirId, }; use rustc_interface::interface; +use rustc_macros::{Decodable, Encodable}; use rustc_middle::hir::map::Map; use rustc_middle::ty::{self, TyCtxt}; -use rustc_span::{def_id::DefId, BytePos, FileName, SourceFile}; -use serde::{Deserialize, Serialize}; +use rustc_serialize::{ + opaque::{Decoder, FileEncoder}, + Decodable, Encodable, +}; +use rustc_span::{def_id::DefId, edition::Edition, BytePos, FileName, SourceFile}; + use std::fs; use std::path::PathBuf; -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Encodable, Decodable, Debug, Clone)] crate struct SyntaxRange { crate byte_span: (u32, u32), crate line_span: (usize, usize), @@ -38,7 +43,7 @@ impl SyntaxRange { } } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Encodable, Decodable, Debug, Clone)] crate struct CallLocation { crate call_expr: SyntaxRange, crate enclosing_item: SyntaxRange, @@ -49,9 +54,10 @@ impl CallLocation { tcx: TyCtxt<'_>, expr_span: rustc_span::Span, expr_id: HirId, - source_file: &rustc_span::SourceFile, + source_file: &SourceFile, ) -> Self { - let enclosing_item_span = tcx.hir().span_with_body(tcx.hir().get_parent_item(expr_id)); + let enclosing_item_span = + tcx.hir().span_with_body(tcx.hir().get_parent_item(expr_id)).source_callsite(); assert!(enclosing_item_span.contains(expr_span)); CallLocation { @@ -61,15 +67,16 @@ impl CallLocation { } } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Encodable, Decodable, Debug, Clone)] crate struct CallData { crate locations: Vec, crate url: String, crate display_name: String, + crate edition: Edition, } -crate type DefIdCallKey = String; + crate type FnCallLocations = FxHashMap; -crate type AllCallLocations = FxHashMap; +crate type AllCallLocations = FxHashMap; /// Visitor for traversing a crate and finding instances of function calls. struct FindCalls<'a, 'tcx> { @@ -79,7 +86,7 @@ struct FindCalls<'a, 'tcx> { calls: &'a mut AllCallLocations, } -crate fn def_id_call_key(tcx: TyCtxt<'_>, def_id: DefId) -> DefIdCallKey { +crate fn def_id_call_key(tcx: TyCtxt<'_>, def_id: DefId) -> String { format!( "{}{}", tcx.crate_name(def_id.krate).to_ident_string(), @@ -101,30 +108,29 @@ where intravisit::walk_expr(self, ex); // Get type of function if expression is a function call + let tcx = self.tcx; let (ty, span) = match ex.kind { hir::ExprKind::Call(f, _) => { - let types = self.tcx.typeck(ex.hir_id.owner); + let types = tcx.typeck(ex.hir_id.owner); (types.node_type(f.hir_id), ex.span) } hir::ExprKind::MethodCall(_, _, _, span) => { - let types = self.tcx.typeck(ex.hir_id.owner); + let types = tcx.typeck(ex.hir_id.owner); let def_id = types.type_dependent_def_id(ex.hir_id).unwrap(); - (self.tcx.type_of(def_id), span) + (tcx.type_of(def_id), span) } _ => { return; } }; - if span.from_expansion() { - return; - } + // We need to get the file the example originates in. If the call is contained + // in a macro, then trace the span back to the macro source (rather than macro definition). + let span = span.source_callsite(); // Save call site if the function resolves to a concrete definition if let ty::FnDef(def_id, _) = ty.kind() { - let fn_key = def_id_call_key(self.tcx, *def_id); - let entries = self.calls.entry(fn_key).or_insert_with(FxHashMap::default); - let file = self.tcx.sess.source_map().lookup_char_pos(span.lo()).file; + let file = tcx.sess.source_map().lookup_char_pos(span.lo()).file; let file_path = match file.name.clone() { FileName::Real(real_filename) => real_filename.into_local_path(), _ => None, @@ -133,18 +139,19 @@ where if let Some(file_path) = file_path { let abs_path = fs::canonicalize(file_path.clone()).unwrap(); let cx = &self.cx; - let location = CallLocation::new(self.tcx, span, ex.hir_id, &file); + let mk_call_data = || { + let clean_span = crate::clean::types::Span::new(span); + let url = cx.href_from_span(clean_span).unwrap(); + let display_name = file_path.display().to_string(); + let edition = tcx.sess.edition(); + CallData { locations: Vec::new(), url, display_name, edition } + }; - entries - .entry(abs_path) - .or_insert_with(|| { - let clean_span = crate::clean::types::Span::new(span); - let url = cx.href_from_span(clean_span).unwrap(); - let display_name = file_path.display().to_string(); - CallData { locations: Vec::new(), url, display_name } - }) - .locations - .push(location); + let fn_key = def_id_call_key(tcx, *def_id); + let fn_entries = self.calls.entry(fn_key).or_default(); + + let location = CallLocation::new(tcx, span, ex.hir_id, &file); + fn_entries.entry(abs_path).or_insert_with(mk_call_data).locations.push(location); } } } @@ -154,55 +161,54 @@ crate fn run( krate: clean::Crate, renderopts: config::RenderOptions, cache: formats::cache::Cache, - tcx: TyCtxt<'tcx>, + tcx: TyCtxt<'_>, example_path: PathBuf, ) -> interface::Result<()> { - let inner = move || { + let inner = move || -> Result<(), String> { // Generates source files for examples - let (cx, _) = Context::init(krate, renderopts, cache, tcx).map_err(|e| format!("{}", e))?; + let (cx, _) = Context::init(krate, renderopts, cache, tcx).map_err(|e| e.to_string())?; // Run call-finder on all items let mut calls = FxHashMap::default(); let mut finder = FindCalls { calls: &mut calls, tcx, map: tcx.hir(), cx }; tcx.hir().krate().visit_all_item_likes(&mut finder.as_deep_visitor()); - // Save output JSON to provided path - let calls_json = serde_json::to_string(&calls).map_err(|e| format!("{}", e))?; - fs::write(example_path, &calls_json).map_err(|e| format!("{}", e))?; + // Save output to provided path + let mut encoder = FileEncoder::new(example_path).map_err(|e| e.to_string())?; + calls.encode(&mut encoder).map_err(|e| e.to_string())?; + encoder.flush().map_err(|e| e.to_string())?; Ok(()) }; - inner().map_err(|e: String| { - eprintln!("{}", e); - rustc_errors::ErrorReported - }) + if let Err(e) = inner() { + tcx.sess.fatal(&e); + } + + Ok(()) } crate fn load_call_locations( with_examples: Vec, diag: &rustc_errors::Handler, -) -> Result, i32> { - let each_call_locations = with_examples - .into_iter() - .map(|path| { +) -> Result { + let inner = || { + let mut all_calls: AllCallLocations = FxHashMap::default(); + for path in with_examples { let bytes = fs::read(&path).map_err(|e| format!("{} (for path {})", e, path))?; - let calls: AllCallLocations = - serde_json::from_slice(&bytes).map_err(|e| format!("{}", e))?; - Ok(calls) - }) - .collect::, _>>() - .map_err(|e: String| { - diag.err(&format!("failed to load examples with error: {}", e)); - 1 - })?; + let mut decoder = Decoder::new(&bytes, 0); + let calls = AllCallLocations::decode(&mut decoder)?; - Ok((each_call_locations.len() > 0).then(|| { - each_call_locations.into_iter().fold(FxHashMap::default(), |mut acc, map| { - for (function, calls) in map.into_iter() { - acc.entry(function).or_insert_with(FxHashMap::default).extend(calls.into_iter()); + for (function, fn_calls) in calls.into_iter() { + all_calls.entry(function).or_default().extend(fn_calls.into_iter()); } - acc - }) - })) + } + + Ok(all_calls) + }; + + inner().map_err(|e: String| { + diag.err(&format!("failed to load examples: {}", e)); + 1 + }) }