diff --git a/src/librustdoc/html/escape.rs b/src/librustdoc/html/escape.rs
index ac9e2f42cc6d0..f94e76b1f021a 100644
--- a/src/librustdoc/html/escape.rs
+++ b/src/librustdoc/html/escape.rs
@@ -79,9 +79,12 @@ impl fmt::Display for EscapeBodyTextWithWbr<'_> {
} else if (s.contains(':') && !next_is_colon())
|| (s.contains('_') && !next_is_underscore())
{
- EscapeBodyText(&text[last..i + 1]).fmt(fmt)?;
+ // `i + s.len()`, not `i + 1`: a cluster containing `_` or `:` can still be
+ // several bytes long, because UAX#29 GB9b joins a `Prepend` character to the
+ // one that follows it.
+ EscapeBodyText(&text[last..i + s.len()]).fmt(fmt)?;
fmt.write_str("")?;
- last = i + 1;
+ last = i + s.len();
}
}
if last < text.len() {
diff --git a/src/librustdoc/html/escape/tests.rs b/src/librustdoc/html/escape/tests.rs
index de702e1606353..247a1e42d18eb 100644
--- a/src/librustdoc/html/escape/tests.rs
+++ b/src/librustdoc/html/escape/tests.rs
@@ -40,6 +40,10 @@ fn escape_body_text_with_wbr() {
assert_eq!(&E("ṼẽçÑñéå").to_string(), "ṼẽçÑñéå");
assert_eq!(&E("V\u{0300}e\u{0300}c\u{0300}D\u{0300}e\u{0300}q\u{0300}u\u{0300}e\u{0300}u\u{0300}e\u{0300}").to_string(), "V\u{0300}e\u{0300}c\u{0300}D\u{0300}e\u{0300}q\u{0300}u\u{0300}e\u{0300}u\u{0300}e\u{0300}");
assert_eq!(&E("LPFNACCESSIBLEOBJECTFROMWINDOW").to_string(), "LPFNACCESSIBLEOBJECTFROMWINDOW");
+ // clusters where the `_` or `:` is not the first byte (UAX#29 GB9b `Prepend`)
+ assert_eq!(&E("abc\u{0D4E}_defgh").to_string(), "abc\u{0D4E}_defgh");
+ assert_eq!(&E("abc\u{0605}:defgh").to_string(), "abc\u{0605}:defgh");
+ assert_eq!(&E("first_\u{0300}second").to_string(), "first_\u{0300}second");
}
// property test
#[test]
diff --git a/tests/rustdoc-html/item-name-grapheme-cluster-wbr.rs b/tests/rustdoc-html/item-name-grapheme-cluster-wbr.rs
new file mode 100644
index 0000000000000..5244f970389ca
--- /dev/null
+++ b/tests/rustdoc-html/item-name-grapheme-cluster-wbr.rs
@@ -0,0 +1,8 @@
+// Regression test for #160231: `` insertion sliced mid-character on an item name whose
+// grapheme cluster starts with a `Prepend` character and contains `_`.
+
+#![crate_name = "foo"]
+#![allow(mixed_script_confusables, non_camel_case_types)]
+
+//@ hasraw foo/index.html 'abcൎ_defgh'
+pub struct abcൎ_defgh;