From 1ab23bda9c44989a7d5b4bab557eb2dab18eb509 Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Tue, 1 Feb 2022 17:03:22 -0800 Subject: [PATCH 01/15] Include all contents of first line of scraped item --- src/librustdoc/scrape_examples.rs | 5 ++++- .../run-make/rustdoc-scrape-examples-whitespace/Makefile | 5 +++++ .../rustdoc-scrape-examples-whitespace/examples/ex.rs | 8 ++++++++ .../rustdoc-scrape-examples-whitespace/src/lib.rs | 3 +++ 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/test/run-make/rustdoc-scrape-examples-whitespace/Makefile create mode 100644 src/test/run-make/rustdoc-scrape-examples-whitespace/examples/ex.rs create mode 100644 src/test/run-make/rustdoc-scrape-examples-whitespace/src/lib.rs diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs index 899c9e4c62956..68b3e18275137 100644 --- a/src/librustdoc/scrape_examples.rs +++ b/src/librustdoc/scrape_examples.rs @@ -196,7 +196,8 @@ where return; } - let file = tcx.sess.source_map().lookup_char_pos(span.lo()).file; + let source_map = tcx.sess.source_map(); + let file = source_map.lookup_char_pos(span.lo()).file; let file_path = match file.name.clone() { FileName::Real(real_filename) => real_filename.into_local_path(), _ => None, @@ -217,6 +218,8 @@ where let fn_entries = self.calls.entry(fn_key).or_default(); trace!("Including expr: {:?}", span); + let enclosing_item_span = + source_map.span_extend_to_prev_char(enclosing_item_span, '\n', false); let location = CallLocation::new(span, enclosing_item_span, &file); fn_entries.entry(abs_path).or_insert_with(mk_call_data).locations.push(location); } diff --git a/src/test/run-make/rustdoc-scrape-examples-whitespace/Makefile b/src/test/run-make/rustdoc-scrape-examples-whitespace/Makefile new file mode 100644 index 0000000000000..dce8b83eefe4e --- /dev/null +++ b/src/test/run-make/rustdoc-scrape-examples-whitespace/Makefile @@ -0,0 +1,5 @@ +deps := ex + +-include ../rustdoc-scrape-examples-multiple/scrape.mk + +all: scrape diff --git a/src/test/run-make/rustdoc-scrape-examples-whitespace/examples/ex.rs b/src/test/run-make/rustdoc-scrape-examples-whitespace/examples/ex.rs new file mode 100644 index 0000000000000..44ff689dfc876 --- /dev/null +++ b/src/test/run-make/rustdoc-scrape-examples-whitespace/examples/ex.rs @@ -0,0 +1,8 @@ +struct Foo; +impl Foo { + fn bar() { foobar::ok(); } +} + +fn main() { + Foo::bar(); +} diff --git a/src/test/run-make/rustdoc-scrape-examples-whitespace/src/lib.rs b/src/test/run-make/rustdoc-scrape-examples-whitespace/src/lib.rs new file mode 100644 index 0000000000000..28c34716c2f3b --- /dev/null +++ b/src/test/run-make/rustdoc-scrape-examples-whitespace/src/lib.rs @@ -0,0 +1,3 @@ +// @has foobar/fn.ok.html '//*[@class="docblock scraped-example-list"]//code' ' ' + +pub fn ok() {} From 214ce5702c2f1eb5c098e5e393df09c3b1829572 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 1 Feb 2022 22:22:57 -0700 Subject: [PATCH 02/15] rustdoc: correct unclosed HTML tags as generics --- src/librustdoc/lib.rs | 1 + src/librustdoc/passes/html_tags.rs | 81 ++++++++++++++++--- .../html-as-generics-no-suggestions.rs | 38 +++++++++ .../html-as-generics-no-suggestions.stderr | 38 +++++++++ .../suggestions/html-as-generics.fixed | 38 +++++++++ .../suggestions/html-as-generics.rs | 38 +++++++++ .../suggestions/html-as-generics.stderr | 56 +++++++++++++ 7 files changed, 281 insertions(+), 9 deletions(-) create mode 100644 src/test/rustdoc-ui/suggestions/html-as-generics-no-suggestions.rs create mode 100644 src/test/rustdoc-ui/suggestions/html-as-generics-no-suggestions.stderr create mode 100644 src/test/rustdoc-ui/suggestions/html-as-generics.fixed create mode 100644 src/test/rustdoc-ui/suggestions/html-as-generics.rs create mode 100644 src/test/rustdoc-ui/suggestions/html-as-generics.stderr diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index a7c3c0bb60610..68028604fa463 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -5,6 +5,7 @@ #![feature(rustc_private)] #![feature(array_methods)] #![feature(assert_matches)] +#![feature(bool_to_option)] #![feature(box_patterns)] #![feature(control_flow_enum)] #![feature(box_syntax)] diff --git a/src/librustdoc/passes/html_tags.rs b/src/librustdoc/passes/html_tags.rs index f7a9a0899e390..7ad14bbcbb16f 100644 --- a/src/librustdoc/passes/html_tags.rs +++ b/src/librustdoc/passes/html_tags.rs @@ -38,7 +38,7 @@ fn drop_tag( tags: &mut Vec<(String, Range)>, tag_name: String, range: Range, - f: &impl Fn(&str, &Range), + f: &impl Fn(&str, &Range, bool), ) { let tag_name_low = tag_name.to_lowercase(); if let Some(pos) = tags.iter().rposition(|(t, _)| t.to_lowercase() == tag_name_low) { @@ -59,14 +59,42 @@ fn drop_tag( // `tags` is used as a queue, meaning that everything after `pos` is included inside it. // So `

` will look like `["h2", "h3"]`. So when closing `h2`, we will still // have `h3`, meaning the tag wasn't closed as it should have. - f(&format!("unclosed HTML tag `{}`", last_tag_name), &last_tag_span); + f(&format!("unclosed HTML tag `{}`", last_tag_name), &last_tag_span, true); } // Remove the `tag_name` that was originally closed tags.pop(); } else { // It can happen for example in this case: `

` (the `h2` tag isn't required // but it helps for the visualization). - f(&format!("unopened HTML tag `{}`", tag_name), &range); + f(&format!("unopened HTML tag `{}`", tag_name), &range, false); + } +} + +fn extract_path_backwards(text: &str, end_pos: usize) -> Option { + use rustc_lexer::{is_id_continue, is_id_start}; + let mut current_pos = end_pos; + loop { + if current_pos >= 2 && &text[current_pos - 2..current_pos] == "::" { + current_pos -= 2; + } + let new_pos = text[..current_pos] + .char_indices() + .rev() + .take_while(|(_, c)| is_id_start(*c) || is_id_continue(*c)) + .reduce(|_accum, item| item) + .and_then(|(new_pos, c)| is_id_start(c).then_some(new_pos)); + if let Some(new_pos) = new_pos { + if current_pos != new_pos { + current_pos = new_pos; + continue; + } + } + break; + } + if current_pos == end_pos { + return None; + } else { + return Some(current_pos); } } @@ -76,7 +104,7 @@ fn extract_html_tag( range: &Range, start_pos: usize, iter: &mut Peekable>, - f: &impl Fn(&str, &Range), + f: &impl Fn(&str, &Range, bool), ) { let mut tag_name = String::new(); let mut is_closing = false; @@ -140,7 +168,7 @@ fn extract_tags( text: &str, range: Range, is_in_comment: &mut Option>, - f: &impl Fn(&str, &Range), + f: &impl Fn(&str, &Range, bool), ) { let mut iter = text.char_indices().peekable(); @@ -178,14 +206,49 @@ impl<'a, 'tcx> DocVisitor for InvalidHtmlTagsLinter<'a, 'tcx> { }; let dox = item.attrs.collapsed_doc_value().unwrap_or_default(); if !dox.is_empty() { - let report_diag = |msg: &str, range: &Range| { + let report_diag = |msg: &str, range: &Range, is_open_tag: bool| { let sp = match super::source_span_for_markdown_range(tcx, &dox, range, &item.attrs) { Some(sp) => sp, None => item.attr_span(tcx), }; tcx.struct_span_lint_hir(crate::lint::INVALID_HTML_TAGS, hir_id, sp, |lint| { - lint.build(msg).emit() + use rustc_lint_defs::Applicability; + let mut diag = lint.build(msg); + // If a tag looks like ``, it might actually be a generic. + // We don't try to detect stuff `` because that's not valid HTML, + // and we don' try to detect stuff `` because that's not valid Rust. + if let Some(Some(generics_start)) = (is_open_tag + && &dox[range.end - 1..range.end] == ">") + .then(|| extract_path_backwards(&dox, range.start)) + { + let generics_sp = match super::source_span_for_markdown_range( + tcx, + &dox, + &(generics_start..range.end), + &item.attrs, + ) { + Some(sp) => sp, + None => item.attr_span(tcx), + }; + if let Ok(generics_snippet) = + tcx.sess.source_map().span_to_snippet(generics_sp) + { + // short form is chosen here because ``Vec`` would be confusing. + diag.span_suggestion_short( + generics_sp, + "try marking as source code with `backticks`", + format!("`{}`", generics_snippet), + Applicability::MachineApplicable, + ); + } else { + diag.span_help( + generics_sp, + "try marking as source code with `backticks`", + ); + } + } + diag.emit() }); }; @@ -210,11 +273,11 @@ impl<'a, 'tcx> DocVisitor for InvalidHtmlTagsLinter<'a, 'tcx> { let t = t.to_lowercase(); !ALLOWED_UNCLOSED.contains(&t.as_str()) }) { - report_diag(&format!("unclosed HTML tag `{}`", tag), range); + report_diag(&format!("unclosed HTML tag `{}`", tag), range, true); } if let Some(range) = is_in_comment { - report_diag("Unclosed HTML comment", &range); + report_diag("Unclosed HTML comment", &range, false); } } diff --git a/src/test/rustdoc-ui/suggestions/html-as-generics-no-suggestions.rs b/src/test/rustdoc-ui/suggestions/html-as-generics-no-suggestions.rs new file mode 100644 index 0000000000000..744b3071f1b81 --- /dev/null +++ b/src/test/rustdoc-ui/suggestions/html-as-generics-no-suggestions.rs @@ -0,0 +1,38 @@ +#![deny(rustdoc::invalid_html_tags)] + +/// This Vec<32> thing! +// Numbers aren't valid HTML tags, so no error. +pub struct ConstGeneric; + +/// This Vec thing! +// HTML tags cannot contain commas, so no error. +pub struct MultipleGenerics; + +/// This Vec thing! +//~^ERROR unclosed HTML tag `i32` +// HTML attributes shouldn't be treated as Rust syntax, so no suggestions. +pub struct TagWithAttributes; + +/// This Vec thing! +// There should be no error, and no suggestion, since the tags are balanced. +pub struct DoNotWarnOnMatchingTags; + +/// This Vec thing! +//~^ERROR unopened HTML tag `i32` +// This should produce an error, but no suggestion. +pub struct EndTagsAreNotValidRustSyntax; + +/// This 123 thing! +//~^ERROR unclosed HTML tag `i32` +// This should produce an error, but no suggestion. +pub struct NumbersAreNotPaths; + +/// This Vec: thing! +//~^ERROR unclosed HTML tag `i32` +// This should produce an error, but no suggestion. +pub struct InvalidTurbofish; + +/// This [link](https://rust-lang.org) thing! +//~^ERROR unclosed HTML tag `i32` +// This should produce an error, but no suggestion. +pub struct BareTurbofish; diff --git a/src/test/rustdoc-ui/suggestions/html-as-generics-no-suggestions.stderr b/src/test/rustdoc-ui/suggestions/html-as-generics-no-suggestions.stderr new file mode 100644 index 0000000000000..832b8b2cac79a --- /dev/null +++ b/src/test/rustdoc-ui/suggestions/html-as-generics-no-suggestions.stderr @@ -0,0 +1,38 @@ +error: unclosed HTML tag `i32` + --> $DIR/html-as-generics-no-suggestions.rs:11:13 + | +LL | /// This Vec thing! + | ^^^^ + | +note: the lint level is defined here + --> $DIR/html-as-generics-no-suggestions.rs:1:9 + | +LL | #![deny(rustdoc::invalid_html_tags)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: unopened HTML tag `i32` + --> $DIR/html-as-generics-no-suggestions.rs:20:13 + | +LL | /// This Vec thing! + | ^^^^^^ + +error: unclosed HTML tag `i32` + --> $DIR/html-as-generics-no-suggestions.rs:25:13 + | +LL | /// This 123 thing! + | ^^^^^ + +error: unclosed HTML tag `i32` + --> $DIR/html-as-generics-no-suggestions.rs:30:14 + | +LL | /// This Vec: thing! + | ^^^^^ + +error: unclosed HTML tag `i32` + --> $DIR/html-as-generics-no-suggestions.rs:35:39 + | +LL | /// This [link](https://rust-lang.org) thing! + | ^^^^^ + +error: aborting due to 5 previous errors + diff --git a/src/test/rustdoc-ui/suggestions/html-as-generics.fixed b/src/test/rustdoc-ui/suggestions/html-as-generics.fixed new file mode 100644 index 0000000000000..04bdd038993a4 --- /dev/null +++ b/src/test/rustdoc-ui/suggestions/html-as-generics.fixed @@ -0,0 +1,38 @@ +// run-rustfix +#![deny(rustdoc::invalid_html_tags)] + +/// This `Vec` thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +//~|SUGGESTION `Vec` +pub struct Generic; + +/// This `vec::Vec` thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +//~|SUGGESTION `vec::Vec` +pub struct GenericPath; + +/// This `i32` thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +//~|SUGGESTION `i32` +pub struct PathsCanContainTrailingNumbers; + +/// This `Vec::` thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +//~|SUGGESTION `Vec::` +pub struct Turbofish; + +/// This [link](https://rust-lang.org)`::` thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +//~|SUGGESTION `::` +pub struct BareTurbofish; + +/// This `Vec::` thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +//~|SUGGESTION `Vec::` +pub struct Nested; diff --git a/src/test/rustdoc-ui/suggestions/html-as-generics.rs b/src/test/rustdoc-ui/suggestions/html-as-generics.rs new file mode 100644 index 0000000000000..28e50c0073851 --- /dev/null +++ b/src/test/rustdoc-ui/suggestions/html-as-generics.rs @@ -0,0 +1,38 @@ +// run-rustfix +#![deny(rustdoc::invalid_html_tags)] + +/// This Vec thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +//~|SUGGESTION `Vec` +pub struct Generic; + +/// This vec::Vec thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +//~|SUGGESTION `vec::Vec` +pub struct GenericPath; + +/// This i32 thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +//~|SUGGESTION `i32` +pub struct PathsCanContainTrailingNumbers; + +/// This Vec:: thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +//~|SUGGESTION `Vec::` +pub struct Turbofish; + +/// This [link](https://rust-lang.org):: thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +//~|SUGGESTION `::` +pub struct BareTurbofish; + +/// This Vec:: thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +//~|SUGGESTION `Vec::` +pub struct Nested; diff --git a/src/test/rustdoc-ui/suggestions/html-as-generics.stderr b/src/test/rustdoc-ui/suggestions/html-as-generics.stderr new file mode 100644 index 0000000000000..c0a1603bc6652 --- /dev/null +++ b/src/test/rustdoc-ui/suggestions/html-as-generics.stderr @@ -0,0 +1,56 @@ +error: unclosed HTML tag `i32` + --> $DIR/html-as-generics.rs:4:13 + | +LL | /// This Vec thing! + | ---^^^^^ + | | + | help: try marking as source code with `backticks` + | +note: the lint level is defined here + --> $DIR/html-as-generics.rs:2:9 + | +LL | #![deny(rustdoc::invalid_html_tags)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: unclosed HTML tag `i32` + --> $DIR/html-as-generics.rs:10:18 + | +LL | /// This vec::Vec thing! + | --------^^^^^ + | | + | help: try marking as source code with `backticks` + +error: unclosed HTML tag `i32` + --> $DIR/html-as-generics.rs:16:13 + | +LL | /// This i32 thing! + | ---^^^^^ + | | + | help: try marking as source code with `backticks` + +error: unclosed HTML tag `i32` + --> $DIR/html-as-generics.rs:22:15 + | +LL | /// This Vec:: thing! + | -----^^^^^ + | | + | help: try marking as source code with `backticks` + +error: unclosed HTML tag `i32` + --> $DIR/html-as-generics.rs:28:41 + | +LL | /// This [link](https://rust-lang.org):: thing! + | --^^^^^ + | | + | help: try marking as source code with `backticks` + +error: unclosed HTML tag `i32` + --> $DIR/html-as-generics.rs:34:21 + | +LL | /// This Vec:: thing! + | -----^^^^^ + | | + | help: try marking as source code with `backticks` + +error: aborting due to 6 previous errors + From c9907ad853f9d4837f62c3a00f0d33fba562d8e3 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 1 Feb 2022 23:01:44 -0700 Subject: [PATCH 03/15] In retrospect, MachineApplicable is probably too confident --- src/librustdoc/passes/html_tags.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/librustdoc/passes/html_tags.rs b/src/librustdoc/passes/html_tags.rs index 7ad14bbcbb16f..24fc97f234149 100644 --- a/src/librustdoc/passes/html_tags.rs +++ b/src/librustdoc/passes/html_tags.rs @@ -239,7 +239,7 @@ impl<'a, 'tcx> DocVisitor for InvalidHtmlTagsLinter<'a, 'tcx> { generics_sp, "try marking as source code with `backticks`", format!("`{}`", generics_snippet), - Applicability::MachineApplicable, + Applicability::MaybeIncorrect, ); } else { diag.span_help( From 0db9e4067038f3c70eb04e86b1bba5b14b172f47 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 1 Feb 2022 23:06:13 -0700 Subject: [PATCH 04/15] Fix unicode slicing bug --- src/librustdoc/passes/html_tags.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/librustdoc/passes/html_tags.rs b/src/librustdoc/passes/html_tags.rs index 24fc97f234149..f6e7ec8094e45 100644 --- a/src/librustdoc/passes/html_tags.rs +++ b/src/librustdoc/passes/html_tags.rs @@ -74,7 +74,7 @@ fn extract_path_backwards(text: &str, end_pos: usize) -> Option { use rustc_lexer::{is_id_continue, is_id_start}; let mut current_pos = end_pos; loop { - if current_pos >= 2 && &text[current_pos - 2..current_pos] == "::" { + if current_pos >= 2 && text[..current_pos].ends_with("::") { current_pos -= 2; } let new_pos = text[..current_pos] @@ -217,9 +217,9 @@ impl<'a, 'tcx> DocVisitor for InvalidHtmlTagsLinter<'a, 'tcx> { let mut diag = lint.build(msg); // If a tag looks like ``, it might actually be a generic. // We don't try to detect stuff `` because that's not valid HTML, - // and we don' try to detect stuff `` because that's not valid Rust. + // and we don't try to detect stuff `` because that's not valid Rust. if let Some(Some(generics_start)) = (is_open_tag - && &dox[range.end - 1..range.end] == ">") + && dox[..range.end].ends_with(">")) .then(|| extract_path_backwards(&dox, range.start)) { let generics_sp = match super::source_span_for_markdown_range( From 76b5b27d88234d60a1609b86445b134c81b61cf2 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 1 Feb 2022 23:27:07 -0700 Subject: [PATCH 05/15] Use multipart suggestion for code wrapping Another one of those "good grief, I just submitted it and NOW I think of it" moments. --- src/librustdoc/passes/html_tags.rs | 27 +++----- .../suggestions/html-as-generics.fixed | 6 -- .../suggestions/html-as-generics.rs | 6 -- .../suggestions/html-as-generics.stderr | 63 ++++++++++++------- 4 files changed, 50 insertions(+), 52 deletions(-) diff --git a/src/librustdoc/passes/html_tags.rs b/src/librustdoc/passes/html_tags.rs index f6e7ec8094e45..9caadef3dec7c 100644 --- a/src/librustdoc/passes/html_tags.rs +++ b/src/librustdoc/passes/html_tags.rs @@ -220,7 +220,7 @@ impl<'a, 'tcx> DocVisitor for InvalidHtmlTagsLinter<'a, 'tcx> { // and we don't try to detect stuff `` because that's not valid Rust. if let Some(Some(generics_start)) = (is_open_tag && dox[..range.end].ends_with(">")) - .then(|| extract_path_backwards(&dox, range.start)) + .then(|| extract_path_backwards(&dox, range.start)) { let generics_sp = match super::source_span_for_markdown_range( tcx, @@ -231,22 +231,15 @@ impl<'a, 'tcx> DocVisitor for InvalidHtmlTagsLinter<'a, 'tcx> { Some(sp) => sp, None => item.attr_span(tcx), }; - if let Ok(generics_snippet) = - tcx.sess.source_map().span_to_snippet(generics_sp) - { - // short form is chosen here because ``Vec`` would be confusing. - diag.span_suggestion_short( - generics_sp, - "try marking as source code with `backticks`", - format!("`{}`", generics_snippet), - Applicability::MaybeIncorrect, - ); - } else { - diag.span_help( - generics_sp, - "try marking as source code with `backticks`", - ); - } + // multipart form is chosen here because ``Vec`` would be confusing. + diag.multipart_suggestion( + "try marking as source code", + vec![ + (generics_sp.shrink_to_lo(), String::from("`")), + (generics_sp.shrink_to_hi(), String::from("`")), + ], + Applicability::MaybeIncorrect, + ); } diag.emit() }); diff --git a/src/test/rustdoc-ui/suggestions/html-as-generics.fixed b/src/test/rustdoc-ui/suggestions/html-as-generics.fixed index 04bdd038993a4..c0a0de24c5263 100644 --- a/src/test/rustdoc-ui/suggestions/html-as-generics.fixed +++ b/src/test/rustdoc-ui/suggestions/html-as-generics.fixed @@ -4,35 +4,29 @@ /// This `Vec` thing! //~^ERROR unclosed HTML tag `i32` //~|HELP try marking as source -//~|SUGGESTION `Vec` pub struct Generic; /// This `vec::Vec` thing! //~^ERROR unclosed HTML tag `i32` //~|HELP try marking as source -//~|SUGGESTION `vec::Vec` pub struct GenericPath; /// This `i32` thing! //~^ERROR unclosed HTML tag `i32` //~|HELP try marking as source -//~|SUGGESTION `i32` pub struct PathsCanContainTrailingNumbers; /// This `Vec::` thing! //~^ERROR unclosed HTML tag `i32` //~|HELP try marking as source -//~|SUGGESTION `Vec::` pub struct Turbofish; /// This [link](https://rust-lang.org)`::` thing! //~^ERROR unclosed HTML tag `i32` //~|HELP try marking as source -//~|SUGGESTION `::` pub struct BareTurbofish; /// This `Vec::` thing! //~^ERROR unclosed HTML tag `i32` //~|HELP try marking as source -//~|SUGGESTION `Vec::` pub struct Nested; diff --git a/src/test/rustdoc-ui/suggestions/html-as-generics.rs b/src/test/rustdoc-ui/suggestions/html-as-generics.rs index 28e50c0073851..0b6009b0e59c3 100644 --- a/src/test/rustdoc-ui/suggestions/html-as-generics.rs +++ b/src/test/rustdoc-ui/suggestions/html-as-generics.rs @@ -4,35 +4,29 @@ /// This Vec thing! //~^ERROR unclosed HTML tag `i32` //~|HELP try marking as source -//~|SUGGESTION `Vec` pub struct Generic; /// This vec::Vec thing! //~^ERROR unclosed HTML tag `i32` //~|HELP try marking as source -//~|SUGGESTION `vec::Vec` pub struct GenericPath; /// This i32 thing! //~^ERROR unclosed HTML tag `i32` //~|HELP try marking as source -//~|SUGGESTION `i32` pub struct PathsCanContainTrailingNumbers; /// This Vec:: thing! //~^ERROR unclosed HTML tag `i32` //~|HELP try marking as source -//~|SUGGESTION `Vec::` pub struct Turbofish; /// This [link](https://rust-lang.org):: thing! //~^ERROR unclosed HTML tag `i32` //~|HELP try marking as source -//~|SUGGESTION `::` pub struct BareTurbofish; /// This Vec:: thing! //~^ERROR unclosed HTML tag `i32` //~|HELP try marking as source -//~|SUGGESTION `Vec::` pub struct Nested; diff --git a/src/test/rustdoc-ui/suggestions/html-as-generics.stderr b/src/test/rustdoc-ui/suggestions/html-as-generics.stderr index c0a1603bc6652..df54b71264ebc 100644 --- a/src/test/rustdoc-ui/suggestions/html-as-generics.stderr +++ b/src/test/rustdoc-ui/suggestions/html-as-generics.stderr @@ -2,55 +2,72 @@ error: unclosed HTML tag `i32` --> $DIR/html-as-generics.rs:4:13 | LL | /// This Vec thing! - | ---^^^^^ - | | - | help: try marking as source code with `backticks` + | ^^^^^ | note: the lint level is defined here --> $DIR/html-as-generics.rs:2:9 | LL | #![deny(rustdoc::invalid_html_tags)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: try marking as source code + | +LL | /// This `Vec` thing! + | + + error: unclosed HTML tag `i32` - --> $DIR/html-as-generics.rs:10:18 + --> $DIR/html-as-generics.rs:9:18 | LL | /// This vec::Vec thing! - | --------^^^^^ - | | - | help: try marking as source code with `backticks` + | ^^^^^ + | +help: try marking as source code + | +LL | /// This `vec::Vec` thing! + | + + error: unclosed HTML tag `i32` - --> $DIR/html-as-generics.rs:16:13 + --> $DIR/html-as-generics.rs:14:13 | LL | /// This i32 thing! - | ---^^^^^ - | | - | help: try marking as source code with `backticks` + | ^^^^^ + | +help: try marking as source code + | +LL | /// This `i32` thing! + | + + error: unclosed HTML tag `i32` - --> $DIR/html-as-generics.rs:22:15 + --> $DIR/html-as-generics.rs:19:15 | LL | /// This Vec:: thing! - | -----^^^^^ - | | - | help: try marking as source code with `backticks` + | ^^^^^ + | +help: try marking as source code + | +LL | /// This `Vec::` thing! + | + + error: unclosed HTML tag `i32` - --> $DIR/html-as-generics.rs:28:41 + --> $DIR/html-as-generics.rs:24:41 | LL | /// This [link](https://rust-lang.org):: thing! - | --^^^^^ - | | - | help: try marking as source code with `backticks` + | ^^^^^ + | +help: try marking as source code + | +LL | /// This [link](https://rust-lang.org)`::` thing! + | + + error: unclosed HTML tag `i32` - --> $DIR/html-as-generics.rs:34:21 + --> $DIR/html-as-generics.rs:29:21 | LL | /// This Vec:: thing! - | -----^^^^^ - | | - | help: try marking as source code with `backticks` + | ^^^^^ + | +help: try marking as source code + | +LL | /// This `Vec::` thing! + | + + error: aborting due to 6 previous errors From ac0027d45c1891d56e734b4aa6c3aaa8223a9850 Mon Sep 17 00:00:00 2001 From: lcnr Date: Sat, 5 Feb 2022 10:45:50 +0100 Subject: [PATCH 06/15] update comment --- compiler/rustc_builtin_macros/src/deriving/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_builtin_macros/src/deriving/mod.rs b/compiler/rustc_builtin_macros/src/deriving/mod.rs index 367a5aa732370..0ca7988ca152f 100644 --- a/compiler/rustc_builtin_macros/src/deriving/mod.rs +++ b/compiler/rustc_builtin_macros/src/deriving/mod.rs @@ -134,7 +134,7 @@ fn inject_impl_of_structural_trait( // Create the type of `self`. // - // in addition, remove defaults from type params (impls cannot have them). + // in addition, remove defaults from generic params (impls cannot have them). let self_params: Vec<_> = generics .params .iter_mut() From f026550113b62b7bbd33faf39a7048f5a34fe289 Mon Sep 17 00:00:00 2001 From: Joshua Nelson Date: Sun, 6 Feb 2022 18:39:42 -0600 Subject: [PATCH 07/15] rustdoc: Special-case macro lookups less Previously, rustdoc had 3 fallbacks it used: 1. `resolve_macro_path` 2. `all_macros` 3. `resolve_str_path_error` Ideally, it would only use `resolve_str_path_error`, to be consistent with other namespaces. Unfortunately, that doesn't consider macros that aren't defined at module scope; consider for instance ```rust { struct S; macro_rules! mac { () => {} } // `mac`'s scope starts here /// `mac` <- `resolve_str_path_error` won't see this struct Z; //`mac`'s scope ends here } ``` This changes it to only use `all_macros` and `resolve_str_path_error`, and gives `resolve_str_path_error` precedence over `all_macros` in case there are two macros with the same name in the same module. This also adds a failing test case which will catch trying to remove `all_macros`. --- .../passes/collect_intra_doc_links.rs | 24 ++++--------------- src/test/rustdoc-ui/intra-doc/macro-rules.rs | 9 +++++++ 2 files changed, 14 insertions(+), 19 deletions(-) create mode 100644 src/test/rustdoc-ui/intra-doc/macro-rules.rs diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 86662ebaaca21..8621fe6ba1b93 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -2,10 +2,8 @@ //! //! [RFC 1946]: https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md -use rustc_ast as ast; use rustc_data_structures::{fx::FxHashMap, stable_set::FxHashSet}; use rustc_errors::{Applicability, DiagnosticBuilder}; -use rustc_expand::base::SyntaxExtensionKind; use rustc_hir::def::{ DefKind, Namespace::{self, *}, @@ -14,7 +12,6 @@ use rustc_hir::def::{ use rustc_hir::def_id::{CrateNum, DefId, CRATE_DEF_ID}; use rustc_middle::ty::{DefIdTree, Ty, TyCtxt}; use rustc_middle::{bug, span_bug, ty}; -use rustc_resolve::ParentScope; use rustc_session::lint::Lint; use rustc_span::hygiene::MacroKind; use rustc_span::symbol::{sym, Ident, Symbol}; @@ -486,23 +483,9 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> { path_str: &'a str, module_id: DefId, ) -> Result> { - let path = ast::Path::from_ident(Ident::from_str(path_str)); self.cx.enter_resolver(|resolver| { - // FIXME(jynelson): does this really need 3 separate lookups? - if let Ok((Some(ext), res)) = resolver.resolve_macro_path( - &path, - None, - &ParentScope::module(resolver.graph_root(), resolver), - false, - false, - ) { - if let SyntaxExtensionKind::LegacyBang { .. } = ext.kind { - return Ok(res.try_into().unwrap()); - } - } - if let Some(&res) = resolver.all_macros().get(&Symbol::intern(path_str)) { - return Ok(res.try_into().unwrap()); - } + // NOTE: this needs 2 separate lookups because `resolve_str_path_error` doesn't take + // lexical scope into account (it ignores all macros not defined at the mod-level) debug!("resolving {} as a macro in the module {:?}", path_str, module_id); if let Ok((_, res)) = resolver.resolve_str_path_error(DUMMY_SP, path_str, MacroNS, module_id) @@ -512,6 +495,9 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> { return Ok(res); } } + if let Some(&res) = resolver.all_macros().get(&Symbol::intern(path_str)) { + return Ok(res.try_into().unwrap()); + } Err(ResolutionFailure::NotResolved { module_id, partial_res: None, diff --git a/src/test/rustdoc-ui/intra-doc/macro-rules.rs b/src/test/rustdoc-ui/intra-doc/macro-rules.rs new file mode 100644 index 0000000000000..a14e4bdf1d706 --- /dev/null +++ b/src/test/rustdoc-ui/intra-doc/macro-rules.rs @@ -0,0 +1,9 @@ +// check-pass +#![allow(rustdoc::private_intra_doc_links)] + +macro_rules! foo { + () => {}; +} + +/// [foo!] +pub fn baz() {} From fdd6f4e56c7470e939c874957f9c70e908b92f6f Mon Sep 17 00:00:00 2001 From: kadmin Date: Mon, 7 Feb 2022 04:57:45 +0000 Subject: [PATCH 08/15] Add in ValuePair::Term This adds in an enum when matching on positions which can either be types or consts. It will default to emitting old special cased error messages for types. --- compiler/rustc_infer/src/infer/at.rs | 12 ++---------- .../src/infer/error_reporting/mod.rs | 19 +++++++++++++++++++ compiler/rustc_infer/src/infer/mod.rs | 1 + .../higher-ranked-projection.bad.stderr | 4 ++-- src/test/ui/lifetimes/issue-79187-2.stderr | 8 ++++---- 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_infer/src/infer/at.rs b/compiler/rustc_infer/src/infer/at.rs index 147061dafeb1e..aa74a92ad1f4f 100644 --- a/compiler/rustc_infer/src/infer/at.rs +++ b/compiler/rustc_infer/src/infer/at.rs @@ -288,21 +288,13 @@ impl<'tcx> ToTrace<'tcx> for &'tcx Const<'tcx> { impl<'tcx> ToTrace<'tcx> for ty::Term<'tcx> { fn to_trace( - tcx: TyCtxt<'tcx>, + _: TyCtxt<'tcx>, cause: &ObligationCause<'tcx>, a_is_expected: bool, a: Self, b: Self, ) -> TypeTrace<'tcx> { - match (a, b) { - (ty::Term::Ty(a), ty::Term::Ty(b)) => { - ToTrace::to_trace(tcx, cause, a_is_expected, a, b) - } - (ty::Term::Const(a), ty::Term::Const(b)) => { - ToTrace::to_trace(tcx, cause, a_is_expected, a, b) - } - (_, _) => todo!(), - } + TypeTrace { cause: cause.clone(), values: Terms(ExpectedFound::new(a_is_expected, a, b)) } } } diff --git a/compiler/rustc_infer/src/infer/error_reporting/mod.rs b/compiler/rustc_infer/src/infer/error_reporting/mod.rs index 1eb8190bd7d2f..7b82b5a8b8f87 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/mod.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/mod.rs @@ -2127,6 +2127,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { infer::Types(exp_found) => self.expected_found_str_ty(exp_found), infer::Regions(exp_found) => self.expected_found_str(exp_found), infer::Consts(exp_found) => self.expected_found_str(exp_found), + infer::Terms(exp_found) => self.expected_found_str_term(exp_found), infer::TraitRefs(exp_found) => { let pretty_exp_found = ty::error::ExpectedFound { expected: exp_found.expected.print_only_trait_path(), @@ -2166,6 +2167,24 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { Some(self.cmp(exp_found.expected, exp_found.found)) } + fn expected_found_str_term( + &self, + exp_found: ty::error::ExpectedFound>, + ) -> Option<(DiagnosticStyledString, DiagnosticStyledString)> { + let exp_found = self.resolve_vars_if_possible(exp_found); + if exp_found.references_error() { + return None; + } + + Some(match (exp_found.expected, exp_found.found) { + (ty::Term::Ty(expected), ty::Term::Ty(found)) => self.cmp(expected, found), + (expected, found) => ( + DiagnosticStyledString::highlighted(expected.to_string()), + DiagnosticStyledString::highlighted(found.to_string()), + ), + }) + } + /// Returns a string of the form "expected `{}`, found `{}`". fn expected_found_str>( &self, diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index d1b24b332bdcc..19ac40e7e8a9a 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -371,6 +371,7 @@ pub enum ValuePairs<'tcx> { Types(ExpectedFound>), Regions(ExpectedFound>), Consts(ExpectedFound<&'tcx ty::Const<'tcx>>), + Terms(ExpectedFound>), TraitRefs(ExpectedFound>), PolyTraitRefs(ExpectedFound>), } diff --git a/src/test/ui/associated-types/higher-ranked-projection.bad.stderr b/src/test/ui/associated-types/higher-ranked-projection.bad.stderr index 1ac72e4b90c75..e2847b6b72b86 100644 --- a/src/test/ui/associated-types/higher-ranked-projection.bad.stderr +++ b/src/test/ui/associated-types/higher-ranked-projection.bad.stderr @@ -4,8 +4,8 @@ error[E0308]: mismatched types LL | foo(()); | ^^^ lifetime mismatch | - = note: expected reference `&'a ()` - found type `&()` + = note: expected type `&'a ()` + found type `&()` note: the lifetime requirement is introduced here --> $DIR/higher-ranked-projection.rs:15:33 | diff --git a/src/test/ui/lifetimes/issue-79187-2.stderr b/src/test/ui/lifetimes/issue-79187-2.stderr index 2aca8faff9f2c..9a0b40d43a063 100644 --- a/src/test/ui/lifetimes/issue-79187-2.stderr +++ b/src/test/ui/lifetimes/issue-79187-2.stderr @@ -23,8 +23,8 @@ error[E0308]: mismatched types LL | take_foo(|a: &i32| a); | ^^^^^^^^ lifetime mismatch | - = note: expected reference `&i32` - found reference `&i32` + = note: expected type `&i32` + found type `&i32` note: the anonymous lifetime #1 defined here doesn't meet the lifetime requirements --> $DIR/issue-79187-2.rs:9:14 | @@ -42,8 +42,8 @@ error[E0308]: mismatched types LL | take_foo(|a: &i32| -> &i32 { a }); | ^^^^^^^^ lifetime mismatch | - = note: expected reference `&i32` - found reference `&i32` + = note: expected type `&i32` + found type `&i32` note: the anonymous lifetime #1 defined here doesn't meet the lifetime requirements --> $DIR/issue-79187-2.rs:10:14 | From fe9271af22f4a24a3fb06336c2e38fa124158bc6 Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Sun, 9 Jan 2022 18:14:01 +0100 Subject: [PATCH 09/15] Add `#[no_coverage]` tests for nested functions --- .../expected_show_coverage.no_cov_crate.txt | 66 ++++++++++++++++--- .../coverage/no_cov_crate.rs | 52 ++++++++++++++- 2 files changed, 109 insertions(+), 9 deletions(-) diff --git a/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.no_cov_crate.txt b/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.no_cov_crate.txt index 324b9138c4d9c..83a9204136f7c 100644 --- a/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.no_cov_crate.txt +++ b/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.no_cov_crate.txt @@ -6,8 +6,8 @@ 6| | println!("called but not covered"); 7| |} 8| | - 9| |#[no_coverage] - 10| |fn do_not_add_coverage_2() { + 9| |fn do_not_add_coverage_2() { + 10| | #![no_coverage] 11| | println!("called but not covered"); 12| |} 13| | @@ -28,10 +28,60 @@ 28| 0| println!("not called but covered"); 29| 0|} 30| | - 31| 1|fn main() { - 32| 1| do_not_add_coverage_1(); - 33| 1| do_not_add_coverage_2(); - 34| 1| add_coverage_1(); - 35| 1| add_coverage_2(); - 36| 1|} + 31| |// FIXME: These test-cases illustrate confusing results of nested functions. + 32| |// See https://github.com/rust-lang/rust/issues/93319 + 33| |mod nested_fns { + 34| | #[no_coverage] + 35| | pub fn outer_not_covered(is_true: bool) { + 36| 1| fn inner(is_true: bool) { + 37| 1| if is_true { + 38| 1| println!("called and covered"); + 39| 1| } else { + 40| 0| println!("absolutely not covered"); + 41| 0| } + 42| 1| } + 43| | println!("called but not covered"); + 44| | inner(is_true); + 45| | } + 46| | + 47| 1| pub fn outer(is_true: bool) { + 48| 1| println!("called and covered"); + 49| 1| inner_not_covered(is_true); + 50| 1| + 51| 1| #[no_coverage] + 52| 1| fn inner_not_covered(is_true: bool) { + 53| 1| if is_true { + 54| 1| println!("called but not covered"); + 55| 1| } else { + 56| 1| println!("absolutely not covered"); + 57| 1| } + 58| 1| } + 59| 1| } + 60| | + 61| 1| pub fn outer_both_covered(is_true: bool) { + 62| 1| println!("called and covered"); + 63| 1| inner(is_true); + 64| 1| + 65| 1| fn inner(is_true: bool) { + 66| 1| if is_true { + 67| 1| println!("called and covered"); + 68| 1| } else { + 69| 0| println!("absolutely not covered"); + 70| 0| } + 71| 1| } + 72| 1| } + 73| |} + 74| | + 75| 1|fn main() { + 76| 1| let is_true = std::env::args().len() == 1; + 77| 1| + 78| 1| do_not_add_coverage_1(); + 79| 1| do_not_add_coverage_2(); + 80| 1| add_coverage_1(); + 81| 1| add_coverage_2(); + 82| 1| + 83| 1| nested_fns::outer_not_covered(is_true); + 84| 1| nested_fns::outer(is_true); + 85| 1| nested_fns::outer_both_covered(is_true); + 86| 1|} diff --git a/src/test/run-make-fulldeps/coverage/no_cov_crate.rs b/src/test/run-make-fulldeps/coverage/no_cov_crate.rs index 6f8586d9f5ca6..0bfbdda2cab03 100644 --- a/src/test/run-make-fulldeps/coverage/no_cov_crate.rs +++ b/src/test/run-make-fulldeps/coverage/no_cov_crate.rs @@ -6,8 +6,8 @@ fn do_not_add_coverage_1() { println!("called but not covered"); } -#[no_coverage] fn do_not_add_coverage_2() { + #![no_coverage] println!("called but not covered"); } @@ -28,9 +28,59 @@ fn add_coverage_not_called() { println!("not called but covered"); } +// FIXME: These test-cases illustrate confusing results of nested functions. +// See https://github.com/rust-lang/rust/issues/93319 +mod nested_fns { + #[no_coverage] + pub fn outer_not_covered(is_true: bool) { + fn inner(is_true: bool) { + if is_true { + println!("called and covered"); + } else { + println!("absolutely not covered"); + } + } + println!("called but not covered"); + inner(is_true); + } + + pub fn outer(is_true: bool) { + println!("called and covered"); + inner_not_covered(is_true); + + #[no_coverage] + fn inner_not_covered(is_true: bool) { + if is_true { + println!("called but not covered"); + } else { + println!("absolutely not covered"); + } + } + } + + pub fn outer_both_covered(is_true: bool) { + println!("called and covered"); + inner(is_true); + + fn inner(is_true: bool) { + if is_true { + println!("called and covered"); + } else { + println!("absolutely not covered"); + } + } + } +} + fn main() { + let is_true = std::env::args().len() == 1; + do_not_add_coverage_1(); do_not_add_coverage_2(); add_coverage_1(); add_coverage_2(); + + nested_fns::outer_not_covered(is_true); + nested_fns::outer(is_true); + nested_fns::outer_both_covered(is_true); } From e3942874a06677f17be284f27b5494531cc7224a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 5 Feb 2022 14:45:29 +0100 Subject: [PATCH 10/15] Fix horizontal trim for block doc comments --- compiler/rustc_ast/src/util/comments.rs | 16 +++++++---- compiler/rustc_ast/src/util/comments/tests.rs | 28 ++++++++++++++++++- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_ast/src/util/comments.rs b/compiler/rustc_ast/src/util/comments.rs index 612ee71f350f1..f51b0086dc8b6 100644 --- a/compiler/rustc_ast/src/util/comments.rs +++ b/compiler/rustc_ast/src/util/comments.rs @@ -43,7 +43,7 @@ pub fn beautify_doc_string(data: Symbol, kind: CommentKind) -> Symbol { if i != 0 || j != lines.len() { Some((i, j)) } else { None } } - fn get_horizontal_trim(lines: &[&str], kind: CommentKind) -> Option { + fn get_horizontal_trim<'a>(lines: &'a [&str], kind: CommentKind) -> Option { let mut i = usize::MAX; let mut first = true; @@ -51,7 +51,8 @@ pub fn beautify_doc_string(data: Symbol, kind: CommentKind) -> Symbol { // present. However, we first need to strip the empty lines so they don't get in the middle // when we try to compute the "horizontal trim". let lines = if kind == CommentKind::Block { - let mut i = 0; + // Whatever happens, we skip the first line. + let mut i = if lines[0].trim_start().starts_with('*') { 0 } else { 1 }; let mut j = lines.len(); while i < j && lines[i].trim().is_empty() { @@ -84,7 +85,7 @@ pub fn beautify_doc_string(data: Symbol, kind: CommentKind) -> Symbol { return None; } } - Some(i) + if lines.is_empty() { None } else { Some(lines[0][..i].into()) } } let data_s = data.as_str(); @@ -102,8 +103,13 @@ pub fn beautify_doc_string(data: Symbol, kind: CommentKind) -> Symbol { changes = true; // remove a "[ \t]*\*" block from each line, if possible for line in lines.iter_mut() { - if horizontal + 1 < line.len() { - *line = &line[horizontal + 1..]; + if let Some(tmp) = line.strip_prefix(&horizontal) { + *line = tmp; + if kind == CommentKind::Block + && (*line == "*" || line.starts_with("* ") || line.starts_with("**")) + { + *line = &line[1..]; + } } } } diff --git a/compiler/rustc_ast/src/util/comments/tests.rs b/compiler/rustc_ast/src/util/comments/tests.rs index 98f692a7724e2..0b8772947e6e9 100644 --- a/compiler/rustc_ast/src/util/comments/tests.rs +++ b/compiler/rustc_ast/src/util/comments/tests.rs @@ -24,7 +24,7 @@ fn test_block_doc_comment_3() { create_default_session_globals_then(|| { let comment = "\n let a: *i32;\n *a = 5;\n"; let stripped = beautify_doc_string(Symbol::intern(comment), CommentKind::Block); - assert_eq!(stripped.as_str(), " let a: *i32;\n *a = 5;"); + assert_eq!(stripped.as_str(), "let a: *i32;\n*a = 5;"); }) } @@ -41,3 +41,29 @@ fn test_line_doc_comment() { assert_eq!(stripped.as_str(), "!test"); }) } + +#[test] +fn test_doc_blocks() { + create_default_session_globals_then(|| { + let stripped = beautify_doc_string( + Symbol::intern( + " # Returns + * + ", + ), + CommentKind::Block, + ); + assert_eq!(stripped.as_str(), " # Returns\n\n"); + + let stripped = beautify_doc_string( + Symbol::intern( + " + * # Returns + * + ", + ), + CommentKind::Block, + ); + assert_eq!(stripped.as_str(), " # Returns\n\n"); + }) +} From 33cbf8908d4cf1c971128ee35e7bd2ea50225b63 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 6 Feb 2022 22:21:09 +0100 Subject: [PATCH 11/15] Add test for block doc comments horizontal trim --- compiler/rustc_ast/src/util/comments/tests.rs | 20 ++++++------------- src/test/rustdoc-ui/block-doc-comment.rs | 16 +++++++++++++++ src/test/rustdoc-ui/block-doc-comment.stdout | 5 +++++ 3 files changed, 27 insertions(+), 14 deletions(-) create mode 100644 src/test/rustdoc-ui/block-doc-comment.rs create mode 100644 src/test/rustdoc-ui/block-doc-comment.stdout diff --git a/compiler/rustc_ast/src/util/comments/tests.rs b/compiler/rustc_ast/src/util/comments/tests.rs index 0b8772947e6e9..11d50603a1011 100644 --- a/compiler/rustc_ast/src/util/comments/tests.rs +++ b/compiler/rustc_ast/src/util/comments/tests.rs @@ -45,25 +45,17 @@ fn test_line_doc_comment() { #[test] fn test_doc_blocks() { create_default_session_globals_then(|| { - let stripped = beautify_doc_string( - Symbol::intern( - " # Returns - * - ", - ), - CommentKind::Block, - ); + let stripped = + beautify_doc_string(Symbol::intern(" # Returns\n *\n "), CommentKind::Block); assert_eq!(stripped.as_str(), " # Returns\n\n"); let stripped = beautify_doc_string( - Symbol::intern( - " - * # Returns - * - ", - ), + Symbol::intern("\n * # Returns\n *\n "), CommentKind::Block, ); assert_eq!(stripped.as_str(), " # Returns\n\n"); + + let stripped = beautify_doc_string(Symbol::intern("\n * a\n "), CommentKind::Block); + assert_eq!(stripped.as_str(), " a\n"); }) } diff --git a/src/test/rustdoc-ui/block-doc-comment.rs b/src/test/rustdoc-ui/block-doc-comment.rs new file mode 100644 index 0000000000000..c60dfa3f9518e --- /dev/null +++ b/src/test/rustdoc-ui/block-doc-comment.rs @@ -0,0 +1,16 @@ +// check-pass +// compile-flags:--test + +// This test ensures that no code block is detected in the doc comments. + +pub mod Wormhole { + /** # Returns + * + */ + pub fn foofoo() {} + /** + * # Returns + * + */ + pub fn barbar() {} +} diff --git a/src/test/rustdoc-ui/block-doc-comment.stdout b/src/test/rustdoc-ui/block-doc-comment.stdout new file mode 100644 index 0000000000000..e5c27bebbdb23 --- /dev/null +++ b/src/test/rustdoc-ui/block-doc-comment.stdout @@ -0,0 +1,5 @@ + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + From a476ec8bd0b1925d349383110b75ff51567d7534 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 6 Feb 2022 22:51:47 +0100 Subject: [PATCH 12/15] Update rustdoc test --- src/test/rustdoc/strip-block-doc-comments-stars.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/rustdoc/strip-block-doc-comments-stars.rs b/src/test/rustdoc/strip-block-doc-comments-stars.rs index ed2297b4fac5d..ea28d84f1ffdf 100644 --- a/src/test/rustdoc/strip-block-doc-comments-stars.rs +++ b/src/test/rustdoc/strip-block-doc-comments-stars.rs @@ -1,6 +1,6 @@ #![crate_name = "foo"] -// The goal of this test is to answer that it won't be generated as a list because +// The goal of this test is to ensure that it won't be generated as a list because // block doc comments can have their lines starting with a star. // @has foo/fn.foo.html From be236d7fc2bab5edbd62d5250f62b448bbbaf858 Mon Sep 17 00:00:00 2001 From: kadmin Date: Mon, 7 Feb 2022 16:40:16 +0000 Subject: [PATCH 13/15] Rm ValuePairs::Ty/Const Remove old value pairs which is a strict subset of Terms. --- compiler/rustc_infer/src/infer/at.rs | 12 ++++-- .../src/infer/error_reporting/mod.rs | 42 ++++++------------- .../trait_impl_difference.rs | 12 +++--- compiler/rustc_infer/src/infer/mod.rs | 26 ++++++++++-- .../src/traits/error_reporting/mod.rs | 25 +++-------- .../rustc_typeck/src/check/compare_method.rs | 12 +++--- .../higher-ranked-projection.bad.stderr | 4 +- src/test/ui/lifetimes/issue-79187-2.stderr | 8 ++-- 8 files changed, 67 insertions(+), 74 deletions(-) diff --git a/compiler/rustc_infer/src/infer/at.rs b/compiler/rustc_infer/src/infer/at.rs index aa74a92ad1f4f..c26ea4c966903 100644 --- a/compiler/rustc_infer/src/infer/at.rs +++ b/compiler/rustc_infer/src/infer/at.rs @@ -258,7 +258,10 @@ impl<'tcx> ToTrace<'tcx> for Ty<'tcx> { a: Self, b: Self, ) -> TypeTrace<'tcx> { - TypeTrace { cause: cause.clone(), values: Types(ExpectedFound::new(a_is_expected, a, b)) } + TypeTrace { + cause: cause.clone(), + values: Terms(ExpectedFound::new(a_is_expected, a.into(), b.into())), + } } } @@ -282,7 +285,10 @@ impl<'tcx> ToTrace<'tcx> for &'tcx Const<'tcx> { a: Self, b: Self, ) -> TypeTrace<'tcx> { - TypeTrace { cause: cause.clone(), values: Consts(ExpectedFound::new(a_is_expected, a, b)) } + TypeTrace { + cause: cause.clone(), + values: Terms(ExpectedFound::new(a_is_expected, a.into(), b.into())), + } } } @@ -340,7 +346,7 @@ impl<'tcx> ToTrace<'tcx> for ty::ProjectionTy<'tcx> { let b_ty = tcx.mk_projection(b.item_def_id, b.substs); TypeTrace { cause: cause.clone(), - values: Types(ExpectedFound::new(a_is_expected, a_ty, b_ty)), + values: Terms(ExpectedFound::new(a_is_expected, a_ty.into(), b_ty.into())), } } } diff --git a/compiler/rustc_infer/src/infer/error_reporting/mod.rs b/compiler/rustc_infer/src/infer/error_reporting/mod.rs index 7b82b5a8b8f87..22f25ee396b51 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/mod.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/mod.rs @@ -1582,18 +1582,18 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { None => (None, Mismatch::Fixed("type"), false), Some(values) => { let (is_simple_error, exp_found) = match values { - ValuePairs::Types(exp_found) => { - let is_simple_err = - exp_found.expected.is_simple_text() && exp_found.found.is_simple_text(); - OpaqueTypesVisitor::visit_expected_found( - self.tcx, - exp_found.expected, - exp_found.found, - span, - ) - .report(diag); + ValuePairs::Terms(infer::ExpectedFound { + expected: ty::Term::Ty(expected), + found: ty::Term::Ty(found), + }) => { + let is_simple_err = expected.is_simple_text() && found.is_simple_text(); + OpaqueTypesVisitor::visit_expected_found(self.tcx, expected, found, span) + .report(diag); - (is_simple_err, Mismatch::Variable(exp_found)) + ( + is_simple_err, + Mismatch::Variable(infer::ExpectedFound { expected, found }), + ) } ValuePairs::TraitRefs(_) => (false, Mismatch::Fixed("trait")), _ => (false, Mismatch::Fixed("type")), @@ -1624,7 +1624,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { }; if let Some((sp, msg)) = secondary_span { if swap_secondary_and_primary { - let terr = if let Some(infer::ValuePairs::Types(infer::ExpectedFound { + let terr = if let Some(infer::ValuePairs::Terms(infer::ExpectedFound { expected, .. })) = values @@ -2036,9 +2036,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { } FailureCode::Error0308(failure_str) => { let mut err = struct_span_err!(self.tcx.sess, span, E0308, "{}", failure_str); - if let ValuePairs::Types(ty::error::ExpectedFound { expected, found }) = - trace.values - { + if let Some((expected, found)) = trace.values.ty() { match (expected.kind(), found.kind()) { (ty::Tuple(_), ty::Tuple(_)) => {} // If a tuple of length one was expected and the found expression has @@ -2124,9 +2122,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { values: ValuePairs<'tcx>, ) -> Option<(DiagnosticStyledString, DiagnosticStyledString)> { match values { - infer::Types(exp_found) => self.expected_found_str_ty(exp_found), infer::Regions(exp_found) => self.expected_found_str(exp_found), - infer::Consts(exp_found) => self.expected_found_str(exp_found), infer::Terms(exp_found) => self.expected_found_str_term(exp_found), infer::TraitRefs(exp_found) => { let pretty_exp_found = ty::error::ExpectedFound { @@ -2155,18 +2151,6 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { } } - fn expected_found_str_ty( - &self, - exp_found: ty::error::ExpectedFound>, - ) -> Option<(DiagnosticStyledString, DiagnosticStyledString)> { - let exp_found = self.resolve_vars_if_possible(exp_found); - if exp_found.references_error() { - return None; - } - - Some(self.cmp(exp_found.expected, exp_found.found)) - } - fn expected_found_str_term( &self, exp_found: ty::error::ExpectedFound>, diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/trait_impl_difference.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/trait_impl_difference.rs index bbea450a76973..a79ed20730b5c 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/trait_impl_difference.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/trait_impl_difference.rs @@ -2,7 +2,7 @@ use crate::infer::error_reporting::nice_region_error::NiceRegionError; use crate::infer::lexical_region_resolve::RegionResolutionError; -use crate::infer::{SubregionOrigin, Subtype, ValuePairs}; +use crate::infer::{SubregionOrigin, Subtype}; use crate::traits::ObligationCauseCode::CompareImplMethodObligation; use rustc_errors::ErrorReported; use rustc_hir as hir; @@ -34,16 +34,16 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> { { if let (&Subtype(ref sup_trace), &Subtype(ref sub_trace)) = (&sup_origin, &sub_origin) { if let ( - ValuePairs::Types(sub_expected_found), - ValuePairs::Types(sup_expected_found), + sub_expected_found @ Some((sub_expected, sub_found)), + sup_expected_found @ Some(_), CompareImplMethodObligation { trait_item_def_id, .. }, - ) = (&sub_trace.values, &sup_trace.values, sub_trace.cause.code()) + ) = (&sub_trace.values.ty(), &sup_trace.values.ty(), sub_trace.cause.code()) { if sup_expected_found == sub_expected_found { self.emit_err( var_origin.span(), - sub_expected_found.expected, - sub_expected_found.found, + sub_expected, + sub_found, *trait_item_def_id, ); return Some(ErrorReported); diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index 19ac40e7e8a9a..c18d36d1f74a2 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -368,14 +368,26 @@ pub struct InferCtxt<'a, 'tcx> { /// See the `error_reporting` module for more details. #[derive(Clone, Copy, Debug, PartialEq, Eq, TypeFoldable)] pub enum ValuePairs<'tcx> { - Types(ExpectedFound>), Regions(ExpectedFound>), - Consts(ExpectedFound<&'tcx ty::Const<'tcx>>), Terms(ExpectedFound>), TraitRefs(ExpectedFound>), PolyTraitRefs(ExpectedFound>), } +impl<'tcx> ValuePairs<'tcx> { + pub fn ty(&self) -> Option<(Ty<'tcx>, Ty<'tcx>)> { + if let ValuePairs::Terms(ExpectedFound { + expected: ty::Term::Ty(expected), + found: ty::Term::Ty(found), + }) = self + { + Some((expected, found)) + } else { + None + } + } +} + /// The trace designates the path through inference that we took to /// encounter an error or subtyping constraint. /// @@ -1791,7 +1803,10 @@ impl<'tcx> TypeTrace<'tcx> { a: Ty<'tcx>, b: Ty<'tcx>, ) -> TypeTrace<'tcx> { - TypeTrace { cause: cause.clone(), values: Types(ExpectedFound::new(a_is_expected, a, b)) } + TypeTrace { + cause: cause.clone(), + values: Terms(ExpectedFound::new(a_is_expected, a.into(), b.into())), + } } pub fn consts( @@ -1800,7 +1815,10 @@ impl<'tcx> TypeTrace<'tcx> { a: &'tcx ty::Const<'tcx>, b: &'tcx ty::Const<'tcx>, ) -> TypeTrace<'tcx> { - TypeTrace { cause: cause.clone(), values: Consts(ExpectedFound::new(a_is_expected, a, b)) } + TypeTrace { + cause: cause.clone(), + values: Terms(ExpectedFound::new(a_is_expected, a.into(), b.into())), + } } } diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs index 6cb19416cd769..7f44f55c0d82b 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs @@ -1378,26 +1378,11 @@ impl<'a, 'tcx> InferCtxtPrivExt<'a, 'tcx> for InferCtxt<'a, 'tcx> { normalized_ty, data.term, ) { - values = Some(match (normalized_ty, data.term) { - (ty::Term::Ty(normalized_ty), ty::Term::Ty(ty)) => { - infer::ValuePairs::Types(ExpectedFound::new( - is_normalized_ty_expected, - normalized_ty, - ty, - )) - } - (ty::Term::Const(normalized_ct), ty::Term::Const(ct)) => { - infer::ValuePairs::Consts(ExpectedFound::new( - is_normalized_ty_expected, - normalized_ct, - ct, - )) - } - (_, _) => span_bug!( - obligation.cause.span, - "found const or type where other expected" - ), - }); + values = Some(infer::ValuePairs::Terms(ExpectedFound::new( + is_normalized_ty_expected, + normalized_ty, + data.term, + ))); err_buf = error; err = &err_buf; } diff --git a/compiler/rustc_typeck/src/check/compare_method.rs b/compiler/rustc_typeck/src/check/compare_method.rs index 74910234b7edc..5bb528458c59e 100644 --- a/compiler/rustc_typeck/src/check/compare_method.rs +++ b/compiler/rustc_typeck/src/check/compare_method.rs @@ -377,9 +377,9 @@ fn compare_predicate_entailment<'tcx>( &mut diag, &cause, trait_err_span.map(|sp| (sp, "type in trait".to_owned())), - Some(infer::ValuePairs::Types(ExpectedFound { - expected: trait_fty, - found: impl_fty, + Some(infer::ValuePairs::Terms(ExpectedFound { + expected: trait_fty.into(), + found: impl_fty.into(), })), &terr, false, @@ -1068,9 +1068,9 @@ crate fn compare_const_impl<'tcx>( &mut diag, &cause, trait_c_span.map(|span| (span, "type in trait".to_owned())), - Some(infer::ValuePairs::Types(ExpectedFound { - expected: trait_ty, - found: impl_ty, + Some(infer::ValuePairs::Terms(ExpectedFound { + expected: trait_ty.into(), + found: impl_ty.into(), })), &terr, false, diff --git a/src/test/ui/associated-types/higher-ranked-projection.bad.stderr b/src/test/ui/associated-types/higher-ranked-projection.bad.stderr index e2847b6b72b86..1ac72e4b90c75 100644 --- a/src/test/ui/associated-types/higher-ranked-projection.bad.stderr +++ b/src/test/ui/associated-types/higher-ranked-projection.bad.stderr @@ -4,8 +4,8 @@ error[E0308]: mismatched types LL | foo(()); | ^^^ lifetime mismatch | - = note: expected type `&'a ()` - found type `&()` + = note: expected reference `&'a ()` + found type `&()` note: the lifetime requirement is introduced here --> $DIR/higher-ranked-projection.rs:15:33 | diff --git a/src/test/ui/lifetimes/issue-79187-2.stderr b/src/test/ui/lifetimes/issue-79187-2.stderr index 9a0b40d43a063..2aca8faff9f2c 100644 --- a/src/test/ui/lifetimes/issue-79187-2.stderr +++ b/src/test/ui/lifetimes/issue-79187-2.stderr @@ -23,8 +23,8 @@ error[E0308]: mismatched types LL | take_foo(|a: &i32| a); | ^^^^^^^^ lifetime mismatch | - = note: expected type `&i32` - found type `&i32` + = note: expected reference `&i32` + found reference `&i32` note: the anonymous lifetime #1 defined here doesn't meet the lifetime requirements --> $DIR/issue-79187-2.rs:9:14 | @@ -42,8 +42,8 @@ error[E0308]: mismatched types LL | take_foo(|a: &i32| -> &i32 { a }); | ^^^^^^^^ lifetime mismatch | - = note: expected type `&i32` - found type `&i32` + = note: expected reference `&i32` + found reference `&i32` note: the anonymous lifetime #1 defined here doesn't meet the lifetime requirements --> $DIR/issue-79187-2.rs:10:14 | From edd318c313763d8c4cf3e8cc339f433832d6454a Mon Sep 17 00:00:00 2001 From: ltdk Date: Sun, 20 Jun 2021 16:24:10 -0400 Subject: [PATCH 14/15] Add {floor,ceil}_char_boundary methods to str --- library/alloc/tests/lib.rs | 1 + library/alloc/tests/str.rs | 92 +++++++++++++++++++++++++++++ library/core/src/num/mod.rs | 5 ++ library/core/src/str/mod.rs | 88 +++++++++++++++++++++++---- library/core/src/str/validations.rs | 13 ---- 5 files changed, 176 insertions(+), 23 deletions(-) diff --git a/library/alloc/tests/lib.rs b/library/alloc/tests/lib.rs index dcf51e3142a61..cbb86265233b0 100644 --- a/library/alloc/tests/lib.rs +++ b/library/alloc/tests/lib.rs @@ -29,6 +29,7 @@ #![feature(binary_heap_as_slice)] #![feature(inplace_iteration)] #![feature(iter_advance_by)] +#![feature(round_char_boundary)] #![feature(slice_group_by)] #![feature(slice_partition_dedup)] #![feature(string_remove_matches)] diff --git a/library/alloc/tests/str.rs b/library/alloc/tests/str.rs index 7b07821ab1d31..6b8be2506b64e 100644 --- a/library/alloc/tests/str.rs +++ b/library/alloc/tests/str.rs @@ -2272,3 +2272,95 @@ fn utf8_char_counts() { } } } + +#[test] +fn floor_char_boundary() { + fn check_many(s: &str, arg: impl IntoIterator, ret: usize) { + for idx in arg { + assert_eq!( + s.floor_char_boundary(idx), + ret, + "{:?}.floor_char_boundary({:?}) != {:?}", + s, + idx, + ret + ); + } + } + + // edge case + check_many("", [0, 1, isize::MAX as usize, usize::MAX], 0); + + // basic check + check_many("x", [0], 0); + check_many("x", [1, isize::MAX as usize, usize::MAX], 1); + + // 1-byte chars + check_many("jp", [0], 0); + check_many("jp", [1], 1); + check_many("jp", 2..4, 2); + + // 2-byte chars + check_many("ĵƥ", 0..2, 0); + check_many("ĵƥ", 2..4, 2); + check_many("ĵƥ", 4..6, 4); + + // 3-byte chars + check_many("日本", 0..3, 0); + check_many("日本", 3..6, 3); + check_many("日本", 6..8, 6); + + // 4-byte chars + check_many("🇯🇵", 0..4, 0); + check_many("🇯🇵", 4..8, 4); + check_many("🇯🇵", 8..10, 8); +} + +#[test] +fn ceil_char_boundary() { + fn check_many(s: &str, arg: impl IntoIterator, ret: usize) { + for idx in arg { + assert_eq!( + s.ceil_char_boundary(idx), + ret, + "{:?}.ceil_char_boundary({:?}) != {:?}", + s, + idx, + ret + ); + } + } + + // edge case + check_many("", [0], 0); + + // basic check + check_many("x", [0], 0); + check_many("x", [1], 1); + + // 1-byte chars + check_many("jp", [0], 0); + check_many("jp", [1], 1); + check_many("jp", [2], 2); + + // 2-byte chars + check_many("ĵƥ", 0..=0, 0); + check_many("ĵƥ", 1..=2, 2); + check_many("ĵƥ", 3..=4, 4); + + // 3-byte chars + check_many("日本", 0..=0, 0); + check_many("日本", 1..=3, 3); + check_many("日本", 4..=6, 6); + + // 4-byte chars + check_many("🇯🇵", 0..=0, 0); + check_many("🇯🇵", 1..=4, 4); + check_many("🇯🇵", 5..=8, 8); +} + +#[test] +#[should_panic] +fn ceil_char_boundary_above_len_panic() { + let _ = "x".ceil_char_boundary(2); +} diff --git a/library/core/src/num/mod.rs b/library/core/src/num/mod.rs index 721c030b410ae..864a253299f6e 100644 --- a/library/core/src/num/mod.rs +++ b/library/core/src/num/mod.rs @@ -809,6 +809,11 @@ impl u8 { pub fn escape_ascii(&self) -> ascii::EscapeDefault { ascii::escape_default(*self) } + + pub(crate) fn is_utf8_char_boundary(self) -> bool { + // This is bit magic equivalent to: b < 128 || b >= 192 + (self as i8) >= -0x40 + } } #[lang = "u16"] diff --git a/library/core/src/str/mod.rs b/library/core/src/str/mod.rs index fceea2366da54..09709dc3cf6df 100644 --- a/library/core/src/str/mod.rs +++ b/library/core/src/str/mod.rs @@ -76,15 +76,14 @@ use iter::MatchIndicesInternal; use iter::SplitInternal; use iter::{MatchesInternal, SplitNInternal}; -use validations::truncate_to_char_boundary; - #[inline(never)] #[cold] #[track_caller] fn slice_error_fail(s: &str, begin: usize, end: usize) -> ! { const MAX_DISPLAY_LENGTH: usize = 256; - let (truncated, s_trunc) = truncate_to_char_boundary(s, MAX_DISPLAY_LENGTH); - let ellipsis = if truncated { "[...]" } else { "" }; + let trunc_len = s.floor_char_boundary(MAX_DISPLAY_LENGTH); + let s_trunc = &s[..trunc_len]; + let ellipsis = if trunc_len < s.len() { "[...]" } else { "" }; // 1. out of bounds if begin > s.len() || end > s.len() { @@ -105,10 +104,7 @@ fn slice_error_fail(s: &str, begin: usize, end: usize) -> ! { // 3. character boundary let index = if !s.is_char_boundary(begin) { begin } else { end }; // find the character - let mut char_start = index; - while !s.is_char_boundary(char_start) { - char_start -= 1; - } + let char_start = s.floor_char_boundary(index); // `char_start` must be less than len and a char boundary let ch = s[char_start..].chars().next().unwrap(); let char_range = char_start..char_start + ch.len_utf8(); @@ -215,8 +211,80 @@ impl str { // code on higher opt-levels. See PR #84751 for more details. None => index == self.len(), - // This is bit magic equivalent to: b < 128 || b >= 192 - Some(&b) => (b as i8) >= -0x40, + Some(&b) => b.is_utf8_char_boundary(), + } + } + + /// Finds the closest `x` not exceeding `index` where `is_char_boundary(x)` is `true`. + /// + /// This method can help you truncate a string so that it's still valid UTF-8, but doesn't + /// exceed a given number of bytes. Note that this is done purely at the character level + /// and can still visually split graphemes, even though the underlying characters aren't + /// split. For example, the emoji 🧑‍🔬 (scientist) could be split so that the string only + /// includes 🧑 (person) instead. + /// + /// # Examples + /// + /// ``` + /// #![feature(round_char_boundary)] + /// let s = "❤️🧡💛💚💙💜"; + /// assert_eq!(s.len(), 26); + /// assert!(!s.is_char_boundary(13)); + /// + /// let closest = s.floor_char_boundary(13); + /// assert_eq!(closest, 10); + /// assert_eq!(&s[..closest], "❤️🧡"); + /// ``` + #[unstable(feature = "round_char_boundary", issue = "93743")] + #[inline] + pub fn floor_char_boundary(&self, index: usize) -> usize { + if index >= self.len() { + self.len() + } else { + let lower_bound = index.saturating_sub(3); + let new_index = self.as_bytes()[lower_bound..=index] + .iter() + .rposition(|b| b.is_utf8_char_boundary()); + + // SAFETY: we know that the character boundary will be within four bytes + unsafe { lower_bound + new_index.unwrap_unchecked() } + } + } + + /// Finds the closest `x` not below `index` where `is_char_boundary(x)` is `true`. + /// + /// This method is the natural complement to [`floor_char_boundary`]. See that method + /// for more details. + /// + /// [`floor_char_boundary`]: str::floor_char_boundary + /// + /// # Panics + /// + /// Panics if `index > self.len()`. + /// + /// # Examples + /// + /// ``` + /// #![feature(round_char_boundary)] + /// let s = "❤️🧡💛💚💙💜"; + /// assert_eq!(s.len(), 26); + /// assert!(!s.is_char_boundary(13)); + /// + /// let closest = s.ceil_char_boundary(13); + /// assert_eq!(closest, 14); + /// assert_eq!(&s[..closest], "❤️🧡💛"); + /// ``` + #[unstable(feature = "round_char_boundary", issue = "93743")] + #[inline] + pub fn ceil_char_boundary(&self, index: usize) -> usize { + if index > self.len() { + slice_error_fail(self, index, index) + } else { + let upper_bound = Ord::min(index + 4, self.len()); + self.as_bytes()[index..upper_bound] + .iter() + .position(|b| b.is_utf8_char_boundary()) + .map_or(upper_bound, |pos| pos + index) } } diff --git a/library/core/src/str/validations.rs b/library/core/src/str/validations.rs index b2ea86d699aa6..0d3dc856be577 100644 --- a/library/core/src/str/validations.rs +++ b/library/core/src/str/validations.rs @@ -273,16 +273,3 @@ pub const fn utf8_char_width(b: u8) -> usize { /// Mask of the value bits of a continuation byte. const CONT_MASK: u8 = 0b0011_1111; - -// truncate `&str` to length at most equal to `max` -// return `true` if it were truncated, and the new str. -pub(super) fn truncate_to_char_boundary(s: &str, mut max: usize) -> (bool, &str) { - if max >= s.len() { - (false, s) - } else { - while !s.is_char_boundary(max) { - max -= 1; - } - (true, &s[..max]) - } -} From c20e2a9c237cab7b481137f8f34a4b1822c43aae Mon Sep 17 00:00:00 2001 From: Jacob Hoffman-Andrews Date: Mon, 31 Jan 2022 05:53:43 -0800 Subject: [PATCH 15/15] Fix hover effects in sidebar The dark and ayu themes have a menu-like highlight on sidebar items. The light theme used to, but it was accidentally lost in the sidebar unification. The change brings back the hover effect in the light theme. It also makes the hover effect apply consistently to all links in the sidebar, including headings. It also simplifies the "In _path_" heading so it's one big link. The breadcrumbs are still readily available at the top of the page. --- src/librustdoc/html/static/css/rustdoc.css | 19 +++++++------------ src/librustdoc/html/static/css/themes/ayu.css | 16 ++++++---------- .../html/static/css/themes/dark.css | 9 +++------ .../html/static/css/themes/light.css | 7 ++----- src/test/rustdoc-gui/mobile.goml | 2 +- src/test/rustdoc-gui/sidebar-mobile.goml | 2 +- src/test/rustdoc-gui/sidebar.goml | 2 +- .../rustdoc-gui/type-declation-overflow.goml | 4 ++-- 8 files changed, 23 insertions(+), 38 deletions(-) diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index cf3c63d6a8fb6..6d94c70eadee6 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -463,9 +463,6 @@ nav.sub { .location a:first-of-type { font-weight: 500; } -.location a:hover { - text-decoration: underline; -} .block { padding: 0; @@ -476,10 +473,11 @@ nav.sub { list-style: none; } -.block a { +.block a, +h2.location a { display: block; - padding: 0.3em; - margin-left: -0.3em; + padding: 0.3rem; + margin-left: -0.3rem; text-overflow: ellipsis; overflow: hidden; @@ -494,8 +492,8 @@ nav.sub { font-weight: 500; padding: 0; margin: 0; - margin-top: 1rem; - margin-bottom: 1rem; + margin-top: 0.7rem; + margin-bottom: 0.7rem; } .sidebar h3 { @@ -1812,10 +1810,7 @@ details.rustdoc-toggle[open] > summary.hideme::after { .mobile-topbar .location { border: none; - margin: 0; - margin-left: auto; - padding: 0.3em; - padding-right: 0.6em; + margin: auto 0.5em auto auto; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; diff --git a/src/librustdoc/html/static/css/themes/ayu.css b/src/librustdoc/html/static/css/themes/ayu.css index 0aaf4f78c34ef..e402b3583f399 100644 --- a/src/librustdoc/html/static/css/themes/ayu.css +++ b/src/librustdoc/html/static/css/themes/ayu.css @@ -91,7 +91,8 @@ pre, .rustdoc.source .example-wrap { background-color: #5c6773; } -.sidebar .current { +.sidebar .current, +.sidebar a:hover { background-color: transparent; color: #ffb44c; } @@ -104,15 +105,6 @@ pre, .rustdoc.source .example-wrap { color: #ff7733; } -.sidebar-elems .location a { - color: #fff; -} - -.block a:hover { - background: transparent; - color: #ffb44c; -} - .line-numbers span { color: #5c6773; } .line-numbers .line-highlighted { color: #708090; @@ -220,6 +212,10 @@ pre.rust a, .in-band a { color: #c5c5c5; } +.sidebar h2 a, +.sidebar h3 a { + color: white; +} .search-results a { color: #0096cf; } diff --git a/src/librustdoc/html/static/css/themes/dark.css b/src/librustdoc/html/static/css/themes/dark.css index 4fad2359ff0eb..0a56055b8cbf6 100644 --- a/src/librustdoc/html/static/css/themes/dark.css +++ b/src/librustdoc/html/static/css/themes/dark.css @@ -61,18 +61,15 @@ pre, .rustdoc.source .example-wrap { background-color: rgba(32, 34, 37, .6); } -.sidebar .current { - background-color: #333; +.sidebar .current, +.sidebar a:hover { + background: #444; } .source .sidebar { background-color: #565656; } -.block a:hover { - background: #444; -} - .line-numbers span { color: #3B91E2; } .line-numbers .line-highlighted { background-color: #0a042f !important; diff --git a/src/librustdoc/html/static/css/themes/light.css b/src/librustdoc/html/static/css/themes/light.css index 16a777b7e672a..dc1715b2a78f3 100644 --- a/src/librustdoc/html/static/css/themes/light.css +++ b/src/librustdoc/html/static/css/themes/light.css @@ -63,7 +63,8 @@ pre, .rustdoc.source .example-wrap { background-color: rgba(36, 37, 39, 0.6); } -.sidebar .current { +.sidebar .current, +.sidebar a:hover { background-color: #fff; } @@ -71,10 +72,6 @@ pre, .rustdoc.source .example-wrap { background-color: #f1f1f1; } -.block a:hover { - background: #F5F5F5; -} - .line-numbers span { color: #c67e2d; } .line-numbers .line-highlighted { background-color: #FDFFD3 !important; diff --git a/src/test/rustdoc-gui/mobile.goml b/src/test/rustdoc-gui/mobile.goml index 7be46a613c4fb..2e44dd32d45b4 100644 --- a/src/test/rustdoc-gui/mobile.goml +++ b/src/test/rustdoc-gui/mobile.goml @@ -11,7 +11,7 @@ assert-css: (".main-heading", { "flex-direction": "column" }) -assert-property: (".mobile-topbar h2.location", {"offsetHeight": 45}) +assert-property: (".mobile-topbar h2.location", {"offsetHeight": 48}) // Note: We can't use assert-text here because the 'Since' is set by CSS and // is therefore not part of the DOM. diff --git a/src/test/rustdoc-gui/sidebar-mobile.goml b/src/test/rustdoc-gui/sidebar-mobile.goml index ef588a69f1d5f..9581aa74b0f64 100644 --- a/src/test/rustdoc-gui/sidebar-mobile.goml +++ b/src/test/rustdoc-gui/sidebar-mobile.goml @@ -39,4 +39,4 @@ assert-position: ("#method\.must_use", {"y": 45}) // Check that the bottom-most item on the sidebar menu can be scrolled fully into view. click: ".sidebar-menu-toggle" scroll-to: ".block.keyword li:nth-child(1)" -assert-position: (".block.keyword li:nth-child(1)", {"y": 542.234375}) +compare-elements-position-near: (".block.keyword li:nth-child(1)", ".mobile-topbar", {"y": 543}) diff --git a/src/test/rustdoc-gui/sidebar.goml b/src/test/rustdoc-gui/sidebar.goml index 9505e00512f4c..877cc61b66f24 100644 --- a/src/test/rustdoc-gui/sidebar.goml +++ b/src/test/rustdoc-gui/sidebar.goml @@ -77,7 +77,7 @@ assert-text: ("#functions + .item-table .item-left > a", "foo") // Links to trait implementations in the sidebar should not wrap even if they are long. goto: file://|DOC_PATH|/lib2/struct.HasALongTraitWithParams.html -assert-property: (".sidebar-links a", {"offsetHeight": 29}) +assert-property: (".sidebar-links a", {"offsetHeight": 30}) // Test that clicking on of the "In " headings in the sidebar links to the // appropriate anchor in index.html. diff --git a/src/test/rustdoc-gui/type-declation-overflow.goml b/src/test/rustdoc-gui/type-declation-overflow.goml index 99aa38e87e925..d4142511e4373 100644 --- a/src/test/rustdoc-gui/type-declation-overflow.goml +++ b/src/test/rustdoc-gui/type-declation-overflow.goml @@ -32,6 +32,6 @@ assert-property: (".item-decl pre", {"scrollWidth": "950"}) size: (600, 600) goto: file://|DOC_PATH|/lib2/too_long/struct.SuperIncrediblyLongLongLongLongLongLongLongGigaGigaGigaMegaLongLongLongStructName.html // It shouldn't have an overflow in the topbar either. -assert-property: (".mobile-topbar .location", {"scrollWidth": "986"}) -assert-property: (".mobile-topbar .location", {"clientWidth": "504"}) +assert-property: (".mobile-topbar .location", {"scrollWidth": "493"}) +assert-property: (".mobile-topbar .location", {"clientWidth": "493"}) assert-css: (".mobile-topbar .location", {"overflow-x": "hidden"})