crate::ix!();
pub fn finalize_operator_io_path(mut op_path: syn::Path) -> (syn::Path, syn::Ident) {
info!("finalize_operator_io_path: START");
info!(" original op_path = {}", quote::ToTokens::to_token_stream(&op_path));
if let Some(last_seg) = op_path.segments.last_mut() {
let old_ident = &last_seg.ident;
let new_io_ident = syn::Ident::new(
&format!("{}IO", old_ident),
old_ident.span(),
);
info!(" found last segment '{}'; renaming to '{}'", old_ident, new_io_ident);
last_seg.ident = new_io_ident.clone();
info!(" op_path now = {}", quote::ToTokens::to_token_stream(&op_path));
info!("finalize_operator_io_path: FINISH (renamed segment)");
(op_path, new_io_ident)
} else {
info!(" no last segment found, using error ident");
let err_ident = syn::Ident::new("_____Error_NoLastSeg", op_path.span());
info!("finalize_operator_io_path: FINISH (error ident = {})", err_ident);
(op_path, err_ident)
}
}
#[cfg(test)]
mod test_finalize_operator_io_path {
use super::*;
use syn::{parse_str, parse_quote, DeriveInput, Path, punctuated::Punctuated, GenericParam};
use syn::token::Comma;
use quote::ToTokens;
fn try_parse_path(s: &str) -> Result<Path, syn::Error> {
info!("try_parse_path: attempting to parse '{}'", s);
match parse_str::<Path>(s) {
Ok(p) => {
info!(" parse succeeded => {}", p.to_token_stream());
Ok(p)
}
Err(e) => {
info!(" parse failed => {}", e);
Err(e)
}
}
}
#[test]
fn test_finalize_operator_io_path_valid() -> Result<(), syn::Error> {
info!("test_finalize_operator_io_path_valid: START");
let original = try_parse_path("FooOp")?;
let (mutated_path, new_ident) = finalize_operator_io_path(original.clone());
assert_eq!(new_ident.to_string(), "FooOpIO");
let last_seg = mutated_path
.segments
.last()
.ok_or_else(|| syn::Error::new(
original.span(),
"Expected at least one segment after finalization"
))?;
assert_eq!(last_seg.ident.to_string(), "FooOpIO");
Ok(())
}
#[test]
fn test_finalize_operator_io_path_no_segments() -> Result<(), syn::Error> {
info!("test_finalize_operator_io_path_no_segments: START");
let empty_path = Path {
leading_colon: None,
segments: Punctuated::new(),
};
let (_, new_ident) = finalize_operator_io_path(empty_path.clone());
assert_eq!(new_ident.to_string(), "_____Error_NoLastSeg");
Ok(())
}
#[test]
fn test_parse_available_operators_attribute_ok() -> Result<(), syn::Error> {
info!("test_parse_available_operators_attribute_ok: START");
let derive_input: DeriveInput = parse_quote! {
#[available_operators(op="AddOp", op="SubOp<X>", op="MulOp<A,B>")]
pub struct MyWire<T> { field: T }
};
let (ops, gens) = match parse_available_operators_attribute(&derive_input) {
Ok((o, g)) => {
info!(" parse_available_operators_attribute => Ok");
(o, g)
}
Err(ts) => {
info!(" parse_available_operators_attribute => Error: {}", ts.to_string());
return Err(syn::Error::new(derive_input.ident.span(), ts.to_string()));
}
};
assert_eq!(ops.len(), 3);
assert!(gens.params.iter().any(|p| match p {
GenericParam::Type(t) => t.ident == "T",
_ => false,
}), "Expected generic param T in struct MyWire<T>");
Ok(())
}
#[test]
fn test_parse_available_operators_attribute_missing() -> Result<(), syn::Error> {
info!("test_parse_available_operators_attribute_missing: START");
let derive_input: DeriveInput = parse_quote! {
pub struct MyWire {}
};
let result = parse_available_operators_attribute(&derive_input);
assert!(result.is_err(), "Expected parse failure for missing attribute.");
Ok(())
}
#[test]
fn test_parse_available_operators_attribute_malformed_path() -> Result<(), syn::Error> {
info!("test_parse_available_operators_attribute_malformed_path: START");
let derive_input: DeriveInput = parse_quote! {
#[available_operators(op="Not<A<>")]
pub struct MyWire {}
};
let result = parse_available_operators_attribute(&derive_input);
assert!(result.is_err(), "Expected parse failure for malformed path in attribute.");
Ok(())
}
#[test]
fn test_operator_items_parser_multiple() -> Result<(), syn::Error> {
info!("test_operator_items_parser_multiple: START");
let attr_tokens = quote! { op="Foo", op="Bar<Baz>", op="Spam<Z,Q>" };
let parser = parse_quote! { #attr_tokens };
let items_parser: OperatorItemsParser = parser;
let items = items_parser.items();
assert_eq!(items.len(), 3);
Ok(())
}
#[test]
fn test_operator_items_parser_invalid_key() -> Result<(), syn::Error> {
info!("test_operator_items_parser_invalid_key: START");
let attr_tokens = quote! { something_else="Foo" };
let attempt = syn::parse2::<OperatorItemsParser>(attr_tokens);
assert!(attempt.is_err());
Ok(())
}
#[test]
fn test_build_network_io_enum_normal() -> Result<(), syn::Error> {
info!("test_build_network_io_enum_normal: START");
let enum_ident = syn::Ident::new("MyWireIO", proc_macro2::Span::call_site());
let op_items = vec![
OperatorSpecItem::new(parse_str::<Path>("AddOp")?),
OperatorSpecItem::new(parse_str::<Path>("MulOp<X>")?),
];
let (enum_ts,_) = build_network_io_enum(
&enum_ident,
&op_items,
"e! { <T> },
"e! { <T> },
"e! {}
);
let enum_str = enum_ts.to_string();
info!(" resulting enum ts: {}", enum_str);
assert!(enum_str.contains("enum MyWireIO < T >"));
assert!(enum_str.contains("None"));
assert!(enum_str.contains("AddOpIO"));
assert!(enum_str.contains("MulOpIO"));
Ok(())
}
#[test]
fn test_build_network_io_enum_empty_ops() -> Result<(), syn::Error> {
info!("test_build_network_io_enum_empty_ops: START");
let enum_ident = syn::Ident::new("EmptyWireIO", proc_macro2::Span::call_site());
let op_items = vec![];
let (enum_ts,_) = build_network_io_enum(
&enum_ident,
&op_items,
"e! {},
"e! {},
"e! {}
);
let enum_str = enum_ts.to_string();
info!(" resulting enum ts: {}", enum_str);
assert!(enum_str.contains("enum EmptyWireIO"));
assert!(enum_str.contains("None"));
Ok(())
}
#[test]
fn test_build_operator_signature_map_ok() -> Result<(), syn::Error> {
info!("test_build_operator_signature_map_ok: START");
let items = vec![
OperatorSpecItem::new(try_parse_path("AddOp")?),
OperatorSpecItem::new(try_parse_path("SubOp<T>")?),
OperatorSpecItem::new(try_parse_path("CustomOp<X>")?),
];
let sig_map = build_operator_signature_map(&items);
info!(" sig_map keys: {:?}", sig_map.keys());
assert_eq!(sig_map["AddOp"].to_string(), "AddOpOperatorSignature");
assert_eq!(sig_map["SubOp"].to_string(), "SubOpOperatorSignature");
assert_eq!(sig_map["CustomOp"].to_string(), "CustomOpOperatorSignature");
Ok(())
}
#[test]
fn test_merge_generics() -> Result<(), syn::Error> {
info!("test_merge_generics: START");
let wire_ast: syn::DeriveInput = syn::parse_quote! {
struct Wire<T, U> {}
};
let wire_gens = wire_ast.generics;
let op_ast: syn::DeriveInput = syn::parse_quote! {
struct Op<V, W> where V: Clone, W: Copy {}
};
let op_gens = op_ast.generics;
let merged = merge_generics(&wire_gens, &op_gens);
let (impl_gen, _, wc_opt) = merged.split_for_impl();
let merged_str = if let Some(wc) = wc_opt {
quote::quote! { #impl_gen #wc }.to_string()
} else {
quote::quote! { #impl_gen }.to_string()
};
info!(" merged_str = {}", merged_str);
assert!(merged_str.contains("T , U , V , W"), "Got: {}", merged_str);
assert!(merged_str.contains("where V : Clone , W : Copy"), "Got: {}", merged_str);
Ok(())
}
#[test]
fn test_combine_where_clauses() -> Result<(), syn::Error> {
info!("test_combine_where_clauses: START");
let clause_a: syn::WhereClause = parse_quote! { where T: Clone };
let clause_b: syn::WhereClause = parse_quote! { where U: Copy };
let combined = combine_where_clauses(Some(&clause_a), Some(&clause_b));
let combined_str = combined.to_string();
info!(" combined_str = {}", combined_str);
assert!(combined_str.contains("T : Clone , U : Copy"));
Ok(())
}
#[test]
fn test_build_single_operator_impl_basic() -> Result<(), syn::Error> {
info!("test_build_single_operator_impl_basic: START");
let wire_gens: Generics = parse_quote! { <T> };
let mut op_item = OperatorSpecItem::new(parse_quote! { AddOp<Z> });
let sig_map = {
let mut m = std::collections::HashMap::new();
m.insert("AddOp".to_string(), parse_quote! { AddOpOperatorSignature });
m
};
info!(" finalizing op_item with wire_gens <T>");
op_item = op_item.finalize_with_wire_gens(&wire_gens);
let bridging_ts = build_single_operator_impl(
&syn::Ident::new("MyWireIO", proc_macro2::Span::call_site()),
&op_item,
&sig_map,
&wire_gens
);
let bridging_str = bridging_ts.to_string();
info!(" bridging_str = {}", bridging_str);
assert!(bridging_str.contains("impl < T , OpTy0 >"));
assert!(bridging_str.contains("Operator < MyWireIO < T > >"));
assert!(bridging_str.contains("AddOp < OpTy0 >"));
Ok(())
}
#[test]
fn test_finalize_operator_io_path() {
info!("test_finalize_operator_io_path: START");
let p: syn::Path = parse_quote!(AddOp);
info!(" original path = {}", p.to_token_stream());
let (new_path, new_ident) = finalize_operator_io_path(p);
info!(" new path = {}", new_path.to_token_stream());
info!(" new ident = {}", new_ident);
assert_eq!(new_ident.to_string(), "AddOpIO");
let last_seg = new_path.segments.last().unwrap();
assert_eq!(last_seg.ident.to_string(), "AddOpIO");
}
#[test]
fn test_finalize_operator_io_path_no_last_seg() {
info!("test_finalize_operator_io_path_no_last_seg: START");
let p = syn::Path {
leading_colon: None,
segments: syn::punctuated::Punctuated::new(),
};
info!(" original path is empty");
let (new_path, new_ident) = finalize_operator_io_path(p);
info!(" new path = {}", new_path.to_token_stream());
info!(" new ident = {}", new_ident);
assert_eq!(new_ident.to_string(), "_____Error_NoLastSeg");
assert_eq!(new_path.segments.len(), 0, "Still no segments");
}
}