Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 0d13f6a

Browse files
committedApr 13, 2022
Auto merge of #96015 - Dylan-DPC:rollup-vhdprid, r=Dylan-DPC
Rollup of 6 pull requests Successful merges: - #93217 (Improve Rustdoc UI for scraped examples with multiline arguments, fix overflow in line numbers) - #95885 (Improve error message in case of missing checksum) - #95962 (Document that DirEntry holds the directory open) - #95991 (fix: wrong trait import suggestion for T:) - #96005 (Add missing article to fix "few" to "a few".) - #96006 (Add a missing article) Failed merges: r? `@ghost` `@rustbot` modify labels: rollup
2 parents ab33f71 + e95f2db commit 0d13f6a

File tree

19 files changed

+391
-93
lines changed

19 files changed

+391
-93
lines changed
 

‎compiler/rustc_typeck/src/check/method/suggest.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1880,9 +1880,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
18801880
};
18811881
let sp = hir.span(id);
18821882
let sp = if let Some(first_bound) = has_bounds {
1883-
// `sp` only covers `T`, change it so that it covers
1884-
// `T:` when appropriate
18851883
sp.until(first_bound.span())
1884+
} else if let Some(colon_sp) =
1885+
// If the generic param is declared with a colon but without bounds:
1886+
// fn foo<T:>(t: T) { ... }
1887+
param.colon_span_for_suggestions(
1888+
self.inh.tcx.sess.source_map(),
1889+
)
1890+
{
1891+
sp.to(colon_sp)
18861892
} else {
18871893
sp
18881894
};

‎library/core/src/convert/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,11 @@ pub const fn identity<T>(x: T) -> T {
108108
/// If you need to do a costly conversion it is better to implement [`From`] with type
109109
/// `&T` or write a custom function.
110110
///
111-
/// `AsRef` has the same signature as [`Borrow`], but [`Borrow`] is different in few aspects:
111+
/// `AsRef` has the same signature as [`Borrow`], but [`Borrow`] is different in a few aspects:
112112
///
113113
/// - Unlike `AsRef`, [`Borrow`] has a blanket impl for any `T`, and can be used to accept either
114114
/// a reference or a value.
115-
/// - [`Borrow`] also requires that [`Hash`], [`Eq`] and [`Ord`] for borrowed value are
115+
/// - [`Borrow`] also requires that [`Hash`], [`Eq`] and [`Ord`] for a borrowed value are
116116
/// equivalent to those of the owned value. For this reason, if you want to
117117
/// borrow only a single field of a struct you can implement `AsRef`, but not [`Borrow`].
118118
///

‎library/std/src/fs.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,16 @@ pub struct ReadDir(fs_imp::ReadDir);
132132
/// An instance of `DirEntry` represents an entry inside of a directory on the
133133
/// filesystem. Each entry can be inspected via methods to learn about the full
134134
/// path or possibly other metadata through per-platform extension traits.
135+
///
136+
/// # Platform-specific behavior
137+
///
138+
/// On Unix, the `DirEntry` struct contains an internal reference to the open
139+
/// directory. Holding `DirEntry` objects will consume a file handle even
140+
/// after the `ReadDir` iterator is dropped.
141+
///
142+
/// Note that this [may change in the future][changes].
143+
///
144+
/// [changes]: io#platform-specific-behavior
135145
#[stable(feature = "rust1", since = "1.0.0")]
136146
pub struct DirEntry(fs_imp::DirEntry);
137147

‎src/bootstrap/bootstrap.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,11 @@ def get(base, url, path, checksums, verbose=False, do_verify=True, help_on_error
7070
try:
7171
if do_verify:
7272
if url not in checksums:
73-
raise RuntimeError("src/stage0.json doesn't contain a checksum for {}".format(url))
73+
raise RuntimeError(("src/stage0.json doesn't contain a checksum for {}. "
74+
"Pre-built artifacts might not available for this "
75+
"target at this time, see https://doc.rust-lang.org/nightly"
76+
"/rustc/platform-support.html for more information.")
77+
.format(url))
7478
sha256 = checksums[url]
7579
if os.path.exists(path):
7680
if verify(path, sha256, False):

‎src/doc/rustdoc/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- [Linking to items by name](write-documentation/linking-to-items-by-name.md)
1010
- [Documentation tests](write-documentation/documentation-tests.md)
1111
- [Rustdoc-specific lints](lints.md)
12+
- [Scraped examples](scraped-examples.md)
1213
- [Advanced features](advanced-features.md)
1314
- [Unstable features](unstable-features.md)
1415
- [Deprecated features](deprecated-features.md)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Scraped examples
2+
3+
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:
4+
5+
```rust,ignore (needs-other-file)
6+
// a_crate/src/lib.rs
7+
pub fn a_func() {}
8+
```
9+
10+
And you have an example calling this function:
11+
12+
```rust,ignore (needs-other-file)
13+
// a_crate/examples/ex.rs
14+
fn main() {
15+
a_crate::a_func();
16+
}
17+
```
18+
19+
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.
20+
21+
22+
## How to use this feature
23+
24+
This feature is unstable, so you can enable it by calling Rustdoc with the unstable `rustdoc-scrape-examples` flag:
25+
26+
```bash
27+
cargo doc -Zunstable-options -Zrustdoc-scrape-examples=examples
28+
```
29+
30+
To enable this feature on [docs.rs](https://docs.rs), add this to your Cargo.toml:
31+
32+
```toml
33+
[package.metadata.docs.rs]
34+
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples=examples"]
35+
```
36+
37+
38+
## How it works
39+
40+
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.
41+
42+
Rustdoc has a few techniques to ensure these examples don't overwhelm documentation readers, and that it doesn't blow up the page size:
43+
44+
1. For a given item, a maximum of 5 examples are included in the page. The remaining examples are just links to source code.
45+
2. Only one example is shown by default, and the remaining examples are hidden behind a toggle.
46+
3. For a given file that contains examples, only the item containing the examples will be included in the generated documentation.
47+
48+
For a given item, Rustdoc sorts its examples based on the size of the example &mdash; smaller ones are shown first.
49+
50+
51+
## FAQ
52+
53+
### My example is not showing up in the documentation
54+
55+
This feature uses Cargo's convention for finding examples. You should ensure that `cargo check --examples` includes your example file.

‎src/librustdoc/html/render/context.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ use super::print_item::{full_path, item_path, print_item};
1717
use super::search_index::build_index;
1818
use super::write_shared::write_shared;
1919
use super::{
20-
collect_spans_and_sources, print_sidebar, settings, AllTypes, LinkFromSrc, NameDoc, StylePath,
21-
BASIC_KEYWORDS,
20+
collect_spans_and_sources, print_sidebar, scrape_examples_help, settings, AllTypes,
21+
LinkFromSrc, NameDoc, StylePath, BASIC_KEYWORDS,
2222
};
2323

2424
use crate::clean::{self, types::ExternalLocation, ExternalCrate};
@@ -551,6 +551,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
551551
let crate_name = self.tcx().crate_name(LOCAL_CRATE);
552552
let final_file = self.dst.join(crate_name.as_str()).join("all.html");
553553
let settings_file = self.dst.join("settings.html");
554+
let scrape_examples_help_file = self.dst.join("scrape-examples-help.html");
554555

555556
let mut root_path = self.dst.to_str().expect("invalid path").to_owned();
556557
if !root_path.ends_with('/') {
@@ -606,6 +607,20 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
606607
&self.shared.style_files,
607608
);
608609
self.shared.fs.write(settings_file, v)?;
610+
611+
if self.shared.layout.scrape_examples_extension {
612+
page.title = "About scraped examples";
613+
page.description = "How the scraped examples feature works in Rustdoc";
614+
let v = layout::render(
615+
&self.shared.layout,
616+
&page,
617+
"",
618+
scrape_examples_help(&*self.shared),
619+
&self.shared.style_files,
620+
);
621+
self.shared.fs.write(scrape_examples_help_file, v)?;
622+
}
623+
609624
if let Some(ref redirections) = self.shared.redirections {
610625
if !redirections.borrow().is_empty() {
611626
let redirect_map_path =

‎src/librustdoc/html/render/mod.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,10 @@ use crate::html::format::{
7575
use crate::html::highlight;
7676
use crate::html::markdown::{HeadingOffset, IdMap, Markdown, MarkdownHtml, MarkdownSummaryLine};
7777
use crate::html::sources;
78+
use crate::html::static_files::SCRAPE_EXAMPLES_HELP_MD;
7879
use crate::scrape_examples::{CallData, CallLocation};
7980
use crate::try_none;
81+
use crate::DOC_RUST_LANG_ORG_CHANNEL;
8082

8183
/// A pair of name and its optional document.
8284
crate type NameDoc = (String, Option<String>);
@@ -460,6 +462,34 @@ fn settings(root_path: &str, suffix: &str, theme_names: Vec<String>) -> Result<S
460462
))
461463
}
462464

465+
fn scrape_examples_help(shared: &SharedContext<'_>) -> String {
466+
let mut content = SCRAPE_EXAMPLES_HELP_MD.to_owned();
467+
content.push_str(&format!(
468+
"## More information\n\n\
469+
If you want more information about this feature, please read the [corresponding chapter in the Rustdoc book]({}/rustdoc/scraped-examples.html).",
470+
DOC_RUST_LANG_ORG_CHANNEL));
471+
472+
let mut ids = IdMap::default();
473+
format!(
474+
"<div class=\"main-heading\">\
475+
<h1 class=\"fqn\">\
476+
<span class=\"in-band\">About scraped examples</span>\
477+
</h1>\
478+
</div>\
479+
<div>{}</div>",
480+
Markdown {
481+
content: &content,
482+
links: &[],
483+
ids: &mut ids,
484+
error_codes: shared.codes,
485+
edition: shared.edition(),
486+
playground: &shared.playground,
487+
heading_offset: HeadingOffset::H1
488+
}
489+
.into_string()
490+
)
491+
}
492+
463493
fn document(
464494
w: &mut Buffer,
465495
cx: &Context<'_>,
@@ -2743,7 +2773,9 @@ fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) {
27432773
<span></span>\
27442774
<h5 id=\"{id}\">\
27452775
<a href=\"#{id}\">Examples found in repository</a>\
2776+
<a class=\"scrape-help\" href=\"{root_path}scrape-examples-help.html\">?</a>\
27462777
</h5>",
2778+
root_path = cx.root_path(),
27472779
id = id
27482780
);
27492781

@@ -2795,9 +2827,10 @@ fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) {
27952827
.locations
27962828
.iter()
27972829
.map(|loc| {
2798-
let (byte_lo, byte_hi) = loc.call_expr.byte_span;
2830+
let (byte_lo, byte_hi) = loc.call_ident.byte_span;
27992831
let (line_lo, line_hi) = loc.call_expr.line_span;
28002832
let byte_range = (byte_lo - byte_min, byte_hi - byte_min);
2833+
28012834
let line_range = (line_lo - line_min, line_hi - line_min);
28022835
let (line_url, line_title) = link_to_loc(call_data, loc);
28032836

@@ -2913,6 +2946,7 @@ fn render_call_locations(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item) {
29132946
<summary class=\"hideme\">\
29142947
<span>More examples</span>\
29152948
</summary>\
2949+
<div class=\"hide-more\">Hide additional examples</div>\
29162950
<div class=\"more-scraped-examples\">\
29172951
<div class=\"toggle-line\"><div class=\"toggle-line-inner\"></div></div>\
29182952
<div class=\"more-scraped-examples-inner\">"

‎src/librustdoc/html/static/css/rustdoc.css

Lines changed: 44 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,7 @@ h2.location a {
618618
position: relative;
619619
}
620620

621-
.docblock > :not(.information) {
621+
.docblock > :not(.information):not(.more-examples-toggle) {
622622
max-width: 100%;
623623
overflow-x: auto;
624624
}
@@ -840,8 +840,8 @@ h2.small-section-header > .anchor {
840840
content: '§';
841841
}
842842

843-
.docblock a:not(.srclink):not(.test-arrow):hover,
844-
.docblock-short a:not(.srclink):not(.test-arrow):hover, .item-info a {
843+
.docblock a:not(.srclink):not(.test-arrow):not(.scrape-help):hover,
844+
.docblock-short a:not(.srclink):not(.test-arrow):not(.scrape-help):hover, .item-info a {
845845
text-decoration: underline;
846846
}
847847

@@ -2038,21 +2038,45 @@ details.rustdoc-toggle[open] > summary.hideme::after {
20382038

20392039
/* Begin: styles for --scrape-examples feature */
20402040

2041+
.scraped-example-list .scrape-help {
2042+
margin-left: 10px;
2043+
padding: 0 4px;
2044+
font-weight: normal;
2045+
font-size: 12px;
2046+
position: relative;
2047+
bottom: 1px;
2048+
background: transparent;
2049+
border-width: 1px;
2050+
border-style: solid;
2051+
border-radius: 50px;
2052+
}
2053+
20412054
.scraped-example-title {
20422055
font-family: 'Fira Sans';
20432056
}
20442057

2045-
.scraped-example:not(.expanded) .code-wrapper pre.line-numbers {
2046-
overflow: hidden;
2058+
.scraped-example .code-wrapper {
2059+
position: relative;
2060+
display: flex;
2061+
flex-direction: row;
2062+
flex-wrap: wrap;
2063+
width: 100%;
2064+
}
2065+
2066+
.scraped-example:not(.expanded) .code-wrapper {
20472067
max-height: 240px;
20482068
}
20492069

2050-
.scraped-example:not(.expanded) .code-wrapper .example-wrap pre.rust {
2070+
.scraped-example:not(.expanded) .code-wrapper pre {
20512071
overflow-y: hidden;
20522072
max-height: 240px;
20532073
padding-bottom: 0;
20542074
}
20552075

2076+
.scraped-example:not(.expanded) .code-wrapper pre.line-numbers {
2077+
overflow-x: hidden;
2078+
}
2079+
20562080
.scraped-example .code-wrapper .prev {
20572081
position: absolute;
20582082
top: 0.25em;
@@ -2077,22 +2101,13 @@ details.rustdoc-toggle[open] > summary.hideme::after {
20772101
cursor: pointer;
20782102
}
20792103

2080-
.scraped-example .code-wrapper {
2081-
position: relative;
2082-
display: flex;
2083-
flex-direction: row;
2084-
flex-wrap: wrap;
2085-
width: 100%;
2086-
}
2087-
20882104
.scraped-example:not(.expanded) .code-wrapper:before {
20892105
content: " ";
20902106
width: 100%;
20912107
height: 5px;
20922108
position: absolute;
20932109
z-index: 100;
20942110
top: 0;
2095-
background: linear-gradient(to bottom, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0));
20962111
}
20972112

20982113
.scraped-example:not(.expanded) .code-wrapper:after {
@@ -2102,12 +2117,6 @@ details.rustdoc-toggle[open] > summary.hideme::after {
21022117
position: absolute;
21032118
z-index: 100;
21042119
bottom: 0;
2105-
background: linear-gradient(to top, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0));
2106-
}
2107-
2108-
.scraped-example:not(.expanded) .code-wrapper {
2109-
overflow: hidden;
2110-
max-height: 240px;
21112120
}
21122121

21132122
.scraped-example .code-wrapper .line-numbers {
@@ -2126,34 +2135,37 @@ details.rustdoc-toggle[open] > summary.hideme::after {
21262135
margin-bottom: 0;
21272136
}
21282137

2138+
.scraped-example:not(.expanded) .code-wrapper .example-wrap {
2139+
overflow-x: hidden;
2140+
}
2141+
21292142
.scraped-example .code-wrapper .example-wrap pre.rust {
21302143
overflow-x: inherit;
21312144
width: inherit;
21322145
overflow-y: hidden;
21332146
}
21342147

2135-
.scraped-example .example-wrap .rust span.highlight {
2136-
background: #fcffd6;
2137-
}
2138-
2139-
.scraped-example .example-wrap .rust span.highlight.focus {
2140-
background: #f6fdb0;
2141-
}
21422148

21432149
.more-examples-toggle {
2150+
max-width: calc(100% + 25px);
21442151
margin-top: 10px;
2152+
margin-left: -25px;
2153+
}
2154+
2155+
.more-examples-toggle .hide-more {
2156+
margin-left: 25px;
2157+
margin-bottom: 5px;
2158+
cursor: pointer;
21452159
}
21462160

2147-
.more-examples-toggle summary {
2148-
color: #999;
2161+
.more-examples-toggle summary, .more-examples-toggle .hide-more {
21492162
font-family: 'Fira Sans';
21502163
}
21512164

21522165
.more-scraped-examples {
2153-
margin-left: 25px;
2166+
margin-left: 5px;
21542167
display: flex;
21552168
flex-direction: row;
2156-
width: calc(100% - 25px);
21572169
}
21582170

21592171
.more-scraped-examples-inner {
@@ -2169,13 +2181,8 @@ details.rustdoc-toggle[open] > summary.hideme::after {
21692181
cursor: pointer;
21702182
}
21712183

2172-
.toggle-line:hover .toggle-line-inner {
2173-
background: #aaa;
2174-
}
2175-
21762184
.toggle-line-inner {
21772185
min-width: 2px;
2178-
background: #ddd;
21792186
height: 100%;
21802187
}
21812188

‎src/librustdoc/html/static/css/themes/ayu.css

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,18 @@ input:checked + .slider {
611611
background-color: #ffb454 !important;
612612
}
613613

614+
615+
.scraped-example-list .scrape-help {
616+
border-color: #aaa;
617+
color: #eee;
618+
}
619+
.scraped-example-list .scrape-help:hover {
620+
border-color: white;
621+
color: white;
622+
}
623+
.more-examples-toggle summary, .more-examples-toggle .hide-more {
624+
color: #999;
625+
}
614626
.scraped-example .example-wrap .rust span.highlight {
615627
background: rgb(91, 59, 1);
616628
}
@@ -624,8 +636,8 @@ input:checked + .slider {
624636
background: linear-gradient(to top, rgba(15, 20, 25, 1), rgba(15, 20, 25, 0));
625637
}
626638
.toggle-line-inner {
627-
background: #616161;
639+
background: #999;
628640
}
629641
.toggle-line:hover .toggle-line-inner {
630-
background: #898989;
642+
background: #c5c5c5;
631643
}

‎src/librustdoc/html/static/css/themes/dark.css

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,17 @@ div.files > .selected {
478478
border-bottom-color: #ddd;
479479
}
480480

481+
.scraped-example-list .scrape-help {
482+
border-color: #aaa;
483+
color: #eee;
484+
}
485+
.scraped-example-list .scrape-help:hover {
486+
border-color: white;
487+
color: white;
488+
}
489+
.more-examples-toggle summary, .more-examples-toggle .hide-more {
490+
color: #999;
491+
}
481492
.scraped-example .example-wrap .rust span.highlight {
482493
background: rgb(91, 59, 1);
483494
}
@@ -491,8 +502,8 @@ div.files > .selected {
491502
background: linear-gradient(to top, rgba(53, 53, 53, 1), rgba(53, 53, 53, 0));
492503
}
493504
.toggle-line-inner {
494-
background: #616161;
505+
background: #999;
495506
}
496507
.toggle-line:hover .toggle-line-inner {
497-
background: #898989;
508+
background: #c5c5c5;
498509
}

‎src/librustdoc/html/static/css/themes/light.css

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,3 +462,33 @@ div.files > .selected {
462462
.setting-line > .title {
463463
border-bottom-color: #D5D5D5;
464464
}
465+
466+
.scraped-example-list .scrape-help {
467+
border-color: #555;
468+
color: #333;
469+
}
470+
.scraped-example-list .scrape-help:hover {
471+
border-color: black;
472+
color: black;
473+
}
474+
.more-examples-toggle summary, .more-examples-toggle .hide-more {
475+
color: #999;
476+
}
477+
.scraped-example .example-wrap .rust span.highlight {
478+
background: #fcffd6;
479+
}
480+
.scraped-example .example-wrap .rust span.highlight.focus {
481+
background: #f6fdb0;
482+
}
483+
.scraped-example:not(.expanded) .code-wrapper:before {
484+
background: linear-gradient(to bottom, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0));
485+
}
486+
.scraped-example:not(.expanded) .code-wrapper:after {
487+
background: linear-gradient(to top, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0));
488+
}
489+
.toggle-line-inner {
490+
background: #ccc;
491+
}
492+
.toggle-line:hover .toggle-line-inner {
493+
background: #999;
494+
}

‎src/librustdoc/html/static/js/scrape-examples.js

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,28 @@
11
/* global addClass, hasClass, removeClass, onEach */
22

33
(function () {
4-
// Scroll code block to put the given code location in the middle of the viewer
4+
// Number of lines shown when code viewer is not expanded
5+
const MAX_LINES = 10;
6+
7+
// Scroll code block to the given code location
58
function scrollToLoc(elt, loc) {
6-
var wrapper = elt.querySelector(".code-wrapper");
7-
var halfHeight = wrapper.offsetHeight / 2;
89
var lines = elt.querySelector('.line-numbers');
9-
var offsetMid = (lines.children[loc[0]].offsetTop
10-
+ lines.children[loc[1]].offsetTop) / 2;
11-
var scrollOffset = offsetMid - halfHeight;
10+
var scrollOffset;
11+
12+
// If the block is greater than the size of the viewer,
13+
// then scroll to the top of the block. Otherwise scroll
14+
// to the middle of the block.
15+
if (loc[1] - loc[0] > MAX_LINES) {
16+
var line = Math.max(0, loc[0] - 1);
17+
scrollOffset = lines.children[line].offsetTop;
18+
} else {
19+
var wrapper = elt.querySelector(".code-wrapper");
20+
var halfHeight = wrapper.offsetHeight / 2;
21+
var offsetMid = (lines.children[loc[0]].offsetTop
22+
+ lines.children[loc[1]].offsetTop) / 2;
23+
scrollOffset = offsetMid - halfHeight;
24+
}
25+
1226
lines.scrollTo(0, scrollOffset);
1327
elt.querySelector(".rust").scrollTo(0, scrollOffset);
1428
}
@@ -70,8 +84,10 @@
7084
onEach(document.querySelectorAll('.more-examples-toggle'), function(toggle) {
7185
// Allow users to click the left border of the <details> section to close it,
7286
// since the section can be large and finding the [+] button is annoying.
73-
toggle.querySelector('.toggle-line').addEventListener('click', function() {
74-
toggle.open = false;
87+
toggle.querySelectorAll('.toggle-line, .hide-more').forEach(button => {
88+
button.addEventListener('click', function() {
89+
toggle.open = false;
90+
});
7591
});
7692

7793
var moreExamples = toggle.querySelectorAll('.scraped-example');
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
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:
2+
3+
```rust
4+
// src/lib.rs
5+
pub fn a_func() {}
6+
```
7+
8+
And you have an example calling this function:
9+
10+
```rust
11+
// examples/ex.rs
12+
fn main() {
13+
a_crate::a_func();
14+
}
15+
```
16+
17+
Then this code snippet will be included in the documentation for `a_func`.
18+
19+
## How to read scraped examples
20+
21+
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 &varr; in the top-right. If a file contains multiple instances of an item, you can use the &pr; and &sc; buttons to toggle through each instance.
22+
23+
If there is more than one file that contains examples, then you should click "More examples" to see these examples.
24+
25+
26+
## How Rustdoc scrapes examples
27+
28+
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.
29+
30+
Rustdoc has a few techniques to ensure this doesn't overwhelm documentation readers, and that it doesn't blow up the page size:
31+
32+
1. For a given item, a maximum of 5 examples are included in the page. The remaining examples are just links to source code.
33+
2. Only one example is shown by default, and the remaining examples are hidden behind a toggle.
34+
3. For a given file that contains examples, only the item containing the examples will be included in the generated documentation.

‎src/librustdoc/html/static_files.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ crate static STORAGE_JS: &str = include_str!("static/js/storage.js");
3939
/// --scrape-examples flag that inserts automatically-found examples of usages of items.
4040
crate static SCRAPE_EXAMPLES_JS: &str = include_str!("static/js/scrape-examples.js");
4141

42+
crate static SCRAPE_EXAMPLES_HELP_MD: &str = include_str!("static/scrape-examples-help.md");
43+
4244
/// The file contents of `brush.svg`, the icon used for the theme-switch button.
4345
crate static BRUSH_SVG: &[u8] = include_bytes!("static/images/brush.svg");
4446

‎src/librustdoc/scrape_examples.rs

Lines changed: 71 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -71,33 +71,36 @@ crate struct SyntaxRange {
7171
}
7272

7373
impl SyntaxRange {
74-
fn new(span: rustc_span::Span, file: &SourceFile) -> Self {
74+
fn new(span: rustc_span::Span, file: &SourceFile) -> Option<Self> {
7575
let get_pos = |bytepos: BytePos| file.original_relative_byte_pos(bytepos).0;
76-
let get_line = |bytepos: BytePos| file.lookup_line(bytepos).unwrap();
76+
let get_line = |bytepos: BytePos| file.lookup_line(bytepos);
7777

78-
SyntaxRange {
78+
Some(SyntaxRange {
7979
byte_span: (get_pos(span.lo()), get_pos(span.hi())),
80-
line_span: (get_line(span.lo()), get_line(span.hi())),
81-
}
80+
line_span: (get_line(span.lo())?, get_line(span.hi())?),
81+
})
8282
}
8383
}
8484

8585
#[derive(Encodable, Decodable, Debug, Clone)]
8686
crate struct CallLocation {
8787
crate call_expr: SyntaxRange,
88+
crate call_ident: SyntaxRange,
8889
crate enclosing_item: SyntaxRange,
8990
}
9091

9192
impl CallLocation {
9293
fn new(
9394
expr_span: rustc_span::Span,
95+
ident_span: rustc_span::Span,
9496
enclosing_item_span: rustc_span::Span,
9597
source_file: &SourceFile,
96-
) -> Self {
97-
CallLocation {
98-
call_expr: SyntaxRange::new(expr_span, source_file),
99-
enclosing_item: SyntaxRange::new(enclosing_item_span, source_file),
100-
}
98+
) -> Option<Self> {
99+
Some(CallLocation {
100+
call_expr: SyntaxRange::new(expr_span, source_file)?,
101+
call_ident: SyntaxRange::new(ident_span, source_file)?,
102+
enclosing_item: SyntaxRange::new(enclosing_item_span, source_file)?,
103+
})
101104
}
102105
}
103106

@@ -146,24 +149,26 @@ where
146149
}
147150

148151
// Get type of function if expression is a function call
149-
let (ty, span) = match ex.kind {
152+
let (ty, call_span, ident_span) = match ex.kind {
150153
hir::ExprKind::Call(f, _) => {
151154
let types = tcx.typeck(ex.hir_id.owner);
152155

153156
if let Some(ty) = types.node_type_opt(f.hir_id) {
154-
(ty, ex.span)
157+
(ty, ex.span, f.span)
155158
} else {
156159
trace!("node_type_opt({}) = None", f.hir_id);
157160
return;
158161
}
159162
}
160-
hir::ExprKind::MethodCall(_, _, span) => {
163+
hir::ExprKind::MethodCall(path, _, call_span) => {
161164
let types = tcx.typeck(ex.hir_id.owner);
162165
let Some(def_id) = types.type_dependent_def_id(ex.hir_id) else {
163166
trace!("type_dependent_def_id({}) = None", ex.hir_id);
164167
return;
165168
};
166-
(tcx.type_of(def_id), span)
169+
170+
let ident_span = path.ident.span;
171+
(tcx.type_of(def_id), call_span, ident_span)
167172
}
168173
_ => {
169174
return;
@@ -172,8 +177,8 @@ where
172177

173178
// If this span comes from a macro expansion, then the source code may not actually show
174179
// a use of the given item, so it would be a poor example. Hence, we skip all uses in macros.
175-
if span.from_expansion() {
176-
trace!("Rejecting expr from macro: {:?}", span);
180+
if call_span.from_expansion() {
181+
trace!("Rejecting expr from macro: {call_span:?}");
177182
return;
178183
}
179184

@@ -183,49 +188,82 @@ where
183188
.hir()
184189
.span_with_body(tcx.hir().local_def_id_to_hir_id(tcx.hir().get_parent_item(ex.hir_id)));
185190
if enclosing_item_span.from_expansion() {
186-
trace!("Rejecting expr ({:?}) from macro item: {:?}", span, enclosing_item_span);
191+
trace!("Rejecting expr ({call_span:?}) from macro item: {enclosing_item_span:?}");
192+
return;
193+
}
194+
195+
// If the enclosing item doesn't actually enclose the call, this means we probably have a weird
196+
// macro issue even though the spans aren't tagged as being from an expansion.
197+
if !enclosing_item_span.contains(call_span) {
198+
warn!(
199+
"Attempted to scrape call at [{call_span:?}] whose enclosing item [{enclosing_item_span:?}] doesn't contain the span of the call."
200+
);
187201
return;
188202
}
189203

190-
assert!(
191-
enclosing_item_span.contains(span),
192-
"Attempted to scrape call at [{:?}] whose enclosing item [{:?}] doesn't contain the span of the call.",
193-
span,
194-
enclosing_item_span
195-
);
204+
// Similarly for the call w/ the function ident.
205+
if !call_span.contains(ident_span) {
206+
warn!(
207+
"Attempted to scrape call at [{call_span:?}] whose identifier [{ident_span:?}] was not contained in the span of the call."
208+
);
209+
return;
210+
}
196211

197212
// Save call site if the function resolves to a concrete definition
198213
if let ty::FnDef(def_id, _) = ty.kind() {
199214
if self.target_crates.iter().all(|krate| *krate != def_id.krate) {
200-
trace!("Rejecting expr from crate not being documented: {:?}", span);
215+
trace!("Rejecting expr from crate not being documented: {call_span:?}");
201216
return;
202217
}
203218

204219
let source_map = tcx.sess.source_map();
205-
let file = source_map.lookup_char_pos(span.lo()).file;
220+
let file = source_map.lookup_char_pos(call_span.lo()).file;
206221
let file_path = match file.name.clone() {
207222
FileName::Real(real_filename) => real_filename.into_local_path(),
208223
_ => None,
209224
};
210225

211226
if let Some(file_path) = file_path {
212-
let abs_path = fs::canonicalize(file_path.clone()).unwrap();
227+
let abs_path = match fs::canonicalize(file_path.clone()) {
228+
Ok(abs_path) => abs_path,
229+
Err(_) => {
230+
trace!("Could not canonicalize file path: {}", file_path.display());
231+
return;
232+
}
233+
};
234+
213235
let cx = &self.cx;
236+
let clean_span = crate::clean::types::Span::new(call_span);
237+
let url = match cx.href_from_span(clean_span, false) {
238+
Some(url) => url,
239+
None => {
240+
trace!(
241+
"Rejecting expr ({call_span:?}) whose clean span ({clean_span:?}) cannot be turned into a link"
242+
);
243+
return;
244+
}
245+
};
246+
214247
let mk_call_data = || {
215-
let clean_span = crate::clean::types::Span::new(span);
216-
let url = cx.href_from_span(clean_span, false).unwrap();
217248
let display_name = file_path.display().to_string();
218-
let edition = span.edition();
249+
let edition = call_span.edition();
219250
CallData { locations: Vec::new(), url, display_name, edition }
220251
};
221252

222253
let fn_key = tcx.def_path_hash(*def_id);
223254
let fn_entries = self.calls.entry(fn_key).or_default();
224255

225-
trace!("Including expr: {:?}", span);
256+
trace!("Including expr: {:?}", call_span);
226257
let enclosing_item_span =
227258
source_map.span_extend_to_prev_char(enclosing_item_span, '\n', false);
228-
let location = CallLocation::new(span, enclosing_item_span, &file);
259+
let location =
260+
match CallLocation::new(call_span, ident_span, enclosing_item_span, &file) {
261+
Some(location) => location,
262+
None => {
263+
trace!("Could not get serializable call location for {call_span:?}");
264+
return;
265+
}
266+
};
229267
fn_entries.entry(abs_path).or_insert_with(mk_call_data).locations.push(location);
230268
}
231269
}
@@ -259,8 +297,8 @@ crate fn run(
259297
.map(|(crate_num, _)| **crate_num)
260298
.collect::<Vec<_>>();
261299

262-
debug!("All crates in TyCtxt: {:?}", all_crates);
263-
debug!("Scrape examples target_crates: {:?}", target_crates);
300+
debug!("All crates in TyCtxt: {all_crates:?}");
301+
debug!("Scrape examples target_crates: {target_crates:?}");
264302

265303
// Run call-finder on all items
266304
let mut calls = FxHashMap::default();
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// @has foobar/fn.ok.html '//*[@class="docblock scraped-example-list"]' 'ex2'
22
// @has foobar/fn.ok.html '//*[@class="more-scraped-examples"]' 'ex1'
3-
// @has foobar/fn.ok.html '//*[@class="highlight focus"]' '1'
4-
// @has foobar/fn.ok.html '//*[@class="highlight"]' '2'
5-
// @has foobar/fn.ok.html '//*[@class="highlight focus"]' '0'
3+
// @has foobar/fn.ok.html '//*[@class="highlight focus"]' 'ok'
4+
// @has foobar/fn.ok.html '//*[@class="highlight"]' 'ok'
65

76
pub fn ok(_x: i32) {}

‎src/test/ui/traits/issue-95898.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Test for #95898: The trait suggestion had an extra `:` after the trait.
2+
// edition:2021
3+
4+
fn foo<T:>(t: T) {
5+
t.clone();
6+
//~^ ERROR no method named `clone` found for type parameter `T` in the current scope
7+
}
8+
9+
fn main() {}

‎src/test/ui/traits/issue-95898.stderr

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error[E0599]: no method named `clone` found for type parameter `T` in the current scope
2+
--> $DIR/issue-95898.rs:5:7
3+
|
4+
LL | t.clone();
5+
| ^^^^^ method not found in `T`
6+
|
7+
= help: items from traits can only be used if the type parameter is bounded by the trait
8+
help: the following trait defines an item `clone`, perhaps you need to restrict type parameter `T` with it:
9+
|
10+
LL | fn foo<T: Clone>(t: T) {
11+
| ~~~~~~~~
12+
13+
error: aborting due to previous error
14+
15+
For more information about this error, try `rustc --explain E0599`.

0 commit comments

Comments
 (0)
Please sign in to comment.