use proc_macro2::TokenStream;
use quote::{format_ident, quote};
#[test]
fn struct_unit() {
let ts: TokenStream = quote! { struct Unit; };
let item: syn::ItemStruct = syn::parse2(ts).unwrap();
assert_eq!(item.ident, "Unit");
assert!(item.fields.is_empty());
}
#[test]
fn struct_named_fields() {
let ts = quote! {
struct Point {
x: f64,
y: f64,
}
};
let item: syn::ItemStruct = syn::parse2(ts).unwrap();
assert_eq!(item.ident, "Point");
assert_eq!(item.fields.len(), 2);
}
#[test]
fn struct_tuple_fields() {
let ts = quote! { struct Pair(i32, i32); };
let item: syn::ItemStruct = syn::parse2(ts).unwrap();
assert_eq!(item.ident, "Pair");
assert_eq!(item.fields.len(), 2);
}
#[test]
fn struct_with_generics() {
let ts = quote! {
struct Wrapper<T> {
inner: T,
}
};
let item: syn::ItemStruct = syn::parse2(ts).unwrap();
assert_eq!(item.generics.params.len(), 1);
}
#[test]
fn struct_with_lifetime() {
let ts = quote! {
struct Borrowed<'a> {
data: &'a str,
}
};
let item: syn::ItemStruct = syn::parse2(ts).unwrap();
assert_eq!(item.generics.params.len(), 1);
}
#[test]
fn struct_with_derive() {
let ts = quote! {
#[derive(Debug, Clone)]
struct Tagged {
label: String,
}
};
let item: syn::ItemStruct = syn::parse2(ts).unwrap();
assert_eq!(item.attrs.len(), 1);
}
#[test]
fn struct_pub_visibility() {
let ts = quote! {
pub struct Public {
pub field: u8,
}
};
let item: syn::ItemStruct = syn::parse2(ts).unwrap();
assert!(matches!(item.vis, syn::Visibility::Public(_)));
}
#[test]
fn enum_unit_variants() {
let ts = quote! {
enum Color {
Red,
Green,
Blue,
}
};
let item: syn::ItemEnum = syn::parse2(ts).unwrap();
assert_eq!(item.ident, "Color");
assert_eq!(item.variants.len(), 3);
}
#[test]
fn enum_tuple_variant() {
let ts = quote! {
enum Shape {
Circle(f64),
Rect(f64, f64),
}
};
let item: syn::ItemEnum = syn::parse2(ts).unwrap();
assert_eq!(item.variants.len(), 2);
}
#[test]
fn enum_struct_variant() {
let ts = quote! {
enum Event {
Click { x: i32, y: i32 },
Key { code: u32 },
}
};
let item: syn::ItemEnum = syn::parse2(ts).unwrap();
assert_eq!(item.variants.len(), 2);
}
#[test]
fn enum_with_discriminant() {
let ts = quote! {
enum Status {
Ok = 0,
Err = 1,
}
};
let item: syn::ItemEnum = syn::parse2(ts).unwrap();
assert!(item.variants[0].discriminant.is_some());
}
#[test]
fn enum_with_generics() {
let ts = quote! {
enum Maybe<T> {
Some(T),
None,
}
};
let item: syn::ItemEnum = syn::parse2(ts).unwrap();
assert_eq!(item.generics.params.len(), 1);
}
#[test]
fn fn_empty_body() {
let ts = quote! { fn noop() {} };
let item: syn::ItemFn = syn::parse2(ts).unwrap();
assert_eq!(item.sig.ident, "noop");
assert!(item.sig.inputs.is_empty());
}
#[test]
fn fn_with_args() {
let ts = quote! { fn add(a: i32, b: i32) -> i32 { a + b } };
let item: syn::ItemFn = syn::parse2(ts).unwrap();
assert_eq!(item.sig.inputs.len(), 2);
assert!(item.sig.output != syn::ReturnType::Default);
}
#[test]
fn fn_async() {
let ts = quote! { async fn fetch() -> String { String::new() } };
let item: syn::ItemFn = syn::parse2(ts).unwrap();
assert!(item.sig.asyncness.is_some());
}
#[test]
fn fn_unsafe() {
let ts = quote! { unsafe fn dangerous() {} };
let item: syn::ItemFn = syn::parse2(ts).unwrap();
assert!(item.sig.unsafety.is_some());
}
#[test]
fn fn_with_where_clause() {
let ts = quote! {
fn process<T>(val: T) where T: std::fmt::Debug {}
};
let item: syn::ItemFn = syn::parse2(ts).unwrap();
assert!(item.sig.generics.where_clause.is_some());
}
#[test]
fn fn_with_body_statements() {
let ts = quote! {
fn compute() -> u32 {
let x = 1;
let y = 2;
x + y
}
};
let item: syn::ItemFn = syn::parse2(ts).unwrap();
assert_eq!(item.block.stmts.len(), 3);
}
#[test]
fn impl_inherent() {
let ts = quote! {
impl Foo {
fn bar(&self) {}
}
};
let item: syn::ItemImpl = syn::parse2(ts).unwrap();
assert!(item.trait_.is_none());
}
#[test]
fn impl_trait() {
let ts = quote! {
impl Clone for Foo {
fn clone(&self) -> Self { Foo }
}
};
let item: syn::ItemImpl = syn::parse2(ts).unwrap();
assert!(item.trait_.is_some());
}
#[test]
fn impl_multiple_methods() {
let ts = quote! {
impl Counter {
fn new() -> Self { Counter }
fn increment(&mut self) {}
fn value(&self) -> u32 { 0 }
}
};
let item: syn::ItemImpl = syn::parse2(ts).unwrap();
assert_eq!(item.items.len(), 3);
}
#[test]
fn impl_with_generics() {
let ts = quote! {
impl<T: Clone> Container<T> {
fn get(&self) -> T { unimplemented!() }
}
};
let item: syn::ItemImpl = syn::parse2(ts).unwrap();
assert_eq!(item.generics.params.len(), 1);
}
#[test]
fn impl_associated_const() {
let ts = quote! {
impl Limits {
const MAX: u32 = 100;
}
};
let item: syn::ItemImpl = syn::parse2(ts).unwrap();
assert_eq!(item.items.len(), 1);
}
#[test]
fn interpolate_ident() {
let name = format_ident!("Foo");
let ts = quote! { struct #name; };
let item: syn::ItemStruct = syn::parse2(ts).unwrap();
assert_eq!(item.ident, "Foo");
}
#[test]
fn interpolate_type() {
let ty: syn::Type = syn::parse_str("Vec<u8>").unwrap();
let ts = quote! {
struct Holder {
data: #ty,
}
};
let item: syn::ItemStruct = syn::parse2(ts).unwrap();
assert_eq!(item.fields.len(), 1);
}
#[test]
fn interpolate_literal_integer() {
let val = 42u32;
let ts = quote! { const N: u32 = #val; };
let text = ts.to_string();
assert!(text.contains("42"));
}
#[test]
fn interpolate_literal_string() {
let msg = "hello world";
let ts = quote! { const MSG: &str = #msg; };
let text = ts.to_string();
assert!(text.contains("hello world"));
}
#[test]
fn interpolate_expr() {
let expr: syn::Expr = syn::parse_str("a + b").unwrap();
let ts = quote! { fn result() -> i32 { #expr } };
let item: syn::ItemFn = syn::parse2(ts).unwrap();
assert_eq!(item.sig.ident, "result");
}
#[test]
fn interpolate_multiple_idents() {
let struct_name = format_ident!("MyStruct");
let field_name = format_ident!("my_field");
let ts = quote! {
struct #struct_name {
#field_name: u32,
}
};
let item: syn::ItemStruct = syn::parse2(ts).unwrap();
assert_eq!(item.ident, "MyStruct");
}
#[test]
fn interpolate_token_stream() {
let body: TokenStream = quote! { x + 1 };
let ts = quote! { fn calc() -> i32 { #body } };
let item: syn::ItemFn = syn::parse2(ts).unwrap();
assert!(!item.block.stmts.is_empty());
}
#[test]
fn repetition_fields() {
let names = vec![format_ident!("a"), format_ident!("b"), format_ident!("c")];
let ts = quote! {
struct S {
#(#names: u32,)*
}
};
let item: syn::ItemStruct = syn::parse2(ts).unwrap();
assert_eq!(item.fields.len(), 3);
}
#[test]
fn repetition_enum_variants() {
let variants = vec![format_ident!("X"), format_ident!("Y"), format_ident!("Z")];
let ts = quote! {
enum E {
#(#variants,)*
}
};
let item: syn::ItemEnum = syn::parse2(ts).unwrap();
assert_eq!(item.variants.len(), 3);
}
#[test]
fn repetition_statements() {
let assignments: Vec<TokenStream> = (0u32..3)
.map(|i| {
let var = format_ident!("x{}", i);
let val = i;
quote! { let #var = #val; }
})
.collect();
let ts = quote! { fn init() { #(#assignments)* } };
let item: syn::ItemFn = syn::parse2(ts).unwrap();
assert_eq!(item.block.stmts.len(), 3);
}
#[test]
fn repetition_with_separator() {
let args = vec![format_ident!("a"), format_ident!("b"), format_ident!("c")];
let ts = quote! { fn f(#(#args: i32),*) {} };
let item: syn::ItemFn = syn::parse2(ts).unwrap();
assert_eq!(item.sig.inputs.len(), 3);
}
#[test]
fn repetition_nested_pairs() {
let names = vec![format_ident!("x"), format_ident!("y")];
let types: Vec<syn::Type> = vec![
syn::parse_str("i32").unwrap(),
syn::parse_str("f64").unwrap(),
];
let ts = quote! {
struct Pair {
#(#names: #types,)*
}
};
let item: syn::ItemStruct = syn::parse2(ts).unwrap();
assert_eq!(item.fields.len(), 2);
}
#[test]
fn repetition_empty() {
let items: Vec<TokenStream> = vec![];
let ts = quote! {
struct Empty {
#(#items)*
}
};
let item: syn::ItemStruct = syn::parse2(ts).unwrap();
assert!(item.fields.is_empty());
}
#[test]
fn repetition_trait_bounds() {
let bounds = vec![quote! { Clone }, quote! { Debug }, quote! { Send }];
let ts = quote! { fn constrained<T: #(#bounds)+*>() {} };
let text = ts.to_string();
assert!(text.contains("Clone"));
assert!(text.contains("Debug"));
assert!(text.contains("Send"));
}
#[test]
fn repetition_match_arms() {
let variants = vec![format_ident!("A"), format_ident!("B")];
let values: Vec<u32> = vec![1, 2];
let ts = quote! {
fn to_num(e: E) -> u32 {
match e {
#(E::#variants => #values,)*
}
}
};
let item: syn::ItemFn = syn::parse2(ts).unwrap();
assert_eq!(item.sig.ident, "to_num");
}
#[test]
fn format_ident_simple() {
let id = format_ident!("my_var");
assert_eq!(id.to_string(), "my_var");
}
#[test]
fn format_ident_with_number() {
let id = format_ident!("field_{}", 42u32);
assert_eq!(id.to_string(), "field_42");
}
#[test]
fn format_ident_prefix_suffix() {
let base = format_ident!("Node");
let id = format_ident!("visit_{}", base);
assert_eq!(id.to_string(), "visit_Node");
}
#[test]
fn format_ident_in_loop() {
let idents: Vec<_> = (0u32..5).map(|i| format_ident!("arg{}", i)).collect();
assert_eq!(idents.len(), 5);
assert_eq!(idents[3].to_string(), "arg3");
}
#[test]
fn format_ident_uppercase() {
let name = "widget";
let id = format_ident!("CONST_{}", name.to_uppercase());
assert_eq!(id.to_string(), "CONST_WIDGET");
}
#[test]
fn format_ident_in_struct_generation() {
let prefix = "my";
let struct_name = format_ident!("{}Struct", prefix);
let getter = format_ident!("get_{}", prefix);
let ts = quote! {
impl #struct_name {
fn #getter(&self) -> u32 { 0 }
}
};
let item: syn::ItemImpl = syn::parse2(ts).unwrap();
assert_eq!(item.items.len(), 1);
}
#[test]
fn nested_quote_method_bodies() {
let methods: Vec<TokenStream> = vec!["alpha", "beta"]
.into_iter()
.map(|n| {
let name = format_ident!("{}", n);
quote! { fn #name(&self) -> &str { stringify!(#name) } }
})
.collect();
let ts = quote! {
impl Widget {
#(#methods)*
}
};
let item: syn::ItemImpl = syn::parse2(ts).unwrap();
assert_eq!(item.items.len(), 2);
}
#[test]
fn nested_quote_struct_and_impl() {
let name = format_ident!("Config");
let struct_ts = quote! {
struct #name {
value: u32,
}
};
let impl_ts = quote! {
impl #name {
fn new() -> Self { #name { value: 0 } }
}
};
let combined = quote! { #struct_ts #impl_ts };
let file: syn::File = syn::parse2(combined).unwrap();
assert_eq!(file.items.len(), 2);
}
#[test]
fn nested_quote_enum_with_impl() {
let variants: Vec<TokenStream> = vec!["On", "Off"]
.into_iter()
.map(|v| {
let id = format_ident!("{}", v);
quote! { #id }
})
.collect();
let ts = quote! {
enum Toggle {
#(#variants,)*
}
};
let item: syn::ItemEnum = syn::parse2(ts).unwrap();
assert_eq!(item.variants.len(), 2);
}
#[test]
fn nested_quote_conditional_field() {
let include_extra = true;
let extra_field = if include_extra {
quote! { extra: bool, }
} else {
quote! {}
};
let ts = quote! {
struct Flexible {
base: u32,
#extra_field
}
};
let item: syn::ItemStruct = syn::parse2(ts).unwrap();
assert_eq!(item.fields.len(), 2);
}
#[test]
fn nested_quote_builder_pattern() {
let fields = [("width", "u32"), ("height", "u32")];
let setters: Vec<TokenStream> = fields
.iter()
.map(|(name, _ty)| {
let field = format_ident!("{}", name);
quote! {
fn #field(mut self, val: u32) -> Self {
self.#field = val;
self
}
}
})
.collect();
let ts = quote! {
impl Builder {
#(#setters)*
}
};
let item: syn::ItemImpl = syn::parse2(ts).unwrap();
assert_eq!(item.items.len(), 2);
}
#[test]
fn to_string_simple() {
let ts = quote! { let x = 5; };
let s = ts.to_string();
assert!(s.contains("let"));
assert!(s.contains("x"));
assert!(s.contains("5"));
}
#[test]
fn to_string_preserves_structure() {
let ts = quote! { fn add(a: i32, b: i32) -> i32 { a + b } };
let s = ts.to_string();
assert!(s.contains("fn add"));
assert!(s.contains("-> i32"));
}
#[test]
fn to_string_with_attributes() {
let ts = quote! {
#[inline]
fn fast() {}
};
let s = ts.to_string();
assert!(s.contains("inline"));
}
#[test]
fn to_string_interpolated_values() {
let n = format_ident!("counter");
let v = 99u32;
let ts = quote! { static #n: u32 = #v; };
let s = ts.to_string();
assert!(s.contains("counter"));
assert!(s.contains("99"));
}
#[test]
fn to_string_roundtrip_parse() {
let ts = quote! { struct Roundtrip { val: u8 } };
let s = ts.to_string();
let reparsed: TokenStream = s.parse().unwrap();
let item: syn::ItemStruct = syn::parse2(reparsed).unwrap();
assert_eq!(item.ident, "Roundtrip");
}
#[test]
fn parse_back_file() {
let ts = quote! {
struct A;
struct B;
fn c() {}
};
let file: syn::File = syn::parse2(ts).unwrap();
assert_eq!(file.items.len(), 3);
}
#[test]
fn parse_back_use_statement() {
let ts = quote! { use std::collections::HashMap; };
let item: syn::ItemUse = syn::parse2(ts).unwrap();
let text = quote! { #item }.to_string();
assert!(text.contains("HashMap"));
}
#[test]
fn parse_back_trait_def() {
let ts = quote! {
trait Greet {
fn hello(&self) -> String;
}
};
let item: syn::ItemTrait = syn::parse2(ts).unwrap();
assert_eq!(item.ident, "Greet");
assert_eq!(item.items.len(), 1);
}
#[test]
fn parse_back_type_alias() {
let ts = quote! { type Result<T> = std::result::Result<T, Error>; };
let item: syn::ItemType = syn::parse2(ts).unwrap();
assert_eq!(item.ident, "Result");
}
#[test]
fn parse_back_const() {
let ts = quote! { const PI: f64 = 3.14; };
let item: syn::ItemConst = syn::parse2(ts).unwrap();
assert_eq!(item.ident, "PI");
}
#[test]
fn parse_back_static() {
let ts = quote! { static COUNT: u32 = 0; };
let item: syn::ItemStatic = syn::parse2(ts).unwrap();
assert_eq!(item.ident, "COUNT");
}
#[test]
fn parse_back_mod() {
let ts = quote! {
mod inner {
fn hidden() {}
}
};
let item: syn::ItemMod = syn::parse2(ts).unwrap();
assert_eq!(item.ident, "inner");
}
#[test]
fn parse_back_extern_block() {
let ts = quote! {
unsafe extern "C" {
fn puts(s: *const u8) -> i32;
}
};
let item: syn::ItemForeignMod = syn::parse2(ts).unwrap();
assert_eq!(item.items.len(), 1);
}
#[test]
fn generate_from_trait_impl() {
let source = format_ident!("String");
let target = format_ident!("MyType");
let ts = quote! {
impl From<#source> for #target {
fn from(val: #source) -> Self {
#target
}
}
};
let item: syn::ItemImpl = syn::parse2(ts).unwrap();
assert!(item.trait_.is_some());
}
#[test]
fn generate_display_impl() {
let name = format_ident!("Token");
let ts = quote! {
impl std::fmt::Display for #name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", stringify!(#name))
}
}
};
let item: syn::ItemImpl = syn::parse2(ts).unwrap();
assert!(item.trait_.is_some());
}
#[test]
fn generate_default_impl() {
let name = format_ident!("Settings");
let ts = quote! {
impl Default for #name {
fn default() -> Self {
#name { verbose: false }
}
}
};
let item: syn::ItemImpl = syn::parse2(ts).unwrap();
assert!(item.trait_.is_some());
}
#[test]
fn generate_new_constructor() {
let fields = [("name", "String"), ("age", "u32")];
let params: Vec<TokenStream> = fields
.iter()
.map(|(n, t)| {
let n = format_ident!("{}", n);
let t: syn::Type = syn::parse_str(t).unwrap();
quote! { #n: #t }
})
.collect();
let inits: Vec<TokenStream> = fields
.iter()
.map(|(n, _)| {
let n = format_ident!("{}", n);
quote! { #n }
})
.collect();
let ts = quote! {
impl Person {
fn new(#(#params),*) -> Self {
Self { #(#inits),* }
}
}
};
let item: syn::ItemImpl = syn::parse2(ts).unwrap();
assert_eq!(item.items.len(), 1);
}
#[test]
fn generate_getters() {
let fields = [("name", "String"), ("id", "u64")];
let methods: Vec<TokenStream> = fields
.iter()
.map(|(name, ty)| {
let getter = format_ident!("{}", name);
let ret: syn::Type = syn::parse_str(ty).unwrap();
quote! { fn #getter(&self) -> &#ret { &self.#getter } }
})
.collect();
let ts = quote! {
impl Record {
#(#methods)*
}
};
let item: syn::ItemImpl = syn::parse2(ts).unwrap();
assert_eq!(item.items.len(), 2);
}
#[test]
fn generate_module_with_reexport() {
let mod_name = format_ident!("generated");
let ts = quote! {
mod #mod_name {
pub struct Inner;
}
};
let item: syn::ItemMod = syn::parse2(ts).unwrap();
assert_eq!(item.ident, "generated");
}
#[test]
fn generate_closure_expression() {
let ts = quote! {
fn apply() -> u32 {
let f = |x: u32| x + 1;
f(5)
}
};
let item: syn::ItemFn = syn::parse2(ts).unwrap();
assert_eq!(item.sig.ident, "apply");
}
#[test]
fn interpolate_path() {
let ts = quote! {
fn make() -> std::io::Result<()> {
Ok(())
}
};
let item: syn::ItemFn = syn::parse2(ts).unwrap();
assert!(item.sig.output != syn::ReturnType::Default);
}
#[test]
fn repetition_chained_calls() {
let methods = vec![format_ident!("step1"), format_ident!("step2")];
let ts = quote! {
fn chain(b: Builder) -> Builder {
b #(.#methods())*
}
};
let text = ts.to_string();
assert!(text.contains("step1"));
assert!(text.contains("step2"));
}
#[test]
fn empty_token_stream() {
let ts = quote! {};
assert!(ts.is_empty());
}
#[test]
fn token_stream_extend() {
let mut ts = quote! { struct A; };
let more = quote! { struct B; };
ts.extend(more);
let file: syn::File = syn::parse2(ts).unwrap();
assert_eq!(file.items.len(), 2);
}
#[test]
fn format_ident_raw_keyword() {
let id = format_ident!("r#type");
let ts = quote! { let #id = 5; };
let text = ts.to_string();
assert!(text.contains("r#type") || text.contains("r # type"));
}