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..11d50603a1011 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,21 @@ 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\n *\n "), CommentKind::Block); + assert_eq!(stripped.as_str(), " # Returns\n\n"); + + let stripped = beautify_doc_string( + 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/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() diff --git a/compiler/rustc_infer/src/infer/at.rs b/compiler/rustc_infer/src/infer/at.rs index 7a82b169c5720..6515f948dd3bc 100644 --- a/compiler/rustc_infer/src/infer/at.rs +++ b/compiler/rustc_infer/src/infer/at.rs @@ -268,7 +268,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())), + } } } @@ -292,27 +295,22 @@ 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())), + } } } 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)) } } } @@ -358,7 +356,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 c5da9977db782..6fdfc8e39f10d 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 @@ -2148,9 +2146,8 @@ 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 { expected: exp_found.expected.print_only_trait_path(), @@ -2178,16 +2175,22 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { } } - fn expected_found_str_ty( + fn expected_found_str_term( &self, - exp_found: ty::error::ExpectedFound>, + 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)) + 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 `{}`". 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 fa17a2d66f117..08320a0ff1d42 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -366,13 +366,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. /// @@ -1817,7 +1830,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( @@ -1826,7 +1842,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 8624137d77687..2cb2ac8666120 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs @@ -1382,26 +1382,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/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]) - } -} 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/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/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/librustdoc/passes/html_tags.rs b/src/librustdoc/passes/html_tags.rs index f7a9a0899e390..9caadef3dec7c 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].ends_with("::") { + 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,42 @@ 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'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)) + { + 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), + }; + // 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() }); }; @@ -210,11 +266,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/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs index 93292efdcb629..16882cf83d09c 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-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); } 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() {} 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"}) 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 + 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() {} 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..c0a0de24c5263 --- /dev/null +++ b/src/test/rustdoc-ui/suggestions/html-as-generics.fixed @@ -0,0 +1,32 @@ +// run-rustfix +#![deny(rustdoc::invalid_html_tags)] + +/// This `Vec` thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +pub struct Generic; + +/// This `vec::Vec` thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +pub struct GenericPath; + +/// This `i32` thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +pub struct PathsCanContainTrailingNumbers; + +/// This `Vec::` thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +pub struct Turbofish; + +/// This [link](https://rust-lang.org)`::` thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +pub struct BareTurbofish; + +/// This `Vec::` thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +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..0b6009b0e59c3 --- /dev/null +++ b/src/test/rustdoc-ui/suggestions/html-as-generics.rs @@ -0,0 +1,32 @@ +// run-rustfix +#![deny(rustdoc::invalid_html_tags)] + +/// This Vec thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +pub struct Generic; + +/// This vec::Vec thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +pub struct GenericPath; + +/// This i32 thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +pub struct PathsCanContainTrailingNumbers; + +/// This Vec:: thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +pub struct Turbofish; + +/// This [link](https://rust-lang.org):: thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +pub struct BareTurbofish; + +/// This Vec:: thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +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..df54b71264ebc --- /dev/null +++ b/src/test/rustdoc-ui/suggestions/html-as-generics.stderr @@ -0,0 +1,73 @@ +error: unclosed HTML tag `i32` + --> $DIR/html-as-generics.rs:4:13 + | +LL | /// This Vec thing! + | ^^^^^ + | +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:9:18 + | +LL | /// This vec::Vec thing! + | ^^^^^ + | +help: try marking as source code + | +LL | /// This `vec::Vec` thing! + | + + + +error: unclosed HTML tag `i32` + --> $DIR/html-as-generics.rs:14:13 + | +LL | /// This i32 thing! + | ^^^^^ + | +help: try marking as source code + | +LL | /// This `i32` thing! + | + + + +error: unclosed HTML tag `i32` + --> $DIR/html-as-generics.rs:19:15 + | +LL | /// This Vec:: thing! + | ^^^^^ + | +help: try marking as source code + | +LL | /// This `Vec::` thing! + | + + + +error: unclosed HTML tag `i32` + --> $DIR/html-as-generics.rs:24:41 + | +LL | /// This [link](https://rust-lang.org):: thing! + | ^^^^^ + | +help: try marking as source code + | +LL | /// This [link](https://rust-lang.org)`::` thing! + | + + + +error: unclosed HTML tag `i32` + --> $DIR/html-as-generics.rs:29:21 + | +LL | /// This Vec:: thing! + | ^^^^^ + | +help: try marking as source code + | +LL | /// This `Vec::` thing! + | + + + +error: aborting due to 6 previous errors + 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