Skip to content

Commit b984ac0

Browse files
authored
Rollup merge of rust-lang#93347 - WaffleLapkin:better_char_decode_utf16_size_hint, r=dtolnay
Make `char::DecodeUtf16::size_hist` more precise New implementation takes into account contents of `self.buf` and rounds lower bound up instead of down. Fixes rust-lang#88762 Revival of rust-lang#88763
2 parents b3f609b + 17cd2cd commit b984ac0

File tree

2 files changed

+55
-3
lines changed

2 files changed

+55
-3
lines changed

library/core/src/char/decode.rs

+28-3
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,34 @@ impl<I: Iterator<Item = u16>> Iterator for DecodeUtf16<I> {
120120
#[inline]
121121
fn size_hint(&self) -> (usize, Option<usize>) {
122122
let (low, high) = self.iter.size_hint();
123-
// we could be entirely valid surrogates (2 elements per
124-
// char), or entirely non-surrogates (1 element per char)
125-
(low / 2, high)
123+
124+
let (low_buf, high_buf) = match self.buf {
125+
// buf is empty, no additional elements from it.
126+
None => (0, 0),
127+
// `u` is a non surrogate, so it's always an additional character.
128+
Some(u) if u < 0xD800 || 0xDFFF < u => (1, 1),
129+
// `u` is a leading surrogate (it can never be a trailing surrogate and
130+
// it's a surrogate due to the previous branch) and `self.iter` is empty.
131+
//
132+
// `u` can't be paired, since the `self.iter` is empty,
133+
// so it will always become an additional element (error).
134+
Some(_u) if high == Some(0) => (1, 1),
135+
// `u` is a leading surrogate and `iter` may be non-empty.
136+
//
137+
// `u` can either pair with a trailing surrogate, in which case no additional elements
138+
// are produced, or it can become an error, in which case it's an additional character (error).
139+
Some(_u) => (0, 1),
140+
};
141+
142+
// `self.iter` could contain entirely valid surrogates (2 elements per
143+
// char), or entirely non-surrogates (1 element per char).
144+
//
145+
// On odd lower bound, at least one element must stay unpaired
146+
// (with other elements from `self.iter`), so we round up.
147+
let low = low.div_ceil(2) + low_buf;
148+
let high = high.and_then(|h| h.checked_add(high_buf));
149+
150+
(low, high)
126151
}
127152
}
128153

library/core/tests/char.rs

+27
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,33 @@ fn test_decode_utf16() {
308308
check(&[0xD800, 0], &[Err(0xD800), Ok('\0')]);
309309
}
310310

311+
#[test]
312+
fn test_decode_utf16_size_hint() {
313+
fn check(s: &[u16]) {
314+
let mut iter = char::decode_utf16(s.iter().cloned());
315+
316+
loop {
317+
let count = iter.clone().count();
318+
let (lower, upper) = iter.size_hint();
319+
320+
assert!(
321+
lower <= count && count <= upper.unwrap(),
322+
"lower = {lower}, count = {count}, upper = {upper:?}"
323+
);
324+
325+
if let None = iter.next() {
326+
break;
327+
}
328+
}
329+
}
330+
331+
check(&[0xD800, 0xD800, 0xDC00]);
332+
check(&[0xD800, 0xD800, 0x0]);
333+
check(&[0xD800, 0x41, 0x42]);
334+
check(&[0xD800, 0]);
335+
check(&[0xD834, 0x006d]);
336+
}
337+
311338
#[test]
312339
fn ed_iterator_specializations() {
313340
// Check counting

0 commit comments

Comments
 (0)