use {
crate::hkt::{
AssociatedTypes,
Canonicalizer,
generate_name,
},
quickcheck::{
Arbitrary,
Gen,
quickcheck,
},
syn::{
Generics,
Token,
TypeParamBound,
parse_quote,
punctuated::Punctuated,
},
};
#[derive(Debug, Clone)]
struct ArbTraitBound {
name: String,
}
const TRAIT_NAMES: &[&str] = &[
"Clone",
"Copy",
"Send",
"Sync",
"Debug",
"Display",
"Default",
"Eq",
"PartialEq",
"Ord",
"PartialOrd",
"Hash",
"Iterator",
"IntoIterator",
"AsRef",
"AsMut",
"From",
"Into",
"TryFrom",
"TryInto",
];
impl Arbitrary for ArbTraitBound {
fn arbitrary(g: &mut Gen) -> Self {
let idx = usize::arbitrary(g) % TRAIT_NAMES.len();
ArbTraitBound {
name: TRAIT_NAMES[idx].to_string(),
}
}
}
#[derive(Debug, Clone)]
struct ArbTypeName {
name: String,
}
const TYPE_NAMES: &[&str] =
&["A", "B", "C", "D", "E", "F", "G", "T", "U", "V", "W", "X", "Y", "Z", "Item", "Key", "Value"];
impl Arbitrary for ArbTypeName {
fn arbitrary(g: &mut Gen) -> Self {
let idx = usize::arbitrary(g) % TYPE_NAMES.len();
ArbTypeName {
name: TYPE_NAMES[idx].to_string(),
}
}
}
#[derive(Debug, Clone)]
struct ArbLifetime {
name: char,
}
impl Arbitrary for ArbLifetime {
fn arbitrary(g: &mut Gen) -> Self {
let idx = usize::arbitrary(g) % 26;
let name = (b'a' + idx as u8) as char;
ArbLifetime {
name,
}
}
}
#[derive(Debug, Clone)]
struct UniqueLifetimes {
names: Vec<char>,
}
impl Arbitrary for UniqueLifetimes {
fn arbitrary(g: &mut Gen) -> Self {
let count = usize::arbitrary(g) % 5;
let mut names = Vec::with_capacity(count);
let available: Vec<char> = ('a' ..= 'z').collect();
for i in 0 .. count {
if i < available.len() {
names.push(available[i]);
}
}
UniqueLifetimes {
names,
}
}
}
#[derive(Debug, Clone)]
struct UniqueBounds {
bounds: Vec<String>,
}
impl Arbitrary for UniqueBounds {
fn arbitrary(g: &mut Gen) -> Self {
let count = usize::arbitrary(g) % 6;
let mut bounds = Vec::with_capacity(count);
let mut used = std::collections::HashSet::new();
for _ in 0 .. count {
let arb = ArbTraitBound::arbitrary(g);
if !used.contains(&arb.name) {
used.insert(arb.name.clone());
bounds.push(arb.name);
}
}
UniqueBounds {
bounds,
}
}
}
#[test]
fn prop_hash_determinism_simple() {
fn property(
bounds: UniqueBounds,
lifetimes: UniqueLifetimes,
) -> bool {
let mut params = Vec::new();
for lt in &lifetimes.names {
params.push(format!("'{}", lt));
}
if !bounds.bounds.is_empty() && !lifetimes.names.is_empty() {
let lt = lifetimes.names.first().unwrap();
params.push(format!("A: '{} + {}", lt, bounds.bounds.join(" + ")));
} else if !bounds.bounds.is_empty() {
params.push(format!("A: {}", bounds.bounds.join(" + ")));
} else {
params.push("A".to_string());
}
let params_str = params.join(", ");
let input_str = format!("type Of<{}>;", params_str);
let result1: Result<AssociatedTypes, _> = syn::parse_str(&input_str);
let result2: Result<AssociatedTypes, _> = syn::parse_str(&input_str);
match (result1, result2) {
(Ok(input1), Ok(input2)) => {
let name1 = generate_name(&input1).unwrap();
let name2 = generate_name(&input2).unwrap();
name1 == name2
}
_ => true, }
}
quickcheck(property as fn(UniqueBounds, UniqueLifetimes) -> bool);
}
#[test]
fn prop_hash_determinism_repeated() {
fn property(iterations: u8) -> bool {
let iterations = (iterations % 10) + 1; let input_str = "type Of<'a, A: 'a>;";
let first_name = {
let input: AssociatedTypes = syn::parse_str(input_str).unwrap();
generate_name(&input).unwrap().to_string()
};
for _ in 0 .. iterations {
let input: AssociatedTypes = syn::parse_str(input_str).unwrap();
let name = generate_name(&input).unwrap().to_string();
if name != first_name {
return false;
}
}
true
}
quickcheck(property as fn(u8) -> bool);
}
#[test]
fn prop_bound_order_independence() {
fn property(
b1: ArbTraitBound,
b2: ArbTraitBound,
) -> bool {
if b1.name == b2.name {
return true;
}
let generics: Generics = parse_quote!(<>);
let mut canon = Canonicalizer::new(&generics);
let bound1: Result<TypeParamBound, _> = syn::parse_str(&b1.name);
let bound2: Result<TypeParamBound, _> = syn::parse_str(&b2.name);
match (bound1, bound2) {
(Ok(b1_parsed), Ok(b2_parsed)) => {
let mut bounds1: Punctuated<TypeParamBound, Token![+]> = Punctuated::new();
bounds1.push(b1_parsed.clone());
bounds1.push(b2_parsed.clone());
let mut bounds2: Punctuated<TypeParamBound, Token![+]> = Punctuated::new();
bounds2.push(b2_parsed);
bounds2.push(b1_parsed);
let canonical1 = canon.canonicalize_bounds(&bounds1).unwrap();
let canonical2 = canon.canonicalize_bounds(&bounds2).unwrap();
canonical1 == canonical2
}
_ => true, }
}
quickcheck(property as fn(ArbTraitBound, ArbTraitBound) -> bool);
}
#[test]
fn prop_bound_permutation_independence() {
fn property(bounds: UniqueBounds) -> bool {
if bounds.bounds.len() < 2 {
return true; }
let generics: Generics = parse_quote!(<>);
let mut canon = Canonicalizer::new(&generics);
let parsed_bounds: Result<Vec<TypeParamBound>, _> =
bounds.bounds.iter().map(|b| syn::parse_str(b)).collect();
match parsed_bounds {
Ok(parsed) => {
let mut original: Punctuated<TypeParamBound, Token![+]> = Punctuated::new();
for b in parsed.iter() {
original.push(b.clone());
}
let mut reversed: Punctuated<TypeParamBound, Token![+]> = Punctuated::new();
for b in parsed.iter().rev() {
reversed.push(b.clone());
}
let canonical_original = canon.canonicalize_bounds(&original).unwrap();
let canonical_reversed = canon.canonicalize_bounds(&reversed).unwrap();
canonical_original == canonical_reversed
}
_ => true,
}
}
quickcheck(property as fn(UniqueBounds) -> bool);
}
#[test]
fn prop_lifetime_name_independence() {
fn property(
lt1: ArbLifetime,
lt2: ArbLifetime,
) -> bool {
if lt1.name == lt2.name {
return true;
}
let generics1: Generics = syn::parse_str(&format!("<'{}>", lt1.name)).unwrap();
let mut canon1 = Canonicalizer::new(&generics1);
let generics2: Generics = syn::parse_str(&format!("<'{}>", lt2.name)).unwrap();
let mut canon2 = Canonicalizer::new(&generics2);
let bound1: TypeParamBound = syn::parse_str(&format!("'{}", lt1.name)).unwrap();
let bound2: TypeParamBound = syn::parse_str(&format!("'{}", lt2.name)).unwrap();
let canonical1 = canon1.canonicalize_bound(&bound1).unwrap();
let canonical2 = canon2.canonicalize_bound(&bound2).unwrap();
canonical1 == canonical2 && canonical1 == "l0"
}
quickcheck(property as fn(ArbLifetime, ArbLifetime) -> bool);
}
#[test]
fn prop_multiple_lifetime_positions() {
fn property(lts: UniqueLifetimes) -> bool {
if lts.names.len() < 2 {
return true;
}
let lts_str = lts.names.iter().map(|n| format!("'{}", n)).collect::<Vec<_>>().join(", ");
let generics: Generics = syn::parse_str(&format!("<{}>", lts_str)).unwrap();
let mut canon = Canonicalizer::new(&generics);
for (i, name) in lts.names.iter().enumerate() {
let bound: TypeParamBound = syn::parse_str(&format!("'{}", name)).unwrap();
let canonical = canon.canonicalize_bound(&bound).unwrap();
let expected = format!("l{}", i);
if canonical != expected {
return false;
}
}
true
}
quickcheck(property as fn(UniqueLifetimes) -> bool);
}
#[test]
fn prop_generated_name_format() {
fn property(bounds: UniqueBounds) -> bool {
let bounds_str = if bounds.bounds.is_empty() {
"A".to_string()
} else {
format!("A: {}", bounds.bounds.join(" + "))
};
let input_str = format!("type Of<{}>;", bounds_str);
let result: Result<AssociatedTypes, _> = syn::parse_str(&input_str);
match result {
Ok(input) => {
let name = generate_name(&input).unwrap().to_string();
name.starts_with("Kind_") && name.len() == "Kind_".len() + 16
}
_ => true,
}
}
quickcheck(property as fn(UniqueBounds) -> bool);
}
#[test]
fn prop_generated_name_valid_identifier() {
fn property(
bounds: UniqueBounds,
lifetimes: UniqueLifetimes,
) -> bool {
let mut params = Vec::new();
for lt in &lifetimes.names {
params.push(format!("'{}", lt));
}
if !bounds.bounds.is_empty() {
params.push(format!("A: {}", bounds.bounds.join(" + ")));
} else {
params.push("A".to_string());
}
let input_str = format!("type Of<{}>;", params.join(", "));
let result: Result<AssociatedTypes, _> = syn::parse_str(&input_str);
match result {
Ok(input) => {
let name = generate_name(&input).unwrap().to_string();
name.chars().next().is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
&& name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
}
_ => true,
}
}
quickcheck(property as fn(UniqueBounds, UniqueLifetimes) -> bool);
}
#[test]
fn prop_adding_bound_changes_name() {
fn property(bound: ArbTraitBound) -> bool {
let input_without: AssociatedTypes = syn::parse_str("type Of<A>;").unwrap();
let input_with: Result<AssociatedTypes, _> =
syn::parse_str(&format!("type Of<A: {}>;", bound.name));
match input_with {
Ok(input) => {
let name_without = generate_name(&input_without).unwrap();
let name_with = generate_name(&input).unwrap();
name_without != name_with
}
_ => true,
}
}
quickcheck(property as fn(ArbTraitBound) -> bool);
}
#[test]
fn prop_adding_lifetime_changes_name() {
fn property(lt: ArbLifetime) -> bool {
let input_without: AssociatedTypes = syn::parse_str("type Of<A>;").unwrap();
let input_with: Result<AssociatedTypes, _> =
syn::parse_str(&format!("type Of<'{}, A: '{}>;", lt.name, lt.name));
match input_with {
Ok(input) => {
let name_without = generate_name(&input_without).unwrap();
let name_with = generate_name(&input).unwrap();
name_without != name_with
}
_ => true,
}
}
quickcheck(property as fn(ArbLifetime) -> bool);
}
#[test]
fn prop_canonicalization_idempotent() {
fn property(bound: ArbTraitBound) -> bool {
let generics: Generics = parse_quote!(<>);
let mut canon = Canonicalizer::new(&generics);
let bound_str = &bound.name;
let parsed: Result<TypeParamBound, _> = syn::parse_str(bound_str);
match parsed {
Ok(b) => {
let canonical1 = canon.canonicalize_bound(&b).unwrap();
let canonical2 = canon.canonicalize_bound(&b).unwrap();
canonical1 == canonical2
}
_ => true,
}
}
quickcheck(property as fn(ArbTraitBound) -> bool);
}
#[test]
fn prop_canonicalize_bounds_idempotent() {
fn property(bounds: UniqueBounds) -> bool {
if bounds.bounds.is_empty() {
return true;
}
let generics: Generics = parse_quote!(<>);
let mut canon = Canonicalizer::new(&generics);
let parsed_bounds: Result<Vec<TypeParamBound>, _> =
bounds.bounds.iter().map(|b| syn::parse_str(b)).collect();
match parsed_bounds {
Ok(parsed) => {
let mut punctuated: Punctuated<TypeParamBound, Token![+]> = Punctuated::new();
for b in parsed {
punctuated.push(b);
}
let canonical1 = canon.canonicalize_bounds(&punctuated).unwrap();
let canonical2 = canon.canonicalize_bounds(&punctuated).unwrap();
canonical1 == canonical2
}
_ => true,
}
}
quickcheck(property as fn(UniqueBounds) -> bool);
}
#[test]
fn prop_output_bounds_order_independence() {
fn property(
b1: ArbTraitBound,
b2: ArbTraitBound,
) -> bool {
if b1.name == b2.name {
return true;
}
let input1_str = format!("type Of<A>: {} + {};", b1.name, b2.name);
let input2_str = format!("type Of<A>: {} + {};", b2.name, b1.name);
let result1: Result<AssociatedTypes, _> = syn::parse_str(&input1_str);
let result2: Result<AssociatedTypes, _> = syn::parse_str(&input2_str);
match (result1, result2) {
(Ok(i1), Ok(i2)) => {
let name1 = generate_name(&i1).unwrap();
let name2 = generate_name(&i2).unwrap();
name1 == name2
}
_ => true,
}
}
quickcheck(property as fn(ArbTraitBound, ArbTraitBound) -> bool);
}
#[derive(Debug, Clone)]
struct ArbSimpleType {
name: String,
}
const SIMPLE_TYPES: &[&str] = &["i32", "u32", "i64", "u64", "bool", "String", "usize", "isize"];
impl Arbitrary for ArbSimpleType {
fn arbitrary(g: &mut Gen) -> Self {
let idx = usize::arbitrary(g) % SIMPLE_TYPES.len();
ArbSimpleType {
name: SIMPLE_TYPES[idx].to_string(),
}
}
}
#[test]
fn prop_fn_bound_determinism() {
fn property(
input_type: ArbSimpleType,
output_type: ArbSimpleType,
) -> bool {
let generics: Generics = parse_quote!(<>);
let mut canon = Canonicalizer::new(&generics);
let bound_str = format!("Fn({}) -> {}", input_type.name, output_type.name);
let bound: Result<TypeParamBound, _> = syn::parse_str(&bound_str);
match bound {
Ok(b) => {
let canonical1 = canon.canonicalize_bound(&b).unwrap();
let canonical2 = canon.canonicalize_bound(&b).unwrap();
canonical1 == canonical2
}
_ => true,
}
}
quickcheck(property as fn(ArbSimpleType, ArbSimpleType) -> bool);
}
#[test]
fn prop_path_preservation() {
fn property() -> bool {
let generics: Generics = parse_quote!(<>);
let mut canon = Canonicalizer::new(&generics);
let paths = vec![
("std::fmt::Debug", "tstd::fmt::Debug"),
("std::marker::Send", "tstd::marker::Send"),
("core::clone::Clone", "tcore::clone::Clone"),
];
for (input_path, expected) in paths {
let bound: TypeParamBound = syn::parse_str(input_path).unwrap();
let canonical = canon.canonicalize_bound(&bound).unwrap();
if canonical != expected {
return false;
}
}
true
}
quickcheck(property as fn() -> bool);
}
#[test]
fn prop_empty_inputs_valid() {
fn property() -> bool {
let input: AssociatedTypes = syn::parse_str("type Of;").unwrap();
let name = generate_name(&input).unwrap().to_string();
name.starts_with("Kind_") && name.len() == "Kind_".len() + 16
}
quickcheck(property as fn() -> bool);
}
#[test]
fn prop_single_lifetime_valid() {
fn property(lt: ArbLifetime) -> bool {
let input_str = format!("type Of<'{} >;", lt.name);
let result: Result<AssociatedTypes, _> = syn::parse_str(&input_str);
match result {
Ok(input) => {
let name = generate_name(&input).unwrap().to_string();
name.starts_with("Kind_") && name.len() == "Kind_".len() + 16
}
_ => true,
}
}
quickcheck(property as fn(ArbLifetime) -> bool);
}
#[test]
fn prop_type_param_bounds_consistent() {
fn property(
type_name: ArbTypeName,
bounds: UniqueBounds,
) -> bool {
if bounds.bounds.is_empty() {
return true;
}
let bounds_str = bounds.bounds.join(" + ");
let input_str = format!("type Of<{}: {}>;", type_name.name, bounds_str);
let result1: Result<AssociatedTypes, _> = syn::parse_str(&input_str);
let result2: Result<AssociatedTypes, _> = syn::parse_str(&input_str);
match (result1, result2) {
(Ok(i1), Ok(i2)) => {
let name1 = generate_name(&i1).unwrap();
let name2 = generate_name(&i2).unwrap();
name1 == name2
}
_ => true,
}
}
quickcheck(property as fn(ArbTypeName, UniqueBounds) -> bool);
}
#[test]
fn prop_hash_collision_resistance() {
fn property(
b1: ArbTraitBound,
b2: ArbTraitBound,
) -> bool {
if b1.name == b2.name {
return true; }
let input1_str = format!("type Of<A: {}>;", b1.name);
let input2_str = format!("type Of<A: {}>;", b2.name);
let result1: Result<AssociatedTypes, _> = syn::parse_str(&input1_str);
let result2: Result<AssociatedTypes, _> = syn::parse_str(&input2_str);
match (result1, result2) {
(Ok(i1), Ok(i2)) => {
let name1 = generate_name(&i1).unwrap();
let name2 = generate_name(&i2).unwrap();
name1 != name2
}
_ => true,
}
}
quickcheck(property as fn(ArbTraitBound, ArbTraitBound) -> bool);
}
#[test]
fn prop_nested_generics_determinism() {
fn property() -> bool {
let generics: Generics = parse_quote!(<>);
let mut canon = Canonicalizer::new(&generics);
let bound: TypeParamBound = parse_quote!(Iterator<Item = Option<String>>);
let canonical1 = canon.canonicalize_bound(&bound).unwrap();
let canonical2 = canon.canonicalize_bound(&bound).unwrap();
canonical1 == canonical2 && canonical1.contains("Iterator") && canonical1.contains("Option")
}
quickcheck(property as fn() -> bool);
}
#[test]
fn prop_reference_lifetime_canonicalization() {
fn property() -> bool {
let generics: Generics = parse_quote!(<'a>);
let mut canon = Canonicalizer::new(&generics);
let bound: TypeParamBound = parse_quote!(AsRef<&'a str>);
let canonical1 = canon.canonicalize_bound(&bound).unwrap();
let canonical2 = canon.canonicalize_bound(&bound).unwrap();
canonical1 == canonical2
}
quickcheck(property as fn() -> bool);
}