use {
crate::{
Pbt, fields,
hash::map,
instantiability,
multiset::Multiset,
reflection::{Constructor, Constructors, Erased, Parts, Uninstantiable, constructors_of},
scc,
size::Size,
unavoidability,
union_find::UnionFind,
},
ahash::{HashMap, HashSet},
alloc::{collections::BTreeMap, sync::Arc},
core::{any::TypeId, mem, num::NonZero},
wyrand::WyRand,
};
#[non_exhaustive]
enum Affordances {
Algebraic {
potential_leaves: Box<[Constructor]>,
potential_loops: Box<[Constructor]>,
},
Literal {
generators: Box<[fn(&mut WyRand) -> Erased]>,
},
}
pub(crate) struct Swarm {
affordances: HashMap<TypeId, Affordances>,
}
impl Affordances {
#[inline]
#[must_use]
fn is_inductive(&self) -> bool {
match *self {
Self::Algebraic {
ref potential_loops,
..
} => !potential_loops.is_empty(),
Self::Literal { .. } => false,
}
}
}
impl Swarm {
#[inline]
fn affordances<T>(&self) -> &Affordances
where
T: 'static,
{
self.affordances_of(TypeId::of::<T>())
}
#[inline]
#[expect(
clippy::expect_used,
reason = "Internal invariants: violations should fail loudly."
)]
fn affordances_of(&self, ty: TypeId) -> &Affordances {
self.affordances
.get(&ty)
.expect("INTERNAL ERROR (`pbt`): unregistered type")
}
#[inline]
#[expect(
clippy::panic,
reason = "Internal invariants: violations should fail loudly."
)]
pub(crate) fn arbitrary<T>(&self, size: Size, prng: &mut WyRand) -> T
where
T: Pbt,
{
let (potential_leaves, potential_loops) = match *self.affordances::<T>() {
Affordances::Algebraic {
ref potential_leaves,
ref potential_loops,
} => (potential_leaves, potential_loops),
Affordances::Literal { ref generators } => {
#[expect(
clippy::expect_used,
reason = "Swarms for uninstantiable literal types are rejected during construction."
)]
let n = NonZero::new(generators.len()).expect(
"INTERNAL ERROR (`pbt`): swarm created for an uninstantiable literal type",
);
#[expect(
clippy::as_conversions,
clippy::cast_possible_truncation,
reason = "Intentional lossy sampling from a 64-bit PRNG into machine-word indices."
)]
let generator_index = prng.rand() as usize % n;
let erased = unsafe { *generators.get_unchecked(generator_index) };
let generate = unsafe {
mem::transmute::<fn(&mut WyRand) -> Erased, fn(&mut WyRand) -> T>(erased)
};
return generate(prng);
}
};
let (ctors, n) = if let Some(n_loops) = NonZero::new(potential_loops.len())
&& size.should_recurse(prng)
{
(potential_loops.as_ref(), n_loops)
} else if let Some(n_leaves) = NonZero::new(potential_leaves.len()) {
(potential_leaves.as_ref(), n_leaves)
} else {
panic!("INTERNAL ERROR (`pbt`): swarm created for an uninstantiable type")
};
#[expect(
clippy::as_conversions,
clippy::cast_possible_truncation,
reason = "OK: `u64` is already huge"
)]
let ctor_index = prng.rand() as usize % n;
let ctor = unsafe { ctors.get_unchecked(ctor_index) };
let n_ind = self.count_inductive_fields(ctor.field_types());
let sizes = size.partition(n_ind, prng);
T::construct(Parts {
fields: fields::Lazy {
prng,
sizes,
swarm: self,
},
variant_index: Some(ctor.index),
})
}
#[inline]
#[expect(
clippy::arithmetic_side_effects,
reason = "if there were more than `usize::MAX` fields, they wouldn't have compiled"
)]
fn count_inductive_fields(&self, field_types: &Multiset<TypeId>) -> usize {
let mut acc = 0_usize;
for (&ty, count) in field_types.iter() {
if self.affordances_of(ty).is_inductive() {
acc += count.get();
}
}
acc
}
#[inline]
pub(crate) fn is_inductive<T>(&self) -> bool
where
T: 'static,
{
self.affordances::<T>().is_inductive()
}
#[inline]
#[expect(
clippy::expect_used,
reason = "Internal invariants: violations should fail loudly."
)]
pub(crate) fn new<T>(
prng: &mut WyRand,
cache: &mut HashMap<BTreeMap<TypeId, Constructors<Erased>>, Option<Arc<Self>>>,
) -> Result<Arc<Self>, Uninstantiable>
where
T: Pbt,
{
let ty = TypeId::of::<T>();
if constructors_of(ty).is_empty() {
return Err(Uninstantiable);
}
'rejection_sampling: loop {
let mut naive_masked_constructors = BTreeMap::new();
let () = mask_all_constructors_reachable_from(ty, &mut naive_masked_constructors, prng);
if let Some(cached) = cache.get(&naive_masked_constructors) {
match *cached {
None => continue 'rejection_sampling, Some(ref swarm) => return Ok(Arc::clone(swarm)),
}
}
let mut masked_constructors = map();
let () =
instantiability::update(ty, &naive_masked_constructors, &mut masked_constructors);
if masked_constructors
.get(&ty)
.is_none_or(Constructors::is_empty)
{
let _: &mut _ = cache.entry(naive_masked_constructors).or_insert(None);
continue 'rejection_sampling;
}
let mut scc_quotient_graph = UnionFind::new();
let () = scc::update(
ty,
&|t| {
masked_constructors
.get(&t)
.expect("INTERNAL ERROR (`pbt`): unregistered type")
.algebraic()
.iter()
.flat_map(Constructor::dedup_fields)
},
&mut scc_quotient_graph,
);
let mut unavoidable = map();
let () = unavoidability::update(
ty,
&mut unavoidable,
&|t| {
masked_constructors
.get(&t)
.expect("INTERNAL ERROR (`pbt`): unregistered type")
.algebraic()
},
&|ctor| ctor.dedup_fields(),
);
let affordances = masked_constructors
.into_iter()
.map(|(t, ctors)| {
(
t,
affordances_of(t, ctors, &mut scc_quotient_graph, &unavoidable),
)
})
.collect();
let arc = Arc::new(Self { affordances });
let _: &mut _ = cache
.entry(naive_masked_constructors)
.or_insert(Some(Arc::clone(&arc)));
return Ok(arc);
}
}
}
#[inline]
#[expect(
clippy::as_conversions,
clippy::cast_possible_truncation,
reason = "OK: `u64` is already huge"
)]
#[expect(
clippy::arithmetic_side_effects,
reason = "if there were more than `usize::MAX` fields, they wouldn't have compiled"
)]
fn how_many_features_to_mask_out_of(n_total: usize, prng: &mut WyRand) -> usize {
let inclusive = unsafe { NonZero::new_unchecked(n_total + 1) };
'rejection_sampling: loop {
let y = prng.rand() as usize % inclusive;
let x = prng.rand() as usize % inclusive;
if x > y {
continue 'rejection_sampling;
}
return x;
}
}
#[inline]
#[expect(
clippy::arithmetic_side_effects,
clippy::as_conversions,
clippy::cast_possible_truncation,
clippy::integer_division_remainder_used,
reason = "OK: `u64` is already huge"
)]
fn mask_for(n_total: usize, prng: &mut WyRand) -> Vec<bool> {
let n_to_mask = how_many_features_to_mask_out_of(n_total, prng);
let mut mask = vec![true; n_total];
for _ in 0..n_to_mask {
'rejection_sampling: loop {
let i = prng.rand() as usize % n_total;
let flip = unsafe { mask.get_unchecked_mut(i) };
if *flip {
*flip = false;
break 'rejection_sampling;
}
}
}
mask
}
#[inline]
#[expect(
clippy::expect_used,
reason = "Internal invariants: violations should fail loudly."
)]
fn affordances_of(
ty: TypeId,
constructors_of_ty: Constructors<Erased>,
scc_quotient_graph: &mut UnionFind<TypeId>,
unavoidable: &HashMap<TypeId, Arc<HashSet<TypeId>>>,
) -> Affordances {
let constructors = match constructors_of_ty {
Constructors::Algebraic(constructors) => constructors,
Constructors::Literal { generators, .. } => {
return Affordances::Literal {
generators: generators.iter().copied().collect(),
};
}
};
let self_scc = scc_quotient_graph
.root(ty)
.expect("INTERNAL ERROR (`pbt`): SCC missing");
let mut potential_leaves = vec![];
let mut potential_loops = vec![];
for constructor in &*constructors {
let mut potential_leaf = true;
let mut potential_loop = false;
for field in constructor.dedup_fields() {
if unavoidable
.get(&field)
.expect("INTERNAL ERROR (`pbt`): unavoidability missing")
.contains(&ty)
{
potential_leaf = false;
}
if scc_quotient_graph
.root(field)
.expect("INTERNAL ERROR (`pbt`): SCC missing")
== self_scc
{
potential_loop = true;
}
}
if potential_leaf {
let () = potential_leaves.push(constructor.clone());
}
if potential_loop {
let () = potential_loops.push(constructor.clone());
}
debug_assert!(
potential_leaf || potential_loop,
"INTERNAL ERROR (`pbt`): constructor is neither a leaf nor a loop",
);
}
Affordances::Algebraic {
potential_leaves: potential_leaves.into_boxed_slice(),
potential_loops: potential_loops.into_boxed_slice(),
}
}
#[inline]
fn mask_all_constructors_reachable_from(
ty: TypeId,
masked_constructors: &mut BTreeMap<TypeId, Constructors<Erased>>,
prng: &mut WyRand,
) {
if masked_constructors.contains_key(&ty) {
return;
}
match constructors_of(ty) {
Constructors::Algebraic(constructors) => {
let mask = mask_for(constructors.len(), prng);
let masked = constructors
.iter()
.zip(mask)
.filter_map(|(constructor, enable)| enable.then_some(constructor.clone()))
.collect();
let _dup: Option<_> =
masked_constructors.insert(ty, Constructors::Algebraic(Arc::clone(&masked)));
for constructor in &*masked {
for field_ty in constructor.dedup_fields() {
let () =
mask_all_constructors_reachable_from(field_ty, masked_constructors, prng);
}
}
}
Constructors::Literal {
deserialize,
generators,
serialize,
shrink,
} => {
let mask = mask_for(generators.len(), prng);
let masked_generators = generators
.iter()
.zip(mask)
.filter_map(|(&generate, enable)| enable.then_some(generate))
.collect();
let _dup: Option<_> = masked_constructors.insert(
ty,
Constructors::Literal {
deserialize,
generators: masked_generators,
serialize,
shrink,
},
);
}
}
}