From a542680228f4344ce407bfb8028da1b8d7986354 Mon Sep 17 00:00:00 2001 From: Abdalrhman M Mohamed Date: Sun, 22 Aug 2021 23:44:48 -0500 Subject: [PATCH 1/2] Add support for modifying examples in the Rust books. Signed-off-by: Abdalrhman M Mohamed --- .github/workflows/copyright.yml | 2 +- Cargo.lock | 1 + src/bootstrap/run.rs | 21 +- src/bootstrap/tool.rs | 58 +++- src/librustdoc/doctest.rs | 6 +- src/librustdoc/html/markdown.rs | 16 +- src/librustdoc/lib.rs | 2 +- src/tools/dashboard/Cargo.toml | 1 + .../Appendices/Glossary/263.diff | 4 + src/tools/dashboard/src/books.rs | 290 ++++++++---------- src/tools/dashboard/src/main.rs | 2 + 11 files changed, 229 insertions(+), 174 deletions(-) create mode 100644 src/tools/dashboard/configs/books/The Rust Reference/Appendices/Glossary/263.diff diff --git a/.github/workflows/copyright.yml b/.github/workflows/copyright.yml index b9a962f4eab7..a2d69c300021 100644 --- a/.github/workflows/copyright.yml +++ b/.github/workflows/copyright.yml @@ -15,7 +15,7 @@ jobs: - name: Get paths for files added id: git-diff run: | - ignore='(.md|.props|expected|ignore|gitignore)$' + ignore='(.diff|.md|.props|expected|ignore|gitignore)$' files=$(git diff --ignore-submodules=all --name-only --diff-filter=A ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} | grep -v -E $ignore | xargs) echo "::set-output name=paths::$files" diff --git a/Cargo.lock b/Cargo.lock index 0d7c53c72b3d..4a9bafeff8ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -901,6 +901,7 @@ version = "0.1.0" dependencies = [ "Inflector", "pulldown-cmark 0.8.0", + "rustdoc", "walkdir", ] diff --git a/src/bootstrap/run.rs b/src/bootstrap/run.rs index 5964f6b050fd..dc8c76fdafec 100644 --- a/src/bootstrap/run.rs +++ b/src/bootstrap/run.rs @@ -1,11 +1,14 @@ use crate::builder::{Builder, RunConfig, ShouldRun, Step}; use crate::dist::distdir; use crate::tool::Tool; +use crate::{compile, tool, Compiler}; use build_helper::output; use std::process::Command; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct Dashboard; +pub struct Dashboard { + pub compiler: Compiler, +} impl Step for Dashboard { type Output = (); @@ -15,11 +18,15 @@ impl Step for Dashboard { /// This tool in `src/tools` extracts examples from books, runs them through /// RMC, and displays their results. fn run(self, builder: &Builder<'_>) { + // Before running the dashboard, we need to ensure that it is already + // built. + let dashboard = builder.ensure(tool::Dashboard { compiler: self.compiler }); + // We also need to ensure that stage n standard library is built for + // rmc-rustc. + builder.ensure(compile::Std { compiler: self.compiler, target: self.compiler.host }); + let target = builder.config.build.triple; builder.info("Generating confidence dashboard"); - try_run( - builder, - &mut builder.tool_cmd(Tool::Dashboard).env("TRIPLE", builder.config.build.triple), - ); + try_run(builder, Command::new(dashboard).env("TRIPLE", target)); } fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { @@ -27,7 +34,9 @@ impl Step for Dashboard { } fn make_run(run: RunConfig<'_>) { - run.builder.ensure(Dashboard); + run.builder.ensure(Dashboard { + compiler: run.builder.compiler(run.builder.top_stage, run.target), + }); } } diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs index 1e5c6210bd78..bfa90ab59323 100644 --- a/src/bootstrap/tool.rs +++ b/src/bootstrap/tool.rs @@ -369,7 +369,6 @@ bootstrap_tool!( Linkchecker, "src/tools/linkchecker", "linkchecker"; CargoTest, "src/tools/cargotest", "cargotest"; Compiletest, "src/tools/compiletest", "compiletest", is_unstable_tool = true; - Dashboard, "src/tools/dashboard", "dashboard"; BuildManifest, "src/tools/build-manifest", "build-manifest"; RemoteTestClient, "src/tools/remote-test-client", "remote-test-client"; RustInstaller, "src/tools/rust-installer", "fabricate", is_external_tool = true; @@ -585,6 +584,63 @@ impl Step for Rustdoc { } } +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)] +pub struct Dashboard { + pub compiler: Compiler, +} + +impl Step for Dashboard { + type Output = PathBuf; + const DEFAULT: bool = true; + const ONLY_HOSTS: bool = true; + + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + run.path("src/tools/dashboard") + } + + fn make_run(run: RunConfig<'_>) { + // Use the same compiler used to compile rustdoc. + run.builder.ensure(Dashboard { + compiler: run.builder.compiler(run.builder.top_stage, run.target), + }); + } + + fn run(self, builder: &Builder<'_>) -> PathBuf { + // Since the dashboard depends on rustdoc, we follow the same steps used + // above to compile rustdoc. + let target_compiler = self.compiler; + let target = target_compiler.host; + let build_compiler = builder.compiler(target_compiler.stage - 1, builder.config.build); + builder.ensure(compile::Std { compiler: build_compiler, target: target_compiler.host }); + builder.ensure(compile::Rustc { compiler: build_compiler, target: target_compiler.host }); + let cargo = prepare_tool_cargo( + builder, + build_compiler, + Mode::ToolRustc, + target, + "build", + "src/tools/dashboard", + SourceType::InTree, + &Vec::new(), + ); + builder.info(&format!( + "Building dashboard for stage{} ({})", + target_compiler.stage, target_compiler.host + )); + builder.run(&mut cargo.into()); + let tool_dashboard = builder + .cargo_out(build_compiler, Mode::ToolRustc, target) + .join(exe("dashboard", target_compiler.host)); + let sysroot = builder.sysroot(target_compiler); + let bindir = sysroot.join("bin"); + t!(fs::create_dir_all(&bindir)); + let bin_dashboard = bindir.join(exe("dashboard", target_compiler.host)); + let _ = fs::remove_file(&bin_dashboard); + builder.copy(&tool_dashboard, &bin_dashboard); + bin_dashboard + } +} + #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct Cargo { pub compiler: Compiler, diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 083d82cb414d..e5847ab188f0 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -35,7 +35,7 @@ use crate::lint::init_lints; use crate::passes::span_of_attrs; #[derive(Clone, Default)] -crate struct TestOptions { +pub struct TestOptions { /// Whether to disable the default `extern crate my_crate;` when creating doctests. crate no_crate_inject: bool, /// Whether to emit compilation warnings when compiling doctests. Setting this will suppress @@ -493,7 +493,7 @@ fn run_test( /// Transforms a test into code that can be compiled into a Rust binary, and returns the number of /// lines before the test code begins as well as if the output stream supports colors or not. -crate fn make_test( +pub fn make_test( s: &str, crate_name: Option<&str>, dont_insert_main: bool, @@ -777,7 +777,7 @@ fn partition_source(s: &str) -> (String, String, String) { (before, after, crates) } -crate trait Tester { +pub trait Tester { fn add_test(&mut self, test: String, config: LangString, line: usize); fn get_line(&self) -> usize { 0 diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 7c6d7dff816d..03d6636b1c86 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -645,7 +645,7 @@ impl<'a, I: Iterator>> Iterator for Footnotes<'a, I> { } } -crate fn find_testable_code( +pub fn find_testable_code( doc: &str, tests: &mut T, error_codes: ErrorCodes, @@ -711,7 +711,7 @@ crate fn find_testable_code( } } -crate struct ExtraInfo<'tcx> { +pub struct ExtraInfo<'tcx> { id: ExtraInfoId, sp: Span, tcx: TyCtxt<'tcx>, @@ -758,21 +758,21 @@ impl<'tcx> ExtraInfo<'tcx> { } #[derive(Eq, PartialEq, Clone, Debug)] -crate struct LangString { +pub struct LangString { original: String, - crate should_panic: bool, + pub should_panic: bool, crate no_run: bool, - crate ignore: Ignore, + pub ignore: Ignore, crate rust: bool, crate test_harness: bool, - crate compile_fail: bool, + pub compile_fail: bool, crate error_codes: Vec, crate allow_fail: bool, - crate edition: Option, + pub edition: Option, } #[derive(Eq, PartialEq, Clone, Debug)] -crate enum Ignore { +pub enum Ignore { All, None, Some(Vec), diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 34fe808dae2e..3729dea09cc5 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -109,7 +109,7 @@ mod docfs; mod doctree; #[macro_use] mod error; -mod doctest; +pub mod doctest; mod fold; mod formats; // used by the error-index generator, so it needs to be public diff --git a/src/tools/dashboard/Cargo.toml b/src/tools/dashboard/Cargo.toml index 7dfddbcc5d55..cb396eb5f41a 100644 --- a/src/tools/dashboard/Cargo.toml +++ b/src/tools/dashboard/Cargo.toml @@ -9,4 +9,5 @@ edition = "2018" [dependencies] Inflector = "0.11.4" pulldown-cmark = { version = "0.8.0", default-features = false } +rustdoc = { path = "../../librustdoc" } walkdir = "2.3.2" diff --git a/src/tools/dashboard/configs/books/The Rust Reference/Appendices/Glossary/263.diff b/src/tools/dashboard/configs/books/The Rust Reference/Appendices/Glossary/263.diff new file mode 100644 index 000000000000..f8eff2a40536 --- /dev/null +++ b/src/tools/dashboard/configs/books/The Rust Reference/Appendices/Glossary/263.diff @@ -0,0 +1,4 @@ +- 1 ++ 1 let ok_num = Ok::<_, ()>(5); ++ 2 assert!(!ok_num.is_err()); ++ 4 assert!([2, 4, 6][..] == vec[..]); diff --git a/src/tools/dashboard/src/books.rs b/src/tools/dashboard/src/books.rs index 6b7b596dfd70..feaa0dc94bbc 100644 --- a/src/tools/dashboard/src/books.rs +++ b/src/tools/dashboard/src/books.rs @@ -3,6 +3,8 @@ //! Utilities to extract examples from Rust books, run them through RMC, and //! display their results. +extern crate rustc_span; + use crate::{ dashboard, litani::Litani, @@ -10,13 +12,17 @@ use crate::{ }; use inflector::cases::{snakecase::to_snake_case, titlecase::to_title_case}; use pulldown_cmark::{Event, Parser, Tag}; +use rustc_span::edition::Edition; +use rustdoc::{ + doctest::Tester, + html::markdown::{ErrorCodes, Ignore, LangString}, +}; use std::{ collections::{HashMap, HashSet}, env, ffi::OsStr, - fmt::{Debug, Formatter, Result, Write}, - fs::{self, File}, - hash::Hash, + fmt::Write, + fs, io::{BufRead, BufReader}, iter::FromIterator, path::{Path, PathBuf}, @@ -181,97 +187,146 @@ fn parse_unstable_book_hierarchy() -> HashMap { map } -/// The data structure represents the "full" path to examples in the Rust books. -#[derive(PartialEq, Eq, Hash)] +/// This data structure contains the code and configs of an example in the Rust books. struct Example { - /// Path to the markdown file containing the example. - path: PathBuf, - /// Line number of the code block introducing the example. + /// The example code extracted from a codeblock. + code: String, + // Line number of the code block. line: usize, + // Configurations in the header of the codeblock. + config: rustdoc::html::markdown::LangString, } -impl Example { - /// Creates a new [`Example`] instance representing "full" path to the - /// Rust example. - fn new(path: PathBuf, line: usize) -> Example { - Example { path, line } +/// Data structure representing a list of examples. Mainly for implementing the +/// [`Tester`] trait +struct Examples(Vec); + +impl Tester for Examples { + fn add_test(&mut self, test: String, config: LangString, line: usize) { + if config.ignore != Ignore::All { + self.0.push(Example { code: test, line, config }) + } } } -impl Debug for Example { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - f.write_fmt(format_args!("{}:{}", self.path.to_str().unwrap(), self.line)) +/// Applies the diff corresponding to `example` with parent `path` (if it exists). +fn apply_diff(path: &Path, example: &mut Example, config_paths: &mut HashSet) { + let config_dir: PathBuf = ["src", "tools", "dashboard", "configs"].iter().collect(); + let test_dir: PathBuf = ["src", "test", "dashboard"].iter().collect(); + // `path` has the following form: + // `src/test/dashboard/books/ + // If `example` has a custom diff file, the path to the diff file will have + // the following form: + // `src/tools/dashboard/configs/books//.diff` + // where is the same for both paths. + let mut diff_path = config_dir.join(path.strip_prefix(&test_dir).unwrap()); + diff_path.extend_one(format!("{}.diff", example.line)); + if diff_path.exists() { + config_paths.remove(&diff_path); + let mut code_lines: Vec<_> = example.code.lines().collect(); + let diff = fs::read_to_string(diff_path).unwrap(); + for line in diff.lines() { + // `*.diff` files have a simple format: + // `- ` for removing lines. + // `+ ` for inserting lines. + // Notice that for a series of `+` and `-`, the developer must keep + // track of the changing line numbers. + let mut split = line.splitn(3, ' '); + let symbol = split.next().unwrap(); + let line = split.next().unwrap().parse::().unwrap() - 1; + if symbol == "+" { + let diff = split.next().unwrap(); + code_lines.insert(line, diff); + } else { + code_lines.remove(line); + } + } + example.code = code_lines.join("\n"); } } -/// Extracts examples from the markdown files specified by each key in the given -/// `map` and saves them in the directory specified by the corresponding value. -/// Returns a mapping from the original location of **_each_** example to the -/// path it was extracted to. -fn extract_examples(par_map: HashMap) -> HashMap { - let mut full_map = HashMap::new(); - for (par_from, par_to) in par_map { - let pairs = extract(&par_from, &par_to); - for (key, val) in pairs { - full_map.insert(key, val); +/// Prepends example properties in `example.config` to the code in `example.code`. +fn prepend_props(path: &Path, example: &mut Example, config_paths: &mut HashSet) { + let config_dir: PathBuf = ["src", "tools", "dashboard", "configs"].iter().collect(); + let test_dir: PathBuf = ["src", "test", "dashboard"].iter().collect(); + // `path` has the following form: + // `src/test/dashboard/books/ + // If `example` has a custom props file, the path to the props file will + // have the following form: + // `src/tools/dashboard/configs/books//.props` + // where is the same for both paths. + let mut props_path = config_dir.join(path.strip_prefix(&test_dir).unwrap()); + props_path.extend_one(format!("{}.props", example.line)); + let mut props = if props_path.exists() { + config_paths.remove(&props_path); + util::parse_test_header(&props_path) + } else { + TestProps::new(path.to_path_buf(), None, Vec::new(), Vec::new()) + }; + if example.config.edition != Some(Edition::Edition2015) { + props.rustc_args.push(String::from("--edition")); + props.rustc_args.push(String::from("2018")); + } + if props.fail_step.is_none() { + if example.config.compile_fail { + // Most examples with `compile_fail` annotation fail because of + // check errors. This heuristic can be overridden by manually + //specifying the fail step in the corresponding config file. + props.fail_step = Some(FailStep::Check); + } else if example.config.should_panic { + // RMC should catch run-time errors. + props.fail_step = Some(FailStep::Verification); } } - full_map + example.code = format!("{}{}", props, example.code); } -/// Extracts examples from the markdown files specified by `par_from` and saves -/// them in the directory specified by `par_to`. Returns a mapping from the -/// original location of **_each_** example to the path it was extracted to. -fn extract(par_from: &Path, par_to: &Path) -> Vec<(Example, PathBuf)> { - let build_dir = &env::var("BUILD_DIR").unwrap(); - let triple = &env::var("TRIPLE").unwrap(); - // Create a temporary directory to save the files generated by `rustdoc`. - let gen_dir: PathBuf = [build_dir, triple, "dashboard"].iter().collect(); - // If `gen_dir` already exists, remove it. - fs::remove_dir_all(&gen_dir).unwrap_or_default(); - fs::create_dir_all(&gen_dir).unwrap(); - let mut cmd = Command::new("rustdoc"); - cmd.args([ - "+nightly", - "--test", - "-Z", - "unstable-options", - par_from.to_str().unwrap(), - "--test-builder", - &["src", "tools", "dashboard", "print.sh"].iter().collect::().to_str().unwrap(), - "--persist-doctests", - gen_dir.to_str().unwrap(), - "--no-run", - ]); - cmd.stdout(Stdio::null()); - cmd.spawn().unwrap().wait().unwrap(); - // Mapping from path and line number of rust example to where it was extracted to. - let mut pairs = Vec::new(); +/// Extracts examples from the markdown file specified by `par_from`, +/// pre-processes those examples, and saves them in the directory specified by +/// `par_to`. +fn extract(par_from: &Path, par_to: &Path, config_paths: &mut HashSet) { + let config_dir: PathBuf = ["src", "tools", "dashboard", "configs"].iter().collect(); + let test_dir: PathBuf = ["src", "test", "dashboard"].iter().collect(); + let code = fs::read_to_string(&par_from).unwrap(); + let mut examples = Examples(Vec::new()); + rustdoc::html::markdown::find_testable_code(&code, &mut examples, ErrorCodes::No, false, None); + for mut example in examples.0 { + apply_diff(par_to, &mut example, config_paths); + example.code = rustdoc::doctest::make_test( + &example.code, + None, + false, + &Default::default(), + example.config.edition.unwrap_or(Edition::Edition2018), + None, + ) + .0; + prepend_props(par_to, &mut example, config_paths); + let rs_path = par_to.join(format!("{}.rs", example.line)); + fs::create_dir_all(rs_path.parent().unwrap()).unwrap(); + fs::write(rs_path, example.code).unwrap(); + } +} - for dir in gen_dir.read_dir().unwrap() { - // Some directories do not contain tests because the markdown file - // instructs `rustdoc` to "ignore" those tests. - let dir = dir.unwrap().path(); - if let Some(from) = dir.read_dir().unwrap().next() { - // The path to each example extracted by `rustdoc` has the form: - // = `/__/rust_out` - // where occurrences of '/', '-', and '.' in are replaced - // by '_'. We copy the file in this path to a new path of the form: - // = `/.rs` - // We omit because all tests have the same number, 0. - let from = from.unwrap().path(); - let path_line_test = dir.file_name().unwrap().to_str().unwrap(); - let splits: Vec<_> = path_line_test.rsplitn(3, '_').collect(); - let line: usize = splits[1].parse().unwrap(); - let to = par_to.join(format!("{}.rs", line)); - fs::create_dir_all(par_to).unwrap(); - fs::copy(&from, &to).unwrap(); - pairs.push((Example::new(par_from.to_path_buf(), line), to)); - } +/// Extracts examples from the markdown files specified by each key in the given +/// `map`, pre-processes those examples, and saves them in the directory +/// specified by the corresponding value. +fn extract_examples(par_map: HashMap) { + let mut config_paths = get_config_paths(); + for (par_from, par_to) in par_map { + extract(&par_from, &par_to, &mut config_paths); + } + if !config_paths.is_empty() { + panic!( + "Error: The examples corresponding to the following config files \ + were not encountered in the pre-processing step:\n{}This is most \ + likely because the line numbers of the config files are not in \ + sync with the line numbers of the corresponding code blocks in \ + the latest versions of the Rust books. Please update the line \ + numbers of the config files and rerun the program.", + paths_to_string(config_paths) + ); } - // Delete the temporary directory. - fs::remove_dir_all(gen_dir).unwrap(); - pairs } /// Returns a set of paths to the config files for examples in the Rust books. @@ -287,13 +342,6 @@ fn get_config_paths() -> HashSet { config_paths } -/// Prepends the given `props` to the test file in `props.test`. -fn prepend_props(props: &TestProps) { - let code = fs::read_to_string(&props.path).unwrap(); - let code = format!("{}{}", props, code); - fs::write(&props.path, code).unwrap(); -} - /// Pretty prints the `paths` set. fn paths_to_string(paths: HashSet) -> String { let mut f = String::new(); @@ -303,71 +351,6 @@ fn paths_to_string(paths: HashSet) -> String { f } -/// Pre-processes the examples in `map` before running them with `compiletest`. -fn preprocess_examples(map: &HashMap) { - let config_dir: PathBuf = ["src", "tools", "dashboard", "configs"].iter().collect(); - let test_dir: PathBuf = ["src", "test", "dashboard"].iter().collect(); - let mut config_paths = get_config_paths(); - // Copy compiler annotations specified in the original markdown code blocks - // and custom configurations under the `config` directory. - for (from, to) in map.iter() { - // Path `to` has the following form: - // `src/test/dashboard/books//.rs` - // If it has a custom props file, the path to the props file will have - // the following form: - // `src/tools/dashboard/configs/books///.props` - // where and are the same for both paths. - let mut props_path = config_dir.join(to.strip_prefix(&test_dir).unwrap()); - props_path.set_extension("props"); - let mut props = if props_path.exists() { - config_paths.remove(&props_path); - // Parse the properties in the file. The format follows the same - // conventions for the headers in RMC regressions. - let mut props = util::parse_test_header(&props_path); - // `util::parse_test_header` thinks `props_path` is the path to the - // test. That is not the case, `to` is the actual path to the - // test/example. - props.path = to.clone(); - props - } else { - TestProps::new(to.clone(), None, Vec::new(), Vec::new()) - }; - let file = File::open(&from.path).unwrap(); - // Skip to the first line of the example code block. - // Line numbers in files start with 1 but `nth(...)` starts with 0. - // Subtract 1 to account for the difference. - let line = BufReader::new(file).lines().nth(from.line - 1).unwrap().unwrap(); - if !line.contains("edition2015") { - props.rustc_args.push(String::from("--edition")); - props.rustc_args.push(String::from("2018")); - } - // Most examples with `compile_fail` annotation fail because of check - // errors. This heuristic can be overridden by manually specifying the - // fail step in the corresponding config file. - if props.fail_step.is_none() && line.contains("compile_fail") { - props.fail_step = Some(FailStep::Check); - } - // RMC should catch run-time errors. - if props.fail_step.is_none() && line.contains("should_panic") { - props.fail_step = Some(FailStep::Verification); - } - // Prepend those properties to test/example file. - prepend_props(&props); - } - if !config_paths.is_empty() { - panic!( - "Error: The examples corresponding to the following config files \ - were not encountered in the pre-processing step:\n{}This is most \ - likely because the line numbers of the config files are not in \ - sync with the line numbers of the corresponding code blocks in \ - the latest versions of the Rust books. Please update the line \ - numbers of the config files and rerun the program.", - paths_to_string(config_paths) - ); - } - // TODO: Add support for manually adding assertions (see issue #324). -} - /// Runs `compiletest` on the `suite` and logs the results to `log_path`. fn run_examples(suite: &str, log_path: &Path) { // Before executing this program, `cargo` populates the environment with @@ -485,11 +468,10 @@ pub fn generate_dashboard() { map.extend(parse_nomicon_hierarchy()); map.extend(parse_unstable_book_hierarchy()); map.extend(parse_rust_by_example_hierarchy()); - // Extract examples from the books, organize them following the partial - // hierarchy in map, and return the full hierarchy map. - let map = extract_examples(map); + // Extract examples from the books, pre-process them, and save them + // following the partial hierarchy in map. + extract_examples(map); // Pre-process the examples before running them through `compiletest`. - preprocess_examples(&map); // Run `compiletest` on the examples. run_examples("dashboard", &log_path); // Parse `compiletest` log file. diff --git a/src/tools/dashboard/src/main.rs b/src/tools/dashboard/src/main.rs index 7cb2f2af3f19..8022e63252a7 100644 --- a/src/tools/dashboard/src/main.rs +++ b/src/tools/dashboard/src/main.rs @@ -1,6 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 OR MIT #![feature(command_access)] +#![feature(extend_one)] +#![feature(rustc_private)] mod books; mod dashboard; From 1b298a6bca72a02798785228e9faddb310c86809 Mon Sep 17 00:00:00 2001 From: Abdalrhman M Mohamed Date: Mon, 23 Aug 2021 07:55:22 -0500 Subject: [PATCH 2/2] Punctuation. Signed-off-by: Abdalrhman M Mohamed --- src/tools/dashboard/src/books.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/dashboard/src/books.rs b/src/tools/dashboard/src/books.rs index feaa0dc94bbc..d4cb8efdc9ed 100644 --- a/src/tools/dashboard/src/books.rs +++ b/src/tools/dashboard/src/books.rs @@ -198,7 +198,7 @@ struct Example { } /// Data structure representing a list of examples. Mainly for implementing the -/// [`Tester`] trait +/// [`Tester`] trait. struct Examples(Vec); impl Tester for Examples {