Code
The identifier is abc, U+0D4E MALAYALAM LETTER DOT REPH, _defgh. U+0D4E is XID_Continue, so rustc accepts this item and emits only the mixed_script_confusables and non_camel_case_types warnings. rustdoc then aborts, so the crate cannot be documented at all — cargo doc fails on it.
rustdoc --edition 2021 --crate-type lib ident.rs -o doc
Meta
rustdoc --version --verbose:
rustdoc 1.97.0 (2d8144b78 2026-07-07)
binary: rustdoc
commit-hash: 2d8144b7880597b6e6d3dfd63a9a9efae3f533d3
commit-date: 2026-07-07
host: x86_64-unknown-linux-gnu
release: 1.97.0
LLVM version: 22.1.6
Also reproduces on current master (be3d26db984c6f96335faca1f254dc04873cb1c1), verified against a local build.
Error output
warning: the usage of Script Group `Malayalam` in this crate consists solely of mixed script confusables
--> ident.rs:1:12
|
1 | pub struct abcൎ_defgh;
| ^^^^^^^^^
|
= note: the usage includes 'ൎ' (U+0D4E)
warning: type `abcൎ_defgh` should have an upper camel case name
--> ident.rs:1:12
thread 'rustc' panicked at src/librustdoc/html/escape.rs:82:37:
end byte index 4 is not a char boundary; it is inside 'ൎ' (bytes 3..6 of string)
error: the compiler unexpectedly panicked. This is a bug
note: rustc 1.97.0 (2d8144b78 2026-07-07) running on x86_64-unknown-linux-gnu
note: compiler flags: --crate-type lib
query stack during panic:
end of query stack
Cause
EscapeBodyTextWithWbr's Display impl (src/librustdoc/html/escape.rs:44-91) iterates text.grapheme_indices(true), so i is the start offset of a grapheme cluster, but the _/: word-break arm slices at i + 1:
} else if (s.contains(':') && !next_is_colon())
|| (s.contains('_') && !next_is_underscore())
{
EscapeBodyText(&text[last..i + 1]).fmt(fmt)?;
fmt.write_str("<wbr>")?;
last = i + 1;
}
That assumes the cluster is exactly one byte whenever it contains _ or :. UAX#29 GB9b makes a Prepend-class character (U+0600–U+0605, U+06DD, U+070F, U+0D4E, U+111C2, …) combine with the character that follows it into a single cluster, so a cluster can begin with a 2–4 byte character and still contain _. Here the cluster is ൎ_ starting at byte 3, and i + 1 == 4 falls inside the 3-byte U+0D4E.
The sibling CamelCase arm above it slices at i, which is always a cluster boundary, so only this arm is affected. The text.len() < 8 early return is what keeps shorter names from tripping it.
The i + 1 came in with 3bf8bcfbe0e ("rustdoc: properly handle path wrapping") for the : case, and ac303df4e21 ("rustdoc: move the wbr after the underscore, instead of before") later folded the _ case into the same arm — both in #126247.
Existing tests don't catch it because they only cover Extend-class clusters, which join backwards onto an ASCII base character and therefore keep i + 1 on a boundary: E("ṼẽçÑñéå") and E("V\u{0300}e\u{0300}…") both start their clusters on a 1-byte char. Prepend is the one class that joins forwards. The property test escape_body_text_with_wbr_makes_sense can't reach it either — its alphabet is const C: [u8; 3] = [b'a', b'A', b'_'], pure ASCII.
I have a fix (i + s.len() in both places) plus regression tests and will open a PR.
@rustbot label +I-ICE +T-rustdoc +A-Unicode
Code
The identifier is
abc,U+0D4E MALAYALAM LETTER DOT REPH,_defgh.U+0D4EisXID_Continue, so rustc accepts this item and emits only themixed_script_confusablesandnon_camel_case_typeswarnings.rustdocthen aborts, so the crate cannot be documented at all —cargo docfails on it.Meta
rustdoc --version --verbose:Also reproduces on current master (
be3d26db984c6f96335faca1f254dc04873cb1c1), verified against a local build.Error output
Cause
EscapeBodyTextWithWbr'sDisplayimpl (src/librustdoc/html/escape.rs:44-91) iteratestext.grapheme_indices(true), soiis the start offset of a grapheme cluster, but the_/:word-break arm slices ati + 1:That assumes the cluster is exactly one byte whenever it contains
_or:. UAX#29 GB9b makes aPrepend-class character (U+0600–U+0605,U+06DD,U+070F,U+0D4E,U+111C2, …) combine with the character that follows it into a single cluster, so a cluster can begin with a 2–4 byte character and still contain_. Here the cluster isൎ_starting at byte 3, andi + 1 == 4falls inside the 3-byteU+0D4E.The sibling CamelCase arm above it slices at
i, which is always a cluster boundary, so only this arm is affected. Thetext.len() < 8early return is what keeps shorter names from tripping it.The
i + 1came in with3bf8bcfbe0e("rustdoc: properly handle path wrapping") for the:case, andac303df4e21("rustdoc: move the wbr after the underscore, instead of before") later folded the_case into the same arm — both in #126247.Existing tests don't catch it because they only cover
Extend-class clusters, which join backwards onto an ASCII base character and therefore keepi + 1on a boundary:E("ṼẽçÑñéå")andE("V\u{0300}e\u{0300}…")both start their clusters on a 1-byte char.Prependis the one class that joins forwards. The property testescape_body_text_with_wbr_makes_sensecan't reach it either — its alphabet isconst C: [u8; 3] = [b'a', b'A', b'_'], pure ASCII.I have a fix (
i + s.len()in both places) plus regression tests and will open a PR.@rustbot label +I-ICE +T-rustdoc +A-Unicode