From 7e81b0a3173470cbc77273b2f984913dd3c02ef8 Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Sat, 22 Jan 2022 15:32:19 -0800 Subject: [PATCH 01/15] Improve Rustdoc UI for scraped examples with multiline arguments, fix overflow in line numbers --- src/librustdoc/html/render/mod.rs | 32 +++++++++++++++++-- src/librustdoc/html/static/css/rustdoc.css | 11 +++---- .../html/static/js/scrape-examples.js | 26 +++++++++++---- 3 files changed, 54 insertions(+), 15 deletions(-) diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 3666767a9d9cb..23ce3b131bbd7 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -2717,6 +2717,30 @@ fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) { // The output code is limited to that byte range. let contents_subset = &contents[(byte_min as usize)..(byte_max as usize)]; + // Given a call-site range, return the set of sub-ranges that exclude leading whitespace + // when the range spans multiple lines. + let strip_leading_whitespace = |(lo, hi): (u32, u32)| -> Vec<(u32, u32)> { + let contents_range = &contents_subset[(lo as usize)..(hi as usize)]; + let mut ignoring_whitespace = false; + let mut ranges = Vec::new(); + let mut cur_lo = 0; + for (idx, chr) in contents_range.char_indices() { + let idx = idx as u32; + if ignoring_whitespace { + if !chr.is_whitespace() { + ignoring_whitespace = false; + cur_lo = idx; + } + } else if chr == '\n' { + ranges.push((lo + cur_lo, lo + idx)); + cur_lo = idx; + ignoring_whitespace = true; + } + } + ranges.push((lo + cur_lo, hi)); + ranges + }; + // The call locations need to be updated to reflect that the size of the program has changed. // Specifically, the ranges are all subtracted by `byte_min` since that's the new zero point. let (mut byte_ranges, line_ranges): (Vec<_>, Vec<_>) = call_data @@ -2726,10 +2750,12 @@ fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) { let (byte_lo, byte_hi) = loc.call_expr.byte_span; let (line_lo, line_hi) = loc.call_expr.line_span; let byte_range = (byte_lo - byte_min, byte_hi - byte_min); + let byte_ranges = strip_leading_whitespace(byte_range); + let line_range = (line_lo - line_min, line_hi - line_min); let (line_url, line_title) = link_to_loc(call_data, loc); - (byte_range, (line_range, line_url, line_title)) + (byte_ranges, (line_range, line_url, line_title)) }) .unzip(); @@ -2784,8 +2810,8 @@ fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) { let root_path = vec!["../"; cx.current.len() - 1].join(""); let mut decoration_info = FxHashMap::default(); - decoration_info.insert("highlight focus", vec![byte_ranges.remove(0)]); - decoration_info.insert("highlight", byte_ranges); + decoration_info.insert("highlight focus", byte_ranges.remove(0)); + decoration_info.insert("highlight", byte_ranges.into_iter().flatten().collect()); sources::print_src( w, diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index ee265b8c4b543..42b66c70c4cb4 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -2038,17 +2038,16 @@ details.rustdoc-toggle[open] > summary.hideme::after { font-family: 'Fira Sans'; } -.scraped-example:not(.expanded) .code-wrapper pre.line-numbers { - overflow: hidden; - max-height: 240px; -} - -.scraped-example:not(.expanded) .code-wrapper .example-wrap pre.rust { +.scraped-example:not(.expanded) .code-wrapper pre { overflow-y: hidden; max-height: 240px; padding-bottom: 0; } +.scraped-example:not(.expanded) .code-wrapper pre.line-numbers { + overflow-x: hidden; +} + .scraped-example .code-wrapper .prev { position: absolute; top: 0.25em; diff --git a/src/librustdoc/html/static/js/scrape-examples.js b/src/librustdoc/html/static/js/scrape-examples.js index 664b187e33e9f..383ae001bc21f 100644 --- a/src/librustdoc/html/static/js/scrape-examples.js +++ b/src/librustdoc/html/static/js/scrape-examples.js @@ -1,14 +1,28 @@ /* global addClass, hasClass, removeClass, onEach */ (function () { - // Scroll code block to put the given code location in the middle of the viewer + // Number of lines shown when code viewer is not expanded + const MAX_LINES = 10; + + // Scroll code block to the given code location 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[0]].offsetTop - + lines.children[loc[1]].offsetTop) / 2; - var scrollOffset = offsetMid - halfHeight; + var scrollOffset; + + // If the block is greater than the size of the viewer, + // then scroll to the top of the block. Otherwise scroll + // to the middle of the block. + if (loc[1] - loc[0] > MAX_LINES) { + var line = Math.max(0, loc[0] - 1); + scrollOffset = lines.children[line].offsetTop; + } else { + var wrapper = elt.querySelector(".code-wrapper"); + var halfHeight = wrapper.offsetHeight / 2; + var offsetMid = (lines.children[loc[0]].offsetTop + + lines.children[loc[1]].offsetTop) / 2; + scrollOffset = offsetMid - halfHeight; + } + lines.scrollTo(0, scrollOffset); elt.querySelector(".rust").scrollTo(0, scrollOffset); } From d58c9dfdb910089afc1296142bae21634ce5eb4b Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Mon, 24 Jan 2022 17:32:33 -0800 Subject: [PATCH 02/15] Only highlight identifier in scraped examples, not arguments --- src/librustdoc/html/render/mod.rs | 33 ++--------- src/librustdoc/scrape_examples.rs | 55 +++++++++++++------ .../src/lib.rs | 5 +- 3 files changed, 44 insertions(+), 49 deletions(-) diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 23ce3b131bbd7..6b57ff5eeba32 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -2717,45 +2717,20 @@ fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) { // The output code is limited to that byte range. let contents_subset = &contents[(byte_min as usize)..(byte_max as usize)]; - // Given a call-site range, return the set of sub-ranges that exclude leading whitespace - // when the range spans multiple lines. - let strip_leading_whitespace = |(lo, hi): (u32, u32)| -> Vec<(u32, u32)> { - let contents_range = &contents_subset[(lo as usize)..(hi as usize)]; - let mut ignoring_whitespace = false; - let mut ranges = Vec::new(); - let mut cur_lo = 0; - for (idx, chr) in contents_range.char_indices() { - let idx = idx as u32; - if ignoring_whitespace { - if !chr.is_whitespace() { - ignoring_whitespace = false; - cur_lo = idx; - } - } else if chr == '\n' { - ranges.push((lo + cur_lo, lo + idx)); - cur_lo = idx; - ignoring_whitespace = true; - } - } - ranges.push((lo + cur_lo, hi)); - ranges - }; - // The call locations need to be updated to reflect that the size of the program has changed. // Specifically, the ranges are all subtracted by `byte_min` since that's the new zero point. let (mut byte_ranges, line_ranges): (Vec<_>, Vec<_>) = call_data .locations .iter() .map(|loc| { - let (byte_lo, byte_hi) = loc.call_expr.byte_span; + let (byte_lo, byte_hi) = loc.call_ident.byte_span; let (line_lo, line_hi) = loc.call_expr.line_span; let byte_range = (byte_lo - byte_min, byte_hi - byte_min); - let byte_ranges = strip_leading_whitespace(byte_range); let line_range = (line_lo - line_min, line_hi - line_min); let (line_url, line_title) = link_to_loc(call_data, loc); - (byte_ranges, (line_range, line_url, line_title)) + (byte_range, (line_range, line_url, line_title)) }) .unzip(); @@ -2810,8 +2785,8 @@ fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) { let root_path = vec!["../"; cx.current.len() - 1].join(""); let mut decoration_info = FxHashMap::default(); - decoration_info.insert("highlight focus", byte_ranges.remove(0)); - decoration_info.insert("highlight", byte_ranges.into_iter().flatten().collect()); + decoration_info.insert("highlight focus", vec![byte_ranges.remove(0)]); + decoration_info.insert("highlight", byte_ranges); sources::print_src( w, diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs index 7cf0ea9e84e4e..21af26a1c913e 100644 --- a/src/librustdoc/scrape_examples.rs +++ b/src/librustdoc/scrape_examples.rs @@ -85,17 +85,20 @@ impl SyntaxRange { #[derive(Encodable, Decodable, Debug, Clone)] crate struct CallLocation { crate call_expr: SyntaxRange, + crate call_ident: SyntaxRange, crate enclosing_item: SyntaxRange, } impl CallLocation { fn new( expr_span: rustc_span::Span, + ident_span: rustc_span::Span, enclosing_item_span: rustc_span::Span, source_file: &SourceFile, ) -> Self { CallLocation { call_expr: SyntaxRange::new(expr_span, source_file), + call_ident: SyntaxRange::new(ident_span, source_file), enclosing_item: SyntaxRange::new(enclosing_item_span, source_file), } } @@ -146,24 +149,39 @@ where } // Get type of function if expression is a function call - let (ty, span) = match ex.kind { + let (ty, call_span, ident_span) = match ex.kind { hir::ExprKind::Call(f, _) => { let types = tcx.typeck(ex.hir_id.owner); if let Some(ty) = types.node_type_opt(f.hir_id) { - (ty, ex.span) + (ty, ex.span, f.span) } else { trace!("node_type_opt({}) = None", f.hir_id); return; } } - hir::ExprKind::MethodCall(_, _, span) => { + hir::ExprKind::MethodCall(_, args, call_span) => { let types = tcx.typeck(ex.hir_id.owner); let Some(def_id) = types.type_dependent_def_id(ex.hir_id) else { trace!("type_dependent_def_id({}) = None", ex.hir_id); return; }; - (tcx.type_of(def_id), span) + + // The MethodCall node doesn't directly contain a span for the + // method identifier, so we have to compute it by trimming the full + // span based on the arguments. + let ident_span = match args.get(1) { + // If there is an argument, e.g. "f(x)", then + // get the span "f(" and delete the lparen. + Some(arg) => { + let with_paren = call_span.until(arg.span); + with_paren.with_hi(with_paren.hi() - BytePos(1)) + } + // Otherwise, just delete both parens directly. + None => call_span.with_hi(call_span.hi() - BytePos(2)), + }; + + (tcx.type_of(def_id), call_span, ident_span) } _ => { return; @@ -172,8 +190,8 @@ where // If this span comes from a macro expansion, then the source code may not actually show // a use of the given item, so it would be a poor example. Hence, we skip all uses in macros. - if span.from_expansion() { - trace!("Rejecting expr from macro: {:?}", span); + if call_span.from_expansion() { + trace!("Rejecting expr from macro: {:?}", call_span); return; } @@ -183,26 +201,29 @@ where .hir() .span_with_body(tcx.hir().local_def_id_to_hir_id(tcx.hir().get_parent_item(ex.hir_id))); if enclosing_item_span.from_expansion() { - trace!("Rejecting expr ({:?}) from macro item: {:?}", span, enclosing_item_span); + trace!("Rejecting expr ({:?}) from macro item: {:?}", call_span, enclosing_item_span); return; } assert!( - enclosing_item_span.contains(span), - "Attempted to scrape call at [{:?}] whose enclosing item [{:?}] doesn't contain the span of the call.", - span, - enclosing_item_span + enclosing_item_span.contains(call_span), + "Attempted to scrape call at [{call_span:?}] whose enclosing item [{enclosing_item_span:?}] doesn't contain the span of the call.", + ); + + assert!( + call_span.contains(ident_span), + "Attempted to scrape call at [{call_span:?}] whose identifier [{ident_span:?}] was not contained in the span of the call." ); // Save call site if the function resolves to a concrete definition if let ty::FnDef(def_id, _) = ty.kind() { if self.target_crates.iter().all(|krate| *krate != def_id.krate) { - trace!("Rejecting expr from crate not being documented: {:?}", span); + trace!("Rejecting expr from crate not being documented: {call_span:?}"); return; } let source_map = tcx.sess.source_map(); - let file = source_map.lookup_char_pos(span.lo()).file; + let file = source_map.lookup_char_pos(call_span.lo()).file; let file_path = match file.name.clone() { FileName::Real(real_filename) => real_filename.into_local_path(), _ => None, @@ -212,20 +233,20 @@ where let abs_path = fs::canonicalize(file_path.clone()).unwrap(); let cx = &self.cx; let mk_call_data = || { - let clean_span = crate::clean::types::Span::new(span); + let clean_span = crate::clean::types::Span::new(call_span); let url = cx.href_from_span(clean_span, false).unwrap(); let display_name = file_path.display().to_string(); - let edition = span.edition(); + let edition = call_span.edition(); CallData { locations: Vec::new(), url, display_name, edition } }; let fn_key = tcx.def_path_hash(*def_id); let fn_entries = self.calls.entry(fn_key).or_default(); - trace!("Including expr: {:?}", span); + trace!("Including expr: {:?}", call_span); let enclosing_item_span = source_map.span_extend_to_prev_char(enclosing_item_span, '\n', false); - let location = CallLocation::new(span, enclosing_item_span, &file); + let location = CallLocation::new(call_span, ident_span, enclosing_item_span, &file); fn_entries.entry(abs_path).or_insert_with(mk_call_data).locations.push(location); } } diff --git a/src/test/run-make/rustdoc-scrape-examples-ordering/src/lib.rs b/src/test/run-make/rustdoc-scrape-examples-ordering/src/lib.rs index 5afffffdf9976..7bd57609fa224 100644 --- a/src/test/run-make/rustdoc-scrape-examples-ordering/src/lib.rs +++ b/src/test/run-make/rustdoc-scrape-examples-ordering/src/lib.rs @@ -1,7 +1,6 @@ // @has foobar/fn.ok.html '//*[@class="docblock scraped-example-list"]' 'ex2' // @has foobar/fn.ok.html '//*[@class="more-scraped-examples"]' 'ex1' -// @has foobar/fn.ok.html '//*[@class="highlight focus"]' '1' -// @has foobar/fn.ok.html '//*[@class="highlight"]' '2' -// @has foobar/fn.ok.html '//*[@class="highlight focus"]' '0' +// @has foobar/fn.ok.html '//*[@class="highlight focus"]' '' +// @has foobar/fn.ok.html '//*[@class="highlight"]' '' pub fn ok(_x: i32) {} From f1e3e2c366b05f232c6b2225af7fc155925029df Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Tue, 25 Jan 2022 12:11:44 -0800 Subject: [PATCH 03/15] Use PathSegment::ident for scraping method calls --- src/librustdoc/scrape_examples.rs | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs index 21af26a1c913e..fee42de746d57 100644 --- a/src/librustdoc/scrape_examples.rs +++ b/src/librustdoc/scrape_examples.rs @@ -160,27 +160,14 @@ where return; } } - hir::ExprKind::MethodCall(_, args, call_span) => { + hir::ExprKind::MethodCall(path, _, call_span) => { let types = tcx.typeck(ex.hir_id.owner); let Some(def_id) = types.type_dependent_def_id(ex.hir_id) else { trace!("type_dependent_def_id({}) = None", ex.hir_id); return; }; - // The MethodCall node doesn't directly contain a span for the - // method identifier, so we have to compute it by trimming the full - // span based on the arguments. - let ident_span = match args.get(1) { - // If there is an argument, e.g. "f(x)", then - // get the span "f(" and delete the lparen. - Some(arg) => { - let with_paren = call_span.until(arg.span); - with_paren.with_hi(with_paren.hi() - BytePos(1)) - } - // Otherwise, just delete both parens directly. - None => call_span.with_hi(call_span.hi() - BytePos(2)), - }; - + let ident_span = path.ident.span; (tcx.type_of(def_id), call_span, ident_span) } _ => { From ae5d0cbe74a07baef9eb92dde82b28feea8961cd Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Thu, 27 Jan 2022 17:00:31 -0800 Subject: [PATCH 04/15] Improve alignment of additional scraped examples, add scrape examples help page --- src/doc/rustdoc/src/SUMMARY.md | 1 + src/doc/rustdoc/src/scraped-examples.md | 55 +++++++++++++ src/librustdoc/html/render/mod.rs | 2 + src/librustdoc/html/static/css/rustdoc.css | 77 +++++++++++-------- src/librustdoc/html/static/css/themes/ayu.css | 16 +++- .../html/static/css/themes/dark.css | 15 +++- .../html/static/css/themes/light.css | 30 ++++++++ .../html/static/js/scrape-examples.js | 6 +- .../src/lib.rs | 4 +- 9 files changed, 164 insertions(+), 42 deletions(-) create mode 100644 src/doc/rustdoc/src/scraped-examples.md diff --git a/src/doc/rustdoc/src/SUMMARY.md b/src/doc/rustdoc/src/SUMMARY.md index d627f5b0389f3..747cc629ba44b 100644 --- a/src/doc/rustdoc/src/SUMMARY.md +++ b/src/doc/rustdoc/src/SUMMARY.md @@ -9,6 +9,7 @@ - [Linking to items by name](write-documentation/linking-to-items-by-name.md) - [Documentation tests](write-documentation/documentation-tests.md) - [Rustdoc-specific lints](lints.md) +- [Scraped examples](scraped-examples.md) - [Advanced features](advanced-features.md) - [Unstable features](unstable-features.md) - [Deprecated features](deprecated-features.md) diff --git a/src/doc/rustdoc/src/scraped-examples.md b/src/doc/rustdoc/src/scraped-examples.md new file mode 100644 index 0000000000000..bab992ccc50ea --- /dev/null +++ b/src/doc/rustdoc/src/scraped-examples.md @@ -0,0 +1,55 @@ +# Scraped examples + +Rustdoc can automatically scrape examples of items being documented from the `examples/` directory of a Cargo workspace. These examples will be included within the generated documentation for that item. For example, if your library contains a public function: + +```rust,ignore(needs-other-file) +// a_crate/src/lib.rs +pub fn a_func() {} +``` + +And you have an example calling this function: + +```rust,ignore(needs-other-file) +// a_crate/examples/ex.rs +fn main() { + a_crate::a_func(); +} +``` + +Then this code snippet will be included in the documentation for `a_func`. This documentation is inserted by Rustdoc and cannot be manually edited by the crate author. + + +## How to use this feature + +This feature is unstable, so you can enable it by calling Rustdoc with the unstable `rustdoc-scrape-examples` flag: + +```bash +cargo doc -Zunstable-options -Zrustdoc-scrape-examples=examples +``` + +To enable this feature on [docs.rs](https://docs.rs), add this to your Cargo.toml: + +```toml +[package.metadata.docs.rs] +cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples=examples"] +``` + + +## How it works + +When you run `cargo doc`, Rustdoc will analyze all the crates that match Cargo's `--examples` filter for instances of items being documented. Then Rustdoc will include the source code of these instances in the generated documentation. + +Rustdoc has a few techniques to ensure these examples don't overwhelm documentation readers, and that it doesn't blow up the page size: + +1. For a given item, a maximum of 5 examples are included in the page. The remaining examples are just links to source code. +2. Only one example is shown by default, and the remaining examples are hidden behind a toggle. +3. For a given file that contains examples, only the item containing the examples will be included in the generated documentation. + +For a given item, Rustdoc sorts its examples based on the size of the example — smaller ones are shown first. + + +## FAQ + +### My example is not showing up in the documentation + +This feature uses Cargo's convention for finding examples. You should ensure that `cargo check --examples` includes your example file. diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 6b57ff5eeba32..b5536dc930faa 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -2671,6 +2671,7 @@ fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) { \
\ Examples found in repository\ + ?\
", id = id ); @@ -2842,6 +2843,7 @@ fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) { \ More examples\ \ +
Hide additional examples
\
\
\
" diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 42b66c70c4cb4..b278ab6b24d3b 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -618,7 +618,7 @@ h2.location a { position: relative; } -.docblock > :not(.information) { +.docblock > :not(.information):not(.more-examples-toggle) { max-width: 100%; overflow-x: auto; } @@ -836,8 +836,8 @@ h2.small-section-header > .anchor { content: 'ยง'; } -.docblock a:not(.srclink):not(.test-arrow):hover, -.docblock-short a:not(.srclink):not(.test-arrow):hover, .item-info a { +.docblock a:not(.srclink):not(.test-arrow):not(.scrape-help):hover, +.docblock-short a:not(.srclink):not(.test-arrow):not(.scrape-help):hover, .item-info a { text-decoration: underline; } @@ -2034,10 +2034,36 @@ details.rustdoc-toggle[open] > summary.hideme::after { /* Begin: styles for --scrape-examples feature */ +.scraped-example-list .section-header .scrape-help { + cursor: pointer; + border-radius: 2px; + margin-left: 10px; + padding: 0 4px; + font-weight: normal; + font-size: 12px; + position: relative; + bottom: 1px; + background: transparent; + border-width: 1px; + border-style: solid; +} + .scraped-example-title { font-family: 'Fira Sans'; } +.scraped-example .code-wrapper { + position: relative; + display: flex; + flex-direction: row; + flex-wrap: wrap; + width: 100%; +} + +.scraped-example:not(.expanded) .code-wrapper { + max-height: 240px; +} + .scraped-example:not(.expanded) .code-wrapper pre { overflow-y: hidden; max-height: 240px; @@ -2072,14 +2098,6 @@ details.rustdoc-toggle[open] > summary.hideme::after { cursor: pointer; } -.scraped-example .code-wrapper { - position: relative; - display: flex; - flex-direction: row; - flex-wrap: wrap; - width: 100%; -} - .scraped-example:not(.expanded) .code-wrapper:before { content: " "; width: 100%; @@ -2087,7 +2105,6 @@ details.rustdoc-toggle[open] > summary.hideme::after { position: absolute; z-index: 100; top: 0; - background: linear-gradient(to bottom, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0)); } .scraped-example:not(.expanded) .code-wrapper:after { @@ -2097,12 +2114,6 @@ details.rustdoc-toggle[open] > summary.hideme::after { position: absolute; z-index: 100; bottom: 0; - background: linear-gradient(to top, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0)); -} - -.scraped-example:not(.expanded) .code-wrapper { - overflow: hidden; - max-height: 240px; } .scraped-example .code-wrapper .line-numbers { @@ -2121,34 +2132,37 @@ details.rustdoc-toggle[open] > summary.hideme::after { margin-bottom: 0; } +.scraped-example:not(.expanded) .code-wrapper .example-wrap { + overflow-x: hidden; +} + .scraped-example .code-wrapper .example-wrap pre.rust { overflow-x: inherit; width: inherit; overflow-y: hidden; } -.scraped-example .example-wrap .rust span.highlight { - background: #fcffd6; -} - -.scraped-example .example-wrap .rust span.highlight.focus { - background: #f6fdb0; -} .more-examples-toggle { + max-width: calc(100% + 25px); margin-top: 10px; + margin-left: -25px; +} + +.more-examples-toggle .hide-more { + margin-left: 25px; + margin-bottom: 5px; + cursor: pointer; } -.more-examples-toggle summary { - color: #999; +.more-examples-toggle summary, .more-examples-toggle .hide-more { font-family: 'Fira Sans'; } .more-scraped-examples { - margin-left: 25px; + margin-left: 5px; display: flex; flex-direction: row; - width: calc(100% - 25px); } .more-scraped-examples-inner { @@ -2164,13 +2178,8 @@ details.rustdoc-toggle[open] > summary.hideme::after { cursor: pointer; } -.toggle-line:hover .toggle-line-inner { - background: #aaa; -} - .toggle-line-inner { min-width: 2px; - background: #ddd; height: 100%; } diff --git a/src/librustdoc/html/static/css/themes/ayu.css b/src/librustdoc/html/static/css/themes/ayu.css index e402b3583f399..0fee0ba54fa82 100644 --- a/src/librustdoc/html/static/css/themes/ayu.css +++ b/src/librustdoc/html/static/css/themes/ayu.css @@ -611,6 +611,18 @@ input:checked + .slider { background-color: #ffb454 !important; } + +.scraped-example-list .section-header .scrape-help { + border-color: #999; + color: #999; +} +.scraped-example-list .section-header .scrape-help:hover { + border-color: #c5c5c5; + color: #c5c5c5; +} +.more-examples-toggle summary, .more-examples-toggle .hide-more { + color: #999; +} .scraped-example .example-wrap .rust span.highlight { background: rgb(91, 59, 1); } @@ -624,8 +636,8 @@ input:checked + .slider { background: linear-gradient(to top, rgba(15, 20, 25, 1), rgba(15, 20, 25, 0)); } .toggle-line-inner { - background: #616161; + background: #999; } .toggle-line:hover .toggle-line-inner { - background: #898989; + background: #c5c5c5; } diff --git a/src/librustdoc/html/static/css/themes/dark.css b/src/librustdoc/html/static/css/themes/dark.css index 0a56055b8cbf6..e121c984dbddf 100644 --- a/src/librustdoc/html/static/css/themes/dark.css +++ b/src/librustdoc/html/static/css/themes/dark.css @@ -478,6 +478,17 @@ div.files > .selected { border-bottom-color: #ddd; } +.scraped-example-list .section-header .scrape-help { + border-color: #999; + color: #999; +} +.scraped-example-list .section-header .scrape-help:hover { + border-color: #c5c5c5; + color: #c5c5c5; +} +.more-examples-toggle summary, .more-examples-toggle .hide-more { + color: #999; +} .scraped-example .example-wrap .rust span.highlight { background: rgb(91, 59, 1); } @@ -491,8 +502,8 @@ div.files > .selected { background: linear-gradient(to top, rgba(53, 53, 53, 1), rgba(53, 53, 53, 0)); } .toggle-line-inner { - background: #616161; + background: #999; } .toggle-line:hover .toggle-line-inner { - background: #898989; + background: #c5c5c5; } diff --git a/src/librustdoc/html/static/css/themes/light.css b/src/librustdoc/html/static/css/themes/light.css index dc1715b2a78f3..be1da8df2b511 100644 --- a/src/librustdoc/html/static/css/themes/light.css +++ b/src/librustdoc/html/static/css/themes/light.css @@ -462,3 +462,33 @@ div.files > .selected { .setting-line > .title { border-bottom-color: #D5D5D5; } + +.scraped-example-list .section-header .scrape-help { + border-color: #999; + color: #999; +} +.scraped-example-list .section-header .scrape-help:hover { + border-color: black; + color: black; +} +.more-examples-toggle summary, .more-examples-toggle .hide-more { + color: #999; +} +.scraped-example .example-wrap .rust span.highlight { + background: #fcffd6; +} +.scraped-example .example-wrap .rust span.highlight.focus { + background: #f6fdb0; +} +.scraped-example:not(.expanded) .code-wrapper:before { + background: linear-gradient(to bottom, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0)); +} +.scraped-example:not(.expanded) .code-wrapper:after { + background: linear-gradient(to top, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0)); +} +.toggle-line-inner { + background: #ccc; +} +.toggle-line:hover .toggle-line-inner { + background: #999; +} diff --git a/src/librustdoc/html/static/js/scrape-examples.js b/src/librustdoc/html/static/js/scrape-examples.js index 383ae001bc21f..a28fb46172990 100644 --- a/src/librustdoc/html/static/js/scrape-examples.js +++ b/src/librustdoc/html/static/js/scrape-examples.js @@ -84,8 +84,10 @@ 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; + toggle.querySelectorAll('.toggle-line, .hide-more').forEach(button => { + button.addEventListener('click', function() { + toggle.open = false; + }); }); var moreExamples = toggle.querySelectorAll('.scraped-example'); diff --git a/src/test/run-make/rustdoc-scrape-examples-ordering/src/lib.rs b/src/test/run-make/rustdoc-scrape-examples-ordering/src/lib.rs index 7bd57609fa224..c53c987a7cbf0 100644 --- a/src/test/run-make/rustdoc-scrape-examples-ordering/src/lib.rs +++ b/src/test/run-make/rustdoc-scrape-examples-ordering/src/lib.rs @@ -1,6 +1,6 @@ // @has foobar/fn.ok.html '//*[@class="docblock scraped-example-list"]' 'ex2' // @has foobar/fn.ok.html '//*[@class="more-scraped-examples"]' 'ex1' -// @has foobar/fn.ok.html '//*[@class="highlight focus"]' '' -// @has foobar/fn.ok.html '//*[@class="highlight"]' '' +// @has foobar/fn.ok.html '//*[@class="highlight focus"]' 'ok' +// @has foobar/fn.ok.html '//*[@class="highlight"]' 'ok' pub fn ok(_x: i32) {} From 7cca69342a3f662041362bc988da851942dadb6c Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Thu, 27 Jan 2022 18:39:10 -0800 Subject: [PATCH 05/15] Fix markdown issue, remove hard-coded rust-lang.org url --- src/doc/rustdoc/src/scraped-examples.md | 4 ++-- src/librustdoc/html/render/mod.rs | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/doc/rustdoc/src/scraped-examples.md b/src/doc/rustdoc/src/scraped-examples.md index bab992ccc50ea..08f220af2a0c4 100644 --- a/src/doc/rustdoc/src/scraped-examples.md +++ b/src/doc/rustdoc/src/scraped-examples.md @@ -2,14 +2,14 @@ Rustdoc can automatically scrape examples of items being documented from the `examples/` directory of a Cargo workspace. These examples will be included within the generated documentation for that item. For example, if your library contains a public function: -```rust,ignore(needs-other-file) +```rust,ignore (needs-other-file) // a_crate/src/lib.rs pub fn a_func() {} ``` And you have an example calling this function: -```rust,ignore(needs-other-file) +```rust,ignore (needs-other-file) // a_crate/examples/ex.rs fn main() { a_crate::a_func(); diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index b5536dc930faa..c3e2eb81e45d2 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -62,6 +62,7 @@ use rustc_span::{ use serde::ser::SerializeSeq; use serde::{Serialize, Serializer}; +use crate::clean::utils::DOC_RUST_LANG_ORG_CHANNEL; use crate::clean::{self, ItemId, RenderedLink, SelfTy}; use crate::error::Error; use crate::formats::cache::Cache; @@ -2671,8 +2672,9 @@ fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) { \
\ Examples found in repository\ - ?\ + ?\
", + doc_prefix = DOC_RUST_LANG_ORG_CHANNEL, id = id ); From bb3ed6f7d6caddd3f2c26f2b5b218b2d750e8c3d Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Thu, 27 Jan 2022 18:51:34 -0800 Subject: [PATCH 06/15] Improve styling on scrape examples help button --- src/librustdoc/html/static/css/rustdoc.css | 3 +-- src/librustdoc/html/static/css/themes/ayu.css | 8 ++++---- src/librustdoc/html/static/css/themes/dark.css | 8 ++++---- src/librustdoc/html/static/css/themes/light.css | 4 ++-- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index b278ab6b24d3b..55a11652c2353 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -2035,8 +2035,6 @@ details.rustdoc-toggle[open] > summary.hideme::after { /* Begin: styles for --scrape-examples feature */ .scraped-example-list .section-header .scrape-help { - cursor: pointer; - border-radius: 2px; margin-left: 10px; padding: 0 4px; font-weight: normal; @@ -2046,6 +2044,7 @@ details.rustdoc-toggle[open] > summary.hideme::after { background: transparent; border-width: 1px; border-style: solid; + border-radius: 50px; } .scraped-example-title { diff --git a/src/librustdoc/html/static/css/themes/ayu.css b/src/librustdoc/html/static/css/themes/ayu.css index 0fee0ba54fa82..e073057528c79 100644 --- a/src/librustdoc/html/static/css/themes/ayu.css +++ b/src/librustdoc/html/static/css/themes/ayu.css @@ -613,12 +613,12 @@ input:checked + .slider { .scraped-example-list .section-header .scrape-help { - border-color: #999; - color: #999; + border-color: #aaa; + color: #eee; } .scraped-example-list .section-header .scrape-help:hover { - border-color: #c5c5c5; - color: #c5c5c5; + border-color: white; + color: white; } .more-examples-toggle summary, .more-examples-toggle .hide-more { color: #999; diff --git a/src/librustdoc/html/static/css/themes/dark.css b/src/librustdoc/html/static/css/themes/dark.css index e121c984dbddf..a10029b637177 100644 --- a/src/librustdoc/html/static/css/themes/dark.css +++ b/src/librustdoc/html/static/css/themes/dark.css @@ -479,12 +479,12 @@ div.files > .selected { } .scraped-example-list .section-header .scrape-help { - border-color: #999; - color: #999; + border-color: #aaa; + color: #eee; } .scraped-example-list .section-header .scrape-help:hover { - border-color: #c5c5c5; - color: #c5c5c5; + border-color: white; + color: white; } .more-examples-toggle summary, .more-examples-toggle .hide-more { color: #999; diff --git a/src/librustdoc/html/static/css/themes/light.css b/src/librustdoc/html/static/css/themes/light.css index be1da8df2b511..a30dcac66dbc5 100644 --- a/src/librustdoc/html/static/css/themes/light.css +++ b/src/librustdoc/html/static/css/themes/light.css @@ -464,8 +464,8 @@ div.files > .selected { } .scraped-example-list .section-header .scrape-help { - border-color: #999; - color: #999; + border-color: #555; + color: #333; } .scraped-example-list .section-header .scrape-help:hover { border-color: black; From 318e45767f068cd3e04d110a0451f31169ac119c Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Fri, 28 Jan 2022 18:18:52 -0800 Subject: [PATCH 07/15] Clarify that scrape examples is unstable --- src/doc/rustdoc/src/scraped-examples.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustdoc/src/scraped-examples.md b/src/doc/rustdoc/src/scraped-examples.md index 08f220af2a0c4..d75f6d522e8ed 100644 --- a/src/doc/rustdoc/src/scraped-examples.md +++ b/src/doc/rustdoc/src/scraped-examples.md @@ -1,6 +1,6 @@ # Scraped examples -Rustdoc can automatically scrape examples of items being documented from the `examples/` directory of a Cargo workspace. These examples will be included within the generated documentation for that item. For example, if your library contains a public function: +Rustdoc has an unstable feature where it can automatically scrape examples of items being documented from the `examples/` directory of a Cargo workspace. These examples will be included within the generated documentation for that item. For example, if your library contains a public function: ```rust,ignore (needs-other-file) // a_crate/src/lib.rs From d1416d528aa17623c15d51839a5cfeee7bfdbc35 Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Mon, 14 Feb 2022 19:49:39 -0800 Subject: [PATCH 08/15] Add scrape examples help page --- src/librustdoc/html/render/context.rs | 19 +++++++++-- src/librustdoc/html/render/mod.rs | 29 ++++++++++++++-- src/librustdoc/html/static/css/rustdoc.css | 2 +- src/librustdoc/html/static/css/themes/ayu.css | 4 +-- .../html/static/css/themes/dark.css | 4 +-- .../html/static/css/themes/light.css | 4 +-- .../html/static/scrape-examples-help.md | 34 +++++++++++++++++++ src/librustdoc/html/static_files.rs | 2 ++ 8 files changed, 86 insertions(+), 12 deletions(-) create mode 100644 src/librustdoc/html/static/scrape-examples-help.md diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 34784bbed0cf5..90123655758cc 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -17,8 +17,8 @@ use super::print_item::{full_path, item_path, print_item}; use super::search_index::build_index; use super::write_shared::write_shared; use super::{ - collect_spans_and_sources, print_sidebar, settings, AllTypes, LinkFromSrc, NameDoc, StylePath, - BASIC_KEYWORDS, + collect_spans_and_sources, print_sidebar, scrape_examples_help, settings, AllTypes, + LinkFromSrc, NameDoc, StylePath, BASIC_KEYWORDS, }; use crate::clean::{self, types::ExternalLocation, ExternalCrate}; @@ -551,6 +551,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { let crate_name = self.tcx().crate_name(LOCAL_CRATE); let final_file = self.dst.join(crate_name.as_str()).join("all.html"); let settings_file = self.dst.join("settings.html"); + let scrape_examples_help_file = self.dst.join("scrape-examples-help.html"); let mut root_path = self.dst.to_str().expect("invalid path").to_owned(); if !root_path.ends_with('/') { @@ -606,6 +607,20 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { &self.shared.style_files, ); self.shared.fs.write(settings_file, v)?; + + if self.shared.layout.scrape_examples_extension { + page.title = "About scraped examples"; + page.description = "How the scraped examples feature works in Rustdoc"; + let v = layout::render( + &self.shared.layout, + &page, + "", + scrape_examples_help(&*self.shared), + &self.shared.style_files, + ); + self.shared.fs.write(scrape_examples_help_file, v)?; + } + if let Some(ref redirections) = self.shared.redirections { if !redirections.borrow().is_empty() { let redirect_map_path = diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index c3e2eb81e45d2..80a18038272c7 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -62,7 +62,6 @@ use rustc_span::{ use serde::ser::SerializeSeq; use serde::{Serialize, Serializer}; -use crate::clean::utils::DOC_RUST_LANG_ORG_CHANNEL; use crate::clean::{self, ItemId, RenderedLink, SelfTy}; use crate::error::Error; use crate::formats::cache::Cache; @@ -77,6 +76,7 @@ use crate::html::format::{ use crate::html::highlight; use crate::html::markdown::{HeadingOffset, IdMap, Markdown, MarkdownHtml, MarkdownSummaryLine}; use crate::html::sources; +use crate::html::static_files::SCRAPE_EXAMPLES_HELP_MD; use crate::scrape_examples::{CallData, CallLocation}; use crate::try_none; @@ -462,6 +462,29 @@ fn settings(root_path: &str, suffix: &str, theme_names: Vec) -> Result) -> String { + let content = SCRAPE_EXAMPLES_HELP_MD; + let mut ids = IdMap::default(); + format!( + "
+

\ + About scraped examples\ +

\ +
\ +
{}
", + Markdown { + content, + links: &[], + ids: &mut ids, + error_codes: shared.codes, + edition: shared.edition(), + playground: &shared.playground, + heading_offset: HeadingOffset::H1 + } + .into_string() + ) +} + fn document( w: &mut Buffer, cx: &Context<'_>, @@ -2672,9 +2695,9 @@ fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) { \
\ Examples found in repository\ - ?\ + ?\
", - doc_prefix = DOC_RUST_LANG_ORG_CHANNEL, + root_path = cx.root_path(), id = id ); diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 55a11652c2353..e3c3d614f015c 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -2034,7 +2034,7 @@ details.rustdoc-toggle[open] > summary.hideme::after { /* Begin: styles for --scrape-examples feature */ -.scraped-example-list .section-header .scrape-help { +.scraped-example-list .scrape-help { margin-left: 10px; padding: 0 4px; font-weight: normal; diff --git a/src/librustdoc/html/static/css/themes/ayu.css b/src/librustdoc/html/static/css/themes/ayu.css index e073057528c79..b1bf06c1865c7 100644 --- a/src/librustdoc/html/static/css/themes/ayu.css +++ b/src/librustdoc/html/static/css/themes/ayu.css @@ -612,11 +612,11 @@ input:checked + .slider { } -.scraped-example-list .section-header .scrape-help { +.scraped-example-list .scrape-help { border-color: #aaa; color: #eee; } -.scraped-example-list .section-header .scrape-help:hover { +.scraped-example-list .scrape-help:hover { border-color: white; color: white; } diff --git a/src/librustdoc/html/static/css/themes/dark.css b/src/librustdoc/html/static/css/themes/dark.css index a10029b637177..236304ccc9f1b 100644 --- a/src/librustdoc/html/static/css/themes/dark.css +++ b/src/librustdoc/html/static/css/themes/dark.css @@ -478,11 +478,11 @@ div.files > .selected { border-bottom-color: #ddd; } -.scraped-example-list .section-header .scrape-help { +.scraped-example-list .scrape-help { border-color: #aaa; color: #eee; } -.scraped-example-list .section-header .scrape-help:hover { +.scraped-example-list .scrape-help:hover { border-color: white; color: white; } diff --git a/src/librustdoc/html/static/css/themes/light.css b/src/librustdoc/html/static/css/themes/light.css index a30dcac66dbc5..c923902aba2d3 100644 --- a/src/librustdoc/html/static/css/themes/light.css +++ b/src/librustdoc/html/static/css/themes/light.css @@ -463,11 +463,11 @@ div.files > .selected { border-bottom-color: #D5D5D5; } -.scraped-example-list .section-header .scrape-help { +.scraped-example-list .scrape-help { border-color: #555; color: #333; } -.scraped-example-list .section-header .scrape-help:hover { +.scraped-example-list .scrape-help:hover { border-color: black; color: black; } diff --git a/src/librustdoc/html/static/scrape-examples-help.md b/src/librustdoc/html/static/scrape-examples-help.md new file mode 100644 index 0000000000000..035b2e18b00eb --- /dev/null +++ b/src/librustdoc/html/static/scrape-examples-help.md @@ -0,0 +1,34 @@ +Rustdoc will automatically scrape examples of documented items from the `examples/` directory of a project. These examples will be included within the generated documentation for that item. For example, if your library contains a public function: + +```rust +// src/lib.rs +pub fn a_func() {} +``` + +And you have an example calling this function: + +```rust +// examples/ex.rs +fn main() { + a_crate::a_func(); +} +``` + +Then this code snippet will be included in the documentation for `a_func`. + +## How to read scraped examples + +Scraped examples are shown as blocks of code from a given file. The relevant item will be highlighted. If the file is larger than a couple lines, only a small window will be shown which you can expand by clicking ↕ in the top-right. If a file contains multiple instances of an item, you can use the ≺ and ≻ buttons to toggle through each instance. + +If there is more than one file that contains examples, then you should click "More examples" to see these examples. + + +## How Rustdoc scrapes examples + +When you run `cargo doc`, Rustdoc will analyze all the crates that match Cargo's `--examples` filter for instances of items that occur in the crates being documented. Then Rustdoc will include the source code of these instances in the generated documentation. + +Rustdoc has a few techniques to ensure this doesn't overwhelm documentation readers, and that it doesn't blow up the page size: + +1. For a given item, a maximum of 5 examples are included in the page. The remaining examples are just links to source code. +2. Only one example is shown by default, and the remaining examples are hidden behind a toggle. +3. For a given file that contains examples, only the item containing the examples will be included in the generated documentation. diff --git a/src/librustdoc/html/static_files.rs b/src/librustdoc/html/static_files.rs index cd369a93d8283..1837e4a3b650a 100644 --- a/src/librustdoc/html/static_files.rs +++ b/src/librustdoc/html/static_files.rs @@ -39,6 +39,8 @@ crate static STORAGE_JS: &str = include_str!("static/js/storage.js"); /// --scrape-examples flag that inserts automatically-found examples of usages of items. crate static SCRAPE_EXAMPLES_JS: &str = include_str!("static/js/scrape-examples.js"); +crate static SCRAPE_EXAMPLES_HELP_MD: &str = include_str!("static/scrape-examples-help.md"); + /// The file contents of `brush.svg`, the icon used for the theme-switch button. crate static BRUSH_SVG: &[u8] = include_bytes!("static/images/brush.svg"); From b9ecdca0d5543cbb3c40042305ae315bf3454c60 Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Sun, 27 Mar 2022 19:36:22 -0700 Subject: [PATCH 09/15] Don't panic when scraping invalid calls --- src/librustdoc/scrape_examples.rs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs index fee42de746d57..bddc4326b7468 100644 --- a/src/librustdoc/scrape_examples.rs +++ b/src/librustdoc/scrape_examples.rs @@ -192,15 +192,22 @@ where return; } - assert!( - enclosing_item_span.contains(call_span), - "Attempted to scrape call at [{call_span:?}] whose enclosing item [{enclosing_item_span:?}] doesn't contain the span of the call.", - ); - - assert!( - call_span.contains(ident_span), - "Attempted to scrape call at [{call_span:?}] whose identifier [{ident_span:?}] was not contained in the span of the call." - ); + // If the enclosing item doesn't actually enclose the call, this means we probably have a weird + // macro issue even though the spans aren't tagged as being from an expansion. + if !enclosing_item_span.contains(call_span) { + warn!( + "Attempted to scrape call at [{call_span:?}] whose enclosing item [{enclosing_item_span:?}] doesn't contain the span of the call." + ); + return; + } + + // Similarly for the call w/ the function ident. + if !call_span.contains(ident_span) { + warn!( + "Attempted to scrape call at [{call_span:?}] whose identifier [{ident_span:?}] was not contained in the span of the call." + ); + return; + } // Save call site if the function resolves to a concrete definition if let ty::FnDef(def_id, _) = ty.kind() { From 7cdef0876deb12e3a5a69c9dec2b386fa61a4384 Mon Sep 17 00:00:00 2001 From: Martin Pool Date: Mon, 11 Apr 2022 19:00:29 -0700 Subject: [PATCH 10/15] Document that DirEntry holds the directory open --- library/std/src/fs.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 10c424269c7d1..433b4d530136b 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -132,6 +132,16 @@ pub struct ReadDir(fs_imp::ReadDir); /// An instance of `DirEntry` represents an entry inside of a directory on the /// filesystem. Each entry can be inspected via methods to learn about the full /// path or possibly other metadata through per-platform extension traits. +/// +/// # Platform-specific behavior +/// +/// On Unix, the `DirEntry` struct contains an internal reference to the open +/// directory. Holding `DirEntry` objects will consume a file handle even +/// after the `ReadDir` iterator is dropped. +/// +/// Note that this [may change in the future][changes]. +/// +/// [changes]: io#platform-specific-behavior #[stable(feature = "rust1", since = "1.0.0")] pub struct DirEntry(fs_imp::DirEntry); From 6a18b6865590704d42777fa7432436e55773d46b Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Tue, 12 Apr 2022 11:05:07 -0700 Subject: [PATCH 11/15] Add Rustdoc book link to scrape examples help. Remove remaining panic locations in scrape examples. --- src/librustdoc/html/render/mod.rs | 12 ++++-- src/librustdoc/scrape_examples.rs | 61 +++++++++++++++++++++---------- 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 80a18038272c7..4825ddc5a2034 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -79,6 +79,7 @@ use crate::html::sources; use crate::html::static_files::SCRAPE_EXAMPLES_HELP_MD; use crate::scrape_examples::{CallData, CallLocation}; use crate::try_none; +use crate::DOC_RUST_LANG_ORG_CHANNEL; /// A pair of name and its optional document. crate type NameDoc = (String, Option); @@ -463,17 +464,22 @@ fn settings(root_path: &str, suffix: &str, theme_names: Vec) -> Result) -> String { - let content = SCRAPE_EXAMPLES_HELP_MD; + let mut content = SCRAPE_EXAMPLES_HELP_MD.to_owned(); + content.push_str(&format!( + "## More information\n\n\ + If you want more information about this feature, please read the [corresponding chapter in the Rustdoc book]({}/rustdoc/scraped-examples.html).", + DOC_RUST_LANG_ORG_CHANNEL)); + let mut ids = IdMap::default(); format!( - "
+ "
\

\ About scraped examples\

\
\
{}
", Markdown { - content, + content: &content, links: &[], ids: &mut ids, error_codes: shared.codes, diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs index bddc4326b7468..0da490f3cd6c8 100644 --- a/src/librustdoc/scrape_examples.rs +++ b/src/librustdoc/scrape_examples.rs @@ -71,14 +71,14 @@ crate struct SyntaxRange { } impl SyntaxRange { - fn new(span: rustc_span::Span, file: &SourceFile) -> Self { + fn new(span: rustc_span::Span, file: &SourceFile) -> Option { let get_pos = |bytepos: BytePos| file.original_relative_byte_pos(bytepos).0; - let get_line = |bytepos: BytePos| file.lookup_line(bytepos).unwrap(); + let get_line = |bytepos: BytePos| file.lookup_line(bytepos); - SyntaxRange { + Some(SyntaxRange { byte_span: (get_pos(span.lo()), get_pos(span.hi())), - line_span: (get_line(span.lo()), get_line(span.hi())), - } + line_span: (get_line(span.lo())?, get_line(span.hi())?), + }) } } @@ -95,12 +95,12 @@ impl CallLocation { ident_span: rustc_span::Span, enclosing_item_span: rustc_span::Span, source_file: &SourceFile, - ) -> Self { - CallLocation { - call_expr: SyntaxRange::new(expr_span, source_file), - call_ident: SyntaxRange::new(ident_span, source_file), - enclosing_item: SyntaxRange::new(enclosing_item_span, source_file), - } + ) -> Option { + Some(CallLocation { + call_expr: SyntaxRange::new(expr_span, source_file)?, + call_ident: SyntaxRange::new(ident_span, source_file)?, + enclosing_item: SyntaxRange::new(enclosing_item_span, source_file)?, + }) } } @@ -178,7 +178,7 @@ where // If this span comes from a macro expansion, then the source code may not actually show // a use of the given item, so it would be a poor example. Hence, we skip all uses in macros. if call_span.from_expansion() { - trace!("Rejecting expr from macro: {:?}", call_span); + trace!("Rejecting expr from macro: {call_span:?}"); return; } @@ -188,7 +188,7 @@ where .hir() .span_with_body(tcx.hir().local_def_id_to_hir_id(tcx.hir().get_parent_item(ex.hir_id))); if enclosing_item_span.from_expansion() { - trace!("Rejecting expr ({:?}) from macro item: {:?}", call_span, enclosing_item_span); + trace!("Rejecting expr ({call_span:?}) from macro item: {enclosing_item_span:?}"); return; } @@ -224,11 +224,27 @@ where }; if let Some(file_path) = file_path { - let abs_path = fs::canonicalize(file_path.clone()).unwrap(); + let abs_path = match fs::canonicalize(file_path.clone()) { + Ok(abs_path) => abs_path, + Err(_) => { + trace!("Could not canonicalize file path: {}", file_path.display()); + return; + } + }; + let cx = &self.cx; + let clean_span = crate::clean::types::Span::new(call_span); + let url = match cx.href_from_span(clean_span, false) { + Some(url) => url, + None => { + trace!( + "Rejecting expr ({call_span:?}) whose clean span ({clean_span:?}) cannot be turned into a link" + ); + return; + } + }; + let mk_call_data = || { - let clean_span = crate::clean::types::Span::new(call_span); - let url = cx.href_from_span(clean_span, false).unwrap(); let display_name = file_path.display().to_string(); let edition = call_span.edition(); CallData { locations: Vec::new(), url, display_name, edition } @@ -240,7 +256,14 @@ where trace!("Including expr: {:?}", call_span); let enclosing_item_span = source_map.span_extend_to_prev_char(enclosing_item_span, '\n', false); - let location = CallLocation::new(call_span, ident_span, enclosing_item_span, &file); + let location = + match CallLocation::new(call_span, ident_span, enclosing_item_span, &file) { + Some(location) => location, + None => { + trace!("Could not get serializable call location for {call_span:?}"); + return; + } + }; fn_entries.entry(abs_path).or_insert_with(mk_call_data).locations.push(location); } } @@ -274,8 +297,8 @@ crate fn run( .map(|(crate_num, _)| **crate_num) .collect::>(); - debug!("All crates in TyCtxt: {:?}", all_crates); - debug!("Scrape examples target_crates: {:?}", target_crates); + debug!("All crates in TyCtxt: {all_crates:?}"); + debug!("Scrape examples target_crates: {target_crates:?}"); // Run call-finder on all items let mut calls = FxHashMap::default(); From 71ad003bf68f8fe89534171778764d84fb50eb68 Mon Sep 17 00:00:00 2001 From: gimbles Date: Wed, 13 Apr 2022 13:54:22 +0530 Subject: [PATCH 12/15] Improve error message when there's no checksum --- src/bootstrap/bootstrap.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/bootstrap/bootstrap.py b/src/bootstrap/bootstrap.py index 0b6bdf474195d..d131a091e3bd3 100644 --- a/src/bootstrap/bootstrap.py +++ b/src/bootstrap/bootstrap.py @@ -70,7 +70,11 @@ def get(base, url, path, checksums, verbose=False, do_verify=True, help_on_error try: if do_verify: if url not in checksums: - raise RuntimeError("src/stage0.json doesn't contain a checksum for {}".format(url)) + raise RuntimeError(("src/stage0.json doesn't contain a checksum for {}. " + "Pre-built artifacts might not available for this " + "target at this time, see https://doc.rust-lang.org/nightly" + "/rustc/platform-support.html for more information.") + .format(url)) sha256 = checksums[url] if os.path.exists(path): if verify(path, sha256, False): From 212e98bc3eb83d89088ffdb5c63bcbeaaf4742ed Mon Sep 17 00:00:00 2001 From: Marijn Schouten Date: Wed, 13 Apr 2022 13:24:28 +0200 Subject: [PATCH 13/15] Add missing article to fix "few" to "a few". Add missing article to fix "few" (not many) to "a few" (some). --- library/core/src/convert/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/convert/mod.rs b/library/core/src/convert/mod.rs index c7a9a8183789f..52c087847151c 100644 --- a/library/core/src/convert/mod.rs +++ b/library/core/src/convert/mod.rs @@ -108,7 +108,7 @@ pub const fn identity(x: T) -> T { /// If you need to do a costly conversion it is better to implement [`From`] with type /// `&T` or write a custom function. /// -/// `AsRef` has the same signature as [`Borrow`], but [`Borrow`] is different in few aspects: +/// `AsRef` has the same signature as [`Borrow`], but [`Borrow`] is different in a few aspects: /// /// - Unlike `AsRef`, [`Borrow`] has a blanket impl for any `T`, and can be used to accept either /// a reference or a value. From c008d45187924704d08533ef148f50b4a39dfb88 Mon Sep 17 00:00:00 2001 From: Marijn Schouten Date: Wed, 13 Apr 2022 13:33:09 +0200 Subject: [PATCH 14/15] Add a missing article Add a missing article --- library/core/src/convert/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/convert/mod.rs b/library/core/src/convert/mod.rs index c7a9a8183789f..077410fbd79db 100644 --- a/library/core/src/convert/mod.rs +++ b/library/core/src/convert/mod.rs @@ -112,7 +112,7 @@ pub const fn identity(x: T) -> T { /// /// - Unlike `AsRef`, [`Borrow`] has a blanket impl for any `T`, and can be used to accept either /// a reference or a value. -/// - [`Borrow`] also requires that [`Hash`], [`Eq`] and [`Ord`] for borrowed value are +/// - [`Borrow`] also requires that [`Hash`], [`Eq`] and [`Ord`] for a borrowed value are /// equivalent to those of the owned value. For this reason, if you want to /// borrow only a single field of a struct you can implement `AsRef`, but not [`Borrow`]. /// From 9b9f6771049ecd30f13138c32f23ba6562a613d5 Mon Sep 17 00:00:00 2001 From: Bruno Felipe Francisco Date: Wed, 13 Apr 2022 11:02:01 -0300 Subject: [PATCH 15/15] fix: wrong trait import suggestion for T: --- compiler/rustc_typeck/src/check/method/suggest.rs | 10 ++++++++-- src/test/ui/traits/issue-95898.rs | 9 +++++++++ src/test/ui/traits/issue-95898.stderr | 15 +++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 src/test/ui/traits/issue-95898.rs create mode 100644 src/test/ui/traits/issue-95898.stderr diff --git a/compiler/rustc_typeck/src/check/method/suggest.rs b/compiler/rustc_typeck/src/check/method/suggest.rs index ecc2996593704..e6560ca4d9b90 100644 --- a/compiler/rustc_typeck/src/check/method/suggest.rs +++ b/compiler/rustc_typeck/src/check/method/suggest.rs @@ -1880,9 +1880,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; let sp = hir.span(id); let sp = if let Some(first_bound) = has_bounds { - // `sp` only covers `T`, change it so that it covers - // `T:` when appropriate sp.until(first_bound.span()) + } else if let Some(colon_sp) = + // If the generic param is declared with a colon but without bounds: + // fn foo(t: T) { ... } + param.colon_span_for_suggestions( + self.inh.tcx.sess.source_map(), + ) + { + sp.to(colon_sp) } else { sp }; diff --git a/src/test/ui/traits/issue-95898.rs b/src/test/ui/traits/issue-95898.rs new file mode 100644 index 0000000000000..41a20b899599a --- /dev/null +++ b/src/test/ui/traits/issue-95898.rs @@ -0,0 +1,9 @@ +// Test for #95898: The trait suggestion had an extra `:` after the trait. +// edition:2021 + +fn foo(t: T) { + t.clone(); + //~^ ERROR no method named `clone` found for type parameter `T` in the current scope +} + +fn main() {} diff --git a/src/test/ui/traits/issue-95898.stderr b/src/test/ui/traits/issue-95898.stderr new file mode 100644 index 0000000000000..d7d4790539604 --- /dev/null +++ b/src/test/ui/traits/issue-95898.stderr @@ -0,0 +1,15 @@ +error[E0599]: no method named `clone` found for type parameter `T` in the current scope + --> $DIR/issue-95898.rs:5:7 + | +LL | t.clone(); + | ^^^^^ method not found in `T` + | + = help: items from traits can only be used if the type parameter is bounded by the trait +help: the following trait defines an item `clone`, perhaps you need to restrict type parameter `T` with it: + | +LL | fn foo(t: T) { + | ~~~~~~~~ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0599`.