From 55bb51786e56a0096a550cf3f26b6c1aed83c872 Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Thu, 26 Aug 2021 14:43:12 -0700 Subject: [PATCH] Move highlighting logic from JS to Rust Continue migrating JS functionality Cleanup Fix compile error Clean up the diff Set toggle font to sans-serif --- src/librustdoc/doctest.rs | 10 +- src/librustdoc/html/highlight.rs | 75 ++++++++-- src/librustdoc/html/highlight/tests.rs | 6 +- src/librustdoc/html/markdown.rs | 1 + src/librustdoc/html/render/mod.rs | 41 ++++-- src/librustdoc/html/render/print_item.rs | 1 + src/librustdoc/html/sources.rs | 3 + src/librustdoc/html/static/css/rustdoc.css | 22 +-- src/librustdoc/html/static/js/main.js | 153 ++++----------------- src/librustdoc/scrape_examples.rs | 62 ++++++--- 10 files changed, 188 insertions(+), 186 deletions(-) diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index f8f5e6be9d5..43abcf095d8 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -45,7 +45,7 @@ crate struct TestOptions { crate attrs: Vec, } -crate fn make_rustc_config(options: &Options) -> interface::Config { +crate fn run(options: Options) -> Result<(), ErrorReported> { let input = config::Input::File(options.input.clone()); let invalid_codeblock_attributes_name = crate::lint::INVALID_CODEBLOCK_ATTRIBUTES.name; @@ -87,7 +87,7 @@ crate fn make_rustc_config(options: &Options) -> interface::Config { let mut cfgs = options.cfgs.clone(); cfgs.push("doc".to_owned()); cfgs.push("doctest".to_owned()); - interface::Config { + let config = interface::Config { opts: sessopts, crate_cfg: interface::parse_cfgspecs(cfgs), input, @@ -103,11 +103,7 @@ crate fn make_rustc_config(options: &Options) -> interface::Config { override_queries: None, make_codegen_backend: None, registry: rustc_driver::diagnostics_registry(), - } -} - -crate fn run(options: Options) -> Result<(), ErrorReported> { - let config = make_rustc_config(&options); + }; let test_args = options.test_args.clone(); let display_doctest_warnings = options.display_doctest_warnings; diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index 43d1b8f794c..b0e907cb059 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -12,6 +12,7 @@ use crate::html::render::Context; use std::collections::VecDeque; use std::fmt::{Display, Write}; +use rustc_data_structures::fx::FxHashMap; use rustc_lexer::{LiteralKind, TokenKind}; use rustc_span::edition::Edition; use rustc_span::symbol::Symbol; @@ -30,6 +31,8 @@ crate struct ContextInfo<'a, 'b, 'c> { crate root_path: &'c str, } +crate type DecorationInfo = FxHashMap<&'static str, Vec<(u32, u32)>>; + /// Highlights `src`, returning the HTML output. crate fn render_with_highlighting( src: &str, @@ -40,6 +43,7 @@ crate fn render_with_highlighting( edition: Edition, extra_content: Option, context_info: Option>, + decoration_info: Option, ) { debug!("highlighting: ================\n{}\n==============", src); if let Some((edition_info, class)) = tooltip { @@ -56,7 +60,7 @@ crate fn render_with_highlighting( } write_header(out, class, extra_content); - write_code(out, &src, edition, context_info); + write_code(out, &src, edition, context_info, decoration_info); write_footer(out, playground_button); } @@ -89,17 +93,23 @@ fn write_code( src: &str, edition: Edition, context_info: Option>, + decoration_info: Option, ) { // This replace allows to fix how the code source with DOS backline characters is displayed. let src = src.replace("\r\n", "\n"); - Classifier::new(&src, edition, context_info.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP)) - .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), - }; - }); + Classifier::new( + &src, + edition, + context_info.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), + }; + }); } fn write_footer(out: &mut Buffer, playground_button: Option<&str>) { @@ -127,6 +137,7 @@ enum Class { PreludeTy, PreludeVal, QuestionMark, + Decoration(&'static str), } impl Class { @@ -150,6 +161,7 @@ impl Class { Class::PreludeTy => "prelude-ty", Class::PreludeVal => "prelude-val", Class::QuestionMark => "question-mark", + Class::Decoration(kind) => kind, } } @@ -244,7 +256,28 @@ impl Iterator for PeekIter<'a> { type Item = (TokenKind, &'a str); fn next(&mut self) -> Option { self.peek_pos = 0; - if let Some(first) = self.stored.pop_front() { Some(first) } else { self.iter.next() } + if let Some(first) = self.stored.pop_front() { + Some(first) + } else { + self.iter.next() + } + } +} + +/// Custom spans inserted into the source. Eg --scrape-examples uses this to highlight function calls +struct Decorations { + starts: Vec<(u32, &'static str)>, + ends: Vec, +} + +impl Decorations { + fn new(info: DecorationInfo) -> Self { + let (starts, ends) = info + .into_iter() + .map(|(kind, ranges)| ranges.into_iter().map(move |(lo, hi)| ((lo, kind), hi))) + .flatten() + .unzip(); + Decorations { starts, ends } } } @@ -259,12 +292,18 @@ struct Classifier<'a> { byte_pos: u32, file_span: Span, src: &'a str, + decorations: Option, } impl<'a> Classifier<'a> { /// Takes as argument the source code to HTML-ify, the rust edition to use and the source code /// file span which will be used later on by the `span_correspondance_map`. - fn new(src: &str, edition: Edition, file_span: Span) -> Classifier<'_> { + fn new( + src: &str, + edition: Edition, + file_span: Span, + decoration_info: Option, + ) -> Classifier<'_> { let tokens = PeekIter::new(TokenIter { src }); Classifier { tokens, @@ -275,6 +314,7 @@ impl<'a> Classifier<'a> { byte_pos: 0, file_span, src, + decorations, } } @@ -356,6 +396,19 @@ impl<'a> Classifier<'a> { /// token is used. fn highlight(mut self, sink: &mut dyn FnMut(Highlight<'a>)) { loop { + if let Some(decs) = self.decorations.as_mut() { + let byte_pos = self.byte_pos; + let n_starts = decs.starts.iter().filter(|(i, _)| byte_pos >= *i).count(); + for (_, kind) in decs.starts.drain(0..n_starts) { + sink(Highlight::EnterSpan { class: Class::Decoration(kind) }); + } + + let n_ends = decs.ends.iter().filter(|i| byte_pos >= **i).count(); + for _ in decs.ends.drain(0..n_ends) { + sink(Highlight::ExitSpan); + } + } + if self .tokens .peek() diff --git a/src/librustdoc/html/highlight/tests.rs b/src/librustdoc/html/highlight/tests.rs index 450bbfea1ea..405bdf0d810 100644 --- a/src/librustdoc/html/highlight/tests.rs +++ b/src/librustdoc/html/highlight/tests.rs @@ -22,7 +22,7 @@ fn test_html_highlighting() { let src = include_str!("fixtures/sample.rs"); let html = { let mut out = Buffer::new(); - write_code(&mut out, src, Edition::Edition2018, None); + write_code(&mut out, src, Edition::Edition2018, None, None); format!("{}
{}
\n", STYLE, out.into_inner()) }; expect_file!["fixtures/sample.html"].assert_eq(&html); @@ -36,7 +36,7 @@ fn test_dos_backline() { println!(\"foo\");\r\n\ }\r\n"; let mut html = Buffer::new(); - write_code(&mut html, src, Edition::Edition2018, None); + write_code(&mut html, src, Edition::Edition2018, None, None); expect_file!["fixtures/dos_line.html"].assert_eq(&html.into_inner()); }); } @@ -50,7 +50,7 @@ let x = super::b::foo; let y = Self::whatever;"; let mut html = Buffer::new(); - write_code(&mut html, src, Edition::Edition2018, None); + write_code(&mut html, src, Edition::Edition2018, None, None); expect_file!["fixtures/highlight.html"].assert_eq(&html.into_inner()); }); } diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 9f2e282fce1..a0f13dd71a5 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -360,6 +360,7 @@ impl<'a, I: Iterator>> Iterator for CodeBlocks<'_, 'a, I> { edition, None, None, + None, ); Some(Event::Html(s.into_inner().into())) } diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index b50aab6351c..24e50ef91ab 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -46,7 +46,7 @@ use std::string::ToString; use rustc_ast_pretty::pprust; use rustc_attr::{ConstStability, Deprecation, StabilityLevel}; -use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir as hir; use rustc_hir::def::CtorKind; use rustc_hir::def_id::DefId; @@ -2496,23 +2496,28 @@ 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); let min_loc = - call_data.locations.iter().min_by_key(|loc| loc.enclosing_item_span.0).unwrap(); - let min_byte = min_loc.enclosing_item_span.0; - let min_line = min_loc.enclosing_item_lines.0; + 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 = - call_data.locations.iter().map(|loc| loc.enclosing_item_span.1).max().unwrap(); + 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..max_byte]; + let contents_subset = &contents[(min_byte as usize)..(max_byte 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. - let locations = call_data + let (byte_ranges, line_ranges): (Vec<_>, Vec<_>) = call_data .locations .iter() - .map(|loc| (loc.call_span.0 - min_byte, loc.call_span.1 - min_byte)) - .collect::>(); + .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)) + }) + .unzip(); let edition = cx.shared.edition(); write!( @@ -2524,7 +2529,7 @@ fn render_call_locations( // The code and locations are encoded as data attributes, so they can be read // later by the JS for interactions. code = contents_subset.replace("\"", """), - locations = serde_json::to_string(&locations).unwrap(), + locations = serde_json::to_string(&line_ranges).unwrap(), ); write!(w, r#" "#); write!(w, r#""#); @@ -2532,7 +2537,18 @@ fn render_call_locations( // FIXME(wcrichto): where should file_span and root_path come from? let file_span = rustc_span::DUMMY_SP; let root_path = "".to_string(); - sources::print_src(w, contents_subset, edition, file_span, cx, &root_path, Some(min_line)); + let mut decoration_info = FxHashMap::default(); + decoration_info.insert("highlight", byte_ranges); + sources::print_src( + w, + contents_subset, + edition, + file_span, + cx, + &root_path, + Some(min_line), + Some(decoration_info), + ); write!(w, ""); }; @@ -2542,7 +2558,8 @@ fn render_call_locations( // understand at a glance. let ordered_locations = { let sort_criterion = |(_, call_data): &(_, &CallData)| { - let (lo, hi) = call_data.locations[0].enclosing_item_span; + // Use the first location because that's what the user will see initially + let (lo, hi) = call_data.locations[0].enclosing_item.byte_span; hi - lo }; diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 1275fa4e156..a9dce1be0d8 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -1117,6 +1117,7 @@ fn item_macro(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Mac it.span(cx.tcx()).inner().edition(), None, None, + None, ); }); document(w, cx, it, None, HeadingOffset::H2) diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs index 6bd335a9b96..c3441036d50 100644 --- a/src/librustdoc/html/sources.rs +++ b/src/librustdoc/html/sources.rs @@ -212,6 +212,7 @@ impl SourceCollector<'_, 'tcx> { &self.cx, &root_path, None, + None, ) }, &self.cx.shared.style_files, @@ -259,6 +260,7 @@ crate fn print_src( context: &Context<'_>, root_path: &str, offset: Option, + decoration_info: Option, ) { let lines = s.lines().count(); let mut line_numbers = Buffer::empty_from(buf); @@ -283,5 +285,6 @@ crate fn print_src( edition, Some(line_numbers), Some(highlight::ContextInfo { context, file_span, root_path }), + decoration_info, ); } diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 2767f6468fb..ccb6bb79868 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -137,7 +137,7 @@ h1.fqn { margin-top: 0; /* workaround to keep flex from breaking below 700 px width due to the float: right on the nav - above the h1 */ + above the h1 */ padding-left: 1px; } h1.fqn > .in-band > a:hover { @@ -974,7 +974,7 @@ body.blur > :not(#help) { text-shadow: 1px 0 0 black, -1px 0 0 black, - 0 1px 0 black, + 0 1px 0 black, 0 -1px 0 black; } @@ -1214,8 +1214,8 @@ a.test-arrow:hover{ .notable-traits-tooltip::after { /* The margin on the tooltip does not capture hover events, - this extends the area of hover enough so that mouse hover is not - lost when moving the mouse to the tooltip */ + this extends the area of hover enough so that mouse hover is not + lost when moving the mouse to the tooltip */ content: "\00a0\00a0\00a0"; } @@ -1715,7 +1715,7 @@ details.undocumented[open] > summary::before { } /* We do NOT hide this element so that alternative device readers still have this information - available. */ + available. */ .sidebar-elems { position: fixed; z-index: 1; @@ -1971,7 +1971,8 @@ details.undocumented[open] > summary::before { } } -/* This part is for the new "examples" components */ + +/* Begin: styles for --scrape-examples feature */ .scraped-example-title { font-family: 'Fira Sans'; @@ -2063,16 +2064,17 @@ details.undocumented[open] > summary::before { overflow-y: hidden; } -.scraped-example .line-numbers span.highlight { - background: #f6fdb0; +.scraped-example .example-wrap .rust span.highlight { + background: #fcffd6; } -.scraped-example .example-wrap .rust span.highlight { +.scraped-example .example-wrap .rust span.highlight.focus { background: #f6fdb0; } .more-examples-toggle summary { color: #999; + font-family: 'Fira Sans'; } .more-scraped-examples { @@ -2115,3 +2117,5 @@ h1 + .scraped-example { .example-links ul { margin-bottom: 0; } + +/* End: styles for --scrape-examples feature */ diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index a52e539fbd3..1b924991139 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -980,154 +980,55 @@ function hideThemeButtonState() { window.addEventListener("hashchange", onHashChange); searchState.setup(); - /////// EXAMPLE ANALYZER - - // Merge the full set of [from, to] offsets into a minimal set of non-overlapping - // [from, to] offsets. - // NB: This is such a archetypal software engineering interview question that - // I can't believe I actually had to write it. Yes, it's O(N) in the input length -- - // but it does assume a sorted input! - function distinctRegions(locs) { - var start = -1; - var end = -1; - var output = []; - for (var i = 0; i < locs.length; i++) { - var loc = locs[i]; - if (loc[0] > end) { - if (end > 0) { - output.push([start, end]); - } - start = loc[0]; - end = loc[1]; - } else { - end = Math.max(end, loc[1]); - } - } - if (end > 0) { - output.push([start, end]); - } - return output; - } - - function convertLocsStartsToLineOffsets(code, locs) { - locs = distinctRegions(locs.slice(0).sort(function (a, b) { - return a[0] === b[0] ? a[1] - b[1] : a[0] - b[0]; - })); // sort by start; use end if start is equal. - var codeLines = code.split("\n"); - var lineIndex = 0; - var totalOffset = 0; - var output = []; - - while (locs.length > 0 && lineIndex < codeLines.length) { - // +1 here and later is due to omitted \n - var lineLength = codeLines[lineIndex].length + 1; - while (locs.length > 0 && totalOffset + lineLength > locs[0][0]) { - var endIndex = lineIndex; - var charsRemaining = locs[0][1] - totalOffset; - while (endIndex < codeLines.length && - charsRemaining > codeLines[endIndex].length + 1) - { - charsRemaining -= codeLines[endIndex].length + 1; - endIndex += 1; - } - output.push({ - from: [lineIndex, locs[0][0] - totalOffset], - to: [endIndex, charsRemaining] - }); - locs.shift(); - } - lineIndex++; - totalOffset += lineLength; - } - return output; - } - - // inserts str into html, *but* calculates idx by eliding anything in html that's not in raw. - // ideally this would work by walking the element tree...but this is good enough for now. - function insertStrAtRawIndex(raw, html, idx, str) { - if (idx > raw.length) { - return html; - } - if (idx == raw.length) { - return html + str; - } - var rawIdx = 0; - var htmlIdx = 0; - while (rawIdx < idx && rawIdx < raw.length) { - while (raw[rawIdx] !== html[htmlIdx] && htmlIdx < html.length) { - htmlIdx++; - } - rawIdx++; - htmlIdx++; - } - return html.substring(0, htmlIdx) + str + html.substr(htmlIdx); - } + /////// --scrape-examples interactions // Scroll code block to put the given code location in the middle of the viewer function scrollToLoc(elt, loc) { var wrapper = elt.querySelector(".code-wrapper"); var halfHeight = wrapper.offsetHeight / 2; var lines = elt.querySelector('.line-numbers'); - var offsetMid = (lines.children[loc.from[0]].offsetTop - + lines.children[loc.to[0]].offsetTop) / 2; + var offsetMid = (lines.children[loc[0]].offsetTop + + lines.children[loc[1]].offsetTop) / 2; var scrollOffset = offsetMid - halfHeight; lines.scrollTo(0, scrollOffset); elt.querySelector(".rust").scrollTo(0, scrollOffset); } function updateScrapedExample(example) { - var code = example.attributes.getNamedItem("data-code").textContent; - var codeLines = code.split("\n"); var locs = JSON.parse(example.attributes.getNamedItem("data-locs").textContent); - locs = convertLocsStartsToLineOffsets(code, locs); - // Add call-site highlights to code listings - var litParent = example.querySelector('.example-wrap pre.rust'); - var litHtml = litParent.innerHTML.split("\n"); - onEach(locs, function (loc) { - for (var i = loc.from[0]; i < loc.to[0] + 1; i++) { - addClass(example.querySelector('.line-numbers').children[i], "highlight"); - } - litHtml[loc.to[0]] = insertStrAtRawIndex( - codeLines[loc.to[0]], - litHtml[loc.to[0]], - loc.to[1], - ""); - litHtml[loc.from[0]] = insertStrAtRawIndex( - codeLines[loc.from[0]], - litHtml[loc.from[0]], - loc.from[1], - ''); - }, true); // do this backwards to avoid shifting later offsets - litParent.innerHTML = litHtml.join('\n'); - - // Toggle through list of examples in a given file var locIndex = 0; + var highlights = example.querySelectorAll('.highlight'); + addClass(highlights[0], 'focus'); if (locs.length > 1) { + // Toggle through list of examples in a given file + var onChangeLoc = function(f) { + removeClass(highlights[locIndex], 'focus'); + f(); + scrollToLoc(example, locs[locIndex]); + addClass(highlights[locIndex], 'focus'); + }; example.querySelector('.prev') - .addEventListener('click', function () { - locIndex = (locIndex - 1 + locs.length) % locs.length; - scrollToLoc(example, locs[locIndex]); + .addEventListener('click', function() { + onChangeLoc(function() { + locIndex = (locIndex - 1 + locs.length) % locs.length; + }); }); example.querySelector('.next') - .addEventListener('click', function () { - locIndex = (locIndex + 1) % locs.length; - scrollToLoc(example, locs[locIndex]); + .addEventListener('click', function() { + onChangeLoc(function() { locIndex = (locIndex + 1) % locs.length; }); }); } else { + // Remove buttons if there's only one example in the file example.querySelector('.prev').remove(); example.querySelector('.next').remove(); } - let codeEl = example.querySelector('.rust'); - let expandButton = example.querySelector('.expand'); - if (codeEl.scrollHeight == codeEl.clientHeight) { - addClass(example, 'expanded'); - expandButton.remove(); - } else { - // Show full code on expansion + var codeEl = example.querySelector('.rust'); + var codeOverflows = codeEl.scrollHeight > codeEl.clientHeight; + var expandButton = example.querySelector('.expand'); + if (codeOverflows) { + // If file is larger than default height, give option to expand the viewer expandButton.addEventListener('click', function () { if (hasClass(example, "expanded")) { removeClass(example, "expanded"); @@ -1136,6 +1037,10 @@ function hideThemeButtonState() { addClass(example, "expanded"); } }); + } else { + // Otherwise remove expansion buttons + addClass(example, 'expanded'); + expandButton.remove(); } // Start with the first example in view @@ -1146,6 +1051,8 @@ function hideThemeButtonState() { var firstExamples = document.querySelectorAll('.scraped-example-list > .scraped-example'); onEach(firstExamples, updateScrapedExample); onEach(document.querySelectorAll('.more-examples-toggle'), function(toggle) { + // Allow users to click the left border of the
section to close it, + // since the section can be large and finding the [+] button is annoying. toggle.querySelector('.toggle-line').addEventListener('click', function() { toggle.open = false; }); diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs index 16a40ed1cb3..3887647ca0a 100644 --- a/src/librustdoc/scrape_examples.rs +++ b/src/librustdoc/scrape_examples.rs @@ -1,5 +1,4 @@ -//! This module analyzes crates to find examples of uses for items in the -//! current crate being documented. +//! This module analyzes crates to find call sites that can serve as examples in the documentation. use crate::clean; use crate::config; @@ -11,20 +10,55 @@ use rustc_data_structures::fx::FxHashMap; use rustc_hir::{ self as hir, intravisit::{self, Visitor}, + HirId, }; use rustc_interface::interface; use rustc_middle::hir::map::Map; use rustc_middle::ty::{self, TyCtxt}; -use rustc_span::{def_id::DefId, FileName}; +use rustc_span::{def_id::DefId, BytePos, FileName, SourceFile}; use serde::{Deserialize, Serialize}; use std::fs; use std::path::PathBuf; +#[derive(Serialize, Deserialize, Debug, Clone)] +crate struct SyntaxRange { + crate byte_span: (u32, u32), + crate line_span: (usize, usize), +} + +impl SyntaxRange { + fn new(span: rustc_span::Span, file: &SourceFile) -> Self { + let get_pos = |bytepos: BytePos| file.original_relative_byte_pos(bytepos).0; + let get_line = |bytepos: BytePos| file.lookup_line(bytepos).unwrap(); + + SyntaxRange { + byte_span: (get_pos(span.lo()), get_pos(span.hi())), + line_span: (get_line(span.lo()), get_line(span.hi())), + } + } +} + #[derive(Serialize, Deserialize, Debug, Clone)] crate struct CallLocation { - crate call_span: (usize, usize), - crate enclosing_item_span: (usize, usize), - crate enclosing_item_lines: (usize, usize), + crate call_expr: SyntaxRange, + crate enclosing_item: SyntaxRange, +} + +impl CallLocation { + fn new( + tcx: TyCtxt<'_>, + expr_span: rustc_span::Span, + expr_id: HirId, + source_file: &rustc_span::SourceFile, + ) -> Self { + let enclosing_item_span = tcx.hir().span_with_body(tcx.hir().get_parent_item(expr_id)); + assert!(enclosing_item_span.contains(expr_span)); + + CallLocation { + call_expr: SyntaxRange::new(expr_span, source_file), + enclosing_item: SyntaxRange::new(enclosing_item_span, source_file), + } + } } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -96,24 +130,10 @@ where _ => None, }; - let get_pos = - |bytepos: rustc_span::BytePos| file.original_relative_byte_pos(bytepos).0 as usize; - let get_range = |span: rustc_span::Span| (get_pos(span.lo()), get_pos(span.hi())); - let get_line = |bytepos: rustc_span::BytePos| file.lookup_line(bytepos).unwrap(); - let get_lines = |span: rustc_span::Span| (get_line(span.lo()), get_line(span.hi())); - if let Some(file_path) = file_path { let abs_path = fs::canonicalize(file_path.clone()).unwrap(); let cx = &self.cx; - let enclosing_item_span = - self.tcx.hir().span_with_body(self.tcx.hir().get_parent_item(ex.hir_id)); - assert!(enclosing_item_span.contains(span)); - - let location = CallLocation { - call_span: get_range(span), - enclosing_item_span: get_range(enclosing_item_span), - enclosing_item_lines: get_lines(enclosing_item_span), - }; + let location = CallLocation::new(self.tcx, span, ex.hir_id, &file); entries .entry(abs_path)