Rollup merge of #154491 - Jules-Bertholet:case-docs, r=Mark-Simulacrum,GuillaumeGomez

Extend `core::char`'s documentation of casing issues (and fix a rustdoc bug)

@rustbot label A-unicode A-docs
This commit is contained in:
Stuart Cook
2026-04-17 16:17:50 +10:00
committed by GitHub
4 changed files with 175 additions and 21 deletions
+15 -4
View File
@@ -335,13 +335,19 @@ pub fn replacen<P: Pattern>(&self, pat: P, to: &str, count: usize) -> String {
/// Returns the lowercase equivalent of this string slice, as a new [`String`].
///
/// 'Lowercase' is defined according to the terms of the Unicode Derived Core Property
/// `Lowercase`.
/// 'Lowercase' is defined according to the terms of
/// [Chapter 3 (Conformance)](https://www.unicode.org/versions/latest/core-spec/chapter-3/#G34432)
/// of the Unicode standard.
///
/// Since some characters can expand into multiple characters when changing
/// the case, this function returns a [`String`] instead of modifying the
/// parameter in-place.
///
/// Unlike [`char::to_lowercase()`], this method fully handles the context-dependent
/// casing of Greek sigma. However, like that method, it does not handle locale-specific
/// casing, like Turkish and Azeri I/ı/İ/i. See that method's documentation
/// for more information.
///
/// # Examples
///
/// Basic usage:
@@ -426,13 +432,18 @@ fn case_ignorable_then_cased<I: Iterator<Item = char>>(iter: I) -> bool {
/// Returns the uppercase equivalent of this string slice, as a new [`String`].
///
/// 'Uppercase' is defined according to the terms of the Unicode Derived Core Property
/// `Uppercase`.
/// 'Uppercase' is defined according to the terms of
/// [Chapter 3 (Conformance)](https://www.unicode.org/versions/latest/core-spec/chapter-3/#G34431)
/// of the Unicode standard.
///
/// Since some characters can expand into multiple characters when changing
/// the case, this function returns a [`String`] instead of modifying the
/// parameter in-place.
///
/// Like [`char::to_uppercase()`] this method does not handle language-specific
/// casing, like Turkish and Azeri I/ı/İ/i. See that method's documentation
/// for more information.
///
/// # Examples
///
/// Basic usage:
+146 -15
View File
@@ -1151,13 +1151,14 @@ pub fn is_numeric(self) -> bool {
/// [ucd]: https://www.unicode.org/reports/tr44/
/// [`UnicodeData.txt`]: https://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt
///
/// If this `char` requires special considerations (e.g. multiple `char`s) the iterator yields
/// the `char`(s) given by [`SpecialCasing.txt`].
/// If this `char` expands to multiple `char`s, the iterator yields the `char`s given by
/// [`SpecialCasing.txt`]. The maximum number of `char`s in a case mapping is 3.
///
/// [`SpecialCasing.txt`]: https://www.unicode.org/Public/UCD/latest/ucd/SpecialCasing.txt
///
/// This operation performs an unconditional mapping without tailoring. That is, the conversion
/// is independent of context and language.
/// is independent of context and language. See [below](#notes-on-context-and-locale)
/// for more information.
///
/// In the [Unicode Standard], Chapter 4 (Character Properties) discusses case mapping in
/// general and Chapter 3 (Conformance) discusses the default algorithm for case conversion.
@@ -1199,6 +1200,48 @@ pub fn is_numeric(self) -> bool {
/// // convert into themselves.
/// assert_eq!('山'.to_lowercase().to_string(), "山");
/// ```
/// # Notes on context and locale
///
/// As stated earlier, this method does not take into account language or context.
/// Below is a non-exhaustive list of situations where this can be relevant.
/// If you need to handle locale-depedendent casing in your code, consider using
/// an external crate, like [`icu_casemap`](https://crates.io/crates/icu_casemap)
/// which is developed by Unicode.
///
/// ## Greek sigma
///
/// In Greek, the letter simga (uppercase Σ) has two lowercase forms:
/// ς which is used only at the end of a word, and σ which is used everywhere else.
/// `to_lowercase()` always uses the second form:
///
/// ```
/// assert_eq!('Σ'.to_lowercase().to_string(), "σ");
/// ```
///
/// ## Turkish and Azeri I/ı/İ/i
///
/// In Turkish and Azeri, the equivalent of 'i' in Latin has five forms instead of two:
///
/// * 'Dotless': I / ı, sometimes written ï
/// * 'Dotted': İ / i
///
/// Note that the uppercase undotted 'I' is the same as the Latin. Therefore:
///
/// ```
/// let lower_i = 'I'.to_lowercase().to_string();
/// ```
///
/// The value of `lower_i` here relies on the language of the text: if we're
/// in `en-US`, it should be `"i"`, but if we're in `tr-TR` or `az-AZ`, it should
/// be `"ı"`. `to_lowercase()` does not take this into account, and so:
///
/// ```
/// let lower_i = 'I'.to_lowercase().to_string();
///
/// assert_eq!(lower_i, "i");
/// ```
///
/// holds across languages.
#[must_use = "this returns the lowercased character as a new iterator, \
without modifying the original"]
#[stable(feature = "rust1", since = "1.0.0")]
@@ -1211,8 +1254,10 @@ pub fn to_lowercase(self) -> ToLowercase {
/// `char`s.
///
/// This is usually, but not always, equivalent to the uppercase mapping
/// returned by [`Self::to_uppercase`]. Prefer this method when seeking to capitalize
/// Only The First Letter of a word, but use [`Self::to_uppercase`] for ALL CAPS.
/// returned by [`to_uppercase()`]. Prefer this method when seeking to capitalize
/// Only The First Letter of a word, but use [`to_uppercase()`] for ALL CAPS.
/// See [below](#difference-from-uppercase) for a thorough explanation
/// of the difference between the two methods.
///
/// If this `char` does not have a titlecase mapping, the iterator yields the same `char`.
///
@@ -1222,13 +1267,14 @@ pub fn to_lowercase(self) -> ToLowercase {
/// [ucd]: https://www.unicode.org/reports/tr44/
/// [`UnicodeData.txt`]: https://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt
///
/// If this `char` requires special considerations (e.g. multiple `char`s) the iterator yields
/// the `char`(s) given by [`SpecialCasing.txt`].
/// If this `char` expands to multiple `char`s, the iterator yields the `char`s given by
/// [`SpecialCasing.txt`]. The maximum number of `char`s in a case mapping is 3.
///
/// [`SpecialCasing.txt`]: https://www.unicode.org/Public/UCD/latest/ucd/SpecialCasing.txt
///
/// This operation performs an unconditional mapping without tailoring. That is, the conversion
/// is independent of context and language.
/// is independent of context and language. See [below](#note-on-locale)
/// for more information.
///
/// In the [Unicode Standard], Chapter 4 (Character Properties) discusses case mapping in
/// general and Chapter 3 (Conformance) discusses the default algorithm for case conversion.
@@ -1265,8 +1311,9 @@ pub fn to_lowercase(self) -> ToLowercase {
/// ```
/// #![feature(titlecase)]
/// assert_eq!('c'.to_titlecase().to_string(), "C");
/// assert_eq!('ა'.to_titlecase().to_string(), "ა");
/// assert_eq!('dž'.to_titlecase().to_string(), "Dž");
/// assert_eq!(''.to_titlecase().to_string(), "");
/// assert_eq!(''.to_titlecase().to_string(), "");
///
/// // Sometimes the result is more than one character:
/// assert_eq!('ß'.to_titlecase().to_string(), "Ss");
@@ -1276,8 +1323,78 @@ pub fn to_lowercase(self) -> ToLowercase {
/// assert_eq!('山'.to_titlecase().to_string(), "山");
/// ```
///
/// # Difference from uppercase
///
/// Currently, there are three classes of characters where [`to_uppercase()`]
/// and `to_titlecase()` give different results:
///
/// ## Georgian script
///
/// Each letter in the modern Georgian alphabet can be written in one of two forms:
/// the typical lowercase-like "mkhedruli" form, and a variant uppercase-like "mtavruli"
/// form. However, unlike uppercase in most cased scripts, mtavruli is not typically used
/// to start sentences, denote proper nouns, or for any other purpose
/// in running text. It is instead confined to titles and headings, which are written entirely
/// in mtavruli. For this reason, [`to_uppercase()`] applied to a Georgian letter
/// will return the mtavruli form, but `to_titlecase()` will return the mkhedruli form.
///
/// ```
/// #![feature(titlecase)]
/// let ani = 'ა'; // First letter of the Georgian alphabet, in mkhedruli form
///
/// // Titlecasing mkhedruli maps it to itself...
/// assert_eq!(ani.to_titlecase().to_string(), ani.to_string());
///
/// // but uppercasing it maps it to mtavruli
/// assert_eq!(ani.to_uppercase().to_string(), "Ა");
/// ```
///
/// ## Compatibility digraphs for Latin-alphabet Serbo-Croatian
///
/// The standard Latin alphabet for the Serbo-Croatian language
/// (Bosnian, Croatian, Montenegrin, and Serbian) contains
/// three digraphs: Dž, Lj, and Nj. These are usually represented as
/// two characters. However, for compatibility with older character sets,
/// Unicode includes single-character versions of these digraphs.
/// Each has a uppercase, titlecase, and lowercase version:
///
/// - `'DŽ'`, `'Dž'`, `'dž'`
/// - `'LJ'`, `'Lj'`, `'lj'`
/// - `'NJ'`, `'Nj'`, `'nj'`
///
/// Unicode additionally encodes a casing triad for the Dz digraph
/// without the caron: `'DZ'`, `'Dz'`, `'dz'`.
///
/// ## Iota-subscritped Greek vowels
///
/// In ancient Greek, the long vowels alpha (α), eta (η), and omega (ω)
/// were sometimes followed by an iota (ι), forming a diphthong. Over time,
/// the diphthong pronunciation was slowly lost, with the iota becoming mute.
/// Eventually, the ι disappeared from the spelling as well.
/// However, there remains a need to represent ancient texts faithfully.
///
/// Modern editions of ancient Greek texts commonly use a reduced-sized
/// ι symbol to denote mute iotas, while distinguishing them from ιs
/// which continued to affect pronunciation. The exact standard differs
/// between different publications. Some render the mute ι below its associated
/// vowel (subscript), while others place it to the right of said vowel (adscript).
/// The interaction of mute ι symbols with casing also varies.
///
/// The Unicode Standard, for its default casing rules, chose to make lowercase
/// Greek vowels with iota subscipt (e.g. `'ᾠ'`) titlecase to the uppercase vowel
/// with iota subscript (`'ᾨ'`) but uppercase to the uppercase vowel followed by
/// full-size uppercase iota (`"ὨΙ"`). This is just one convention among many
/// in common use, but it is the one Unicode settled on,
/// so it is what this method does also.
///
/// # Note on locale
///
/// As stated above, this method is locale-insensitive.
/// If you need locale support, consider using an external crate,
/// like [`icu_casemap`](https://crates.io/crates/icu_casemap)
/// which is developed by Unicode. A description of a common
/// locale-dependent casing issue follows:
///
/// In Turkish and Azeri, the equivalent of 'i' in Latin has five forms instead of two:
///
/// * 'Dotless': I / ı, sometimes written ï
@@ -1302,6 +1419,8 @@ pub fn to_lowercase(self) -> ToLowercase {
/// ```
///
/// holds across languages.
///
/// [`to_uppercase()`]: Self::to_uppercase()
#[must_use = "this returns the titlecased character as a new iterator, \
without modifying the original"]
#[unstable(feature = "titlecase", issue = "153892")]
@@ -1313,8 +1432,9 @@ pub fn to_titlecase(self) -> ToTitlecase {
/// Returns an iterator that yields the uppercase mapping of this `char` as one or more
/// `char`s.
///
/// Prefer this method when converting a word into ALL CAPS, but consider [`Self::to_titlecase`]
/// instead if you seek to capitalize Only The First Letter.
/// Prefer this method when converting a word into ALL CAPS, but consider [`to_titlecase()`]
/// instead if you seek to capitalize Only The First Letter. See that method's documentation
/// for more information on the difference between the two.
///
/// If this `char` does not have an uppercase mapping, the iterator yields the same `char`.
///
@@ -1324,13 +1444,14 @@ pub fn to_titlecase(self) -> ToTitlecase {
/// [ucd]: https://www.unicode.org/reports/tr44/
/// [`UnicodeData.txt`]: https://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt
///
/// If this `char` requires special considerations (e.g. multiple `char`s) the iterator yields
/// the `char`(s) given by [`SpecialCasing.txt`].
/// If this `char` expands to multiple `char`s, the iterator yields the `char`s given by
/// [`SpecialCasing.txt`]. The maximum number of `char`s in a case mapping is 3.
///
/// [`SpecialCasing.txt`]: https://www.unicode.org/Public/UCD/latest/ucd/SpecialCasing.txt
///
/// This operation performs an unconditional mapping without tailoring. That is, the conversion
/// is independent of context and language.
/// is independent of context and language. See [below](#note-on-locale)
/// for more information.
///
/// In the [Unicode Standard], Chapter 4 (Character Properties) discusses case mapping in
/// general and Chapter 3 (Conformance) discusses the default algorithm for case conversion.
@@ -1338,6 +1459,7 @@ pub fn to_titlecase(self) -> ToTitlecase {
/// [Unicode Standard]: https://www.unicode.org/versions/latest/
///
/// # Examples
///
/// `'ſt'` (U+FB05) is a single Unicode code point (a ligature) that maps to "ST" in uppercase.
///
/// As an iterator:
@@ -1365,11 +1487,12 @@ pub fn to_titlecase(self) -> ToTitlecase {
///
/// ```
/// assert_eq!('c'.to_uppercase().to_string(), "C");
/// assert_eq!('ა'.to_uppercase().to_string(), "Ა");
/// assert_eq!('dž'.to_uppercase().to_string(), "DŽ");
///
/// // Sometimes the result is more than one character:
/// assert_eq!('ſt'.to_uppercase().to_string(), "ST");
/// assert_eq!(''.to_uppercase().to_string(), "ΩΙ");
/// assert_eq!(''.to_uppercase().to_string(), "Ι");
///
/// // Characters that do not have both uppercase and lowercase
/// // convert into themselves.
@@ -1378,6 +1501,12 @@ pub fn to_titlecase(self) -> ToTitlecase {
///
/// # Note on locale
///
/// As stated above, this method is locale-insensitive.
/// If you need locale support, consider using an external crate,
/// like [`icu_casemap`](https://crates.io/crates/icu_casemap)
/// which is developed by Unicode. A description of a common
/// locale-dependent casing issue follows:
///
/// In Turkish and Azeri, the equivalent of 'i' in Latin has five forms instead of two:
///
/// * 'Dotless': I / ı, sometimes written ï
@@ -1400,6 +1529,8 @@ pub fn to_titlecase(self) -> ToTitlecase {
/// ```
///
/// holds across languages.
///
/// [`to_titlecase()`]: Self::to_titlecase()
#[must_use = "this returns the uppercased character as a new iterator, \
without modifying the original"]
#[stable(feature = "rust1", since = "1.0.0")]
+4 -2
View File
@@ -582,6 +582,7 @@ fn next(&mut self) -> Option<Self::Item> {
}
}
let id = self.id_map.derive(id);
let percent_encoded_id = small_url_encode(id.clone());
if let Some(ref mut builder) = self.toc {
let mut text_header = String::new();
@@ -596,8 +597,9 @@ fn next(&mut self) -> Option<Self::Item> {
std::cmp::min(level as u32 + (self.heading_offset as u32), MAX_HEADER_LEVEL);
self.buf.push_back((Event::Html(format!("</h{level}>").into()), 0..0));
let start_tags =
format!("<h{level} id=\"{id}\"><a class=\"doc-anchor\" href=\"#{id}\">§</a>");
let start_tags = format!(
"<h{level} id=\"{id}\"><a class=\"doc-anchor\" href=\"#{percent_encoded_id}\">§</a>"
);
return Some((Event::Html(start_tags.into()), 0..0));
}
event
+10
View File
@@ -0,0 +1,10 @@
#![crate_name = "unicode"]
pub struct Foo;
impl Foo {
//@ has unicode/struct.Foo.html //a/@href "#%C3%BA"
//@ !has unicode/struct.Foo.html //a/@href "#ú"
/// # ú
pub fn foo() {}
}