From 7bff720112bd42a9e3bf9c3b9fe8b0d85f29a020 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 3 Dec 2022 17:34:58 +0100 Subject: [PATCH 01/25] Remove unused GUI test --- src/test/rustdoc-gui/basic.goml | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 src/test/rustdoc-gui/basic.goml diff --git a/src/test/rustdoc-gui/basic.goml b/src/test/rustdoc-gui/basic.goml deleted file mode 100644 index 60292835bc0bf..0000000000000 --- a/src/test/rustdoc-gui/basic.goml +++ /dev/null @@ -1,4 +0,0 @@ -goto: "file://" + |DOC_PATH| + "/test_docs/index.html" -assert: ("#functions") -goto: "./struct.Foo.html" -assert: ("div.item-decl") From ee21454e619fb7ab6122ee2349f9f40bb902bf5c Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 5 Dec 2022 11:37:55 +0100 Subject: [PATCH 02/25] attempt to clarify align_to docs --- library/core/src/slice/mod.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs index 04486ed2d14e3..3d7a7cfaf35fb 100644 --- a/library/core/src/slice/mod.rs +++ b/library/core/src/slice/mod.rs @@ -3468,10 +3468,11 @@ impl [T] { /// maintained. /// /// This method splits the slice into three distinct slices: prefix, correctly aligned middle - /// slice of a new type, and the suffix slice. The method may make the middle slice the greatest - /// length possible for a given type and input slice, but only your algorithm's performance - /// should depend on that, not its correctness. It is permissible for all of the input data to - /// be returned as the prefix or suffix slice. + /// slice of a new type, and the suffix slice. How exactly the slice is split up is not + /// specified; the middle part may be smaller than necessary. However, if this fails to return a + /// maximal middle part, that is because code is running in a context where performance does not + /// matter, such as a sanitizer attempting to find alignment bugs. Regular code running + /// in a default (debug or release) execution *will* return a maximal middle part. /// /// This method has no purpose when either input element `T` or output element `U` are /// zero-sized and will return the original slice without splitting anything. @@ -3529,10 +3530,11 @@ impl [T] { /// types is maintained. /// /// This method splits the slice into three distinct slices: prefix, correctly aligned middle - /// slice of a new type, and the suffix slice. The method may make the middle slice the greatest - /// length possible for a given type and input slice, but only your algorithm's performance - /// should depend on that, not its correctness. It is permissible for all of the input data to - /// be returned as the prefix or suffix slice. + /// slice of a new type, and the suffix slice. How exactly the slice is split up is not + /// specified; the middle part may be smaller than necessary. However, if this fails to return a + /// maximal middle part, that is because code is running in a context where performance does not + /// matter, such as a sanitizer attempting to find alignment bugs. Regular code running + /// in a default (debug or release) execution *will* return a maximal middle part. /// /// This method has no purpose when either input element `T` or output element `U` are /// zero-sized and will return the original slice without splitting anything. From 0da7f3e32d3bb2edcaf33cabac5d332236a095d9 Mon Sep 17 00:00:00 2001 From: Petr Ogarok Date: Tue, 6 Dec 2022 19:58:36 +0100 Subject: [PATCH 03/25] Enable profiler in dist-powerpc64le-linux Build the profiler runtime to allow using -C profile-generate and -C instrument-coverage on POWER systems. I have verified locally that the runtime builds and the profiler is working fine on the platform. --- src/ci/docker/host-x86_64/dist-powerpc64le-linux/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ci/docker/host-x86_64/dist-powerpc64le-linux/Dockerfile b/src/ci/docker/host-x86_64/dist-powerpc64le-linux/Dockerfile index 9a290edd561cc..6b7b32a8bc77f 100644 --- a/src/ci/docker/host-x86_64/dist-powerpc64le-linux/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-powerpc64le-linux/Dockerfile @@ -24,5 +24,5 @@ ENV \ ENV HOSTS=powerpc64le-unknown-linux-gnu -ENV RUST_CONFIGURE_ARGS --enable-extended --disable-docs +ENV RUST_CONFIGURE_ARGS --enable-extended --enable-profiler --disable-docs ENV SCRIPT python3 ../x.py dist --host $HOSTS --target $HOSTS From 06693790cef428918c0de61b63cc99d4fc5a5eb8 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 7 Dec 2022 16:08:13 +0100 Subject: [PATCH 04/25] Don't silently ignore errors that happen during rendering --- src/librustdoc/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 6d34f484754c7..25255ba1966d3 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -674,7 +674,7 @@ type MainResult = Result<(), ErrorGuaranteed>; fn wrap_return(diag: &rustc_errors::Handler, res: Result<(), String>) -> MainResult { match res { - Ok(()) => Ok(()), + Ok(()) => diag.has_errors().map_or(Ok(()), Err), Err(err) => { let reported = diag.struct_err(&err).emit(); Err(reported) @@ -689,7 +689,7 @@ fn run_renderer<'tcx, T: formats::FormatRenderer<'tcx>>( tcx: TyCtxt<'tcx>, ) -> MainResult { match formats::run_format::(krate, renderopts, cache, tcx) { - Ok(_) => Ok(()), + Ok(_) => tcx.sess.has_errors().map_or(Ok(()), Err), Err(e) => { let mut msg = tcx.sess.struct_err(&format!("couldn't generate documentation: {}", e.error)); From 6ccd14a782420d27aef9d9fcd9196949a3276427 Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Sun, 27 Nov 2022 13:11:21 -0600 Subject: [PATCH 05/25] Improve several aspects of the Rustdoc scrape-examples UI. * Examples take up less screen height. * Snippets from binary crates are prioritized. * toggle-all-docs does not expand "More examples" sections. --- src/librustdoc/config.rs | 9 +++---- src/librustdoc/core.rs | 6 ++--- src/librustdoc/doctest.rs | 5 ++-- src/librustdoc/html/render/mod.rs | 18 +++++++++---- src/librustdoc/html/static/css/rustdoc.css | 23 +++++++++++++++-- src/librustdoc/html/static/js/main.js | 2 +- .../html/static/js/scrape-examples.js | 20 ++++++++------- src/librustdoc/lib.rs | 10 +++++++- src/librustdoc/scrape_examples.rs | 12 ++++++--- .../scrape-examples-button-focus.goml | 9 +++++++ .../src/scrape_examples/examples/check2.rs | 25 +++++++++++++++++++ 11 files changed, 106 insertions(+), 33 deletions(-) create mode 100644 src/test/rustdoc-gui/src/scrape_examples/examples/check2.rs diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index e0cdb86d9d1dc..41af4f9561b53 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -69,8 +69,8 @@ pub(crate) struct Options { pub(crate) input: PathBuf, /// The name of the crate being documented. pub(crate) crate_name: Option, - /// Whether or not this is a proc-macro crate - pub(crate) proc_macro_crate: bool, + /// The types of the crate being documented. + pub(crate) crate_types: Vec, /// How to format errors and warnings. pub(crate) error_format: ErrorOutputType, /// Width of output buffer to truncate errors appropriately. @@ -176,7 +176,7 @@ impl fmt::Debug for Options { f.debug_struct("Options") .field("input", &self.input) .field("crate_name", &self.crate_name) - .field("proc_macro_crate", &self.proc_macro_crate) + .field("crate_types", &self.crate_types) .field("error_format", &self.error_format) .field("libs", &self.libs) .field("externs", &FmtExterns(&self.externs)) @@ -667,7 +667,6 @@ impl Options { None => OutputFormat::default(), }; let crate_name = matches.opt_str("crate-name"); - let proc_macro_crate = crate_types.contains(&CrateType::ProcMacro); let playground_url = matches.opt_str("playground-url"); let maybe_sysroot = matches.opt_str("sysroot").map(PathBuf::from); let module_sorting = if matches.opt_present("sort-modules-by-appearance") { @@ -718,7 +717,7 @@ impl Options { rustc_feature::UnstableFeatures::from_environment(crate_name.as_deref()); let options = Options { input, - proc_macro_crate, + crate_types, error_format, diagnostic_width, libs, diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index da0df596c41e3..c6358874c6162 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -13,7 +13,7 @@ use rustc_interface::interface; use rustc_middle::hir::nested_filter; use rustc_middle::ty::{ParamEnv, Ty, TyCtxt}; use rustc_resolve as resolve; -use rustc_session::config::{self, CrateType, ErrorOutputType}; +use rustc_session::config::{self, ErrorOutputType}; use rustc_session::lint; use rustc_session::Session; use rustc_span::symbol::sym; @@ -203,7 +203,7 @@ pub(crate) fn create_config( RustdocOptions { input, crate_name, - proc_macro_crate, + crate_types, error_format, diagnostic_width, libs, @@ -247,8 +247,6 @@ pub(crate) fn create_config( Some((lint.name_lower(), lint::Allow)) }); - let crate_types = - if proc_macro_crate { vec![CrateType::ProcMacro] } else { vec![CrateType::Rlib] }; let test = scrape_examples_options.map(|opts| opts.scrape_tests).unwrap_or(false); // plays with error output here! let sessopts = config::Options { diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 81d9c46447a37..b70444ec67395 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -12,7 +12,7 @@ use rustc_middle::hir::nested_filter; use rustc_middle::ty::TyCtxt; use rustc_parse::maybe_new_parser_from_source_str; use rustc_parse::parser::attr::InnerAttrPolicy; -use rustc_session::config::{self, CrateType, ErrorOutputType}; +use rustc_session::config::{self, ErrorOutputType}; use rustc_session::parse::ParseSess; use rustc_session::{lint, Session}; use rustc_span::edition::Edition; @@ -68,8 +68,7 @@ pub(crate) fn run(options: RustdocOptions) -> Result<(), ErrorGuaranteed> { debug!(?lint_opts); - let crate_types = - if options.proc_macro_crate { vec![CrateType::ProcMacro] } else { vec![CrateType::Rlib] }; + let crate_types = options.crate_types.clone(); let sessopts = config::Options { maybe_sysroot: options.maybe_sysroot.clone(), diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 08f8096b07bd6..ea466c639c39e 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -2957,14 +2957,22 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite // The call locations are output in sequence, so that sequence needs to be determined. // Ideally the most "relevant" examples would be shown first, but there's no general algorithm - // for determining relevance. Instead, we prefer the smallest examples being likely the easiest to - // understand at a glance. + // for determining relevance. We instead make a proxy for relevance with the following heuristics: + // 1. Code written to be an example is better than code not written to be an example, e.g. + // a snippet from examples/foo.rs is better than src/lib.rs. We don't know the Cargo directory + // structure in Rustdoc, so we proxy this by prioriting code that comes from a --crate-type bin. + // 2. Smaller examples are better than large examples. So we prioritize snippets that have the + // smallest line span for their enclosing item. + // 3. Finally we sort by the displayed file name, which is arbitrary but prevents the ordering + // of examples from randomly changing between Rustdoc invocations. let ordered_locations = { - let sort_criterion = |(_, call_data): &(_, &CallData)| { + fn sort_criterion<'a>( + (_, call_data): &(&PathBuf, &'a CallData), + ) -> (bool, u32, &'a String) { // Use the first location because that's what the user will see initially let (lo, hi) = call_data.locations[0].enclosing_item.byte_span; - hi - lo - }; + (!call_data.is_bin, hi - lo, &call_data.display_name) + } let mut locs = call_locations.iter().collect::>(); locs.sort_by_key(sort_criterion); diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 5ebc545d10c2b..4a8babaef7554 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -1901,6 +1901,10 @@ in storage.js border-radius: 50px; } +.scraped-example { + position: relative; +} + .scraped-example .code-wrapper { position: relative; display: flex; @@ -1909,16 +1913,31 @@ in storage.js width: 100%; } +.scraped-example-title { + position: absolute; + z-index: 1000; + background: white; + bottom: 8px; + right: 5px; + padding: 2px 4px; + box-shadow: 0 0 4px white; +} + .scraped-example:not(.expanded) .code-wrapper { - max-height: 240px; + max-height: 120px; } .scraped-example:not(.expanded) .code-wrapper pre { overflow-y: hidden; - max-height: 240px; + max-height: 120px; padding-bottom: 0; } +.more-scraped-examples .scraped-example:not(.expanded) .code-wrapper, +.more-scraped-examples .scraped-example:not(.expanded) .code-wrapper pre { + max-height: 240px; +} + .scraped-example .code-wrapper .next, .scraped-example .code-wrapper .prev, .scraped-example .code-wrapper .expand { diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 623f46b109666..152116089c7fc 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -622,7 +622,7 @@ function loadCss(cssUrl) { const innerToggle = document.getElementById(toggleAllDocsId); removeClass(innerToggle, "will-expand"); onEachLazy(document.getElementsByClassName("rustdoc-toggle"), e => { - if (!hasClass(e, "type-contents-toggle")) { + if (!hasClass(e, "type-contents-toggle") && !hasClass(e, "more-examples-toggle")) { e.open = true; } }); diff --git a/src/librustdoc/html/static/js/scrape-examples.js b/src/librustdoc/html/static/js/scrape-examples.js index e328e656edda4..82b71ad0fca5d 100644 --- a/src/librustdoc/html/static/js/scrape-examples.js +++ b/src/librustdoc/html/static/js/scrape-examples.js @@ -4,17 +4,19 @@ (function() { // Number of lines shown when code viewer is not expanded - const MAX_LINES = 10; + const DEFAULT_MAX_LINES = 5; + const HIDDEN_MAX_LINES = 10; // Scroll code block to the given code location - function scrollToLoc(elt, loc) { + function scrollToLoc(elt, loc, isHidden) { const lines = elt.querySelector(".src-line-numbers"); let 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) { + let maxLines = isHidden ? HIDDEN_MAX_LINES : DEFAULT_MAX_LINES; + if (loc[1] - loc[0] > maxLines) { const line = Math.max(0, loc[0] - 1); scrollOffset = lines.children[line].offsetTop; } else { @@ -29,7 +31,7 @@ elt.querySelector(".rust").scrollTo(0, scrollOffset); } - function updateScrapedExample(example) { + function updateScrapedExample(example, isHidden) { const locs = JSON.parse(example.attributes.getNamedItem("data-locs").textContent); let locIndex = 0; const highlights = Array.prototype.slice.call(example.querySelectorAll(".highlight")); @@ -40,7 +42,7 @@ const onChangeLoc = changeIndex => { removeClass(highlights[locIndex], "focus"); changeIndex(); - scrollToLoc(example, locs[locIndex][0]); + scrollToLoc(example, locs[locIndex][0], isHidden); addClass(highlights[locIndex], "focus"); const url = locs[locIndex][1]; @@ -70,7 +72,7 @@ expandButton.addEventListener("click", () => { if (hasClass(example, "expanded")) { removeClass(example, "expanded"); - scrollToLoc(example, locs[0][0]); + scrollToLoc(example, locs[0][0], isHidden); } else { addClass(example, "expanded"); } @@ -78,11 +80,11 @@ } // Start with the first example in view - scrollToLoc(example, locs[0][0]); + scrollToLoc(example, locs[0][0], isHidden); } const firstExamples = document.querySelectorAll(".scraped-example-list > .scraped-example"); - onEachLazy(firstExamples, updateScrapedExample); + onEachLazy(firstExamples, el => updateScrapedExample(el, false)); onEachLazy(document.querySelectorAll(".more-examples-toggle"), 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. @@ -99,7 +101,7 @@ // depends on offsetHeight, a property that requires an element to be visible to // compute correctly. setTimeout(() => { - onEachLazy(moreExamples, updateScrapedExample); + onEachLazy(moreExamples, el => updateScrapedExample(el, true)); }); }, {once: true}); }); diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 6d34f484754c7..e27af61051c4f 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -774,6 +774,7 @@ fn main_args(at_args: &[String]) -> MainResult { let output_format = options.output_format; let externs = options.externs.clone(); let scrape_examples_options = options.scrape_examples_options.clone(); + let crate_types = options.crate_types.clone(); let config = core::create_config(options); @@ -832,7 +833,14 @@ fn main_args(at_args: &[String]) -> MainResult { info!("finished with rustc"); if let Some(options) = scrape_examples_options { - return scrape_examples::run(krate, render_opts, cache, tcx, options); + return scrape_examples::run( + krate, + render_opts, + cache, + tcx, + options, + crate_types, + ); } cache.crate_version = crate_version; diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs index dfa6ba38b883b..9adccda0e7200 100644 --- a/src/librustdoc/scrape_examples.rs +++ b/src/librustdoc/scrape_examples.rs @@ -20,7 +20,7 @@ use rustc_serialize::{ opaque::{FileEncoder, MemDecoder}, Decodable, Encodable, }; -use rustc_session::getopts; +use rustc_session::{config::CrateType, getopts}; use rustc_span::{ def_id::{CrateNum, DefPathHash, LOCAL_CRATE}, edition::Edition, @@ -110,6 +110,7 @@ pub(crate) struct CallData { pub(crate) url: String, pub(crate) display_name: String, pub(crate) edition: Edition, + pub(crate) is_bin: bool, } pub(crate) type FnCallLocations = FxHashMap; @@ -122,6 +123,7 @@ struct FindCalls<'a, 'tcx> { cx: Context<'tcx>, target_crates: Vec, calls: &'a mut AllCallLocations, + crate_types: Vec, } impl<'a, 'tcx> Visitor<'tcx> for FindCalls<'a, 'tcx> @@ -245,7 +247,9 @@ where let mk_call_data = || { let display_name = file_path.display().to_string(); let edition = call_span.edition(); - CallData { locations: Vec::new(), url, display_name, edition } + let is_bin = self.crate_types.contains(&CrateType::Executable); + + CallData { locations: Vec::new(), url, display_name, edition, is_bin } }; let fn_key = tcx.def_path_hash(*def_id); @@ -274,6 +278,7 @@ pub(crate) fn run( cache: formats::cache::Cache, tcx: TyCtxt<'_>, options: ScrapeExamplesOptions, + crate_types: Vec, ) -> interface::Result<()> { let inner = move || -> Result<(), String> { // Generates source files for examples @@ -300,7 +305,8 @@ pub(crate) fn run( // Run call-finder on all items let mut calls = FxHashMap::default(); - let mut finder = FindCalls { calls: &mut calls, tcx, map: tcx.hir(), cx, target_crates }; + let mut finder = + FindCalls { calls: &mut calls, tcx, map: tcx.hir(), cx, target_crates, crate_types }; tcx.hir().visit_all_item_likes_in_crate(&mut finder); // The visitor might have found a type error, which we need to diff --git a/src/test/rustdoc-gui/scrape-examples-button-focus.goml b/src/test/rustdoc-gui/scrape-examples-button-focus.goml index a222139f1dc44..a353504df6ad0 100644 --- a/src/test/rustdoc-gui/scrape-examples-button-focus.goml +++ b/src/test/rustdoc-gui/scrape-examples-button-focus.goml @@ -25,3 +25,12 @@ store-property: (fullOffsetHeight, ".scraped-example-list > .scraped-example pre assert-property: (".scraped-example-list > .scraped-example pre", { "scrollHeight": |fullOffsetHeight| }) + +assert-attribute-false: (".more-examples-toggle", {"open": ""}) +click: ".more-examples-toggle" +assert-attribute: (".more-examples-toggle", {"open": ""}) +click: "#toggle-all-docs" +assert-attribute-false: (".more-examples-toggle", {"open": ""}) +// After re-opening the docs, the additional examples should stay closed +click: "#toggle-all-docs" +assert-attribute-false: (".more-examples-toggle", {"open": ""}) diff --git a/src/test/rustdoc-gui/src/scrape_examples/examples/check2.rs b/src/test/rustdoc-gui/src/scrape_examples/examples/check2.rs new file mode 100644 index 0000000000000..3e69c6086ae2e --- /dev/null +++ b/src/test/rustdoc-gui/src/scrape_examples/examples/check2.rs @@ -0,0 +1,25 @@ +fn main() { + for i in 0..9 { + println!("hello world!"); + println!("hello world!"); + println!("hello world!"); + println!("hello world!"); + println!("hello world!"); + println!("hello world!"); + println!("hello world!"); + println!("hello world!"); + println!("hello world!"); + } + scrape_examples::test(); + for i in 0..9 { + println!("hello world!"); + println!("hello world!"); + println!("hello world!"); + println!("hello world!"); + println!("hello world!"); + println!("hello world!"); + println!("hello world!"); + println!("hello world!"); + println!("hello world!"); + } +} From acd70e674d1dd28ced1917997df6dd7cd560e095 Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Mon, 5 Dec 2022 23:34:02 -0800 Subject: [PATCH 06/25] Add explanations to scrape-examples integration test --- ...-button-focus.goml => scrape-examples-interactions.goml} | 6 ++++++ 1 file changed, 6 insertions(+) rename src/test/rustdoc-gui/{scrape-examples-button-focus.goml => scrape-examples-interactions.goml} (85%) diff --git a/src/test/rustdoc-gui/scrape-examples-button-focus.goml b/src/test/rustdoc-gui/scrape-examples-interactions.goml similarity index 85% rename from src/test/rustdoc-gui/scrape-examples-button-focus.goml rename to src/test/rustdoc-gui/scrape-examples-interactions.goml index a353504df6ad0..f7b43c4c85bbf 100644 --- a/src/test/rustdoc-gui/scrape-examples-button-focus.goml +++ b/src/test/rustdoc-gui/scrape-examples-interactions.goml @@ -1,5 +1,6 @@ goto: "file://" + |DOC_PATH| + "/scrape_examples/fn.test.html" +// The next/prev buttons vertically scroll the code viewport between examples store-property: (initialScrollTop, ".scraped-example-list > .scraped-example pre", "scrollTop") focus: ".scraped-example-list > .scraped-example .next" press-key: "Enter" @@ -12,6 +13,7 @@ assert-property: (".scraped-example-list > .scraped-example pre", { "scrollTop": |initialScrollTop| }) +// The expand button increases the scrollHeight of the minimized code viewport store-property: (smallOffsetHeight, ".scraped-example-list > .scraped-example pre", "offsetHeight") assert-property-false: (".scraped-example-list > .scraped-example pre", { "scrollHeight": |smallOffsetHeight| @@ -26,11 +28,15 @@ assert-property: (".scraped-example-list > .scraped-example pre", { "scrollHeight": |fullOffsetHeight| }) +// Clicking "More examples..." will open additional examples assert-attribute-false: (".more-examples-toggle", {"open": ""}) click: ".more-examples-toggle" assert-attribute: (".more-examples-toggle", {"open": ""}) + +// Toggling all docs will close additional examples click: "#toggle-all-docs" assert-attribute-false: (".more-examples-toggle", {"open": ""}) + // After re-opening the docs, the additional examples should stay closed click: "#toggle-all-docs" assert-attribute-false: (".more-examples-toggle", {"open": ""}) From 45742170741d6c6b9abda5fc51012a01ce1c704a Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Mon, 5 Dec 2022 23:48:07 -0800 Subject: [PATCH 07/25] Only put title over example on large screens --- src/librustdoc/html/static/css/rustdoc.css | 23 ++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 4a8babaef7554..ec941c3a93813 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -1815,6 +1815,19 @@ in storage.js } } +/* Should have min-width: (N + 1)px where N is the mobile breakpoint above. */ +@media (min-width: 701px) { + .scraped-example-title { + position: absolute; + z-index: 1000; + background: var(--main-background-color); + bottom: 8px; + right: 5px; + padding: 2px 4px; + box-shadow: 0 0 4px var(--main-background-color); + } +} + @media print { nav.sidebar, nav.sub, .out-of-band, a.srclink, #copy-path, details.rustdoc-toggle[open] > summary::before, details.rustdoc-toggle > summary::before, @@ -1913,16 +1926,6 @@ in storage.js width: 100%; } -.scraped-example-title { - position: absolute; - z-index: 1000; - background: white; - bottom: 8px; - right: 5px; - padding: 2px 4px; - box-shadow: 0 0 4px white; -} - .scraped-example:not(.expanded) .code-wrapper { max-height: 120px; } From 679d7ea064491dfc8d067fcef07827e7569a093e Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Tue, 6 Dec 2022 09:34:37 -0800 Subject: [PATCH 08/25] Include additional documentation for scrape-examples changes --- src/librustdoc/html/static/css/rustdoc.css | 6 +++++- src/librustdoc/html/static/js/scrape-examples.js | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index ec941c3a93813..0432d445d53fb 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -1817,9 +1817,12 @@ in storage.js /* Should have min-width: (N + 1)px where N is the mobile breakpoint above. */ @media (min-width: 701px) { + /* Places file-link for a scraped example on top of the example to save space. + We only do this on large screens so the file-link doesn't overlap too much + with the example's content. */ .scraped-example-title { position: absolute; - z-index: 1000; + z-index: 10; background: var(--main-background-color); bottom: 8px; right: 5px; @@ -1915,6 +1918,7 @@ in storage.js } .scraped-example { + /* So .scraped-example-title can be positioned absolutely */ position: relative; } diff --git a/src/librustdoc/html/static/js/scrape-examples.js b/src/librustdoc/html/static/js/scrape-examples.js index 82b71ad0fca5d..d179909ac7fab 100644 --- a/src/librustdoc/html/static/js/scrape-examples.js +++ b/src/librustdoc/html/static/js/scrape-examples.js @@ -3,7 +3,9 @@ "use strict"; (function() { - // Number of lines shown when code viewer is not expanded + // Number of lines shown when code viewer is not expanded. + // DEFAULT is the first example shown by default, while HIDDEN is + // the examples hidden beneath the "More examples" toggle. const DEFAULT_MAX_LINES = 5; const HIDDEN_MAX_LINES = 10; From 212d03dadca627e55273294404bcc6312aa65969 Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Tue, 6 Dec 2022 09:59:49 -0800 Subject: [PATCH 09/25] Factor scrape-examples toggle test into a new file --- ...goml => scrape-examples-button-focus.goml} | 13 ---------- .../rustdoc-gui/scrape-examples-toggle.goml | 14 +++++++++++ .../src/scrape_examples/examples/check2.rs | 25 ------------------- 3 files changed, 14 insertions(+), 38 deletions(-) rename src/test/rustdoc-gui/{scrape-examples-interactions.goml => scrape-examples-button-focus.goml} (71%) create mode 100644 src/test/rustdoc-gui/scrape-examples-toggle.goml delete mode 100644 src/test/rustdoc-gui/src/scrape_examples/examples/check2.rs diff --git a/src/test/rustdoc-gui/scrape-examples-interactions.goml b/src/test/rustdoc-gui/scrape-examples-button-focus.goml similarity index 71% rename from src/test/rustdoc-gui/scrape-examples-interactions.goml rename to src/test/rustdoc-gui/scrape-examples-button-focus.goml index f7b43c4c85bbf..bba518db099a4 100644 --- a/src/test/rustdoc-gui/scrape-examples-interactions.goml +++ b/src/test/rustdoc-gui/scrape-examples-button-focus.goml @@ -27,16 +27,3 @@ store-property: (fullOffsetHeight, ".scraped-example-list > .scraped-example pre assert-property: (".scraped-example-list > .scraped-example pre", { "scrollHeight": |fullOffsetHeight| }) - -// Clicking "More examples..." will open additional examples -assert-attribute-false: (".more-examples-toggle", {"open": ""}) -click: ".more-examples-toggle" -assert-attribute: (".more-examples-toggle", {"open": ""}) - -// Toggling all docs will close additional examples -click: "#toggle-all-docs" -assert-attribute-false: (".more-examples-toggle", {"open": ""}) - -// After re-opening the docs, the additional examples should stay closed -click: "#toggle-all-docs" -assert-attribute-false: (".more-examples-toggle", {"open": ""}) diff --git a/src/test/rustdoc-gui/scrape-examples-toggle.goml b/src/test/rustdoc-gui/scrape-examples-toggle.goml new file mode 100644 index 0000000000000..ee720afb788fe --- /dev/null +++ b/src/test/rustdoc-gui/scrape-examples-toggle.goml @@ -0,0 +1,14 @@ +goto: "file://" + |DOC_PATH| + "/scrape_examples/fn.test_many.html" + +// Clicking "More examples..." will open additional examples +assert-attribute-false: (".more-examples-toggle", {"open": ""}) +click: ".more-examples-toggle" +assert-attribute: (".more-examples-toggle", {"open": ""}) + +// Toggling all docs will close additional examples +click: "#toggle-all-docs" +assert-attribute-false: (".more-examples-toggle", {"open": ""}) + +// After re-opening the docs, the additional examples should stay closed +click: "#toggle-all-docs" +assert-attribute-false: (".more-examples-toggle", {"open": ""}) diff --git a/src/test/rustdoc-gui/src/scrape_examples/examples/check2.rs b/src/test/rustdoc-gui/src/scrape_examples/examples/check2.rs deleted file mode 100644 index 3e69c6086ae2e..0000000000000 --- a/src/test/rustdoc-gui/src/scrape_examples/examples/check2.rs +++ /dev/null @@ -1,25 +0,0 @@ -fn main() { - for i in 0..9 { - println!("hello world!"); - println!("hello world!"); - println!("hello world!"); - println!("hello world!"); - println!("hello world!"); - println!("hello world!"); - println!("hello world!"); - println!("hello world!"); - println!("hello world!"); - } - scrape_examples::test(); - for i in 0..9 { - println!("hello world!"); - println!("hello world!"); - println!("hello world!"); - println!("hello world!"); - println!("hello world!"); - println!("hello world!"); - println!("hello world!"); - println!("hello world!"); - println!("hello world!"); - } -} From ae270f1b991ced071d93eab76219a0e3788b89ad Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Tue, 6 Dec 2022 10:11:55 -0800 Subject: [PATCH 10/25] Update scrape-examples help, fix documentation typos --- src/librustdoc/html/render/mod.rs | 15 ++++++++------- .../html/static/scrape-examples-help.md | 5 +++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index ea466c639c39e..36d15ec3b8640 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -2957,14 +2957,15 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite // The call locations are output in sequence, so that sequence needs to be determined. // Ideally the most "relevant" examples would be shown first, but there's no general algorithm - // for determining relevance. We instead make a proxy for relevance with the following heuristics: + // for determining relevance. We instead proxy relevance with the following heuristics: // 1. Code written to be an example is better than code not written to be an example, e.g. - // a snippet from examples/foo.rs is better than src/lib.rs. We don't know the Cargo directory - // structure in Rustdoc, so we proxy this by prioriting code that comes from a --crate-type bin. - // 2. Smaller examples are better than large examples. So we prioritize snippets that have the - // smallest line span for their enclosing item. - // 3. Finally we sort by the displayed file name, which is arbitrary but prevents the ordering - // of examples from randomly changing between Rustdoc invocations. + // a snippet from examples/foo.rs is better than src/lib.rs. We don't know the Cargo + // directory structure in Rustdoc, so we proxy this by prioritizing code that comes from + // a --crate-type bin. + // 2. Smaller examples are better than large examples. So we prioritize snippets that have + // the smallest number of lines in their enclosing item. + // 3. Finally we sort by the displayed file name, which is arbitrary but prevents the + // ordering of examples from randomly changing between Rustdoc invocations. let ordered_locations = { fn sort_criterion<'a>( (_, call_data): &(&PathBuf, &'a CallData), diff --git a/src/librustdoc/html/static/scrape-examples-help.md b/src/librustdoc/html/static/scrape-examples-help.md index 035b2e18b00eb..002d19ec9b67d 100644 --- a/src/librustdoc/html/static/scrape-examples-help.md +++ b/src/librustdoc/html/static/scrape-examples-help.md @@ -1,4 +1,4 @@ -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: +Rustdoc will automatically scrape examples of documented items from a project's source code. 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 @@ -16,6 +16,7 @@ fn main() { 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. @@ -25,7 +26,7 @@ If there is more than one file that contains examples, then you should click "Mo ## 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. +When you run `cargo doc -Zunstable-options -Zrustdoc-scrape-examples`, Rustdoc will analyze all the documented crates for uses of documented items. 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: From bcdab876c823ef4e66f88e2716bd13d7a42634dd Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Tue, 6 Dec 2022 10:22:23 -0800 Subject: [PATCH 11/25] Fix es-check --- src/librustdoc/html/static/js/scrape-examples.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/librustdoc/html/static/js/scrape-examples.js b/src/librustdoc/html/static/js/scrape-examples.js index d179909ac7fab..830b44d6bc0fd 100644 --- a/src/librustdoc/html/static/js/scrape-examples.js +++ b/src/librustdoc/html/static/js/scrape-examples.js @@ -17,7 +17,7 @@ // 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. - let maxLines = isHidden ? HIDDEN_MAX_LINES : DEFAULT_MAX_LINES; + const maxLines = isHidden ? HIDDEN_MAX_LINES : DEFAULT_MAX_LINES; if (loc[1] - loc[0] > maxLines) { const line = Math.max(0, loc[0] - 1); scrollOffset = lines.children[line].offsetTop; From 0709e534df2a85486f981bfbebd153bb25e3703d Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Tue, 6 Dec 2022 11:24:26 -0800 Subject: [PATCH 12/25] Fix rustdoc error with no providec crate-type, fix scrape examples button colors w/ themes --- src/librustdoc/core.rs | 3 ++- src/librustdoc/doctest.rs | 8 ++++++-- src/librustdoc/html/static/css/rustdoc.css | 1 + 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index c6358874c6162..58954ecc73f1a 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -13,7 +13,7 @@ use rustc_interface::interface; use rustc_middle::hir::nested_filter; use rustc_middle::ty::{ParamEnv, Ty, TyCtxt}; use rustc_resolve as resolve; -use rustc_session::config::{self, ErrorOutputType}; +use rustc_session::config::{self, CrateType, ErrorOutputType}; use rustc_session::lint; use rustc_session::Session; use rustc_span::symbol::sym; @@ -247,6 +247,7 @@ pub(crate) fn create_config( Some((lint.name_lower(), lint::Allow)) }); + let crate_types = if crate_types.is_empty() { vec![CrateType::Rlib] } else { crate_types }; let test = scrape_examples_options.map(|opts| opts.scrape_tests).unwrap_or(false); // plays with error output here! let sessopts = config::Options { diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index b70444ec67395..30bc2f90d2c52 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -12,7 +12,7 @@ use rustc_middle::hir::nested_filter; use rustc_middle::ty::TyCtxt; use rustc_parse::maybe_new_parser_from_source_str; use rustc_parse::parser::attr::InnerAttrPolicy; -use rustc_session::config::{self, ErrorOutputType}; +use rustc_session::config::{self, CrateType, ErrorOutputType}; use rustc_session::parse::ParseSess; use rustc_session::{lint, Session}; use rustc_span::edition::Edition; @@ -68,7 +68,11 @@ pub(crate) fn run(options: RustdocOptions) -> Result<(), ErrorGuaranteed> { debug!(?lint_opts); - let crate_types = options.crate_types.clone(); + let crate_types = if options.crate_types.is_empty() { + vec![CrateType::Rlib] + } else { + options.crate_types.clone() + }; let sessopts = config::Options { maybe_sysroot: options.maybe_sysroot.clone(), diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 0432d445d53fb..6e5e293780d3d 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -1948,6 +1948,7 @@ in storage.js .scraped-example .code-wrapper .next, .scraped-example .code-wrapper .prev, .scraped-example .code-wrapper .expand { + color: var(--main-color); position: absolute; top: 0.25em; z-index: 1; From 8a459384ad02d120f1d1cc81166f95262c1d4fac Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Tue, 6 Dec 2022 12:56:02 -0800 Subject: [PATCH 13/25] Revert crate_types change, add new bin_crate field --- src/librustdoc/config.rs | 14 ++++++++++---- src/librustdoc/core.rs | 5 +++-- src/librustdoc/doctest.rs | 7 ++----- src/librustdoc/lib.rs | 4 ++-- src/librustdoc/scrape_examples.rs | 10 +++++----- 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 41af4f9561b53..56b40d8c66baf 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -69,8 +69,10 @@ pub(crate) struct Options { pub(crate) input: PathBuf, /// The name of the crate being documented. pub(crate) crate_name: Option, - /// The types of the crate being documented. - pub(crate) crate_types: Vec, + /// Whether or not this is a bin crate + pub(crate) bin_crate: bool, + /// Whether or not this is a proc-macro crate + pub(crate) proc_macro_crate: bool, /// How to format errors and warnings. pub(crate) error_format: ErrorOutputType, /// Width of output buffer to truncate errors appropriately. @@ -176,7 +178,8 @@ impl fmt::Debug for Options { f.debug_struct("Options") .field("input", &self.input) .field("crate_name", &self.crate_name) - .field("crate_types", &self.crate_types) + .field("bin_crate", &self.bin_crate) + .field("proc_macro_crate", &self.proc_macro_crate) .field("error_format", &self.error_format) .field("libs", &self.libs) .field("externs", &FmtExterns(&self.externs)) @@ -667,6 +670,8 @@ impl Options { None => OutputFormat::default(), }; let crate_name = matches.opt_str("crate-name"); + let bin_crate = crate_types.contains(&CrateType::Executable); + let proc_macro_crate = crate_types.contains(&CrateType::ProcMacro); let playground_url = matches.opt_str("playground-url"); let maybe_sysroot = matches.opt_str("sysroot").map(PathBuf::from); let module_sorting = if matches.opt_present("sort-modules-by-appearance") { @@ -717,7 +722,8 @@ impl Options { rustc_feature::UnstableFeatures::from_environment(crate_name.as_deref()); let options = Options { input, - crate_types, + bin_crate, + proc_macro_crate, error_format, diagnostic_width, libs, diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index 58954ecc73f1a..da0df596c41e3 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -203,7 +203,7 @@ pub(crate) fn create_config( RustdocOptions { input, crate_name, - crate_types, + proc_macro_crate, error_format, diagnostic_width, libs, @@ -247,7 +247,8 @@ pub(crate) fn create_config( Some((lint.name_lower(), lint::Allow)) }); - let crate_types = if crate_types.is_empty() { vec![CrateType::Rlib] } else { crate_types }; + let crate_types = + if proc_macro_crate { vec![CrateType::ProcMacro] } else { vec![CrateType::Rlib] }; let test = scrape_examples_options.map(|opts| opts.scrape_tests).unwrap_or(false); // plays with error output here! let sessopts = config::Options { diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 30bc2f90d2c52..81d9c46447a37 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -68,11 +68,8 @@ pub(crate) fn run(options: RustdocOptions) -> Result<(), ErrorGuaranteed> { debug!(?lint_opts); - let crate_types = if options.crate_types.is_empty() { - vec![CrateType::Rlib] - } else { - options.crate_types.clone() - }; + let crate_types = + if options.proc_macro_crate { vec![CrateType::ProcMacro] } else { vec![CrateType::Rlib] }; let sessopts = config::Options { maybe_sysroot: options.maybe_sysroot.clone(), diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index e27af61051c4f..3f84eb0b4c655 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -774,7 +774,7 @@ fn main_args(at_args: &[String]) -> MainResult { let output_format = options.output_format; let externs = options.externs.clone(); let scrape_examples_options = options.scrape_examples_options.clone(); - let crate_types = options.crate_types.clone(); + let bin_crate = options.bin_crate; let config = core::create_config(options); @@ -839,7 +839,7 @@ fn main_args(at_args: &[String]) -> MainResult { cache, tcx, options, - crate_types, + bin_crate, ); } diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs index 9adccda0e7200..f2ee99cd9d494 100644 --- a/src/librustdoc/scrape_examples.rs +++ b/src/librustdoc/scrape_examples.rs @@ -20,7 +20,7 @@ use rustc_serialize::{ opaque::{FileEncoder, MemDecoder}, Decodable, Encodable, }; -use rustc_session::{config::CrateType, getopts}; +use rustc_session::getopts; use rustc_span::{ def_id::{CrateNum, DefPathHash, LOCAL_CRATE}, edition::Edition, @@ -123,7 +123,7 @@ struct FindCalls<'a, 'tcx> { cx: Context<'tcx>, target_crates: Vec, calls: &'a mut AllCallLocations, - crate_types: Vec, + bin_crate: bool, } impl<'a, 'tcx> Visitor<'tcx> for FindCalls<'a, 'tcx> @@ -247,7 +247,7 @@ where let mk_call_data = || { let display_name = file_path.display().to_string(); let edition = call_span.edition(); - let is_bin = self.crate_types.contains(&CrateType::Executable); + let is_bin = self.bin_crate; CallData { locations: Vec::new(), url, display_name, edition, is_bin } }; @@ -278,7 +278,7 @@ pub(crate) fn run( cache: formats::cache::Cache, tcx: TyCtxt<'_>, options: ScrapeExamplesOptions, - crate_types: Vec, + bin_crate: bool, ) -> interface::Result<()> { let inner = move || -> Result<(), String> { // Generates source files for examples @@ -306,7 +306,7 @@ pub(crate) fn run( // Run call-finder on all items let mut calls = FxHashMap::default(); let mut finder = - FindCalls { calls: &mut calls, tcx, map: tcx.hir(), cx, target_crates, crate_types }; + FindCalls { calls: &mut calls, tcx, map: tcx.hir(), cx, target_crates, bin_crate }; tcx.hir().visit_all_item_likes_in_crate(&mut finder); // The visitor might have found a type error, which we need to From 9499d2cce3bebef96d8ae64442602f87726a875a Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Wed, 7 Dec 2022 10:24:16 -0800 Subject: [PATCH 14/25] Improve calculation of scraped example minimized height --- src/librustdoc/html/static/css/rustdoc.css | 12 +++++++++--- src/librustdoc/html/static/js/scrape-examples.js | 8 ++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 6e5e293780d3d..afed3da9e91d2 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -1931,18 +1931,24 @@ in storage.js } .scraped-example:not(.expanded) .code-wrapper { - max-height: 120px; + /* scrape-examples.js has a constant DEFAULT_MAX_LINES (call it N) for the number + * of lines shown in the un-expanded example code viewer. This pre needs to have + * a max-height equal to line-height * N. The line-height is currently 1.5em, + * and we include additional 10px for padding. */ + max-height: calc(1.5em * 5 + 10px); } .scraped-example:not(.expanded) .code-wrapper pre { overflow-y: hidden; - max-height: 120px; padding-bottom: 0; + /* See above comment, should be the same max-height. */ + max-height: calc(1.5em * 5 + 10px); } .more-scraped-examples .scraped-example:not(.expanded) .code-wrapper, .more-scraped-examples .scraped-example:not(.expanded) .code-wrapper pre { - max-height: 240px; + /* See above comment, except this height is based on HIDDEN_MAX_LINES. */ + max-height: calc(1.5em * 10 + 10px); } .scraped-example .code-wrapper .next, diff --git a/src/librustdoc/html/static/js/scrape-examples.js b/src/librustdoc/html/static/js/scrape-examples.js index 830b44d6bc0fd..7a3a9c5f34001 100644 --- a/src/librustdoc/html/static/js/scrape-examples.js +++ b/src/librustdoc/html/static/js/scrape-examples.js @@ -6,6 +6,8 @@ // Number of lines shown when code viewer is not expanded. // DEFAULT is the first example shown by default, while HIDDEN is // the examples hidden beneath the "More examples" toggle. + // + // NOTE: these values MUST be synchronized with certain rules in rustdoc.css! const DEFAULT_MAX_LINES = 5; const HIDDEN_MAX_LINES = 10; @@ -24,8 +26,10 @@ } else { const wrapper = elt.querySelector(".code-wrapper"); const halfHeight = wrapper.offsetHeight / 2; - const offsetMid = (lines.children[loc[0]].offsetTop - + lines.children[loc[1]].offsetTop) / 2; + const offsetTop = lines.children[loc[0]].offsetTop; + const lastLine = lines.children[loc[1]]; + const offsetBot = lastLine.offsetTop + lastLine.offsetHeight; + const offsetMid = (offsetTop + offsetBot) / 2; scrollOffset = offsetMid - halfHeight; } From 48270044b04750477da7276a8845ceb06d7b2185 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 8 Dec 2022 04:02:50 +0000 Subject: [PATCH 15/25] Move has_structural_eq_impls provider to rustc_ty_utils --- .../rustc_trait_selection/src/traits/mod.rs | 1 - .../src/traits/structural_match.rs | 48 ------------------- compiler/rustc_ty_utils/src/lib.rs | 2 + .../rustc_ty_utils/src/structural_match.rs | 44 +++++++++++++++++ 4 files changed, 46 insertions(+), 49 deletions(-) create mode 100644 compiler/rustc_ty_utils/src/structural_match.rs diff --git a/compiler/rustc_trait_selection/src/traits/mod.rs b/compiler/rustc_trait_selection/src/traits/mod.rs index d3cfd61e1956d..6d731ac3f7595 100644 --- a/compiler/rustc_trait_selection/src/traits/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/mod.rs @@ -922,7 +922,6 @@ pub fn vtable_trait_upcasting_coercion_new_vptr_slot<'tcx>( pub fn provide(providers: &mut ty::query::Providers) { object_safety::provide(providers); - structural_match::provide(providers); *providers = ty::query::Providers { specialization_graph_of: specialize::specialization_graph_provider, specializes: specialize::specializes, diff --git a/compiler/rustc_trait_selection/src/traits/structural_match.rs b/compiler/rustc_trait_selection/src/traits/structural_match.rs index 40dbe0b3ff063..4dc08e0f9dab0 100644 --- a/compiler/rustc_trait_selection/src/traits/structural_match.rs +++ b/compiler/rustc_trait_selection/src/traits/structural_match.rs @@ -1,10 +1,5 @@ -use crate::infer::{InferCtxt, TyCtxtInferExt}; -use crate::traits::{ObligationCause, ObligationCtxt}; - use rustc_data_structures::fx::FxHashSet; use rustc_hir as hir; -use rustc_hir::lang_items::LangItem; -use rustc_middle::ty::query::Providers; use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor}; use rustc_span::Span; use std::ops::ControlFlow; @@ -59,41 +54,6 @@ pub fn search_for_adt_const_param_violation<'tcx>( .break_value() } -/// This method returns true if and only if `adt_ty` itself has been marked as -/// eligible for structural-match: namely, if it implements both -/// `StructuralPartialEq` and `StructuralEq` (which are respectively injected by -/// `#[derive(PartialEq)]` and `#[derive(Eq)]`). -/// -/// Note that this does *not* recursively check if the substructure of `adt_ty` -/// implements the traits. -fn type_marked_structural<'tcx>( - infcx: &InferCtxt<'tcx>, - adt_ty: Ty<'tcx>, - cause: ObligationCause<'tcx>, -) -> bool { - let ocx = ObligationCtxt::new(infcx); - // require `#[derive(PartialEq)]` - let structural_peq_def_id = - infcx.tcx.require_lang_item(LangItem::StructuralPeq, Some(cause.span)); - ocx.register_bound(cause.clone(), ty::ParamEnv::empty(), adt_ty, structural_peq_def_id); - // for now, require `#[derive(Eq)]`. (Doing so is a hack to work around - // the type `for<'a> fn(&'a ())` failing to implement `Eq` itself.) - let structural_teq_def_id = - infcx.tcx.require_lang_item(LangItem::StructuralTeq, Some(cause.span)); - ocx.register_bound(cause, ty::ParamEnv::empty(), adt_ty, structural_teq_def_id); - - // We deliberately skip *reporting* fulfillment errors (via - // `report_fulfillment_errors`), for two reasons: - // - // 1. The error messages would mention `std::marker::StructuralPartialEq` - // (a trait which is solely meant as an implementation detail - // for now), and - // - // 2. We are sometimes doing future-incompatibility lints for - // now, so we do not want unconditional errors here. - ocx.select_all_or_error().is_empty() -} - /// This implements the traversal over the structure of a given type to try to /// find instances of ADTs (specifically structs or enums) that do not implement /// the structural-match traits (`StructuralPartialEq` and `StructuralEq`). @@ -249,11 +209,3 @@ impl<'tcx> TypeVisitor<'tcx> for Search<'tcx> { }) } } - -pub fn provide(providers: &mut Providers) { - providers.has_structural_eq_impls = |tcx, ty| { - let infcx = tcx.infer_ctxt().build(); - let cause = ObligationCause::dummy(); - type_marked_structural(&infcx, ty, cause) - }; -} diff --git a/compiler/rustc_ty_utils/src/lib.rs b/compiler/rustc_ty_utils/src/lib.rs index cce5a79ddc855..7ad5cbc01ccf2 100644 --- a/compiler/rustc_ty_utils/src/lib.rs +++ b/compiler/rustc_ty_utils/src/lib.rs @@ -29,6 +29,7 @@ mod layout; mod layout_sanity_check; mod needs_drop; pub mod representability; +mod structural_match; mod ty; pub fn provide(providers: &mut Providers) { @@ -42,4 +43,5 @@ pub fn provide(providers: &mut Providers) { representability::provide(providers); ty::provide(providers); instance::provide(providers); + structural_match::provide(providers); } diff --git a/compiler/rustc_ty_utils/src/structural_match.rs b/compiler/rustc_ty_utils/src/structural_match.rs new file mode 100644 index 0000000000000..a55bb7e7e904d --- /dev/null +++ b/compiler/rustc_ty_utils/src/structural_match.rs @@ -0,0 +1,44 @@ +use rustc_hir::lang_items::LangItem; +use rustc_middle::ty::query::Providers; +use rustc_middle::ty::{self, Ty, TyCtxt}; + +use rustc_infer::infer::TyCtxtInferExt; +use rustc_trait_selection::traits::{ObligationCause, ObligationCtxt}; + +/// This method returns true if and only if `adt_ty` itself has been marked as +/// eligible for structural-match: namely, if it implements both +/// `StructuralPartialEq` and `StructuralEq` (which are respectively injected by +/// `#[derive(PartialEq)]` and `#[derive(Eq)]`). +/// +/// Note that this does *not* recursively check if the substructure of `adt_ty` +/// implements the traits. +fn has_structural_eq_impls<'tcx>(tcx: TyCtxt<'tcx>, adt_ty: Ty<'tcx>) -> bool { + let ref infcx = tcx.infer_ctxt().build(); + let cause = ObligationCause::dummy(); + + let ocx = ObligationCtxt::new(infcx); + // require `#[derive(PartialEq)]` + let structural_peq_def_id = + infcx.tcx.require_lang_item(LangItem::StructuralPeq, Some(cause.span)); + ocx.register_bound(cause.clone(), ty::ParamEnv::empty(), adt_ty, structural_peq_def_id); + // for now, require `#[derive(Eq)]`. (Doing so is a hack to work around + // the type `for<'a> fn(&'a ())` failing to implement `Eq` itself.) + let structural_teq_def_id = + infcx.tcx.require_lang_item(LangItem::StructuralTeq, Some(cause.span)); + ocx.register_bound(cause, ty::ParamEnv::empty(), adt_ty, structural_teq_def_id); + + // We deliberately skip *reporting* fulfillment errors (via + // `report_fulfillment_errors`), for two reasons: + // + // 1. The error messages would mention `std::marker::StructuralPartialEq` + // (a trait which is solely meant as an implementation detail + // for now), and + // + // 2. We are sometimes doing future-incompatibility lints for + // now, so we do not want unconditional errors here. + ocx.select_all_or_error().is_empty() +} + +pub fn provide(providers: &mut Providers) { + providers.has_structural_eq_impls = has_structural_eq_impls; +} From b36035c20f89e33d5acc4f41d913a13700bd77c2 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 8 Dec 2022 04:51:46 +0000 Subject: [PATCH 16/25] Move vtable methods into its own module --- .../rustc_trait_selection/src/traits/mod.rs | 367 +---------------- .../src/traits/select/confirmation.rs | 13 +- .../rustc_trait_selection/src/traits/util.rs | 10 - .../src/traits/vtable.rs | 386 ++++++++++++++++++ 4 files changed, 398 insertions(+), 378 deletions(-) create mode 100644 compiler/rustc_trait_selection/src/traits/vtable.rs diff --git a/compiler/rustc_trait_selection/src/traits/mod.rs b/compiler/rustc_trait_selection/src/traits/mod.rs index 6d731ac3f7595..c2104a98cea78 100644 --- a/compiler/rustc_trait_selection/src/traits/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/mod.rs @@ -20,9 +20,9 @@ mod select; mod specialize; mod structural_match; mod util; +mod vtable; pub mod wf; -use crate::errors::DumpVTableEntries; use crate::infer::outlives::env::OutlivesEnvironment; use crate::infer::{InferCtxt, TyCtxtInferExt}; use crate::traits::error_reporting::TypeErrCtxtExt as _; @@ -30,15 +30,11 @@ use crate::traits::query::evaluate_obligation::InferCtxtExt as _; use rustc_errors::ErrorGuaranteed; use rustc_hir as hir; use rustc_hir::def_id::DefId; -use rustc_hir::lang_items::LangItem; use rustc_middle::ty::fold::TypeFoldable; use rustc_middle::ty::visit::TypeVisitable; -use rustc_middle::ty::{ - self, DefIdTree, GenericParamDefKind, ToPredicate, Ty, TyCtxt, TypeSuperVisitable, VtblEntry, -}; +use rustc_middle::ty::{self, DefIdTree, ToPredicate, Ty, TyCtxt, TypeSuperVisitable}; use rustc_middle::ty::{InternalSubsts, SubstsRef}; -use rustc_span::{sym, Span}; -use smallvec::SmallVec; +use rustc_span::Span; use std::fmt::Debug; use std::ops::ControlFlow; @@ -567,368 +563,13 @@ fn is_impossible_method<'tcx>( false } -#[derive(Clone, Debug)] -enum VtblSegment<'tcx> { - MetadataDSA, - TraitOwnEntries { trait_ref: ty::PolyTraitRef<'tcx>, emit_vptr: bool }, -} - -/// Prepare the segments for a vtable -fn prepare_vtable_segments<'tcx, T>( - tcx: TyCtxt<'tcx>, - trait_ref: ty::PolyTraitRef<'tcx>, - mut segment_visitor: impl FnMut(VtblSegment<'tcx>) -> ControlFlow, -) -> Option { - // The following constraints holds for the final arrangement. - // 1. The whole virtual table of the first direct super trait is included as the - // the prefix. If this trait doesn't have any super traits, then this step - // consists of the dsa metadata. - // 2. Then comes the proper pointer metadata(vptr) and all own methods for all - // other super traits except those already included as part of the first - // direct super trait virtual table. - // 3. finally, the own methods of this trait. - - // This has the advantage that trait upcasting to the first direct super trait on each level - // is zero cost, and to another trait includes only replacing the pointer with one level indirection, - // while not using too much extra memory. - - // For a single inheritance relationship like this, - // D --> C --> B --> A - // The resulting vtable will consists of these segments: - // DSA, A, B, C, D - - // For a multiple inheritance relationship like this, - // D --> C --> A - // \-> B - // The resulting vtable will consists of these segments: - // DSA, A, B, B-vptr, C, D - - // For a diamond inheritance relationship like this, - // D --> B --> A - // \-> C -/ - // The resulting vtable will consists of these segments: - // DSA, A, B, C, C-vptr, D - - // For a more complex inheritance relationship like this: - // O --> G --> C --> A - // \ \ \-> B - // | |-> F --> D - // | \-> E - // |-> N --> J --> H - // \ \-> I - // |-> M --> K - // \-> L - // The resulting vtable will consists of these segments: - // DSA, A, B, B-vptr, C, D, D-vptr, E, E-vptr, F, F-vptr, G, - // H, H-vptr, I, I-vptr, J, J-vptr, K, K-vptr, L, L-vptr, M, M-vptr, - // N, N-vptr, O - - // emit dsa segment first. - if let ControlFlow::Break(v) = (segment_visitor)(VtblSegment::MetadataDSA) { - return Some(v); - } - - let mut emit_vptr_on_new_entry = false; - let mut visited = util::PredicateSet::new(tcx); - let predicate = trait_ref.without_const().to_predicate(tcx); - let mut stack: SmallVec<[(ty::PolyTraitRef<'tcx>, _, _); 5]> = - smallvec![(trait_ref, emit_vptr_on_new_entry, None)]; - visited.insert(predicate); - - // the main traversal loop: - // basically we want to cut the inheritance directed graph into a few non-overlapping slices of nodes - // that each node is emitted after all its descendents have been emitted. - // so we convert the directed graph into a tree by skipping all previously visited nodes using a visited set. - // this is done on the fly. - // Each loop run emits a slice - it starts by find a "childless" unvisited node, backtracking upwards, and it - // stops after it finds a node that has a next-sibling node. - // This next-sibling node will used as the starting point of next slice. - - // Example: - // For a diamond inheritance relationship like this, - // D#1 --> B#0 --> A#0 - // \-> C#1 -/ - - // Starting point 0 stack [D] - // Loop run #0: Stack after diving in is [D B A], A is "childless" - // after this point, all newly visited nodes won't have a vtable that equals to a prefix of this one. - // Loop run #0: Emitting the slice [B A] (in reverse order), B has a next-sibling node, so this slice stops here. - // Loop run #0: Stack after exiting out is [D C], C is the next starting point. - // Loop run #1: Stack after diving in is [D C], C is "childless", since its child A is skipped(already emitted). - // Loop run #1: Emitting the slice [D C] (in reverse order). No one has a next-sibling node. - // Loop run #1: Stack after exiting out is []. Now the function exits. - - loop { - // dive deeper into the stack, recording the path - 'diving_in: loop { - if let Some((inner_most_trait_ref, _, _)) = stack.last() { - let inner_most_trait_ref = *inner_most_trait_ref; - let mut direct_super_traits_iter = tcx - .super_predicates_of(inner_most_trait_ref.def_id()) - .predicates - .into_iter() - .filter_map(move |(pred, _)| { - pred.subst_supertrait(tcx, &inner_most_trait_ref).to_opt_poly_trait_pred() - }); - - 'diving_in_skip_visited_traits: loop { - if let Some(next_super_trait) = direct_super_traits_iter.next() { - if visited.insert(next_super_trait.to_predicate(tcx)) { - // We're throwing away potential constness of super traits here. - // FIXME: handle ~const super traits - let next_super_trait = next_super_trait.map_bound(|t| t.trait_ref); - stack.push(( - next_super_trait, - emit_vptr_on_new_entry, - Some(direct_super_traits_iter), - )); - break 'diving_in_skip_visited_traits; - } else { - continue 'diving_in_skip_visited_traits; - } - } else { - break 'diving_in; - } - } - } - } - - // Other than the left-most path, vptr should be emitted for each trait. - emit_vptr_on_new_entry = true; - - // emit innermost item, move to next sibling and stop there if possible, otherwise jump to outer level. - 'exiting_out: loop { - if let Some((inner_most_trait_ref, emit_vptr, siblings_opt)) = stack.last_mut() { - if let ControlFlow::Break(v) = (segment_visitor)(VtblSegment::TraitOwnEntries { - trait_ref: *inner_most_trait_ref, - emit_vptr: *emit_vptr, - }) { - return Some(v); - } - - 'exiting_out_skip_visited_traits: loop { - if let Some(siblings) = siblings_opt { - if let Some(next_inner_most_trait_ref) = siblings.next() { - if visited.insert(next_inner_most_trait_ref.to_predicate(tcx)) { - // We're throwing away potential constness of super traits here. - // FIXME: handle ~const super traits - let next_inner_most_trait_ref = - next_inner_most_trait_ref.map_bound(|t| t.trait_ref); - *inner_most_trait_ref = next_inner_most_trait_ref; - *emit_vptr = emit_vptr_on_new_entry; - break 'exiting_out; - } else { - continue 'exiting_out_skip_visited_traits; - } - } - } - stack.pop(); - continue 'exiting_out; - } - } - // all done - return None; - } - } -} - -fn dump_vtable_entries<'tcx>( - tcx: TyCtxt<'tcx>, - sp: Span, - trait_ref: ty::PolyTraitRef<'tcx>, - entries: &[VtblEntry<'tcx>], -) { - tcx.sess.emit_err(DumpVTableEntries { - span: sp, - trait_ref, - entries: format!("{:#?}", entries), - }); -} - -fn own_existential_vtable_entries<'tcx>(tcx: TyCtxt<'tcx>, trait_def_id: DefId) -> &'tcx [DefId] { - let trait_methods = tcx - .associated_items(trait_def_id) - .in_definition_order() - .filter(|item| item.kind == ty::AssocKind::Fn); - // Now list each method's DefId (for within its trait). - let own_entries = trait_methods.filter_map(move |trait_method| { - debug!("own_existential_vtable_entry: trait_method={:?}", trait_method); - let def_id = trait_method.def_id; - - // Some methods cannot be called on an object; skip those. - if !is_vtable_safe_method(tcx, trait_def_id, &trait_method) { - debug!("own_existential_vtable_entry: not vtable safe"); - return None; - } - - Some(def_id) - }); - - tcx.arena.alloc_from_iter(own_entries.into_iter()) -} - -/// Given a trait `trait_ref`, iterates the vtable entries -/// that come from `trait_ref`, including its supertraits. -fn vtable_entries<'tcx>( - tcx: TyCtxt<'tcx>, - trait_ref: ty::PolyTraitRef<'tcx>, -) -> &'tcx [VtblEntry<'tcx>] { - debug!("vtable_entries({:?})", trait_ref); - - let mut entries = vec![]; - - let vtable_segment_callback = |segment| -> ControlFlow<()> { - match segment { - VtblSegment::MetadataDSA => { - entries.extend(TyCtxt::COMMON_VTABLE_ENTRIES); - } - VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => { - let existential_trait_ref = trait_ref - .map_bound(|trait_ref| ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref)); - - // Lookup the shape of vtable for the trait. - let own_existential_entries = - tcx.own_existential_vtable_entries(existential_trait_ref.def_id()); - - let own_entries = own_existential_entries.iter().copied().map(|def_id| { - debug!("vtable_entries: trait_method={:?}", def_id); - - // The method may have some early-bound lifetimes; add regions for those. - let substs = trait_ref.map_bound(|trait_ref| { - InternalSubsts::for_item(tcx, def_id, |param, _| match param.kind { - GenericParamDefKind::Lifetime => tcx.lifetimes.re_erased.into(), - GenericParamDefKind::Type { .. } - | GenericParamDefKind::Const { .. } => { - trait_ref.substs[param.index as usize] - } - }) - }); - - // The trait type may have higher-ranked lifetimes in it; - // erase them if they appear, so that we get the type - // at some particular call site. - let substs = tcx - .normalize_erasing_late_bound_regions(ty::ParamEnv::reveal_all(), substs); - - // It's possible that the method relies on where-clauses that - // do not hold for this particular set of type parameters. - // Note that this method could then never be called, so we - // do not want to try and codegen it, in that case (see #23435). - let predicates = tcx.predicates_of(def_id).instantiate_own(tcx, substs); - if impossible_predicates(tcx, predicates.predicates) { - debug!("vtable_entries: predicates do not hold"); - return VtblEntry::Vacant; - } - - let instance = ty::Instance::resolve_for_vtable( - tcx, - ty::ParamEnv::reveal_all(), - def_id, - substs, - ) - .expect("resolution failed during building vtable representation"); - VtblEntry::Method(instance) - }); - - entries.extend(own_entries); - - if emit_vptr { - entries.push(VtblEntry::TraitVPtr(trait_ref)); - } - } - } - - ControlFlow::Continue(()) - }; - - let _ = prepare_vtable_segments(tcx, trait_ref, vtable_segment_callback); - - if tcx.has_attr(trait_ref.def_id(), sym::rustc_dump_vtable) { - let sp = tcx.def_span(trait_ref.def_id()); - dump_vtable_entries(tcx, sp, trait_ref, &entries); - } - - tcx.arena.alloc_from_iter(entries.into_iter()) -} - -/// Find slot base for trait methods within vtable entries of another trait -fn vtable_trait_first_method_offset<'tcx>( - tcx: TyCtxt<'tcx>, - key: ( - ty::PolyTraitRef<'tcx>, // trait_to_be_found - ty::PolyTraitRef<'tcx>, // trait_owning_vtable - ), -) -> usize { - let (trait_to_be_found, trait_owning_vtable) = key; - - // #90177 - let trait_to_be_found_erased = tcx.erase_regions(trait_to_be_found); - - let vtable_segment_callback = { - let mut vtable_base = 0; - - move |segment| { - match segment { - VtblSegment::MetadataDSA => { - vtable_base += TyCtxt::COMMON_VTABLE_ENTRIES.len(); - } - VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => { - if tcx.erase_regions(trait_ref) == trait_to_be_found_erased { - return ControlFlow::Break(vtable_base); - } - vtable_base += util::count_own_vtable_entries(tcx, trait_ref); - if emit_vptr { - vtable_base += 1; - } - } - } - ControlFlow::Continue(()) - } - }; - - if let Some(vtable_base) = - prepare_vtable_segments(tcx, trait_owning_vtable, vtable_segment_callback) - { - vtable_base - } else { - bug!("Failed to find info for expected trait in vtable"); - } -} - -/// Find slot offset for trait vptr within vtable entries of another trait -pub fn vtable_trait_upcasting_coercion_new_vptr_slot<'tcx>( - tcx: TyCtxt<'tcx>, - key: ( - Ty<'tcx>, // trait object type whose trait owning vtable - Ty<'tcx>, // trait object for supertrait - ), -) -> Option { - let (source, target) = key; - assert!(matches!(&source.kind(), &ty::Dynamic(..)) && !source.needs_infer()); - assert!(matches!(&target.kind(), &ty::Dynamic(..)) && !target.needs_infer()); - - // this has been typecked-before, so diagnostics is not really needed. - let unsize_trait_did = tcx.require_lang_item(LangItem::Unsize, None); - - let trait_ref = tcx.mk_trait_ref(unsize_trait_did, [source, target]); - - match tcx.codegen_select_candidate((ty::ParamEnv::reveal_all(), ty::Binder::dummy(trait_ref))) { - Ok(ImplSource::TraitUpcasting(implsrc_traitcasting)) => { - implsrc_traitcasting.vtable_vptr_slot - } - otherwise => bug!("expected TraitUpcasting candidate, got {otherwise:?}"), - } -} - pub fn provide(providers: &mut ty::query::Providers) { object_safety::provide(providers); + vtable::provide(providers); *providers = ty::query::Providers { specialization_graph_of: specialize::specialization_graph_provider, specializes: specialize::specializes, codegen_select_candidate: codegen::codegen_select_candidate, - own_existential_vtable_entries, - vtable_entries, - vtable_trait_upcasting_coercion_new_vptr_slot, subst_and_check_impossible_predicates, is_impossible_method, ..*providers diff --git a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs index 22cd700dcb5f4..fda415155c469 100644 --- a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs +++ b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs @@ -19,6 +19,10 @@ use rustc_span::def_id::DefId; use crate::traits::project::{normalize_with_depth, normalize_with_depth_to}; use crate::traits::util::{self, closure_trait_ref_and_return_type, predicate_for_trait_def}; +use crate::traits::vtable::{ + count_own_vtable_entries, prepare_vtable_segments, vtable_trait_first_method_offset, + VtblSegment, +}; use crate::traits::{ BuiltinDerivedObligation, ImplDerivedObligation, ImplDerivedObligationCause, ImplSource, ImplSourceAutoImplData, ImplSourceBuiltinData, ImplSourceClosureData, @@ -26,7 +30,7 @@ use crate::traits::{ ImplSourceGeneratorData, ImplSourceObjectData, ImplSourceTraitAliasData, ImplSourceTraitUpcastingData, ImplSourceUserDefinedData, Normalized, ObjectCastObligation, Obligation, ObligationCause, OutputTypeParameterMismatch, PredicateObligation, Selection, - SelectionError, TraitNotObjectSafe, TraitObligation, Unimplemented, VtblSegment, + SelectionError, TraitNotObjectSafe, TraitObligation, Unimplemented, }; use super::BuiltinImplConditions; @@ -583,7 +587,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { debug!(?nested, "object nested obligations"); - let vtable_base = super::super::vtable_trait_first_method_offset( + let vtable_base = vtable_trait_first_method_offset( tcx, (unnormalized_upcast_trait_ref, ty::Binder::dummy(object_trait_ref)), ); @@ -904,7 +908,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { vptr_offset += TyCtxt::COMMON_VTABLE_ENTRIES.len(); } VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => { - vptr_offset += util::count_own_vtable_entries(tcx, trait_ref); + vptr_offset += count_own_vtable_entries(tcx, trait_ref); if trait_ref == upcast_trait_ref { if emit_vptr { return ControlFlow::Break(Some(vptr_offset)); @@ -923,8 +927,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { }; let vtable_vptr_slot = - super::super::prepare_vtable_segments(tcx, source_trait_ref, vtable_segment_callback) - .unwrap(); + prepare_vtable_segments(tcx, source_trait_ref, vtable_segment_callback).unwrap(); Ok(ImplSourceTraitUpcastingData { upcast_trait_ref, vtable_vptr_slot, nested }) } diff --git a/compiler/rustc_trait_selection/src/traits/util.rs b/compiler/rustc_trait_selection/src/traits/util.rs index 51968c2d7a191..f3ca6a6c779a0 100644 --- a/compiler/rustc_trait_selection/src/traits/util.rs +++ b/compiler/rustc_trait_selection/src/traits/util.rs @@ -261,16 +261,6 @@ pub fn upcast_choices<'tcx>( supertraits(tcx, source_trait_ref).filter(|r| r.def_id() == target_trait_def_id).collect() } -/// Given a trait `trait_ref`, returns the number of vtable entries -/// that come from `trait_ref`, excluding its supertraits. Used in -/// computing the vtable base for an upcast trait of a trait object. -pub fn count_own_vtable_entries<'tcx>( - tcx: TyCtxt<'tcx>, - trait_ref: ty::PolyTraitRef<'tcx>, -) -> usize { - tcx.own_existential_vtable_entries(trait_ref.def_id()).len() -} - /// Given an upcast trait object described by `object`, returns the /// index of the method `method_def_id` (which should be part of /// `object.upcast_trait_ref`) within the vtable for `object`. diff --git a/compiler/rustc_trait_selection/src/traits/vtable.rs b/compiler/rustc_trait_selection/src/traits/vtable.rs new file mode 100644 index 0000000000000..41ce6cdf789da --- /dev/null +++ b/compiler/rustc_trait_selection/src/traits/vtable.rs @@ -0,0 +1,386 @@ +use crate::errors::DumpVTableEntries; +use crate::traits::{impossible_predicates, is_vtable_safe_method}; +use rustc_hir::def_id::DefId; +use rustc_hir::lang_items::LangItem; +use rustc_infer::traits::util::PredicateSet; +use rustc_infer::traits::ImplSource; +use rustc_middle::ty::visit::TypeVisitable; +use rustc_middle::ty::InternalSubsts; +use rustc_middle::ty::{self, GenericParamDefKind, ToPredicate, Ty, TyCtxt, VtblEntry}; +use rustc_span::{sym, Span}; +use smallvec::SmallVec; + +use std::fmt::Debug; +use std::ops::ControlFlow; + +#[derive(Clone, Debug)] +pub(super) enum VtblSegment<'tcx> { + MetadataDSA, + TraitOwnEntries { trait_ref: ty::PolyTraitRef<'tcx>, emit_vptr: bool }, +} + +/// Prepare the segments for a vtable +pub(super) fn prepare_vtable_segments<'tcx, T>( + tcx: TyCtxt<'tcx>, + trait_ref: ty::PolyTraitRef<'tcx>, + mut segment_visitor: impl FnMut(VtblSegment<'tcx>) -> ControlFlow, +) -> Option { + // The following constraints holds for the final arrangement. + // 1. The whole virtual table of the first direct super trait is included as the + // the prefix. If this trait doesn't have any super traits, then this step + // consists of the dsa metadata. + // 2. Then comes the proper pointer metadata(vptr) and all own methods for all + // other super traits except those already included as part of the first + // direct super trait virtual table. + // 3. finally, the own methods of this trait. + + // This has the advantage that trait upcasting to the first direct super trait on each level + // is zero cost, and to another trait includes only replacing the pointer with one level indirection, + // while not using too much extra memory. + + // For a single inheritance relationship like this, + // D --> C --> B --> A + // The resulting vtable will consists of these segments: + // DSA, A, B, C, D + + // For a multiple inheritance relationship like this, + // D --> C --> A + // \-> B + // The resulting vtable will consists of these segments: + // DSA, A, B, B-vptr, C, D + + // For a diamond inheritance relationship like this, + // D --> B --> A + // \-> C -/ + // The resulting vtable will consists of these segments: + // DSA, A, B, C, C-vptr, D + + // For a more complex inheritance relationship like this: + // O --> G --> C --> A + // \ \ \-> B + // | |-> F --> D + // | \-> E + // |-> N --> J --> H + // \ \-> I + // |-> M --> K + // \-> L + // The resulting vtable will consists of these segments: + // DSA, A, B, B-vptr, C, D, D-vptr, E, E-vptr, F, F-vptr, G, + // H, H-vptr, I, I-vptr, J, J-vptr, K, K-vptr, L, L-vptr, M, M-vptr, + // N, N-vptr, O + + // emit dsa segment first. + if let ControlFlow::Break(v) = (segment_visitor)(VtblSegment::MetadataDSA) { + return Some(v); + } + + let mut emit_vptr_on_new_entry = false; + let mut visited = PredicateSet::new(tcx); + let predicate = trait_ref.without_const().to_predicate(tcx); + let mut stack: SmallVec<[(ty::PolyTraitRef<'tcx>, _, _); 5]> = + smallvec![(trait_ref, emit_vptr_on_new_entry, None)]; + visited.insert(predicate); + + // the main traversal loop: + // basically we want to cut the inheritance directed graph into a few non-overlapping slices of nodes + // that each node is emitted after all its descendents have been emitted. + // so we convert the directed graph into a tree by skipping all previously visited nodes using a visited set. + // this is done on the fly. + // Each loop run emits a slice - it starts by find a "childless" unvisited node, backtracking upwards, and it + // stops after it finds a node that has a next-sibling node. + // This next-sibling node will used as the starting point of next slice. + + // Example: + // For a diamond inheritance relationship like this, + // D#1 --> B#0 --> A#0 + // \-> C#1 -/ + + // Starting point 0 stack [D] + // Loop run #0: Stack after diving in is [D B A], A is "childless" + // after this point, all newly visited nodes won't have a vtable that equals to a prefix of this one. + // Loop run #0: Emitting the slice [B A] (in reverse order), B has a next-sibling node, so this slice stops here. + // Loop run #0: Stack after exiting out is [D C], C is the next starting point. + // Loop run #1: Stack after diving in is [D C], C is "childless", since its child A is skipped(already emitted). + // Loop run #1: Emitting the slice [D C] (in reverse order). No one has a next-sibling node. + // Loop run #1: Stack after exiting out is []. Now the function exits. + + loop { + // dive deeper into the stack, recording the path + 'diving_in: loop { + if let Some((inner_most_trait_ref, _, _)) = stack.last() { + let inner_most_trait_ref = *inner_most_trait_ref; + let mut direct_super_traits_iter = tcx + .super_predicates_of(inner_most_trait_ref.def_id()) + .predicates + .into_iter() + .filter_map(move |(pred, _)| { + pred.subst_supertrait(tcx, &inner_most_trait_ref).to_opt_poly_trait_pred() + }); + + 'diving_in_skip_visited_traits: loop { + if let Some(next_super_trait) = direct_super_traits_iter.next() { + if visited.insert(next_super_trait.to_predicate(tcx)) { + // We're throwing away potential constness of super traits here. + // FIXME: handle ~const super traits + let next_super_trait = next_super_trait.map_bound(|t| t.trait_ref); + stack.push(( + next_super_trait, + emit_vptr_on_new_entry, + Some(direct_super_traits_iter), + )); + break 'diving_in_skip_visited_traits; + } else { + continue 'diving_in_skip_visited_traits; + } + } else { + break 'diving_in; + } + } + } + } + + // Other than the left-most path, vptr should be emitted for each trait. + emit_vptr_on_new_entry = true; + + // emit innermost item, move to next sibling and stop there if possible, otherwise jump to outer level. + 'exiting_out: loop { + if let Some((inner_most_trait_ref, emit_vptr, siblings_opt)) = stack.last_mut() { + if let ControlFlow::Break(v) = (segment_visitor)(VtblSegment::TraitOwnEntries { + trait_ref: *inner_most_trait_ref, + emit_vptr: *emit_vptr, + }) { + return Some(v); + } + + 'exiting_out_skip_visited_traits: loop { + if let Some(siblings) = siblings_opt { + if let Some(next_inner_most_trait_ref) = siblings.next() { + if visited.insert(next_inner_most_trait_ref.to_predicate(tcx)) { + // We're throwing away potential constness of super traits here. + // FIXME: handle ~const super traits + let next_inner_most_trait_ref = + next_inner_most_trait_ref.map_bound(|t| t.trait_ref); + *inner_most_trait_ref = next_inner_most_trait_ref; + *emit_vptr = emit_vptr_on_new_entry; + break 'exiting_out; + } else { + continue 'exiting_out_skip_visited_traits; + } + } + } + stack.pop(); + continue 'exiting_out; + } + } + // all done + return None; + } + } +} + +fn dump_vtable_entries<'tcx>( + tcx: TyCtxt<'tcx>, + sp: Span, + trait_ref: ty::PolyTraitRef<'tcx>, + entries: &[VtblEntry<'tcx>], +) { + tcx.sess.emit_err(DumpVTableEntries { + span: sp, + trait_ref, + entries: format!("{:#?}", entries), + }); +} + +fn own_existential_vtable_entries<'tcx>(tcx: TyCtxt<'tcx>, trait_def_id: DefId) -> &'tcx [DefId] { + let trait_methods = tcx + .associated_items(trait_def_id) + .in_definition_order() + .filter(|item| item.kind == ty::AssocKind::Fn); + // Now list each method's DefId (for within its trait). + let own_entries = trait_methods.filter_map(move |trait_method| { + debug!("own_existential_vtable_entry: trait_method={:?}", trait_method); + let def_id = trait_method.def_id; + + // Some methods cannot be called on an object; skip those. + if !is_vtable_safe_method(tcx, trait_def_id, &trait_method) { + debug!("own_existential_vtable_entry: not vtable safe"); + return None; + } + + Some(def_id) + }); + + tcx.arena.alloc_from_iter(own_entries.into_iter()) +} + +/// Given a trait `trait_ref`, iterates the vtable entries +/// that come from `trait_ref`, including its supertraits. +fn vtable_entries<'tcx>( + tcx: TyCtxt<'tcx>, + trait_ref: ty::PolyTraitRef<'tcx>, +) -> &'tcx [VtblEntry<'tcx>] { + debug!("vtable_entries({:?})", trait_ref); + + let mut entries = vec![]; + + let vtable_segment_callback = |segment| -> ControlFlow<()> { + match segment { + VtblSegment::MetadataDSA => { + entries.extend(TyCtxt::COMMON_VTABLE_ENTRIES); + } + VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => { + let existential_trait_ref = trait_ref + .map_bound(|trait_ref| ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref)); + + // Lookup the shape of vtable for the trait. + let own_existential_entries = + tcx.own_existential_vtable_entries(existential_trait_ref.def_id()); + + let own_entries = own_existential_entries.iter().copied().map(|def_id| { + debug!("vtable_entries: trait_method={:?}", def_id); + + // The method may have some early-bound lifetimes; add regions for those. + let substs = trait_ref.map_bound(|trait_ref| { + InternalSubsts::for_item(tcx, def_id, |param, _| match param.kind { + GenericParamDefKind::Lifetime => tcx.lifetimes.re_erased.into(), + GenericParamDefKind::Type { .. } + | GenericParamDefKind::Const { .. } => { + trait_ref.substs[param.index as usize] + } + }) + }); + + // The trait type may have higher-ranked lifetimes in it; + // erase them if they appear, so that we get the type + // at some particular call site. + let substs = tcx + .normalize_erasing_late_bound_regions(ty::ParamEnv::reveal_all(), substs); + + // It's possible that the method relies on where-clauses that + // do not hold for this particular set of type parameters. + // Note that this method could then never be called, so we + // do not want to try and codegen it, in that case (see #23435). + let predicates = tcx.predicates_of(def_id).instantiate_own(tcx, substs); + if impossible_predicates(tcx, predicates.predicates) { + debug!("vtable_entries: predicates do not hold"); + return VtblEntry::Vacant; + } + + let instance = ty::Instance::resolve_for_vtable( + tcx, + ty::ParamEnv::reveal_all(), + def_id, + substs, + ) + .expect("resolution failed during building vtable representation"); + VtblEntry::Method(instance) + }); + + entries.extend(own_entries); + + if emit_vptr { + entries.push(VtblEntry::TraitVPtr(trait_ref)); + } + } + } + + ControlFlow::Continue(()) + }; + + let _ = prepare_vtable_segments(tcx, trait_ref, vtable_segment_callback); + + if tcx.has_attr(trait_ref.def_id(), sym::rustc_dump_vtable) { + let sp = tcx.def_span(trait_ref.def_id()); + dump_vtable_entries(tcx, sp, trait_ref, &entries); + } + + tcx.arena.alloc_from_iter(entries.into_iter()) +} + +/// Find slot base for trait methods within vtable entries of another trait +pub(super) fn vtable_trait_first_method_offset<'tcx>( + tcx: TyCtxt<'tcx>, + key: ( + ty::PolyTraitRef<'tcx>, // trait_to_be_found + ty::PolyTraitRef<'tcx>, // trait_owning_vtable + ), +) -> usize { + let (trait_to_be_found, trait_owning_vtable) = key; + + // #90177 + let trait_to_be_found_erased = tcx.erase_regions(trait_to_be_found); + + let vtable_segment_callback = { + let mut vtable_base = 0; + + move |segment| { + match segment { + VtblSegment::MetadataDSA => { + vtable_base += TyCtxt::COMMON_VTABLE_ENTRIES.len(); + } + VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => { + if tcx.erase_regions(trait_ref) == trait_to_be_found_erased { + return ControlFlow::Break(vtable_base); + } + vtable_base += count_own_vtable_entries(tcx, trait_ref); + if emit_vptr { + vtable_base += 1; + } + } + } + ControlFlow::Continue(()) + } + }; + + if let Some(vtable_base) = + prepare_vtable_segments(tcx, trait_owning_vtable, vtable_segment_callback) + { + vtable_base + } else { + bug!("Failed to find info for expected trait in vtable"); + } +} + +/// Find slot offset for trait vptr within vtable entries of another trait +pub(crate) fn vtable_trait_upcasting_coercion_new_vptr_slot<'tcx>( + tcx: TyCtxt<'tcx>, + key: ( + Ty<'tcx>, // trait object type whose trait owning vtable + Ty<'tcx>, // trait object for supertrait + ), +) -> Option { + let (source, target) = key; + assert!(matches!(&source.kind(), &ty::Dynamic(..)) && !source.needs_infer()); + assert!(matches!(&target.kind(), &ty::Dynamic(..)) && !target.needs_infer()); + + // this has been typecked-before, so diagnostics is not really needed. + let unsize_trait_did = tcx.require_lang_item(LangItem::Unsize, None); + + let trait_ref = tcx.mk_trait_ref(unsize_trait_did, [source, target]); + + match tcx.codegen_select_candidate((ty::ParamEnv::reveal_all(), ty::Binder::dummy(trait_ref))) { + Ok(ImplSource::TraitUpcasting(implsrc_traitcasting)) => { + implsrc_traitcasting.vtable_vptr_slot + } + otherwise => bug!("expected TraitUpcasting candidate, got {otherwise:?}"), + } +} + +/// Given a trait `trait_ref`, returns the number of vtable entries +/// that come from `trait_ref`, excluding its supertraits. Used in +/// computing the vtable base for an upcast trait of a trait object. +pub(crate) fn count_own_vtable_entries<'tcx>( + tcx: TyCtxt<'tcx>, + trait_ref: ty::PolyTraitRef<'tcx>, +) -> usize { + tcx.own_existential_vtable_entries(trait_ref.def_id()).len() +} + +pub(super) fn provide(providers: &mut ty::query::Providers) { + *providers = ty::query::Providers { + own_existential_vtable_entries, + vtable_entries, + vtable_trait_upcasting_coercion_new_vptr_slot, + ..*providers + }; +} From 25a6daccab2c8a266bea8a03091175ff21a9a465 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 8 Dec 2022 05:16:39 +0000 Subject: [PATCH 17/25] Move codegen_select_candidate to a rustc_traits --- compiler/rustc_trait_selection/src/traits/mod.rs | 2 -- .../src/traits => rustc_traits/src}/codegen.rs | 12 ++++++------ compiler/rustc_traits/src/lib.rs | 2 ++ 3 files changed, 8 insertions(+), 8 deletions(-) rename compiler/{rustc_trait_selection/src/traits => rustc_traits/src}/codegen.rs (95%) diff --git a/compiler/rustc_trait_selection/src/traits/mod.rs b/compiler/rustc_trait_selection/src/traits/mod.rs index c2104a98cea78..ea4bf42c515b5 100644 --- a/compiler/rustc_trait_selection/src/traits/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/mod.rs @@ -4,7 +4,6 @@ pub mod auto_trait; mod chalk_fulfill; -pub mod codegen; mod coherence; pub mod const_evaluatable; mod engine; @@ -569,7 +568,6 @@ pub fn provide(providers: &mut ty::query::Providers) { *providers = ty::query::Providers { specialization_graph_of: specialize::specialization_graph_provider, specializes: specialize::specializes, - codegen_select_candidate: codegen::codegen_select_candidate, subst_and_check_impossible_predicates, is_impossible_method, ..*providers diff --git a/compiler/rustc_trait_selection/src/traits/codegen.rs b/compiler/rustc_traits/src/codegen.rs similarity index 95% rename from compiler/rustc_trait_selection/src/traits/codegen.rs rename to compiler/rustc_traits/src/codegen.rs index 0102d268b42e1..f8f74b732efd3 100644 --- a/compiler/rustc_trait_selection/src/traits/codegen.rs +++ b/compiler/rustc_traits/src/codegen.rs @@ -3,15 +3,15 @@ // seems likely that they should eventually be merged into more // general routines. -use crate::infer::{DefiningAnchor, TyCtxtInferExt}; -use crate::traits::error_reporting::TypeErrCtxtExt; -use crate::traits::{ - ImplSource, Obligation, ObligationCause, SelectionContext, TraitEngine, TraitEngineExt, - Unimplemented, -}; +use rustc_infer::infer::{DefiningAnchor, TyCtxtInferExt}; use rustc_infer::traits::FulfillmentErrorCode; use rustc_middle::traits::CodegenObligationError; use rustc_middle::ty::{self, TyCtxt}; +use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt; +use rustc_trait_selection::traits::{ + ImplSource, Obligation, ObligationCause, SelectionContext, TraitEngine, TraitEngineExt, + Unimplemented, +}; /// Attempts to resolve an obligation to an `ImplSource`. The result is /// a shallow `ImplSource` resolution, meaning that we do not diff --git a/compiler/rustc_traits/src/lib.rs b/compiler/rustc_traits/src/lib.rs index 0ffa92f1ad55d..4041fbc2ffa43 100644 --- a/compiler/rustc_traits/src/lib.rs +++ b/compiler/rustc_traits/src/lib.rs @@ -18,6 +18,7 @@ mod implied_outlives_bounds; mod normalize_erasing_regions; mod normalize_projection_ty; mod type_op; +mod codegen; pub use type_op::{type_op_ascribe_user_type_with_span, type_op_prove_predicate_with_cause}; @@ -31,4 +32,5 @@ pub fn provide(p: &mut Providers) { normalize_projection_ty::provide(p); normalize_erasing_regions::provide(p); type_op::provide(p); + p.codegen_select_candidate = codegen::codegen_select_candidate; } From a09e8c55c663d2b070f99ab0fdadbcc2c45656b2 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Wed, 7 Dec 2022 21:59:54 -0700 Subject: [PATCH 18/25] rustdoc: clean up docblock table CSS * The rule `display: block` had no noticeable effect. Technically, because markdown tables have a tbody and thead, they get wrapped in an [anonymous table box] in the CSS tree, nested within the `` element's block layout box. This rule was added in #87230 to make the table side-scrolling, but this same issue was doubly fixed in #88742 by wrapping it in an explicit `
` tag. Since accessibility advocates recommend the wrapper div over marking the table as `display: block`, we'll stick with that. https://adrianroselli.com/2020/11/under-engineered-responsive-tables.html * The rule `width: calc(100% - 2px)` had no visible effect, because the anonymous table box was not affected. * The style is tweaked to basically be the same style GitHub uses. In particular, it adds zebra stripes, and removes dotted borders. [anonymous table box]: https://www.w3.org/TR/CSS2/tables.html#anonymous-boxes --- src/librustdoc/html/static/css/rustdoc.css | 14 ++++---------- src/librustdoc/html/static/css/themes/ayu.css | 1 + src/librustdoc/html/static/css/themes/dark.css | 1 + src/librustdoc/html/static/css/themes/light.css | 1 + 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 0cd15768ac6e8..63bb2cdbaf238 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -631,22 +631,16 @@ pre, .rustdoc.source .example-wrap { .docblock table { margin: .5em 0; - width: calc(100% - 2px); - overflow-x: auto; - display: block; border-collapse: collapse; } -.docblock table td { +.docblock table td, .docblock table th { padding: .5em; - border: 1px dashed var(--border-color); - vertical-align: top; + border: 1px solid var(--border-color); } -.docblock table th { - padding: .5em; - text-align: left; - border: 1px solid var(--border-color); +.docblock table tr:nth-child(2n) { + background: var(--table-alt-row-background-color); } /* Shift "where ..." part of method or fn definition down a line */ diff --git a/src/librustdoc/html/static/css/themes/ayu.css b/src/librustdoc/html/static/css/themes/ayu.css index 6e0905e730d08..0436fe0138edc 100644 --- a/src/librustdoc/html/static/css/themes/ayu.css +++ b/src/librustdoc/html/static/css/themes/ayu.css @@ -87,6 +87,7 @@ Original by Dempfi (https://github.com/dempfi/ayu) --crate-search-hover-border: #e0e0e0; --source-sidebar-background-selected: #14191f; --source-sidebar-background-hover: #14191f; + --table-alt-row-background-color: #191f26; } h1, h2, h3, h4 { diff --git a/src/librustdoc/html/static/css/themes/dark.css b/src/librustdoc/html/static/css/themes/dark.css index 334fc3de5616d..d945e956c533e 100644 --- a/src/librustdoc/html/static/css/themes/dark.css +++ b/src/librustdoc/html/static/css/themes/dark.css @@ -82,6 +82,7 @@ --crate-search-hover-border: #2196f3; --source-sidebar-background-selected: #333; --source-sidebar-background-hover: #444; + --table-alt-row-background-color: #2A2A2A; } .content .item-info::before { color: #ccc; } diff --git a/src/librustdoc/html/static/css/themes/light.css b/src/librustdoc/html/static/css/themes/light.css index 453e7508af4ba..58955a79316af 100644 --- a/src/librustdoc/html/static/css/themes/light.css +++ b/src/librustdoc/html/static/css/themes/light.css @@ -79,6 +79,7 @@ --crate-search-hover-border: #717171; --source-sidebar-background-selected: #fff; --source-sidebar-background-hover: #e0e0e0; + --table-alt-row-background-color: #F5F5F5; } .content .item-info::before { color: #ccc; } From 3b9daac6a27159bbe08e39fbc2722f40e91941d7 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 8 Dec 2022 05:58:30 +0000 Subject: [PATCH 19/25] Move some suggestions from error_reporting to error_reporting::suggest --- .../src/infer/error_reporting/mod.rs | 662 +---------------- .../src/infer/error_reporting/note_region.rs | 427 +++++++++++ .../src/infer/error_reporting/suggest.rs | 672 ++++++++++++++++++ compiler/rustc_infer/src/infer/note.rs | 203 ++++++ compiler/rustc_traits/src/lib.rs | 2 +- 5 files changed, 1305 insertions(+), 661 deletions(-) create mode 100644 compiler/rustc_infer/src/infer/error_reporting/note_region.rs create mode 100644 compiler/rustc_infer/src/infer/error_reporting/suggest.rs create mode 100644 compiler/rustc_infer/src/infer/note.rs diff --git a/compiler/rustc_infer/src/infer/error_reporting/mod.rs b/compiler/rustc_infer/src/infer/error_reporting/mod.rs index 3256ca1fb20fb..afcdfef79fcc0 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/mod.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/mod.rs @@ -56,22 +56,17 @@ use crate::infer::ExpectedFound; use crate::traits::error_reporting::report_object_safety_error; use crate::traits::{ IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode, - StatementAsExpression, }; -use crate::errors::SuggAddLetForLetChains; -use hir::intravisit::{walk_expr, walk_stmt}; use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_errors::{pluralize, struct_span_err, Diagnostic, ErrorGuaranteed, IntoDiagnosticArg}; use rustc_errors::{Applicability, DiagnosticBuilder, DiagnosticStyledString, MultiSpan}; use rustc_hir as hir; -use rustc_hir::def::{CtorKind, DefKind}; +use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LocalDefId}; -use rustc_hir::intravisit::Visitor; use rustc_hir::lang_items::LangItem; use rustc_hir::Node; use rustc_middle::dep_graph::DepContext; -use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::relate::{self, RelateResult, TypeRelation}; use rustc_middle::ty::{ self, error::TypeError, List, Region, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, @@ -83,6 +78,7 @@ use std::ops::{ControlFlow, Deref}; use std::{cmp, fmt, iter}; mod note; +mod suggest; pub(crate) mod need_type_info; pub use need_type_info::TypeAnnotationNeeded; @@ -806,87 +802,6 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } } - fn suggest_remove_semi_or_return_binding( - &self, - err: &mut Diagnostic, - first_id: Option, - first_ty: Ty<'tcx>, - first_span: Span, - second_id: Option, - second_ty: Ty<'tcx>, - second_span: Span, - ) { - let remove_semicolon = [ - (first_id, self.resolve_vars_if_possible(second_ty)), - (second_id, self.resolve_vars_if_possible(first_ty)), - ] - .into_iter() - .find_map(|(id, ty)| { - let hir::Node::Block(blk) = self.tcx.hir().get(id?) else { return None }; - self.could_remove_semicolon(blk, ty) - }); - match remove_semicolon { - Some((sp, StatementAsExpression::NeedsBoxing)) => { - err.multipart_suggestion( - "consider removing this semicolon and boxing the expressions", - vec![ - (first_span.shrink_to_lo(), "Box::new(".to_string()), - (first_span.shrink_to_hi(), ")".to_string()), - (second_span.shrink_to_lo(), "Box::new(".to_string()), - (second_span.shrink_to_hi(), ")".to_string()), - (sp, String::new()), - ], - Applicability::MachineApplicable, - ); - } - Some((sp, StatementAsExpression::CorrectType)) => { - err.span_suggestion_short( - sp, - "consider removing this semicolon", - "", - Applicability::MachineApplicable, - ); - } - None => { - for (id, ty) in [(first_id, second_ty), (second_id, first_ty)] { - if let Some(id) = id - && let hir::Node::Block(blk) = self.tcx.hir().get(id) - && self.consider_returning_binding(blk, ty, err) - { - break; - } - } - } - } - } - - fn suggest_boxing_for_return_impl_trait( - &self, - err: &mut Diagnostic, - return_sp: Span, - arm_spans: impl Iterator, - ) { - err.multipart_suggestion( - "you could change the return type to be a boxed trait object", - vec![ - (return_sp.with_hi(return_sp.lo() + BytePos(4)), "Box".to_string()), - ], - Applicability::MaybeIncorrect, - ); - let sugg = arm_spans - .flat_map(|sp| { - [(sp.shrink_to_lo(), "Box::new(".to_string()), (sp.shrink_to_hi(), ")".to_string())] - .into_iter() - }) - .collect::>(); - err.multipart_suggestion( - "if you change the return type to expect trait objects, box the returned expressions", - sugg, - Applicability::MaybeIncorrect, - ); - } - /// Given that `other_ty` is the same as a type argument for `name` in `sub`, populate `value` /// highlighting `name` and every type argument that isn't at `pos` (which is `other_ty`), and /// populate `other_value` with `other_ty`. @@ -1940,310 +1855,6 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { debug!(?diag); } - fn suggest_tuple_pattern( - &self, - cause: &ObligationCause<'tcx>, - exp_found: &ty::error::ExpectedFound>, - diag: &mut Diagnostic, - ) { - // Heavily inspired by `FnCtxt::suggest_compatible_variants`, with - // some modifications due to that being in typeck and this being in infer. - if let ObligationCauseCode::Pattern { .. } = cause.code() { - if let ty::Adt(expected_adt, substs) = exp_found.expected.kind() { - let compatible_variants: Vec<_> = expected_adt - .variants() - .iter() - .filter(|variant| { - variant.fields.len() == 1 && variant.ctor_kind() == Some(CtorKind::Fn) - }) - .filter_map(|variant| { - let sole_field = &variant.fields[0]; - let sole_field_ty = sole_field.ty(self.tcx, substs); - if self.same_type_modulo_infer(sole_field_ty, exp_found.found) { - let variant_path = - with_no_trimmed_paths!(self.tcx.def_path_str(variant.def_id)); - // FIXME #56861: DRYer prelude filtering - if let Some(path) = variant_path.strip_prefix("std::prelude::") { - if let Some((_, path)) = path.split_once("::") { - return Some(path.to_string()); - } - } - Some(variant_path) - } else { - None - } - }) - .collect(); - match &compatible_variants[..] { - [] => {} - [variant] => { - diag.multipart_suggestion_verbose( - &format!("try wrapping the pattern in `{}`", variant), - vec![ - (cause.span.shrink_to_lo(), format!("{}(", variant)), - (cause.span.shrink_to_hi(), ")".to_string()), - ], - Applicability::MaybeIncorrect, - ); - } - _ => { - // More than one matching variant. - diag.multipart_suggestions( - &format!( - "try wrapping the pattern in a variant of `{}`", - self.tcx.def_path_str(expected_adt.did()) - ), - compatible_variants.into_iter().map(|variant| { - vec![ - (cause.span.shrink_to_lo(), format!("{}(", variant)), - (cause.span.shrink_to_hi(), ")".to_string()), - ] - }), - Applicability::MaybeIncorrect, - ); - } - } - } - } - } - - /// A possible error is to forget to add `.await` when using futures: - /// - /// ```compile_fail,E0308 - /// async fn make_u32() -> u32 { - /// 22 - /// } - /// - /// fn take_u32(x: u32) {} - /// - /// async fn foo() { - /// let x = make_u32(); - /// take_u32(x); - /// } - /// ``` - /// - /// This routine checks if the found type `T` implements `Future` where `U` is the - /// expected type. If this is the case, and we are inside of an async body, it suggests adding - /// `.await` to the tail of the expression. - fn suggest_await_on_expect_found( - &self, - cause: &ObligationCause<'tcx>, - exp_span: Span, - exp_found: &ty::error::ExpectedFound>, - diag: &mut Diagnostic, - ) { - debug!( - "suggest_await_on_expect_found: exp_span={:?}, expected_ty={:?}, found_ty={:?}", - exp_span, exp_found.expected, exp_found.found, - ); - - if let ObligationCauseCode::CompareImplItemObligation { .. } = cause.code() { - return; - } - - match ( - self.get_impl_future_output_ty(exp_found.expected), - self.get_impl_future_output_ty(exp_found.found), - ) { - (Some(exp), Some(found)) if self.same_type_modulo_infer(exp, found) => match cause - .code() - { - ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => { - let then_span = self.find_block_span_from_hir_id(*then_id); - diag.multipart_suggestion( - "consider `await`ing on both `Future`s", - vec![ - (then_span.shrink_to_hi(), ".await".to_string()), - (exp_span.shrink_to_hi(), ".await".to_string()), - ], - Applicability::MaybeIncorrect, - ); - } - ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause { - prior_arms, - .. - }) => { - if let [.., arm_span] = &prior_arms[..] { - diag.multipart_suggestion( - "consider `await`ing on both `Future`s", - vec![ - (arm_span.shrink_to_hi(), ".await".to_string()), - (exp_span.shrink_to_hi(), ".await".to_string()), - ], - Applicability::MaybeIncorrect, - ); - } else { - diag.help("consider `await`ing on both `Future`s"); - } - } - _ => { - diag.help("consider `await`ing on both `Future`s"); - } - }, - (_, Some(ty)) if self.same_type_modulo_infer(exp_found.expected, ty) => { - diag.span_suggestion_verbose( - exp_span.shrink_to_hi(), - "consider `await`ing on the `Future`", - ".await", - Applicability::MaybeIncorrect, - ); - } - (Some(ty), _) if self.same_type_modulo_infer(ty, exp_found.found) => match cause.code() - { - ObligationCauseCode::Pattern { span: Some(then_span), .. } => { - diag.span_suggestion_verbose( - then_span.shrink_to_hi(), - "consider `await`ing on the `Future`", - ".await", - Applicability::MaybeIncorrect, - ); - } - ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => { - let then_span = self.find_block_span_from_hir_id(*then_id); - diag.span_suggestion_verbose( - then_span.shrink_to_hi(), - "consider `await`ing on the `Future`", - ".await", - Applicability::MaybeIncorrect, - ); - } - ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause { - ref prior_arms, - .. - }) => { - diag.multipart_suggestion_verbose( - "consider `await`ing on the `Future`", - prior_arms - .iter() - .map(|arm| (arm.shrink_to_hi(), ".await".to_string())) - .collect(), - Applicability::MaybeIncorrect, - ); - } - _ => {} - }, - _ => {} - } - } - - fn suggest_accessing_field_where_appropriate( - &self, - cause: &ObligationCause<'tcx>, - exp_found: &ty::error::ExpectedFound>, - diag: &mut Diagnostic, - ) { - debug!( - "suggest_accessing_field_where_appropriate(cause={:?}, exp_found={:?})", - cause, exp_found - ); - if let ty::Adt(expected_def, expected_substs) = exp_found.expected.kind() { - if expected_def.is_enum() { - return; - } - - if let Some((name, ty)) = expected_def - .non_enum_variant() - .fields - .iter() - .filter(|field| field.vis.is_accessible_from(field.did, self.tcx)) - .map(|field| (field.name, field.ty(self.tcx, expected_substs))) - .find(|(_, ty)| self.same_type_modulo_infer(*ty, exp_found.found)) - { - if let ObligationCauseCode::Pattern { span: Some(span), .. } = *cause.code() { - if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) { - let suggestion = if expected_def.is_struct() { - format!("{}.{}", snippet, name) - } else if expected_def.is_union() { - format!("unsafe {{ {}.{} }}", snippet, name) - } else { - return; - }; - diag.span_suggestion( - span, - &format!( - "you might have meant to use field `{}` whose type is `{}`", - name, ty - ), - suggestion, - Applicability::MaybeIncorrect, - ); - } - } - } - } - } - - /// When encountering a case where `.as_ref()` on a `Result` or `Option` would be appropriate, - /// suggests it. - fn suggest_as_ref_where_appropriate( - &self, - span: Span, - exp_found: &ty::error::ExpectedFound>, - diag: &mut Diagnostic, - ) { - if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) - && let Some(msg) = self.should_suggest_as_ref(exp_found.expected, exp_found.found) - { - diag.span_suggestion( - span, - msg, - // HACK: fix issue# 100605, suggesting convert from &Option to Option<&T>, remove the extra `&` - format!("{}.as_ref()", snippet.trim_start_matches('&')), - Applicability::MachineApplicable, - ); - } - } - - pub fn should_suggest_as_ref(&self, expected: Ty<'tcx>, found: Ty<'tcx>) -> Option<&str> { - if let (ty::Adt(exp_def, exp_substs), ty::Ref(_, found_ty, _)) = - (expected.kind(), found.kind()) - { - if let ty::Adt(found_def, found_substs) = *found_ty.kind() { - if exp_def == &found_def { - let have_as_ref = &[ - ( - sym::Option, - "you can convert from `&Option` to `Option<&T>` using \ - `.as_ref()`", - ), - ( - sym::Result, - "you can convert from `&Result` to \ - `Result<&T, &E>` using `.as_ref()`", - ), - ]; - if let Some(msg) = have_as_ref.iter().find_map(|(name, msg)| { - self.tcx.is_diagnostic_item(*name, exp_def.did()).then_some(msg) - }) { - let mut show_suggestion = true; - for (exp_ty, found_ty) in - iter::zip(exp_substs.types(), found_substs.types()) - { - match *exp_ty.kind() { - ty::Ref(_, exp_ty, _) => { - match (exp_ty.kind(), found_ty.kind()) { - (_, ty::Param(_)) - | (_, ty::Infer(_)) - | (ty::Param(_), _) - | (ty::Infer(_), _) => {} - _ if self.same_type_modulo_infer(exp_ty, found_ty) => {} - _ => show_suggestion = false, - }; - } - ty::Param(_) | ty::Infer(_) => {} - _ => show_suggestion = false, - } - } - if show_suggestion { - return Some(*msg); - } - } - } - } - } - None - } - pub fn report_and_explain_type_error( &self, trace: TypeTrace<'tcx>, @@ -2357,67 +1968,6 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { diag } - /// Try to find code with pattern `if Some(..) = expr` - /// use a `visitor` to mark the `if` which its span contains given error span, - /// and then try to find a assignment in the `cond` part, which span is equal with error span - fn suggest_let_for_letchains( - &self, - err: &mut Diagnostic, - cause: &ObligationCause<'_>, - span: Span, - ) { - let hir = self.tcx.hir(); - let fn_hir_id = hir.get_parent_node(cause.body_id); - if let Some(node) = self.tcx.hir().find(fn_hir_id) && - let hir::Node::Item(hir::Item { - kind: hir::ItemKind::Fn(_sig, _, body_id), .. - }) = node { - let body = hir.body(*body_id); - - /// Find the if expression with given span - struct IfVisitor { - pub result: bool, - pub found_if: bool, - pub err_span: Span, - } - - impl<'v> Visitor<'v> for IfVisitor { - fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) { - if self.result { return; } - match ex.kind { - hir::ExprKind::If(cond, _, _) => { - self.found_if = true; - walk_expr(self, cond); - self.found_if = false; - } - _ => walk_expr(self, ex), - } - } - - fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) { - if let hir::StmtKind::Local(hir::Local { - span, pat: hir::Pat{..}, ty: None, init: Some(_), .. - }) = &ex.kind - && self.found_if - && span.eq(&self.err_span) { - self.result = true; - } - walk_stmt(self, ex); - } - - fn visit_body(&mut self, body: &'v hir::Body<'v>) { - hir::intravisit::walk_body(self, body); - } - } - - let mut visitor = IfVisitor { err_span: span, found_if: false, result: false }; - visitor.visit_body(&body); - if visitor.result { - err.subdiagnostic(SuggAddLetForLetChains{span: span.shrink_to_lo()}); - } - } - } - fn emit_tuple_wrap_err( &self, err: &mut Diagnostic, @@ -3228,211 +2778,3 @@ impl<'tcx> InferCtxt<'tcx> { } } } - -impl<'tcx> TypeErrCtxt<'_, 'tcx> { - /// Be helpful when the user wrote `{... expr; }` and taking the `;` off - /// is enough to fix the error. - pub fn could_remove_semicolon( - &self, - blk: &'tcx hir::Block<'tcx>, - expected_ty: Ty<'tcx>, - ) -> Option<(Span, StatementAsExpression)> { - let blk = blk.innermost_block(); - // Do not suggest if we have a tail expr. - if blk.expr.is_some() { - return None; - } - let last_stmt = blk.stmts.last()?; - let hir::StmtKind::Semi(ref last_expr) = last_stmt.kind else { - return None; - }; - let last_expr_ty = self.typeck_results.as_ref()?.expr_ty_opt(*last_expr)?; - let needs_box = match (last_expr_ty.kind(), expected_ty.kind()) { - _ if last_expr_ty.references_error() => return None, - _ if self.same_type_modulo_infer(last_expr_ty, expected_ty) => { - StatementAsExpression::CorrectType - } - (ty::Opaque(last_def_id, _), ty::Opaque(exp_def_id, _)) - if last_def_id == exp_def_id => - { - StatementAsExpression::CorrectType - } - (ty::Opaque(last_def_id, last_bounds), ty::Opaque(exp_def_id, exp_bounds)) => { - debug!( - "both opaque, likely future {:?} {:?} {:?} {:?}", - last_def_id, last_bounds, exp_def_id, exp_bounds - ); - - let last_local_id = last_def_id.as_local()?; - let exp_local_id = exp_def_id.as_local()?; - - match ( - &self.tcx.hir().expect_item(last_local_id).kind, - &self.tcx.hir().expect_item(exp_local_id).kind, - ) { - ( - hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: last_bounds, .. }), - hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: exp_bounds, .. }), - ) if iter::zip(*last_bounds, *exp_bounds).all(|(left, right)| { - match (left, right) { - ( - hir::GenericBound::Trait(tl, ml), - hir::GenericBound::Trait(tr, mr), - ) if tl.trait_ref.trait_def_id() == tr.trait_ref.trait_def_id() - && ml == mr => - { - true - } - ( - hir::GenericBound::LangItemTrait(langl, _, _, argsl), - hir::GenericBound::LangItemTrait(langr, _, _, argsr), - ) if langl == langr => { - // FIXME: consider the bounds! - debug!("{:?} {:?}", argsl, argsr); - true - } - _ => false, - } - }) => - { - StatementAsExpression::NeedsBoxing - } - _ => StatementAsExpression::CorrectType, - } - } - _ => return None, - }; - let span = if last_stmt.span.from_expansion() { - let mac_call = rustc_span::source_map::original_sp(last_stmt.span, blk.span); - self.tcx.sess.source_map().mac_call_stmt_semi_span(mac_call)? - } else { - last_stmt.span.with_lo(last_stmt.span.hi() - BytePos(1)) - }; - Some((span, needs_box)) - } - - /// Suggest returning a local binding with a compatible type if the block - /// has no return expression. - pub fn consider_returning_binding( - &self, - blk: &'tcx hir::Block<'tcx>, - expected_ty: Ty<'tcx>, - err: &mut Diagnostic, - ) -> bool { - let blk = blk.innermost_block(); - // Do not suggest if we have a tail expr. - if blk.expr.is_some() { - return false; - } - let mut shadowed = FxIndexSet::default(); - let mut candidate_idents = vec![]; - let mut find_compatible_candidates = |pat: &hir::Pat<'_>| { - if let hir::PatKind::Binding(_, hir_id, ident, _) = &pat.kind - && let Some(pat_ty) = self - .typeck_results - .as_ref() - .and_then(|typeck_results| typeck_results.node_type_opt(*hir_id)) - { - let pat_ty = self.resolve_vars_if_possible(pat_ty); - if self.same_type_modulo_infer(pat_ty, expected_ty) - && !(pat_ty, expected_ty).references_error() - && shadowed.insert(ident.name) - { - candidate_idents.push((*ident, pat_ty)); - } - } - true - }; - - let hir = self.tcx.hir(); - for stmt in blk.stmts.iter().rev() { - let hir::StmtKind::Local(local) = &stmt.kind else { continue; }; - local.pat.walk(&mut find_compatible_candidates); - } - match hir.find(hir.get_parent_node(blk.hir_id)) { - Some(hir::Node::Expr(hir::Expr { hir_id, .. })) => { - match hir.find(hir.get_parent_node(*hir_id)) { - Some(hir::Node::Arm(hir::Arm { pat, .. })) => { - pat.walk(&mut find_compatible_candidates); - } - Some( - hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(_, _, body), .. }) - | hir::Node::ImplItem(hir::ImplItem { - kind: hir::ImplItemKind::Fn(_, body), - .. - }) - | hir::Node::TraitItem(hir::TraitItem { - kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(body)), - .. - }) - | hir::Node::Expr(hir::Expr { - kind: hir::ExprKind::Closure(hir::Closure { body, .. }), - .. - }), - ) => { - for param in hir.body(*body).params { - param.pat.walk(&mut find_compatible_candidates); - } - } - Some(hir::Node::Expr(hir::Expr { - kind: - hir::ExprKind::If( - hir::Expr { kind: hir::ExprKind::Let(let_), .. }, - then_block, - _, - ), - .. - })) if then_block.hir_id == *hir_id => { - let_.pat.walk(&mut find_compatible_candidates); - } - _ => {} - } - } - _ => {} - } - - match &candidate_idents[..] { - [(ident, _ty)] => { - let sm = self.tcx.sess.source_map(); - if let Some(stmt) = blk.stmts.last() { - let stmt_span = sm.stmt_span(stmt.span, blk.span); - let sugg = if sm.is_multiline(blk.span) - && let Some(spacing) = sm.indentation_before(stmt_span) - { - format!("\n{spacing}{ident}") - } else { - format!(" {ident}") - }; - err.span_suggestion_verbose( - stmt_span.shrink_to_hi(), - format!("consider returning the local binding `{ident}`"), - sugg, - Applicability::MaybeIncorrect, - ); - } else { - let sugg = if sm.is_multiline(blk.span) - && let Some(spacing) = sm.indentation_before(blk.span.shrink_to_lo()) - { - format!("\n{spacing} {ident}\n{spacing}") - } else { - format!(" {ident} ") - }; - let left_span = sm.span_through_char(blk.span, '{').shrink_to_hi(); - err.span_suggestion_verbose( - sm.span_extend_while(left_span, |c| c.is_whitespace()).unwrap_or(left_span), - format!("consider returning the local binding `{ident}`"), - sugg, - Applicability::MaybeIncorrect, - ); - } - true - } - values if (1..3).contains(&values.len()) => { - let spans = values.iter().map(|(ident, _)| ident.span).collect::>(); - err.span_note(spans, "consider returning one of these bindings"); - true - } - _ => false, - } - } -} diff --git a/compiler/rustc_infer/src/infer/error_reporting/note_region.rs b/compiler/rustc_infer/src/infer/error_reporting/note_region.rs new file mode 100644 index 0000000000000..41b115f3377ac --- /dev/null +++ b/compiler/rustc_infer/src/infer/error_reporting/note_region.rs @@ -0,0 +1,427 @@ +use crate::errors::RegionOriginNote; +use crate::infer::error_reporting::{note_and_explain_region, TypeErrCtxt}; +use crate::infer::{self, SubregionOrigin}; +use rustc_errors::{ + fluent, struct_span_err, AddToDiagnostic, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, +}; +use rustc_middle::traits::ObligationCauseCode; +use rustc_middle::ty::error::TypeError; +use rustc_middle::ty::{self, Region}; + +use super::ObligationCauseAsDiagArg; + +impl<'tcx> TypeErrCtxt<'_, 'tcx> { + pub(super) fn note_region_origin(&self, err: &mut Diagnostic, origin: &SubregionOrigin<'tcx>) { + match *origin { + infer::Subtype(ref trace) => RegionOriginNote::WithRequirement { + span: trace.cause.span, + requirement: ObligationCauseAsDiagArg(trace.cause.clone()), + expected_found: self.values_str(trace.values), + } + .add_to_diagnostic(err), + infer::Reborrow(span) => { + RegionOriginNote::Plain { span, msg: fluent::infer_reborrow }.add_to_diagnostic(err) + } + infer::ReborrowUpvar(span, ref upvar_id) => { + let var_name = self.tcx.hir().name(upvar_id.var_path.hir_id); + RegionOriginNote::WithName { + span, + msg: fluent::infer_reborrow, + name: &var_name.to_string(), + continues: false, + } + .add_to_diagnostic(err); + } + infer::RelateObjectBound(span) => { + RegionOriginNote::Plain { span, msg: fluent::infer_relate_object_bound } + .add_to_diagnostic(err); + } + infer::DataBorrowed(ty, span) => { + RegionOriginNote::WithName { + span, + msg: fluent::infer_data_borrowed, + name: &self.ty_to_string(ty), + continues: false, + } + .add_to_diagnostic(err); + } + infer::ReferenceOutlivesReferent(ty, span) => { + RegionOriginNote::WithName { + span, + msg: fluent::infer_reference_outlives_referent, + name: &self.ty_to_string(ty), + continues: false, + } + .add_to_diagnostic(err); + } + infer::RelateParamBound(span, ty, opt_span) => { + RegionOriginNote::WithName { + span, + msg: fluent::infer_relate_param_bound, + name: &self.ty_to_string(ty), + continues: opt_span.is_some(), + } + .add_to_diagnostic(err); + if let Some(span) = opt_span { + RegionOriginNote::Plain { span, msg: fluent::infer_relate_param_bound_2 } + .add_to_diagnostic(err); + } + } + infer::RelateRegionParamBound(span) => { + RegionOriginNote::Plain { span, msg: fluent::infer_relate_region_param_bound } + .add_to_diagnostic(err); + } + infer::CompareImplItemObligation { span, .. } => { + RegionOriginNote::Plain { span, msg: fluent::infer_compare_impl_item_obligation } + .add_to_diagnostic(err); + } + infer::CheckAssociatedTypeBounds { ref parent, .. } => { + self.note_region_origin(err, &parent); + } + infer::AscribeUserTypeProvePredicate(span) => { + RegionOriginNote::Plain { + span, + msg: fluent::infer_ascribe_user_type_prove_predicate, + } + .add_to_diagnostic(err); + } + } + } + + pub(super) fn report_concrete_failure( + &self, + origin: SubregionOrigin<'tcx>, + sub: Region<'tcx>, + sup: Region<'tcx>, + ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { + match origin { + infer::Subtype(box trace) => { + let terr = TypeError::RegionsDoesNotOutlive(sup, sub); + let mut err = self.report_and_explain_type_error(trace, terr); + match (*sub, *sup) { + (ty::RePlaceholder(_), ty::RePlaceholder(_)) => {} + (ty::RePlaceholder(_), _) => { + note_and_explain_region( + self.tcx, + &mut err, + "", + sup, + " doesn't meet the lifetime requirements", + None, + ); + } + (_, ty::RePlaceholder(_)) => { + note_and_explain_region( + self.tcx, + &mut err, + "the required lifetime does not necessarily outlive ", + sub, + "", + None, + ); + } + _ => { + note_and_explain_region(self.tcx, &mut err, "", sup, "...", None); + note_and_explain_region( + self.tcx, + &mut err, + "...does not necessarily outlive ", + sub, + "", + None, + ); + } + } + err + } + infer::Reborrow(span) => { + let mut err = struct_span_err!( + self.tcx.sess, + span, + E0312, + "lifetime of reference outlives lifetime of borrowed content..." + ); + note_and_explain_region( + self.tcx, + &mut err, + "...the reference is valid for ", + sub, + "...", + None, + ); + note_and_explain_region( + self.tcx, + &mut err, + "...but the borrowed content is only valid for ", + sup, + "", + None, + ); + err + } + infer::ReborrowUpvar(span, ref upvar_id) => { + let var_name = self.tcx.hir().name(upvar_id.var_path.hir_id); + let mut err = struct_span_err!( + self.tcx.sess, + span, + E0313, + "lifetime of borrowed pointer outlives lifetime of captured variable `{}`...", + var_name + ); + note_and_explain_region( + self.tcx, + &mut err, + "...the borrowed pointer is valid for ", + sub, + "...", + None, + ); + note_and_explain_region( + self.tcx, + &mut err, + &format!("...but `{}` is only valid for ", var_name), + sup, + "", + None, + ); + err + } + infer::RelateObjectBound(span) => { + let mut err = struct_span_err!( + self.tcx.sess, + span, + E0476, + "lifetime of the source pointer does not outlive lifetime bound of the \ + object type" + ); + note_and_explain_region( + self.tcx, + &mut err, + "object type is valid for ", + sub, + "", + None, + ); + note_and_explain_region( + self.tcx, + &mut err, + "source pointer is only valid for ", + sup, + "", + None, + ); + err + } + infer::RelateParamBound(span, ty, opt_span) => { + let mut err = struct_span_err!( + self.tcx.sess, + span, + E0477, + "the type `{}` does not fulfill the required lifetime", + self.ty_to_string(ty) + ); + match *sub { + ty::ReStatic => note_and_explain_region( + self.tcx, + &mut err, + "type must satisfy ", + sub, + if opt_span.is_some() { " as required by this binding" } else { "" }, + opt_span, + ), + _ => note_and_explain_region( + self.tcx, + &mut err, + "type must outlive ", + sub, + if opt_span.is_some() { " as required by this binding" } else { "" }, + opt_span, + ), + } + err + } + infer::RelateRegionParamBound(span) => { + let mut err = + struct_span_err!(self.tcx.sess, span, E0478, "lifetime bound not satisfied"); + note_and_explain_region( + self.tcx, + &mut err, + "lifetime parameter instantiated with ", + sup, + "", + None, + ); + note_and_explain_region( + self.tcx, + &mut err, + "but lifetime parameter must outlive ", + sub, + "", + None, + ); + err + } + infer::DataBorrowed(ty, span) => { + let mut err = struct_span_err!( + self.tcx.sess, + span, + E0490, + "a value of type `{}` is borrowed for too long", + self.ty_to_string(ty) + ); + note_and_explain_region( + self.tcx, + &mut err, + "the type is valid for ", + sub, + "", + None, + ); + note_and_explain_region( + self.tcx, + &mut err, + "but the borrow lasts for ", + sup, + "", + None, + ); + err + } + infer::ReferenceOutlivesReferent(ty, span) => { + let mut err = struct_span_err!( + self.tcx.sess, + span, + E0491, + "in type `{}`, reference has a longer lifetime than the data it references", + self.ty_to_string(ty) + ); + note_and_explain_region( + self.tcx, + &mut err, + "the pointer is valid for ", + sub, + "", + None, + ); + note_and_explain_region( + self.tcx, + &mut err, + "but the referenced data is only valid for ", + sup, + "", + None, + ); + err + } + infer::CompareImplItemObligation { span, impl_item_def_id, trait_item_def_id } => self + .report_extra_impl_obligation( + span, + impl_item_def_id, + trait_item_def_id, + &format!("`{}: {}`", sup, sub), + ), + infer::CheckAssociatedTypeBounds { impl_item_def_id, trait_item_def_id, parent } => { + let mut err = self.report_concrete_failure(*parent, sub, sup); + + let trait_item_span = self.tcx.def_span(trait_item_def_id); + let item_name = self.tcx.item_name(impl_item_def_id.to_def_id()); + err.span_label( + trait_item_span, + format!("definition of `{}` from trait", item_name), + ); + + let trait_predicates = self.tcx.explicit_predicates_of(trait_item_def_id); + let impl_predicates = self.tcx.explicit_predicates_of(impl_item_def_id); + + let impl_predicates: rustc_data_structures::fx::FxHashSet<_> = + impl_predicates.predicates.into_iter().map(|(pred, _)| pred).collect(); + let clauses: Vec<_> = trait_predicates + .predicates + .into_iter() + .filter(|&(pred, _)| !impl_predicates.contains(pred)) + .map(|(pred, _)| format!("{}", pred)) + .collect(); + + if !clauses.is_empty() { + let generics = self.tcx.hir().get_generics(impl_item_def_id).unwrap(); + let where_clause_span = generics.tail_span_for_predicate_suggestion(); + + let suggestion = format!( + "{} {}", + generics.add_where_or_trailing_comma(), + clauses.join(", "), + ); + err.span_suggestion( + where_clause_span, + &format!( + "try copying {} from the trait", + if clauses.len() > 1 { "these clauses" } else { "this clause" } + ), + suggestion, + rustc_errors::Applicability::MaybeIncorrect, + ); + } + + err + } + infer::AscribeUserTypeProvePredicate(span) => { + let mut err = + struct_span_err!(self.tcx.sess, span, E0478, "lifetime bound not satisfied"); + note_and_explain_region( + self.tcx, + &mut err, + "lifetime instantiated with ", + sup, + "", + None, + ); + note_and_explain_region( + self.tcx, + &mut err, + "but lifetime must outlive ", + sub, + "", + None, + ); + err + } + } + } + + pub(super) fn report_placeholder_failure( + &self, + placeholder_origin: SubregionOrigin<'tcx>, + sub: Region<'tcx>, + sup: Region<'tcx>, + ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { + // I can't think how to do better than this right now. -nikomatsakis + debug!(?placeholder_origin, ?sub, ?sup, "report_placeholder_failure"); + match placeholder_origin { + infer::Subtype(box ref trace) + if matches!( + &trace.cause.code().peel_derives(), + ObligationCauseCode::BindingObligation(..) + | ObligationCauseCode::ExprBindingObligation(..) + ) => + { + // Hack to get around the borrow checker because trace.cause has an `Rc`. + if let ObligationCauseCode::BindingObligation(_, span) + | ObligationCauseCode::ExprBindingObligation(_, span, ..) = + &trace.cause.code().peel_derives() + { + let span = *span; + let mut err = self.report_concrete_failure(placeholder_origin, sub, sup); + err.span_note(span, "the lifetime requirement is introduced here"); + err + } else { + unreachable!() + } + } + infer::Subtype(box trace) => { + let terr = TypeError::RegionsPlaceholderMismatch; + return self.report_and_explain_type_error(trace, terr); + } + _ => return self.report_concrete_failure(placeholder_origin, sub, sup), + } + } +} diff --git a/compiler/rustc_infer/src/infer/error_reporting/suggest.rs b/compiler/rustc_infer/src/infer/error_reporting/suggest.rs new file mode 100644 index 0000000000000..73b5a2cc4ad23 --- /dev/null +++ b/compiler/rustc_infer/src/infer/error_reporting/suggest.rs @@ -0,0 +1,672 @@ +use hir::def::CtorKind; +use hir::intravisit::{walk_expr, walk_stmt, Visitor}; +use rustc_data_structures::fx::FxIndexSet; +use rustc_errors::{Applicability, Diagnostic}; +use rustc_hir as hir; +use rustc_middle::traits::{ + IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode, + StatementAsExpression, +}; +use rustc_middle::ty::print::with_no_trimmed_paths; +use rustc_middle::ty::{self as ty, Ty, TypeVisitable}; +use rustc_span::{sym, BytePos, Span}; + +use crate::errors::SuggAddLetForLetChains; + +use super::TypeErrCtxt; + +impl<'tcx> TypeErrCtxt<'_, 'tcx> { + pub(super) fn suggest_remove_semi_or_return_binding( + &self, + err: &mut Diagnostic, + first_id: Option, + first_ty: Ty<'tcx>, + first_span: Span, + second_id: Option, + second_ty: Ty<'tcx>, + second_span: Span, + ) { + let remove_semicolon = [ + (first_id, self.resolve_vars_if_possible(second_ty)), + (second_id, self.resolve_vars_if_possible(first_ty)), + ] + .into_iter() + .find_map(|(id, ty)| { + let hir::Node::Block(blk) = self.tcx.hir().get(id?) else { return None }; + self.could_remove_semicolon(blk, ty) + }); + match remove_semicolon { + Some((sp, StatementAsExpression::NeedsBoxing)) => { + err.multipart_suggestion( + "consider removing this semicolon and boxing the expressions", + vec![ + (first_span.shrink_to_lo(), "Box::new(".to_string()), + (first_span.shrink_to_hi(), ")".to_string()), + (second_span.shrink_to_lo(), "Box::new(".to_string()), + (second_span.shrink_to_hi(), ")".to_string()), + (sp, String::new()), + ], + Applicability::MachineApplicable, + ); + } + Some((sp, StatementAsExpression::CorrectType)) => { + err.span_suggestion_short( + sp, + "consider removing this semicolon", + "", + Applicability::MachineApplicable, + ); + } + None => { + for (id, ty) in [(first_id, second_ty), (second_id, first_ty)] { + if let Some(id) = id + && let hir::Node::Block(blk) = self.tcx.hir().get(id) + && self.consider_returning_binding(blk, ty, err) + { + break; + } + } + } + } + } + + pub(super) fn suggest_boxing_for_return_impl_trait( + &self, + err: &mut Diagnostic, + return_sp: Span, + arm_spans: impl Iterator, + ) { + err.multipart_suggestion( + "you could change the return type to be a boxed trait object", + vec![ + (return_sp.with_hi(return_sp.lo() + BytePos(4)), "Box".to_string()), + ], + Applicability::MaybeIncorrect, + ); + let sugg = arm_spans + .flat_map(|sp| { + [(sp.shrink_to_lo(), "Box::new(".to_string()), (sp.shrink_to_hi(), ")".to_string())] + .into_iter() + }) + .collect::>(); + err.multipart_suggestion( + "if you change the return type to expect trait objects, box the returned expressions", + sugg, + Applicability::MaybeIncorrect, + ); + } + + pub(super) fn suggest_tuple_pattern( + &self, + cause: &ObligationCause<'tcx>, + exp_found: &ty::error::ExpectedFound>, + diag: &mut Diagnostic, + ) { + // Heavily inspired by `FnCtxt::suggest_compatible_variants`, with + // some modifications due to that being in typeck and this being in infer. + if let ObligationCauseCode::Pattern { .. } = cause.code() { + if let ty::Adt(expected_adt, substs) = exp_found.expected.kind() { + let compatible_variants: Vec<_> = expected_adt + .variants() + .iter() + .filter(|variant| { + variant.fields.len() == 1 && variant.ctor_kind() == Some(CtorKind::Fn) + }) + .filter_map(|variant| { + let sole_field = &variant.fields[0]; + let sole_field_ty = sole_field.ty(self.tcx, substs); + if self.same_type_modulo_infer(sole_field_ty, exp_found.found) { + let variant_path = + with_no_trimmed_paths!(self.tcx.def_path_str(variant.def_id)); + // FIXME #56861: DRYer prelude filtering + if let Some(path) = variant_path.strip_prefix("std::prelude::") { + if let Some((_, path)) = path.split_once("::") { + return Some(path.to_string()); + } + } + Some(variant_path) + } else { + None + } + }) + .collect(); + match &compatible_variants[..] { + [] => {} + [variant] => { + diag.multipart_suggestion_verbose( + &format!("try wrapping the pattern in `{}`", variant), + vec![ + (cause.span.shrink_to_lo(), format!("{}(", variant)), + (cause.span.shrink_to_hi(), ")".to_string()), + ], + Applicability::MaybeIncorrect, + ); + } + _ => { + // More than one matching variant. + diag.multipart_suggestions( + &format!( + "try wrapping the pattern in a variant of `{}`", + self.tcx.def_path_str(expected_adt.did()) + ), + compatible_variants.into_iter().map(|variant| { + vec![ + (cause.span.shrink_to_lo(), format!("{}(", variant)), + (cause.span.shrink_to_hi(), ")".to_string()), + ] + }), + Applicability::MaybeIncorrect, + ); + } + } + } + } + } + + /// A possible error is to forget to add `.await` when using futures: + /// + /// ```compile_fail,E0308 + /// async fn make_u32() -> u32 { + /// 22 + /// } + /// + /// fn take_u32(x: u32) {} + /// + /// async fn foo() { + /// let x = make_u32(); + /// take_u32(x); + /// } + /// ``` + /// + /// This routine checks if the found type `T` implements `Future` where `U` is the + /// expected type. If this is the case, and we are inside of an async body, it suggests adding + /// `.await` to the tail of the expression. + pub(super) fn suggest_await_on_expect_found( + &self, + cause: &ObligationCause<'tcx>, + exp_span: Span, + exp_found: &ty::error::ExpectedFound>, + diag: &mut Diagnostic, + ) { + debug!( + "suggest_await_on_expect_found: exp_span={:?}, expected_ty={:?}, found_ty={:?}", + exp_span, exp_found.expected, exp_found.found, + ); + + if let ObligationCauseCode::CompareImplItemObligation { .. } = cause.code() { + return; + } + + match ( + self.get_impl_future_output_ty(exp_found.expected), + self.get_impl_future_output_ty(exp_found.found), + ) { + (Some(exp), Some(found)) if self.same_type_modulo_infer(exp, found) => match cause + .code() + { + ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => { + let then_span = self.find_block_span_from_hir_id(*then_id); + diag.multipart_suggestion( + "consider `await`ing on both `Future`s", + vec![ + (then_span.shrink_to_hi(), ".await".to_string()), + (exp_span.shrink_to_hi(), ".await".to_string()), + ], + Applicability::MaybeIncorrect, + ); + } + ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause { + prior_arms, + .. + }) => { + if let [.., arm_span] = &prior_arms[..] { + diag.multipart_suggestion( + "consider `await`ing on both `Future`s", + vec![ + (arm_span.shrink_to_hi(), ".await".to_string()), + (exp_span.shrink_to_hi(), ".await".to_string()), + ], + Applicability::MaybeIncorrect, + ); + } else { + diag.help("consider `await`ing on both `Future`s"); + } + } + _ => { + diag.help("consider `await`ing on both `Future`s"); + } + }, + (_, Some(ty)) if self.same_type_modulo_infer(exp_found.expected, ty) => { + diag.span_suggestion_verbose( + exp_span.shrink_to_hi(), + "consider `await`ing on the `Future`", + ".await", + Applicability::MaybeIncorrect, + ); + } + (Some(ty), _) if self.same_type_modulo_infer(ty, exp_found.found) => match cause.code() + { + ObligationCauseCode::Pattern { span: Some(then_span), .. } => { + diag.span_suggestion_verbose( + then_span.shrink_to_hi(), + "consider `await`ing on the `Future`", + ".await", + Applicability::MaybeIncorrect, + ); + } + ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => { + let then_span = self.find_block_span_from_hir_id(*then_id); + diag.span_suggestion_verbose( + then_span.shrink_to_hi(), + "consider `await`ing on the `Future`", + ".await", + Applicability::MaybeIncorrect, + ); + } + ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause { + ref prior_arms, + .. + }) => { + diag.multipart_suggestion_verbose( + "consider `await`ing on the `Future`", + prior_arms + .iter() + .map(|arm| (arm.shrink_to_hi(), ".await".to_string())) + .collect(), + Applicability::MaybeIncorrect, + ); + } + _ => {} + }, + _ => {} + } + } + + pub(super) fn suggest_accessing_field_where_appropriate( + &self, + cause: &ObligationCause<'tcx>, + exp_found: &ty::error::ExpectedFound>, + diag: &mut Diagnostic, + ) { + debug!( + "suggest_accessing_field_where_appropriate(cause={:?}, exp_found={:?})", + cause, exp_found + ); + if let ty::Adt(expected_def, expected_substs) = exp_found.expected.kind() { + if expected_def.is_enum() { + return; + } + + if let Some((name, ty)) = expected_def + .non_enum_variant() + .fields + .iter() + .filter(|field| field.vis.is_accessible_from(field.did, self.tcx)) + .map(|field| (field.name, field.ty(self.tcx, expected_substs))) + .find(|(_, ty)| self.same_type_modulo_infer(*ty, exp_found.found)) + { + if let ObligationCauseCode::Pattern { span: Some(span), .. } = *cause.code() { + if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) { + let suggestion = if expected_def.is_struct() { + format!("{}.{}", snippet, name) + } else if expected_def.is_union() { + format!("unsafe {{ {}.{} }}", snippet, name) + } else { + return; + }; + diag.span_suggestion( + span, + &format!( + "you might have meant to use field `{}` whose type is `{}`", + name, ty + ), + suggestion, + Applicability::MaybeIncorrect, + ); + } + } + } + } + } + + /// When encountering a case where `.as_ref()` on a `Result` or `Option` would be appropriate, + /// suggests it. + pub(super) fn suggest_as_ref_where_appropriate( + &self, + span: Span, + exp_found: &ty::error::ExpectedFound>, + diag: &mut Diagnostic, + ) { + if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) + && let Some(msg) = self.should_suggest_as_ref(exp_found.expected, exp_found.found) + { + diag.span_suggestion( + span, + msg, + // HACK: fix issue# 100605, suggesting convert from &Option to Option<&T>, remove the extra `&` + format!("{}.as_ref()", snippet.trim_start_matches('&')), + Applicability::MachineApplicable, + ); + } + } + + pub fn should_suggest_as_ref(&self, expected: Ty<'tcx>, found: Ty<'tcx>) -> Option<&str> { + if let (ty::Adt(exp_def, exp_substs), ty::Ref(_, found_ty, _)) = + (expected.kind(), found.kind()) + { + if let ty::Adt(found_def, found_substs) = *found_ty.kind() { + if exp_def == &found_def { + let have_as_ref = &[ + ( + sym::Option, + "you can convert from `&Option` to `Option<&T>` using \ + `.as_ref()`", + ), + ( + sym::Result, + "you can convert from `&Result` to \ + `Result<&T, &E>` using `.as_ref()`", + ), + ]; + if let Some(msg) = have_as_ref.iter().find_map(|(name, msg)| { + self.tcx.is_diagnostic_item(*name, exp_def.did()).then_some(msg) + }) { + let mut show_suggestion = true; + for (exp_ty, found_ty) in + std::iter::zip(exp_substs.types(), found_substs.types()) + { + match *exp_ty.kind() { + ty::Ref(_, exp_ty, _) => { + match (exp_ty.kind(), found_ty.kind()) { + (_, ty::Param(_)) + | (_, ty::Infer(_)) + | (ty::Param(_), _) + | (ty::Infer(_), _) => {} + _ if self.same_type_modulo_infer(exp_ty, found_ty) => {} + _ => show_suggestion = false, + }; + } + ty::Param(_) | ty::Infer(_) => {} + _ => show_suggestion = false, + } + } + if show_suggestion { + return Some(*msg); + } + } + } + } + } + None + } + + /// Try to find code with pattern `if Some(..) = expr` + /// use a `visitor` to mark the `if` which its span contains given error span, + /// and then try to find a assignment in the `cond` part, which span is equal with error span + pub(super) fn suggest_let_for_letchains( + &self, + err: &mut Diagnostic, + cause: &ObligationCause<'_>, + span: Span, + ) { + let hir = self.tcx.hir(); + let fn_hir_id = hir.get_parent_node(cause.body_id); + if let Some(node) = self.tcx.hir().find(fn_hir_id) && + let hir::Node::Item(hir::Item { + kind: hir::ItemKind::Fn(_sig, _, body_id), .. + }) = node { + let body = hir.body(*body_id); + + /// Find the if expression with given span + struct IfVisitor { + pub result: bool, + pub found_if: bool, + pub err_span: Span, + } + + impl<'v> Visitor<'v> for IfVisitor { + fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) { + if self.result { return; } + match ex.kind { + hir::ExprKind::If(cond, _, _) => { + self.found_if = true; + walk_expr(self, cond); + self.found_if = false; + } + _ => walk_expr(self, ex), + } + } + + fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) { + if let hir::StmtKind::Local(hir::Local { + span, pat: hir::Pat{..}, ty: None, init: Some(_), .. + }) = &ex.kind + && self.found_if + && span.eq(&self.err_span) { + self.result = true; + } + walk_stmt(self, ex); + } + + fn visit_body(&mut self, body: &'v hir::Body<'v>) { + hir::intravisit::walk_body(self, body); + } + } + + let mut visitor = IfVisitor { err_span: span, found_if: false, result: false }; + visitor.visit_body(&body); + if visitor.result { + err.subdiagnostic(SuggAddLetForLetChains{span: span.shrink_to_lo()}); + } + } + } +} + +impl<'tcx> TypeErrCtxt<'_, 'tcx> { + /// Be helpful when the user wrote `{... expr; }` and taking the `;` off + /// is enough to fix the error. + pub fn could_remove_semicolon( + &self, + blk: &'tcx hir::Block<'tcx>, + expected_ty: Ty<'tcx>, + ) -> Option<(Span, StatementAsExpression)> { + let blk = blk.innermost_block(); + // Do not suggest if we have a tail expr. + if blk.expr.is_some() { + return None; + } + let last_stmt = blk.stmts.last()?; + let hir::StmtKind::Semi(ref last_expr) = last_stmt.kind else { + return None; + }; + let last_expr_ty = self.typeck_results.as_ref()?.expr_ty_opt(*last_expr)?; + let needs_box = match (last_expr_ty.kind(), expected_ty.kind()) { + _ if last_expr_ty.references_error() => return None, + _ if self.same_type_modulo_infer(last_expr_ty, expected_ty) => { + StatementAsExpression::CorrectType + } + (ty::Opaque(last_def_id, _), ty::Opaque(exp_def_id, _)) + if last_def_id == exp_def_id => + { + StatementAsExpression::CorrectType + } + (ty::Opaque(last_def_id, last_bounds), ty::Opaque(exp_def_id, exp_bounds)) => { + debug!( + "both opaque, likely future {:?} {:?} {:?} {:?}", + last_def_id, last_bounds, exp_def_id, exp_bounds + ); + + let last_local_id = last_def_id.as_local()?; + let exp_local_id = exp_def_id.as_local()?; + + match ( + &self.tcx.hir().expect_item(last_local_id).kind, + &self.tcx.hir().expect_item(exp_local_id).kind, + ) { + ( + hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: last_bounds, .. }), + hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: exp_bounds, .. }), + ) if std::iter::zip(*last_bounds, *exp_bounds).all(|(left, right)| { + match (left, right) { + ( + hir::GenericBound::Trait(tl, ml), + hir::GenericBound::Trait(tr, mr), + ) if tl.trait_ref.trait_def_id() == tr.trait_ref.trait_def_id() + && ml == mr => + { + true + } + ( + hir::GenericBound::LangItemTrait(langl, _, _, argsl), + hir::GenericBound::LangItemTrait(langr, _, _, argsr), + ) if langl == langr => { + // FIXME: consider the bounds! + debug!("{:?} {:?}", argsl, argsr); + true + } + _ => false, + } + }) => + { + StatementAsExpression::NeedsBoxing + } + _ => StatementAsExpression::CorrectType, + } + } + _ => return None, + }; + let span = if last_stmt.span.from_expansion() { + let mac_call = rustc_span::source_map::original_sp(last_stmt.span, blk.span); + self.tcx.sess.source_map().mac_call_stmt_semi_span(mac_call)? + } else { + last_stmt.span.with_lo(last_stmt.span.hi() - BytePos(1)) + }; + Some((span, needs_box)) + } + + /// Suggest returning a local binding with a compatible type if the block + /// has no return expression. + pub fn consider_returning_binding( + &self, + blk: &'tcx hir::Block<'tcx>, + expected_ty: Ty<'tcx>, + err: &mut Diagnostic, + ) -> bool { + let blk = blk.innermost_block(); + // Do not suggest if we have a tail expr. + if blk.expr.is_some() { + return false; + } + let mut shadowed = FxIndexSet::default(); + let mut candidate_idents = vec![]; + let mut find_compatible_candidates = |pat: &hir::Pat<'_>| { + if let hir::PatKind::Binding(_, hir_id, ident, _) = &pat.kind + && let Some(pat_ty) = self + .typeck_results + .as_ref() + .and_then(|typeck_results| typeck_results.node_type_opt(*hir_id)) + { + let pat_ty = self.resolve_vars_if_possible(pat_ty); + if self.same_type_modulo_infer(pat_ty, expected_ty) + && !(pat_ty, expected_ty).references_error() + && shadowed.insert(ident.name) + { + candidate_idents.push((*ident, pat_ty)); + } + } + true + }; + + let hir = self.tcx.hir(); + for stmt in blk.stmts.iter().rev() { + let hir::StmtKind::Local(local) = &stmt.kind else { continue; }; + local.pat.walk(&mut find_compatible_candidates); + } + match hir.find(hir.get_parent_node(blk.hir_id)) { + Some(hir::Node::Expr(hir::Expr { hir_id, .. })) => { + match hir.find(hir.get_parent_node(*hir_id)) { + Some(hir::Node::Arm(hir::Arm { pat, .. })) => { + pat.walk(&mut find_compatible_candidates); + } + Some( + hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(_, _, body), .. }) + | hir::Node::ImplItem(hir::ImplItem { + kind: hir::ImplItemKind::Fn(_, body), + .. + }) + | hir::Node::TraitItem(hir::TraitItem { + kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(body)), + .. + }) + | hir::Node::Expr(hir::Expr { + kind: hir::ExprKind::Closure(hir::Closure { body, .. }), + .. + }), + ) => { + for param in hir.body(*body).params { + param.pat.walk(&mut find_compatible_candidates); + } + } + Some(hir::Node::Expr(hir::Expr { + kind: + hir::ExprKind::If( + hir::Expr { kind: hir::ExprKind::Let(let_), .. }, + then_block, + _, + ), + .. + })) if then_block.hir_id == *hir_id => { + let_.pat.walk(&mut find_compatible_candidates); + } + _ => {} + } + } + _ => {} + } + + match &candidate_idents[..] { + [(ident, _ty)] => { + let sm = self.tcx.sess.source_map(); + if let Some(stmt) = blk.stmts.last() { + let stmt_span = sm.stmt_span(stmt.span, blk.span); + let sugg = if sm.is_multiline(blk.span) + && let Some(spacing) = sm.indentation_before(stmt_span) + { + format!("\n{spacing}{ident}") + } else { + format!(" {ident}") + }; + err.span_suggestion_verbose( + stmt_span.shrink_to_hi(), + format!("consider returning the local binding `{ident}`"), + sugg, + Applicability::MaybeIncorrect, + ); + } else { + let sugg = if sm.is_multiline(blk.span) + && let Some(spacing) = sm.indentation_before(blk.span.shrink_to_lo()) + { + format!("\n{spacing} {ident}\n{spacing}") + } else { + format!(" {ident} ") + }; + let left_span = sm.span_through_char(blk.span, '{').shrink_to_hi(); + err.span_suggestion_verbose( + sm.span_extend_while(left_span, |c| c.is_whitespace()).unwrap_or(left_span), + format!("consider returning the local binding `{ident}`"), + sugg, + Applicability::MaybeIncorrect, + ); + } + true + } + values if (1..3).contains(&values.len()) => { + let spans = values.iter().map(|(ident, _)| ident.span).collect::>(); + err.span_note(spans, "consider returning one of these bindings"); + true + } + _ => false, + } + } +} diff --git a/compiler/rustc_infer/src/infer/note.rs b/compiler/rustc_infer/src/infer/note.rs new file mode 100644 index 0000000000000..2ccbd164faaf1 --- /dev/null +++ b/compiler/rustc_infer/src/infer/note.rs @@ -0,0 +1,203 @@ +impl<'tcx> TypeErrCtxt<'_, 'tcx> { + fn note_error_origin( + &self, + err: &mut Diagnostic, + cause: &ObligationCause<'tcx>, + exp_found: Option>>, + terr: TypeError<'tcx>, + ) { + match *cause.code() { + ObligationCauseCode::Pattern { origin_expr: true, span: Some(span), root_ty } => { + let ty = self.resolve_vars_if_possible(root_ty); + if !matches!(ty.kind(), ty::Infer(ty::InferTy::TyVar(_) | ty::InferTy::FreshTy(_))) + { + // don't show type `_` + if span.desugaring_kind() == Some(DesugaringKind::ForLoop) + && let ty::Adt(def, substs) = ty.kind() + && Some(def.did()) == self.tcx.get_diagnostic_item(sym::Option) + { + err.span_label(span, format!("this is an iterator with items of type `{}`", substs.type_at(0))); + } else { + err.span_label(span, format!("this expression has type `{}`", ty)); + } + } + if let Some(ty::error::ExpectedFound { found, .. }) = exp_found + && ty.is_box() && ty.boxed_ty() == found + && let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) + { + err.span_suggestion( + span, + "consider dereferencing the boxed value", + format!("*{}", snippet), + Applicability::MachineApplicable, + ); + } + } + ObligationCauseCode::Pattern { origin_expr: false, span: Some(span), .. } => { + err.span_label(span, "expected due to this"); + } + ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause { + arm_block_id, + arm_span, + arm_ty, + prior_arm_block_id, + prior_arm_span, + prior_arm_ty, + source, + ref prior_arms, + scrut_hir_id, + opt_suggest_box_span, + scrut_span, + .. + }) => match source { + hir::MatchSource::TryDesugar => { + if let Some(ty::error::ExpectedFound { expected, .. }) = exp_found { + let scrut_expr = self.tcx.hir().expect_expr(scrut_hir_id); + let scrut_ty = if let hir::ExprKind::Call(_, args) = &scrut_expr.kind { + let arg_expr = args.first().expect("try desugaring call w/out arg"); + self.typeck_results.as_ref().and_then(|typeck_results| { + typeck_results.expr_ty_opt(arg_expr) + }) + } else { + bug!("try desugaring w/out call expr as scrutinee"); + }; + + match scrut_ty { + Some(ty) if expected == ty => { + let source_map = self.tcx.sess.source_map(); + err.span_suggestion( + source_map.end_point(cause.span), + "try removing this `?`", + "", + Applicability::MachineApplicable, + ); + } + _ => {} + } + } + } + _ => { + // `prior_arm_ty` can be `!`, `expected` will have better info when present. + let t = self.resolve_vars_if_possible(match exp_found { + Some(ty::error::ExpectedFound { expected, .. }) => expected, + _ => prior_arm_ty, + }); + let source_map = self.tcx.sess.source_map(); + let mut any_multiline_arm = source_map.is_multiline(arm_span); + if prior_arms.len() <= 4 { + for sp in prior_arms { + any_multiline_arm |= source_map.is_multiline(*sp); + err.span_label(*sp, format!("this is found to be of type `{}`", t)); + } + } else if let Some(sp) = prior_arms.last() { + any_multiline_arm |= source_map.is_multiline(*sp); + err.span_label( + *sp, + format!("this and all prior arms are found to be of type `{}`", t), + ); + } + let outer_error_span = if any_multiline_arm { + // Cover just `match` and the scrutinee expression, not + // the entire match body, to reduce diagram noise. + cause.span.shrink_to_lo().to(scrut_span) + } else { + cause.span + }; + let msg = "`match` arms have incompatible types"; + err.span_label(outer_error_span, msg); + self.suggest_remove_semi_or_return_binding( + err, + prior_arm_block_id, + prior_arm_ty, + prior_arm_span, + arm_block_id, + arm_ty, + arm_span, + ); + if let Some(ret_sp) = opt_suggest_box_span { + // Get return type span and point to it. + self.suggest_boxing_for_return_impl_trait( + err, + ret_sp, + prior_arms.iter().chain(std::iter::once(&arm_span)).map(|s| *s), + ); + } + } + }, + ObligationCauseCode::IfExpression(box IfExpressionCause { + then_id, + else_id, + then_ty, + else_ty, + outer_span, + opt_suggest_box_span, + }) => { + let then_span = self.find_block_span_from_hir_id(then_id); + let else_span = self.find_block_span_from_hir_id(else_id); + err.span_label(then_span, "expected because of this"); + if let Some(sp) = outer_span { + err.span_label(sp, "`if` and `else` have incompatible types"); + } + self.suggest_remove_semi_or_return_binding( + err, + Some(then_id), + then_ty, + then_span, + Some(else_id), + else_ty, + else_span, + ); + if let Some(ret_sp) = opt_suggest_box_span { + self.suggest_boxing_for_return_impl_trait( + err, + ret_sp, + [then_span, else_span].into_iter(), + ); + } + } + ObligationCauseCode::LetElse => { + err.help("try adding a diverging expression, such as `return` or `panic!(..)`"); + err.help("...or use `match` instead of `let...else`"); + } + _ => { + if let ObligationCauseCode::BindingObligation(_, span) + | ObligationCauseCode::ExprBindingObligation(_, span, ..) + = cause.code().peel_derives() + && let TypeError::RegionsPlaceholderMismatch = terr + { + err.span_note( * span, + "the lifetime requirement is introduced here"); + } + } + } + } +} + +impl<'tcx> InferCtxt<'tcx> { + /// Given a [`hir::Block`], get the span of its last expression or + /// statement, peeling off any inner blocks. + pub fn find_block_span(&self, block: &'tcx hir::Block<'tcx>) -> Span { + let block = block.innermost_block(); + if let Some(expr) = &block.expr { + expr.span + } else if let Some(stmt) = block.stmts.last() { + // possibly incorrect trailing `;` in the else arm + stmt.span + } else { + // empty block; point at its entirety + block.span + } + } + + /// Given a [`hir::HirId`] for a block, get the span of its last expression + /// or statement, peeling off any inner blocks. + pub fn find_block_span_from_hir_id(&self, hir_id: hir::HirId) -> Span { + match self.tcx.hir().get(hir_id) { + hir::Node::Block(blk) => self.find_block_span(blk), + // The parser was in a weird state if either of these happen, but + // it's better not to panic. + hir::Node::Expr(e) => e.span, + _ => rustc_span::DUMMY_SP, + } + } +} diff --git a/compiler/rustc_traits/src/lib.rs b/compiler/rustc_traits/src/lib.rs index 4041fbc2ffa43..9aa26667e7bf4 100644 --- a/compiler/rustc_traits/src/lib.rs +++ b/compiler/rustc_traits/src/lib.rs @@ -12,13 +12,13 @@ extern crate tracing; extern crate rustc_middle; mod chalk; +mod codegen; mod dropck_outlives; mod evaluate_obligation; mod implied_outlives_bounds; mod normalize_erasing_regions; mod normalize_projection_ty; mod type_op; -mod codegen; pub use type_op::{type_op_ascribe_user_type_with_span, type_op_prove_predicate_with_cause}; From dd9d05cec43deec3918aff7cfee2136c1f366420 Mon Sep 17 00:00:00 2001 From: lcnr Date: Thu, 8 Dec 2022 11:24:25 +0100 Subject: [PATCH 20/25] validate: use the correct reveal during opts --- compiler/rustc_const_eval/src/transform/validate.rs | 6 +++++- compiler/rustc_middle/src/mir/syntax.rs | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_const_eval/src/transform/validate.rs b/compiler/rustc_const_eval/src/transform/validate.rs index bf700d3122465..5c9263dc5e35d 100644 --- a/compiler/rustc_const_eval/src/transform/validate.rs +++ b/compiler/rustc_const_eval/src/transform/validate.rs @@ -2,6 +2,7 @@ use rustc_data_structures::fx::FxHashSet; use rustc_index::bit_set::BitSet; +use rustc_infer::traits::Reveal; use rustc_middle::mir::interpret::Scalar; use rustc_middle::mir::visit::NonUseContext::VarDebugInfo; use rustc_middle::mir::visit::{PlaceContext, Visitor}; @@ -44,8 +45,11 @@ impl<'tcx> MirPass<'tcx> for Validator { return; } let def_id = body.source.def_id(); - let param_env = tcx.param_env(def_id); let mir_phase = self.mir_phase; + let param_env = match mir_phase.reveal() { + Reveal::UserFacing => tcx.param_env(def_id), + Reveal::All => tcx.param_env_reveal_all_normalized(def_id), + }; let always_live_locals = always_storage_live_locals(body); let storage_liveness = MaybeStorageLive::new(always_live_locals) diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index 7d2a6bda56926..988d27ba262c5 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -6,6 +6,7 @@ use super::{BasicBlock, Constant, Field, Local, SwitchTargets, UserTypeProjection}; use crate::mir::coverage::{CodeRegion, CoverageKind}; +use crate::traits::Reveal; use crate::ty::adjustment::PointerCast; use crate::ty::subst::SubstsRef; use crate::ty::{self, List, Ty}; @@ -100,6 +101,13 @@ impl MirPhase { MirPhase::Runtime(RuntimePhase::Optimized) => "runtime-optimized", } } + + pub fn reveal(&self) -> Reveal { + match *self { + MirPhase::Built | MirPhase::Analysis(_) => Reveal::UserFacing, + MirPhase::Runtime(_) => Reveal::All, + } + } } /// See [`MirPhase::Analysis`]. From e73ef59cb6d806d9f5405237383d65bf02095127 Mon Sep 17 00:00:00 2001 From: lcnr Date: Thu, 8 Dec 2022 12:23:10 +0100 Subject: [PATCH 21/25] add regression test for #105009 --- ...sue-95978-validator-lifetime-comparison.rs | 0 src/test/ui/mir/validate/needs-reveal-all.rs | 52 +++++++++++++++++++ 2 files changed, 52 insertions(+) rename src/test/ui/mir/{ => validate}/issue-95978-validator-lifetime-comparison.rs (100%) create mode 100644 src/test/ui/mir/validate/needs-reveal-all.rs diff --git a/src/test/ui/mir/issue-95978-validator-lifetime-comparison.rs b/src/test/ui/mir/validate/issue-95978-validator-lifetime-comparison.rs similarity index 100% rename from src/test/ui/mir/issue-95978-validator-lifetime-comparison.rs rename to src/test/ui/mir/validate/issue-95978-validator-lifetime-comparison.rs diff --git a/src/test/ui/mir/validate/needs-reveal-all.rs b/src/test/ui/mir/validate/needs-reveal-all.rs new file mode 100644 index 0000000000000..3852daf245eb0 --- /dev/null +++ b/src/test/ui/mir/validate/needs-reveal-all.rs @@ -0,0 +1,52 @@ +// Regression test for #105009. the issue here was that even after the `RevealAll` pass, +// `validate` still used `Reveal::UserFacing`. This meant that it now ends up comparing +// opaque types with their revealed version, resulting in an ICE. +// +// We're using these flags to run the `RevealAll` pass while making it less likely to +// accidentally removing the assignment from `Foo` to `Foo`. + +// compile-flags: -Zinline_mir=yes -Zmir-opt-level=0 -Zvalidate-mir +// run-pass + +use std::hint::black_box; + +trait Func { + type Ret: Id; +} + +trait Id { + type Assoc; +} +impl Id for u32 { + type Assoc = u32; +} +impl Id for i32 { + type Assoc = i32; +} + +impl R, R: Id> Func for F { + type Ret = R; +} + +fn bar() -> impl Copy + Id { + 0u32 +} + +struct Foo { + _func: T, + value: Option<<::Ret as Id>::Assoc>, +} + +fn main() { + let mut fn_def = black_box(Foo { + _func: bar, + value: None, + }); + let fn_ptr = black_box(Foo { + _func: bar as fn() -> _, + value: None, + }); + + fn_def.value = fn_ptr.value; + black_box(fn_def); +} From b5a9f6a5373a6e2f188417ed734b9f94fc16ac51 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 7 Dec 2022 16:08:58 +0100 Subject: [PATCH 22/25] Update rustdoc test to check its error output --- .../{rustdoc => rustdoc-ui}/const-evalutation-ice.rs | 3 ++- src/test/rustdoc-ui/const-evalutation-ice.stderr | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) rename src/test/{rustdoc => rustdoc-ui}/const-evalutation-ice.rs (51%) create mode 100644 src/test/rustdoc-ui/const-evalutation-ice.stderr diff --git a/src/test/rustdoc/const-evalutation-ice.rs b/src/test/rustdoc-ui/const-evalutation-ice.rs similarity index 51% rename from src/test/rustdoc/const-evalutation-ice.rs rename to src/test/rustdoc-ui/const-evalutation-ice.rs index 68c7f9c5686ff..0dd3bcaa2895d 100644 --- a/src/test/rustdoc/const-evalutation-ice.rs +++ b/src/test/rustdoc-ui/const-evalutation-ice.rs @@ -7,4 +7,5 @@ pub struct S { s: Cell } -pub const N: usize = 0 - (mem::size_of::() != 4) as usize; +pub const N: usize = 0 - (mem::size_of::() != 400) as usize; +//~^ ERROR evaluation of constant value failed diff --git a/src/test/rustdoc-ui/const-evalutation-ice.stderr b/src/test/rustdoc-ui/const-evalutation-ice.stderr new file mode 100644 index 0000000000000..5d9c16c076529 --- /dev/null +++ b/src/test/rustdoc-ui/const-evalutation-ice.stderr @@ -0,0 +1,9 @@ +error[E0080]: evaluation of constant value failed + --> $DIR/const-evalutation-ice.rs:10:22 + | +LL | pub const N: usize = 0 - (mem::size_of::() != 400) as usize; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ attempt to compute `0_usize - 1_usize`, which would overflow + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0080`. From 34700f6594436b49005151ee12dd2ae06c7cad03 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Thu, 8 Dec 2022 09:38:27 -0700 Subject: [PATCH 23/25] rustdoc: add GUI test case for docblock table colors --- src/librustdoc/html/static/css/rustdoc.css | 2 +- src/test/rustdoc-gui/docblock-table.goml | 47 ++++++++++++++++++++++ src/test/rustdoc-gui/src/test_docs/lib.rs | 3 ++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 63bb2cdbaf238..619bdeb011ecd 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -639,7 +639,7 @@ pre, .rustdoc.source .example-wrap { border: 1px solid var(--border-color); } -.docblock table tr:nth-child(2n) { +.docblock table tbody tr:nth-child(2n) { background: var(--table-alt-row-background-color); } diff --git a/src/test/rustdoc-gui/docblock-table.goml b/src/test/rustdoc-gui/docblock-table.goml index 4e316ce0bcbfd..8645c1b1949e3 100644 --- a/src/test/rustdoc-gui/docblock-table.goml +++ b/src/test/rustdoc-gui/docblock-table.goml @@ -2,3 +2,50 @@ goto: "file://" + |DOC_PATH| + "/test_docs/doc_block_table/struct.DocBlockTable. compare-elements-css: (".impl-items .docblock table th", ".top-doc .docblock table th", ["border"]) compare-elements-css: (".impl-items .docblock table td", ".top-doc .docblock table td", ["border"]) + +define-function: ( + "check-colors", + (theme, border_color, zebra_stripe_color), + [ + ("local-storage", {"rustdoc-use-system-theme": "false", "rustdoc-theme": |theme|}), + ("reload"), + ("assert-css", (".top-doc .docblock table tbody tr:nth-child(1)", { + "background-color": "rgba(0, 0, 0, 0)", + })), + ("assert-css", (".top-doc .docblock table tbody tr:nth-child(2)", { + "background-color": |zebra_stripe_color|, + })), + ("assert-css", (".top-doc .docblock table tbody tr:nth-child(3)", { + "background-color": "rgba(0, 0, 0, 0)", + })), + ("assert-css", (".top-doc .docblock table tbody tr:nth-child(4)", { + "background-color": |zebra_stripe_color|, + })), + ("assert-css", (".top-doc .docblock table td", { + "border-style": "solid", + "border-width": "1px", + "border-color": |border_color|, + })), + ("assert-css", (".top-doc .docblock table th", { + "border-style": "solid", + "border-width": "1px", + "border-color": |border_color|, + })), + ] +) + +call-function: ("check-colors", { + "theme": "dark", + "border_color": "rgb(224, 224, 224)", + "zebra_stripe_color": "rgb(42, 42, 42)", +}) +call-function: ("check-colors", { + "theme": "ayu", + "border_color": "rgb(92, 103, 115)", + "zebra_stripe_color": "rgb(25, 31, 38)", +}) +call-function: ("check-colors", { + "theme": "light", + "border_color": "rgb(224, 224, 224)", + "zebra_stripe_color": "rgb(245, 245, 245)", +}) diff --git a/src/test/rustdoc-gui/src/test_docs/lib.rs b/src/test/rustdoc-gui/src/test_docs/lib.rs index 1340511d4726b..d6eeab803dfec 100644 --- a/src/test/rustdoc-gui/src/test_docs/lib.rs +++ b/src/test/rustdoc-gui/src/test_docs/lib.rs @@ -343,6 +343,9 @@ pub mod doc_block_table { /// | header1 | header2 | /// |--------------------------|--------------------------| /// | Lorem Ipsum, Lorem Ipsum | Lorem Ipsum, Lorem Ipsum | + /// | Lorem Ipsum, Lorem Ipsum | Lorem Ipsum, Lorem Ipsum | + /// | Lorem Ipsum, Lorem Ipsum | Lorem Ipsum, Lorem Ipsum | + /// | Lorem Ipsum, Lorem Ipsum | Lorem Ipsum, Lorem Ipsum | pub struct DocBlockTable {} impl DocBlockTableTrait for DocBlockTable { From 3c29e742d13314365fe0c1887d2e46a9d8a532fb Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Fri, 2 Dec 2022 20:41:29 -0500 Subject: [PATCH 24/25] Fix #10021 --- .../clippy/clippy_lints/src/methods/unnecessary_to_owned.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs index 17b0507682ae9..9263f0519724b 100644 --- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -386,14 +386,12 @@ fn can_change_type<'a>(cx: &LateContext<'a>, mut expr: &'a Expr<'a>, mut ty: Ty< Node::Expr(parent_expr) => { if let Some((callee_def_id, call_substs, recv, call_args)) = get_callee_substs_and_args(cx, parent_expr) { - if Some(callee_def_id) == cx.tcx.lang_items().into_future_fn() { - return false; - } - let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder(); if let Some(arg_index) = recv.into_iter().chain(call_args).position(|arg| arg.hir_id == expr.hir_id) && let Some(param_ty) = fn_sig.inputs().get(arg_index) && let ty::Param(ParamTy { index: param_index , ..}) = param_ty.kind() + // https://github.com/rust-lang/rust-clippy/issues/9504 and https://github.com/rust-lang/rust-clippy/issues/10021 + && (*param_index as usize) < call_substs.len() { if fn_sig .inputs() From a25791ee618a30e161f19bb37e2aea11806a1a82 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 8 Dec 2022 22:36:57 +0100 Subject: [PATCH 25/25] lib docs: fix typo --- library/core/src/ptr/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs index 89b11637ecabe..1a0d9a3d0cecc 100644 --- a/library/core/src/ptr/mod.rs +++ b/library/core/src/ptr/mod.rs @@ -1769,7 +1769,7 @@ pub(crate) const unsafe fn align_offset(p: *const T, a: usize) -> usiz /// (which is what the `PartialEq for &T` implementation does). /// /// When comparing wide pointers, both the address and the metadata are tested for equality. -/// However, note that comparing trait object pointers (`*const dyn Trait`) is unrealiable: pointers +/// However, note that comparing trait object pointers (`*const dyn Trait`) is unreliable: pointers /// to values of the same underlying type can compare inequal (because vtables are duplicated in /// multiple codegen units), and pointers to values of *different* underlying type can compare equal /// (since identical vtables can be deduplicated within a codegen unit).