about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/librustdoc/clean/inline.rs2
-rw-r--r--src/librustdoc/clean/mod.rs24
-rw-r--r--src/librustdoc/clean/types.rs15
-rw-r--r--src/librustdoc/config.rs3
-rw-r--r--src/librustdoc/html/highlight.rs6
-rw-r--r--src/librustdoc/html/render/context.rs5
-rw-r--r--src/librustdoc/html/render/mod.rs77
-rw-r--r--src/librustdoc/html/static/css/rustdoc.css4
-rw-r--r--src/librustdoc/html/static/js/main.js25
-rw-r--r--src/librustdoc/json/conversions.rs4
-rw-r--r--src/librustdoc/lib.rs20
-rw-r--r--src/librustdoc/scrape_examples.rs130
12 files changed, 183 insertions, 132 deletions
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<Function> 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<Item> 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<Item> 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<Item> 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<Item> 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<Item> for (&hir::ForeignItem<'_>, Option<Symbol>) {
     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<Item> for (&hir::ForeignItem<'_>, Option<Symbol>) {
                             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<FnCallLocations>,
-}
-
-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<EmitType>,
     /// If `true`, HTML source pages will generate links for items to their definition.
     crate generate_link_to_definition: bool,
-    crate call_locations: Option<AllCallLocations>,
+    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<DecorationInfo>,
     ) -> 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<rustc_span::Span, LinkFromSrc>,
     /// 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<String>);
@@ -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<String> {
 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<FnCallLocations>,
-    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#"<div class="scraped-example" data-locs="{locations}">
+            r#"<div class="scraped-example" data-locs="{locations}" data-offset="{offset}">
                 <div class="scraped-example-title">
-                   {name} <a href="{root}{url}" target="_blank">[src]</a>
+                   {name} (<a href="{root}{url}" target="_blank">{line_range}</a>)
                 </div>
                 <div class="code-wrapper">"#,
             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, "</div></div>");
 
@@ -2590,12 +2602,14 @@ fn render_call_locations(
         };
 
         let mut locs = call_locations.into_iter().collect::<Vec<_>>();
-        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 <ul /> containing links to the source files.
+        // For the remaining examples, generate a <ul> 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#"<li><a href="{}{}" target="_blank">{}</a></li>"#,
+                    r#"<li><a href="{root}{url}" target="_blank">{name}</a></li>"#,
                     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<Qualifiers> {
 
 impl FromWithTcx<clean::Function> 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<RustcOptGroup> {
                 "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<CallLocation>,
     crate url: String,
     crate display_name: String,
+    crate edition: Edition,
 }
-crate type DefIdCallKey = String;
+
 crate type FnCallLocations = FxHashMap<PathBuf, CallData>;
-crate type AllCallLocations = FxHashMap<DefIdCallKey, FnCallLocations>;
+crate type AllCallLocations = FxHashMap<String, FnCallLocations>;
 
 /// 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);
-
-                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 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 }
+                };
+
+                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<String>,
     diag: &rustc_errors::Handler,
-) -> Result<Option<AllCallLocations>, i32> {
-    let each_call_locations = with_examples
-        .into_iter()
-        .map(|path| {
+) -> Result<AllCallLocations, i32> {
+    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::<Result<Vec<_>, _>>()
-        .map_err(|e: String| {
-            diag.err(&format!("failed to load examples with error: {}", e));
-            1
-        })?;
-
-    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());
+            let mut decoder = Decoder::new(&bytes, 0);
+            let calls = AllCallLocations::decode(&mut decoder)?;
+
+            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
+    })
 }