use quote::ToTokens;
use super::*;
fn key(s: &str) -> crate::registry::TypeKey {
crate::registry::TypeKey::parse(s).expect("a fixture type")
}
fn tref(ty: syn::Type) -> prebindgen_flat::flat::TypeRef {
prebindgen_flat::flat::Flat::builder()
.build()
.expect("an empty model")
.classify(&ty)
.expect("a fixture type the language accepts")
}
use crate::{registry::Registry, test_util::scanned_with as reg_with};
fn src_qualify(id: &syn::Ident) -> syn::Path {
syn::parse_quote!(zenoh_flat::#id)
}
#[test]
fn single_constructor_plan_and_fold() {
let mut reg: Registry<()> = reg_with(&[
"fn z_keyexpr_try_from(s: String) -> Result<ZKeyExpr, Error> { todo!() }",
"fn z_keyexpr_intersects(a: &ZKeyExpr, b: &ZKeyExpr) -> bool { todo!() }",
]);
let mut exp = Expansions::default();
exp.expands.push(ExpandDecl {
func: ident("z_keyexpr_intersects"),
param: ident("a"),
declared_target: Some(key("ZKeyExpr")),
sel: ExpandSel::Subset(vec![Variant::Ctor(ident("z_keyexpr_try_from"))]),
});
apply(
&mut reg,
&exp,
&Default::default(),
&Default::default(),
&Default::default(),
)
.expect("apply");
let plan = reg
.expansion_plans
.get(&(ident("z_keyexpr_intersects"), ident("a")))
.expect("plan for a");
assert!(plan.by_ref, "param was &ZKeyExpr");
assert_eq!(plan.selector, None);
assert_eq!(plan.leaves.len(), 1);
assert_eq!(plan.leaves[0].name.to_string(), "a");
assert_eq!(plan.leaves[0].ty.spell().to_string(), "String");
let locals = vec![ident("a")];
let folded = emit_fold(plan, &locals, &src_qualify);
let s = folded.to_token_stream().to_string();
assert!(s.contains("z_keyexpr_try_from"), "fold calls ctor: {}", s);
assert!(s.contains("map_err"), "fallible ctor mapped: {}", s);
}
#[test]
fn constructor_plan_and_fold() {
let mut reg: Registry<()> = reg_with(&[
"fn z_keyexpr_try_from(s: String) -> Result<ZKeyExpr, Error> { todo!() }",
"fn z_keyexpr_intersects(a: &ZKeyExpr, b: &ZKeyExpr) -> bool { todo!() }",
]);
let mut exp = Expansions::default();
exp.expands.push(ExpandDecl {
func: ident("z_keyexpr_intersects"),
param: ident("a"),
declared_target: Some(key("ZKeyExpr")),
sel: ExpandSel::Subset(vec![
Variant::Ctor(ident("z_keyexpr_try_from")),
Variant::Identity,
]),
});
apply(
&mut reg,
&exp,
&Default::default(),
&Default::default(),
&Default::default(),
)
.expect("apply");
let plan = reg
.expansion_plans
.get(&(ident("z_keyexpr_intersects"), ident("a")))
.unwrap();
assert_eq!(plan.selector, Some(0));
assert_eq!(plan.leaves.len(), 3);
assert_eq!(plan.leaves[0].ty.spell().to_string(), "i32");
assert_eq!(plan.leaves[1].ty.spell().to_string(), "Option < String >");
assert_eq!(
plan.leaves[2].ty.spell().to_string(),
"Option < & ZKeyExpr >"
);
assert_eq!(plan.variants.len(), 2);
assert!(plan.variants[0].ctor.is_some());
assert!(plan.variants[1].ctor.is_none(), "identity arm");
assert!(plan.variants[1].clone, "by-ref identity clones");
assert!(reg.input_types[&plan.leaves[1].ty.key()].root);
let locals = vec![ident("sel"), ident("v0"), ident("vid")];
let folded = emit_fold(plan, &locals, &src_qualify);
let s = folded.to_token_stream().to_string();
assert!(s.contains("match sel"), "dispatch on selector: {}", s);
assert!(s.contains("z_keyexpr_try_from"));
assert!(s.contains("invalid constructor selector"));
}
#[test]
fn optional_byvalue_single_ctor() {
let mut reg: Registry<()> = reg_with(&[
"fn z_zbytes_from_vec(bytes: Vec<u8>) -> ZZBytes { todo!() }",
"fn z_session_delete(s: &ZSession, attachment: Option<ZZBytes>) -> bool { todo!() }",
]);
let mut exp = Expansions::default();
exp.expands.push(ExpandDecl {
func: ident("z_session_delete"),
param: ident("attachment"),
declared_target: Some(key("ZZBytes")),
sel: ExpandSel::Subset(vec![Variant::Ctor(ident("z_zbytes_from_vec"))]),
});
apply(
&mut reg,
&exp,
&Default::default(),
&Default::default(),
&Default::default(),
)
.expect("apply optional by-value");
let plan = reg
.expansion_plans
.get(&(ident("z_session_delete"), ident("attachment")))
.unwrap();
assert!(matches!(plan.shape, FoldShape::Optional((), _)));
assert!(plan.produces_option());
assert!(!plan.by_ref);
assert_eq!(plan.leaves.len(), 1);
assert_eq!(
plan.leaves[0].ty.spell().to_string(),
"Option < Vec < u8 > >"
);
let locals = vec![ident("att")];
let s = emit_fold(plan, &locals, &src_qualify)
.to_token_stream()
.to_string();
assert!(s.contains("z_zbytes_from_vec"), "fold calls ctor: {}", s);
assert!(
s.contains("Some") && s.contains("None"),
"maps Option: {}",
s
);
}
#[test]
fn optional_byref_single_ctor() {
let mut reg: Registry<()> = reg_with(&[
"fn z_encoding_from_string(s: String) -> ZEncoding { todo!() }",
"fn z_session_put(s: &ZSession, encoding: Option<&ZEncoding>) -> bool { todo!() }",
]);
let mut exp = Expansions::default();
exp.expands.push(ExpandDecl {
func: ident("z_session_put"),
param: ident("encoding"),
declared_target: Some(key("ZEncoding")),
sel: ExpandSel::Subset(vec![Variant::Ctor(ident("z_encoding_from_string"))]),
});
apply(
&mut reg,
&exp,
&Default::default(),
&Default::default(),
&Default::default(),
)
.expect("apply optional by-ref");
let plan = reg
.expansion_plans
.get(&(ident("z_session_put"), ident("encoding")))
.unwrap();
assert!(matches!(plan.shape, FoldShape::Optional((), _)));
assert!(plan.produces_option());
assert!(plan.by_ref, "Option<&T> ⇒ by_ref");
assert_eq!(plan.leaves[0].ty.spell().to_string(), "Option < String >");
assert_eq!(
plan.target.spell().to_string(),
"ZEncoding",
"target peeled through Option<&_>"
);
}
#[test]
fn optional_byref_multi_arg_ctor() {
let mut reg: Registry<()> = reg_with(&[
"fn z_encoding_from_id(id: i32, schema: Option<String>) -> ZEncoding { todo!() }",
"fn z_session_put(s: &ZSession, encoding: Option<&ZEncoding>) -> bool { todo!() }",
]);
let mut exp = Expansions::default();
exp.expands.push(ExpandDecl {
func: ident("z_session_put"),
param: ident("encoding"),
declared_target: Some(key("ZEncoding")),
sel: ExpandSel::Subset(vec![Variant::Ctor(ident("z_encoding_from_id"))]),
});
apply(
&mut reg,
&exp,
&Default::default(),
&Default::default(),
&Default::default(),
)
.expect("apply optional multi-arg by-ref");
let plan = reg
.expansion_plans
.get(&(ident("z_session_put"), ident("encoding")))
.unwrap();
assert!(matches!(plan.shape, FoldShape::Optional((), _)));
assert!(plan.produces_option());
assert!(plan.by_ref, "Option<&T> ⇒ by_ref");
assert_eq!(plan.present, Some(0), "explicit presence flag at leaf 0");
assert_eq!(plan.leaves.len(), 3);
assert_eq!(plan.leaves[0].name.to_string(), "encoding_present");
assert_eq!(plan.leaves[0].ty.spell().to_string(), "bool");
assert_eq!(plan.leaves[1].name.to_string(), "encoding_id");
assert_eq!(plan.leaves[1].ty.spell().to_string(), "i32");
assert_eq!(plan.leaves[2].name.to_string(), "encoding_schema");
assert_eq!(plan.leaves[2].ty.spell().to_string(), "Option < String >");
let locals = vec![ident("pres"), ident("id"), ident("schema")];
let s = emit_fold(plan, &locals, &src_qualify)
.to_token_stream()
.to_string();
assert!(s.contains("if pres"), "presence-flag gated: {}", s);
assert!(
s.contains("z_encoding_from_id"),
"fold calls multi-arg ctor: {}",
s
);
assert!(
s.contains("Some") && s.contains("None"),
"maps Option: {}",
s
);
}
#[test]
fn optional_combined_selector_encodes_absence() {
let mut reg: Registry<()> = reg_with(&[
"fn z_encoding_from_id(id: i32, schema: Option<String>) -> ZEncoding { todo!() }",
"fn z_session_put(s: &ZSession, encoding: Option<&ZEncoding>) -> bool { todo!() }",
]);
let mut exp = Expansions::default();
exp.expands.push(ExpandDecl {
func: ident("z_session_put"),
param: ident("encoding"),
declared_target: Some(key("ZEncoding")),
sel: ExpandSel::Subset(vec![
Variant::Ctor(ident("z_encoding_from_id")),
Variant::Identity,
]),
});
apply(
&mut reg,
&exp,
&Default::default(),
&Default::default(),
&Default::default(),
)
.expect("apply optional combined");
let plan = reg
.expansion_plans
.get(&(ident("z_session_put"), ident("encoding")))
.unwrap();
assert!(matches!(plan.shape, FoldShape::Optional((), _)));
assert!(plan.produces_option());
assert!(plan.by_ref, "Option<&T> ⇒ by_ref");
assert_eq!(plan.selector, Some(0), "selector at leaf 0");
assert_eq!(plan.present, None, "absence rides the selector, no flag");
assert_eq!(plan.leaves.len(), 4);
assert_eq!(plan.leaves[0].name.to_string(), "encoding_sel");
assert_eq!(plan.leaves[0].ty.spell().to_string(), "i32");
assert_eq!(plan.leaves[1].ty.spell().to_string(), "Option < i32 >");
assert_eq!(
plan.leaves[2].ty.spell().to_string(),
"Option < String >",
"already-Option ctor arg is NOT double-wrapped"
);
assert_eq!(
plan.leaves[3].ty.spell().to_string(),
"Option < & ZEncoding >"
);
assert!(
matches!(plan.variants[0].inputs[1], FoldArg::Leaf(2, true)),
"schema input marked passthrough"
);
assert!(plan.variants[1].clone, "borrowed identity arm clones");
let locals = vec![ident("sel"), ident("id"), ident("schema"), ident("enc")];
let s = emit_fold(plan, &locals, &src_qualify)
.to_token_stream()
.to_string();
assert!(s.contains("if sel < 0"), "selector absence gate: {}", s);
assert!(
s.contains("z_encoding_from_id (__p0 , schema)"),
"wrapped id unwrapped, passthrough schema passed directly: {}",
s
);
assert!(
s.contains("Clone :: clone"),
"identity arm clones the borrow: {}",
s
);
assert!(
s.contains("Some") && s.contains("None"),
"maps Option: {}",
s
);
}
#[test]
fn iterable_emit_shape() {
let plan = FoldPlan {
target: tref(syn::parse_quote!(ZKeyExpr)),
by_ref: false,
shape: FoldShape::Iterable(Box::new(FoldShape::Base)),
leaves: vec![FoldLeaf {
name: ident("kes"),
ty: tref(syn::parse_quote!(Vec<String>)),
}],
selector: None,
present: None,
variants: vec![FoldVariant {
ctor: Some(ident("z_keyexpr_try_from")),
fallible: true,
clone: false,
inputs: vec![FoldArg::Leaf(0, false)],
}],
};
let locals = vec![ident("kes")];
let s = emit_fold(&plan, &locals, &src_qualify)
.to_token_stream()
.to_string();
assert!(s.contains("into_iter"), "iterates: {}", s);
assert!(s.contains("collect"), "collects: {}", s);
assert!(
s.contains("Vec") && s.contains("z_keyexpr_try_from"),
"collects Result<Vec<_>> via per-elem ctor: {}",
s
);
assert!(!plan.produces_option());
}
#[test]
fn default_constructor_auto_applies_and_skips() {
let mut reg: Registry<()> = reg_with(&[
"fn z_keyexpr_try_from(s: String) -> Result<ZKeyExpr, Error> { todo!() }",
"fn z_keyexpr_intersects(a: &ZKeyExpr, b: &ZKeyExpr) -> bool { todo!() }",
"fn z_session_undeclare(s: &ZSession, k: ZKeyExpr) -> bool { todo!() }",
]);
let mut exp = Expansions::default();
exp.constructors.push(ConstructorDecl {
target: key("ZKeyExpr"),
variants: vec![Variant::Ctor(ident("z_keyexpr_try_from"))],
default: true,
});
exp.skip_construct
.insert((ident("z_session_undeclare"), ident("k")));
let declared: std::collections::HashSet<syn::Ident> =
["z_keyexpr_intersects", "z_session_undeclare"]
.iter()
.map(|s| ident(s))
.collect();
apply(
&mut reg,
&exp,
&declared,
&Default::default(),
&Default::default(),
)
.expect("apply");
assert!(reg
.expansion_plans
.contains_key(&(ident("z_keyexpr_intersects"), ident("a"))));
assert!(reg
.expansion_plans
.contains_key(&(ident("z_keyexpr_intersects"), ident("b"))));
assert!(!reg
.expansion_plans
.contains_key(&(ident("z_session_undeclare"), ident("k"))));
}
#[test]
fn default_constructor_skips_accessor_and_explicit_construct_errors() {
let mut reg: Registry<()> = reg_with(&[
"fn z_keyexpr_try_from(s: String) -> Result<ZKeyExpr, Error> { todo!() }",
"fn z_keyexpr_intersects(a: &ZKeyExpr, b: &ZKeyExpr) -> bool { todo!() }",
"fn z_keyexpr_clone(ke: &ZKeyExpr) -> ZKeyExpr { todo!() }",
]);
let accessor: std::collections::HashSet<syn::Ident> =
["z_keyexpr_clone"].iter().map(|s| ident(s)).collect();
let declared: std::collections::HashSet<syn::Ident> =
["z_keyexpr_intersects", "z_keyexpr_clone"]
.iter()
.map(|s| ident(s))
.collect();
let mut exp = Expansions::default();
exp.constructors.push(ConstructorDecl {
target: key("ZKeyExpr"),
variants: vec![Variant::Ctor(ident("z_keyexpr_try_from"))],
default: true,
});
apply(&mut reg, &exp, &declared, &accessor, &Default::default()).expect("apply");
assert!(reg
.expansion_plans
.contains_key(&(ident("z_keyexpr_intersects"), ident("a"))));
assert!(!reg
.expansion_plans
.contains_key(&(ident("z_keyexpr_clone"), ident("ke"))));
let mut reg2: Registry<()> = reg_with(&[
"fn z_keyexpr_try_from(s: String) -> Result<ZKeyExpr, Error> { todo!() }",
"fn z_keyexpr_clone(ke: &ZKeyExpr) -> ZKeyExpr { todo!() }",
]);
let mut exp2 = Expansions::default();
exp2.expands.push(ExpandDecl {
func: ident("z_keyexpr_clone"),
param: ident("ke"),
declared_target: Some(key("ZKeyExpr")),
sel: ExpandSel::Subset(vec![Variant::Ctor(ident("z_keyexpr_try_from"))]),
});
let err = apply(&mut reg2, &exp2, &declared, &accessor, &Default::default()).unwrap_err();
assert!(matches!(err, ExpandError::ConstructOnAccessor { .. }));
}
#[test]
fn recursive_input_nests_param_constructors() {
let mut reg: Registry<()> = reg_with(&[
"fn z_sample_new(key_expr: ZKeyExpr, payload: ZZBytes) -> ZSample { todo!() }",
"fn z_keyexpr_try_from(s: String) -> ZKeyExpr { todo!() }",
"fn z_zbytes_from_vec(b: Vec<u8>) -> ZZBytes { todo!() }",
"fn z_reply_sample(sample: ZSample) -> bool { todo!() }",
]);
let mut exp = Expansions::default();
exp.constructors.push(ConstructorDecl {
target: key("ZSample"),
variants: vec![Variant::Ctor(ident("z_sample_new"))],
default: true,
});
exp.constructors.push(ConstructorDecl {
target: key("ZKeyExpr"),
variants: vec![
Variant::Ctor(ident("z_keyexpr_try_from")),
Variant::Identity,
],
default: true,
});
exp.constructors.push(ConstructorDecl {
target: key("ZZBytes"),
variants: vec![Variant::Ctor(ident("z_zbytes_from_vec"))],
default: true,
});
let declared: std::collections::HashSet<syn::Ident> =
["z_reply_sample"].iter().map(|s| ident(s)).collect();
apply(
&mut reg,
&exp,
&declared,
&Default::default(),
&Default::default(),
)
.expect("apply");
let plan = reg
.expansion_plans
.get(&(ident("z_reply_sample"), ident("sample")))
.expect("sample plan");
assert_eq!(plan.selector, None);
assert_eq!(plan.variants.len(), 1);
let args = &plan.variants[0].inputs;
assert_eq!(args.len(), 2);
assert!(
matches!(args[0], FoldArg::Build(_)),
"key_expr is a nested build"
);
assert!(
matches!(args[1], FoldArg::Build(_)),
"payload is a nested build"
);
if let FoldArg::Build(b) = &args[0] {
assert!(b.selector.is_some(), "ZKeyExpr default input is combined");
assert_eq!(b.variants.len(), 2);
}
if let FoldArg::Build(b) = &args[1] {
assert!(b.selector.is_none(), "ZZBytes default input is single");
}
let leaf_tys: Vec<String> = plan
.leaves
.iter()
.map(|l| l.ty.spell().to_string())
.collect();
assert!(
leaf_tys.iter().any(|t| t.contains("i32")),
"selector leaf: {leaf_tys:?}"
);
assert!(
leaf_tys.iter().any(|t| t.contains("String")),
"try_from arg: {leaf_tys:?}"
);
}
#[test]
fn recursive_input_cycle_errors() {
let mut reg: Registry<()> = reg_with(&[
"fn make_a(b: B) -> A { todo!() }",
"fn make_b(a: A) -> B { todo!() }",
"fn consume_a(a: A) -> bool { todo!() }",
]);
let mut exp = Expansions::default();
exp.constructors.push(ConstructorDecl {
target: key("A"),
variants: vec![Variant::Ctor(ident("make_a"))],
default: true,
});
exp.constructors.push(ConstructorDecl {
target: key("B"),
variants: vec![Variant::Ctor(ident("make_b"))],
default: true,
});
let declared: std::collections::HashSet<syn::Ident> =
["consume_a"].iter().map(|s| ident(s)).collect();
let err = apply(
&mut reg,
&exp,
&declared,
&Default::default(),
&Default::default(),
)
.unwrap_err();
assert!(matches!(err, ExpandError::InputCycle { .. }), "got {err:?}");
}
#[test]
fn unknown_constructor_errors() {
use prebindgen_flat::types_util::ident;
let mut reg: Registry<()> =
reg_with(&["fn z_keyexpr_intersects(a: &ZKeyExpr, b: &ZKeyExpr) -> bool { todo!() }"]);
let mut exp = Expansions::default();
exp.expands.push(ExpandDecl {
func: ident("z_keyexpr_intersects"),
param: ident("a"),
declared_target: Some(key("ZKeyExpr")),
sel: ExpandSel::Subset(vec![Variant::Ctor(ident("z_keyexpr_try_from_typo"))]),
});
let err = apply(
&mut reg,
&exp,
&Default::default(),
&Default::default(),
&Default::default(),
)
.unwrap_err();
assert!(matches!(err, ExpandError::UnknownConstructor(_)), "{err}");
}
#[test]
fn constructor_target_mismatch_errors() {
use prebindgen_flat::types_util::ident;
let mut reg: Registry<()> = reg_with(&[
"fn z_sample_new(s: String) -> ZSample { todo!() }",
"fn z_keyexpr_intersects(a: &ZKeyExpr, b: &ZKeyExpr) -> bool { todo!() }",
]);
let mut exp = Expansions::default();
exp.expands.push(ExpandDecl {
func: ident("z_keyexpr_intersects"),
param: ident("a"),
declared_target: Some(key("ZKeyExpr")),
sel: ExpandSel::Subset(vec![Variant::Ctor(ident("z_sample_new"))]),
});
let err = apply(
&mut reg,
&exp,
&Default::default(),
&Default::default(),
&Default::default(),
)
.unwrap_err();
assert!(matches!(err, ExpandError::TargetMismatch { .. }), "{err}");
}
#[test]
fn invalid_declarations_collected() {
use prebindgen_flat::types_util::ident;
let mut reg: Registry<()> = reg_with(&[
"fn z_keyexpr_try_from(s: String) -> Result<ZKeyExpr, Error> { todo!() }",
"fn z_session_get(s: &ZSession, k: &ZKeyExpr) -> bool { todo!() }",
]);
let mut exp = Expansions::default();
for _ in 0..2 {
exp.constructors.push(ConstructorDecl {
target: key("ZKeyExpr"),
variants: vec![Variant::Ctor(ident("z_keyexpr_try_from"))],
default: true,
});
}
exp.constructors.push(ConstructorDecl {
target: key("ZEmpty"),
variants: vec![],
default: true,
});
for sel in [
ExpandSel::Subset(vec![Variant::Ctor(ident("z_keyexpr_try_from"))]),
ExpandSel::Subset(vec![]),
] {
exp.expands.push(ExpandDecl {
func: ident("z_session_get"),
param: ident("k"),
declared_target: Some(key("ZKeyExpr")),
sel,
});
}
let err = apply(
&mut reg,
&exp,
&Default::default(),
&Default::default(),
&Default::default(),
)
.unwrap_err();
let ExpandError::InvalidDeclarations { entries } = &err else {
panic!("expected InvalidDeclarations, got {err}");
};
assert_eq!(entries.len(), 4, "{err}");
let text = err.to_string();
assert!(
text.contains("duplicate constructor declaration for `ZKeyExpr`"),
"{text}"
);
assert!(
text.contains("constructor for `ZEmpty` declares no variants"),
"{text}"
);
assert!(
text.contains("expand for parameter `k` of `z_session_get` declares no variants"),
"{text}"
);
assert!(
text.contains("duplicate expand declaration for parameter `k` of `z_session_get`"),
"{text}"
);
}
#[test]
fn a_vec_param_does_not_match_its_elements_constructor() {
let mut reg: Registry<()> = reg_with(&[
"fn z_keyexpr_try_from(s: String) -> Result<ZKeyExpr, Error> { todo!() }",
"fn z_keyexpr_join_all(parts: Vec<ZKeyExpr>) -> bool { todo!() }",
]);
let mut exp = Expansions::default();
exp.constructors.push(ConstructorDecl {
target: key("ZKeyExpr"),
variants: vec![Variant::Ctor(ident("z_keyexpr_try_from"))],
default: true,
});
apply(
&mut reg,
&exp,
&[ident("z_keyexpr_join_all")].into_iter().collect(),
&Default::default(),
&Default::default(),
)
.expect("apply");
assert!(
!reg.expansion_plans
.contains_key(&(ident("z_keyexpr_join_all"), ident("parts"))),
"a Vec<ZKeyExpr> parameter must not be expanded as one ZKeyExpr; \
plans: {:?}",
reg.expansion_plans.keys().collect::<Vec<_>>()
);
}