use {
crate::{
Pbt,
fields::{Fields, Store},
reflection::{Parts, Variants},
registration::Registration,
},
core::iter,
wyrand::WyRand,
};
const N_UNICODE_SCALARS: u64 = 0x10_F800;
const UNBIASED_LIMIT: u64 = unbiased_limit();
impl Pbt for char {
#[inline]
fn construct<F>(
Parts {
mut fields,
variant_index,
}: Parts<F>,
) -> Self
where
F: Fields,
{
debug_assert_eq!(variant_index, None, "`char` is a literal");
fields.field()
}
#[inline]
fn deconstruct(self) -> Parts<Store> {
let mut fields = Store::new();
let () = fields.push(self);
Parts {
fields,
variant_index: None,
}
}
#[inline]
fn register(_registration: &mut Registration<'_>) -> Variants<Self> {
Variants::Literal {
deserialize: |json| {
let serde_json::Value::String(ref s) = *json else {
return None;
};
s.parse().ok()
},
generators: vec![uniform],
serialize: |&i| i.to_string().into(),
shrink,
}
}
}
#[inline]
#[expect(
clippy::arithmetic_side_effects,
reason = "The branch bounds the addition by char::MAX."
)]
fn scalar_from_index(index: u32) -> char {
let scalar = if index < 0xD800 { index } else { index + 0x800 };
unsafe { char::from_u32_unchecked(scalar) }
}
#[inline]
fn shrink(c: char) -> Box<dyn Iterator<Item = char>> {
let n = u32::from(c);
let mut shift = 0;
Box::new(
iter::from_fn(move || {
let delta = n.checked_shr(shift)?;
if delta == 0 {
return None;
}
shift = shift.checked_add(1)?;
n.checked_sub(delta)
})
.filter_map(|u32| char::try_from(u32).ok()),
)
}
#[inline]
#[cfg_attr(test, mutants::skip)] #[expect(
clippy::integer_division_remainder_used,
reason = "The remainder cannot exceed u64::MAX; subtracting it yields the largest exact multiple."
)]
const fn unbiased_limit() -> u64 {
u64::MAX - u64::MAX % N_UNICODE_SCALARS
}
#[inline]
fn uniform(prng: &mut WyRand) -> char {
'rejection_sampling: loop {
let sample = prng.rand();
if sample >= UNBIASED_LIMIT {
continue 'rejection_sampling;
}
#[expect(
clippy::as_conversions,
clippy::integer_division_remainder_used,
reason = "Modulo by the scalar count yields an in-range index; the cast is then lossless."
)]
let index = (sample % N_UNICODE_SCALARS) as u32;
return scalar_from_index(index);
}
}
#[cfg(test)]
mod tests {
#![expect(clippy::unwrap_used, reason = "failing tests ought to panic")]
use {
super::*,
crate::{arbitrary::arbitrary, check_eta_expansion, check_serialization},
pretty_assertions::assert_eq,
wyrand::WyRand,
};
#[test]
fn deterministic() {
let mut prng = WyRand::new(42);
let generated: Vec<char> = arbitrary(&mut prng).unwrap().take(10).collect();
let expected: Vec<char> = vec![
'\u{4b808}',
'\u{4d06a}',
'\u{af7c9}',
'\u{a4ad9}',
'\u{41131}',
'\u{300e6}',
'\u{613a8}',
'\u{680d1}',
'\u{b7a5a}',
'\u{81baf}',
];
assert_eq!(generated, expected);
}
#[test]
fn deterministic_shrink() {
let mut iter = shrink('z');
assert_eq!(iter.next(), Some('\0'));
assert_eq!(iter.next(), Some('='));
assert_eq!(iter.next(), Some('\\'));
assert_eq!(iter.next(), Some('k'));
assert_eq!(iter.next(), Some('s'));
assert_eq!(iter.next(), Some('w'));
assert_eq!(iter.next(), Some('y'));
assert_eq!(iter.next(), None);
}
#[test]
fn eta_expansion() {
let () = check_eta_expansion::<char>();
}
#[test]
fn scalar_index_mapping_boundaries() {
assert_eq!(scalar_from_index(0), '\0');
assert_eq!(scalar_from_index(0xD7FF), '\u{D7FF}');
assert_eq!(scalar_from_index(0xD800), '\u{E000}');
assert_eq!(scalar_from_index(0x10_F7FF), char::MAX);
}
#[test]
fn serialization() {
let () = check_serialization::<char>();
}
#[test]
#[expect(
clippy::integer_division_remainder_used,
reason = "This test verifies that the rejection-sampling limit is an exact multiple."
)]
fn unbiased_limit_is_the_largest_multiple() {
assert_eq!(UNBIASED_LIMIT, 0xFFFF_FFFF_FFFF_4800);
assert_eq!(UNBIASED_LIMIT % N_UNICODE_SCALARS, 0);
assert!(u64::MAX.checked_sub(UNBIASED_LIMIT).unwrap() < N_UNICODE_SCALARS);
}
}