Skip to content

Commit 8c705f8

Browse files
committed
Rustdoc: Fix natural ordering to look at all numbers.
The old implementation only looks at numbers at the end, but not in other places in a name: "u8" and "u16" got sorted properly, but "u8_bla" and "u16_bla" did not.
1 parent 543f03d commit 8c705f8

File tree

2 files changed

+63
-29
lines changed

2 files changed

+63
-29
lines changed

src/librustdoc/html/render/mod.rs

+36-18
Original file line numberDiff line numberDiff line change
@@ -1903,23 +1903,41 @@ fn document_non_exhaustive(w: &mut Buffer, item: &clean::Item) {
19031903
}
19041904
}
19051905

1906-
fn name_key(name: &str) -> (&str, u64, usize) {
1907-
let end = name.bytes().rposition(|b| b.is_ascii_digit()).map_or(name.len(), |i| i + 1);
1908-
1909-
// find number at end
1910-
let split = name[0..end].bytes().rposition(|b| !b.is_ascii_digit()).map_or(0, |i| i + 1);
1911-
1912-
// count leading zeroes
1913-
let after_zeroes =
1914-
name[split..end].bytes().position(|b| b != b'0').map_or(name.len(), |extra| split + extra);
1915-
1916-
// sort leading zeroes last
1917-
let num_zeroes = after_zeroes - split;
1918-
1919-
match name[split..end].parse() {
1920-
Ok(n) => (&name[..split], n, num_zeroes),
1921-
Err(_) => (name, 0, num_zeroes),
1906+
/// Compare two strings treating multi-digit numbers as single units (i.e. natural sort order).
1907+
pub fn compare_names(mut lhs: &str, mut rhs: &str) -> Ordering {
1908+
/// Takes a non-numeric and a numeric part from the given &str.
1909+
fn take_parts<'a>(s: &mut &'a str) -> (&'a str, &'a str) {
1910+
let i = s.find(|c: char| c.is_ascii_digit());
1911+
let (a, b) = s.split_at(i.unwrap_or(s.len()));
1912+
let i = b.find(|c: char| !c.is_ascii_digit());
1913+
let (b, c) = b.split_at(i.unwrap_or(b.len()));
1914+
*s = c;
1915+
(a, b)
1916+
}
1917+
1918+
while !lhs.is_empty() || !rhs.is_empty() {
1919+
let (la, lb) = take_parts(&mut lhs);
1920+
let (ra, rb) = take_parts(&mut rhs);
1921+
// First process the non-numeric part.
1922+
match la.cmp(ra) {
1923+
Ordering::Equal => (),
1924+
x => return x,
1925+
}
1926+
// Then process the numeric part, if both sides have one (and they fit in a u64).
1927+
if let (Ok(ln), Ok(rn)) = (lb.parse::<u64>(), rb.parse::<u64>()) {
1928+
match ln.cmp(&rn) {
1929+
Ordering::Equal => (),
1930+
x => return x,
1931+
}
1932+
}
1933+
// Then process the numeric part again, but this time as strings.
1934+
match lb.cmp(rb) {
1935+
Ordering::Equal => (),
1936+
x => return x,
1937+
}
19221938
}
1939+
1940+
Ordering::Equal
19231941
}
19241942

19251943
fn item_module(w: &mut Buffer, cx: &Context, item: &clean::Item, items: &[clean::Item]) {
@@ -1962,7 +1980,7 @@ fn item_module(w: &mut Buffer, cx: &Context, item: &clean::Item, items: &[clean:
19621980
}
19631981
let lhs = i1.name.as_ref().map_or("", |s| &**s);
19641982
let rhs = i2.name.as_ref().map_or("", |s| &**s);
1965-
name_key(lhs).cmp(&name_key(rhs))
1983+
compare_names(lhs, rhs)
19661984
}
19671985

19681986
if cx.shared.sort_modules_alphabetically {
@@ -2395,7 +2413,7 @@ fn compare_impl<'a, 'b>(lhs: &'a &&Impl, rhs: &'b &&Impl) -> Ordering {
23952413
let rhs = format!("{}", rhs.inner_impl().print());
23962414

23972415
// lhs and rhs are formatted as HTML, which may be unnecessary
2398-
name_key(&lhs).cmp(&name_key(&rhs))
2416+
compare_names(&lhs, &rhs)
23992417
}
24002418

24012419
fn item_trait(w: &mut Buffer, cx: &Context, it: &clean::Item, t: &clean::Trait, cache: &Cache) {

src/librustdoc/html/render/tests.rs

+27-11
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,40 @@
11
use super::*;
22

33
#[test]
4-
fn test_name_key() {
5-
assert_eq!(name_key("0"), ("", 0, 1));
6-
assert_eq!(name_key("123"), ("", 123, 0));
7-
assert_eq!(name_key("Fruit"), ("Fruit", 0, 0));
8-
assert_eq!(name_key("Fruit0"), ("Fruit", 0, 1));
9-
assert_eq!(name_key("Fruit0000"), ("Fruit", 0, 4));
10-
assert_eq!(name_key("Fruit01"), ("Fruit", 1, 1));
11-
assert_eq!(name_key("Fruit10"), ("Fruit", 10, 0));
12-
assert_eq!(name_key("Fruit123"), ("Fruit", 123, 0));
4+
fn test_compare_names() {
5+
for &(a, b) in &[
6+
("hello", "world"),
7+
("", "world"),
8+
("123", "hello"),
9+
("123", ""),
10+
("123test", "123"),
11+
("hello", ""),
12+
("hello", "hello"),
13+
("hello123", "hello123"),
14+
("hello123", "hello12"),
15+
("hello12", "hello123"),
16+
("hello01abc", "hello01xyz"),
17+
("hello0abc", "hello0"),
18+
("hello0", "hello0abc"),
19+
("01", "1"),
20+
] {
21+
assert_eq!(compare_names(a, b), a.cmp(b), "{:?} - {:?}", a, b);
22+
}
23+
assert_eq!(compare_names("u8", "u16"), Ordering::Less);
24+
assert_eq!(compare_names("u32", "u16"), Ordering::Greater);
25+
assert_eq!(compare_names("u8_to_f64", "u16_to_f64"), Ordering::Less);
26+
assert_eq!(compare_names("u32_to_f64", "u16_to_f64"), Ordering::Greater);
27+
assert_eq!(compare_names("u16_to_f64", "u16_to_f64"), Ordering::Equal);
28+
assert_eq!(compare_names("u16_to_f32", "u16_to_f64"), Ordering::Less);
1329
}
1430

1531
#[test]
1632
fn test_name_sorting() {
1733
let names = [
18-
"Apple", "Banana", "Fruit", "Fruit0", "Fruit00", "Fruit1", "Fruit01", "Fruit2", "Fruit02",
34+
"Apple", "Banana", "Fruit", "Fruit0", "Fruit00", "Fruit01", "Fruit1", "Fruit02", "Fruit2",
1935
"Fruit20", "Fruit30x", "Fruit100", "Pear",
2036
];
2137
let mut sorted = names.to_owned();
22-
sorted.sort_by_key(|&s| name_key(s));
38+
sorted.sort_by(|&l, r| compare_names(l, r));
2339
assert_eq!(names, sorted);
2440
}

0 commit comments

Comments
 (0)