use crate::{ToTokens, *};
use proc_macro2::Delimiter;
use quote::{TokenStreamExt as _, quote};
use crate::plugin::{extract_derive_plugins, generate_plugin_chain};
use crate::{LifetimeName, RenameRule, process_enum, process_struct};
fn flatten_transparent_groups(input: TokenStream) -> TokenStream {
input
.into_iter()
.flat_map(|tt| match tt {
TokenTree::Group(group) if group.delimiter() == Delimiter::None => {
flatten_transparent_groups(group.stream())
}
TokenTree::Group(group) => {
let flattened_stream = flatten_transparent_groups(group.stream());
let mut new_group = proc_macro2::Group::new(group.delimiter(), flattened_stream);
new_group.set_span(group.span());
std::iter::once(TokenTree::Group(new_group)).collect()
}
other => std::iter::once(other).collect(),
})
.collect()
}
pub(crate) fn generate_static_decl(
type_name: &Ident,
facet_crate: &TokenStream,
has_type_or_const_generics: bool,
) -> TokenStream {
if has_type_or_const_generics {
return quote! {};
}
let type_name_str = type_name.to_string();
let screaming_snake_name = RenameRule::ScreamingSnakeCase.apply(&type_name_str);
let static_name_ident = quote::format_ident!("{}_SHAPE", screaming_snake_name);
quote! {
#[cfg(not(debug_assertions))]
static #static_name_ident: &'static #facet_crate::Shape = <#type_name as #facet_crate::Facet>::SHAPE;
}
}
pub fn facet_macros(input: TokenStream) -> TokenStream {
let input = flatten_transparent_groups(input);
let mut i = input.clone().to_token_iter();
match i.parse::<Cons<AdtDecl, EndOfStream>>() {
Ok(it) => {
let attrs = match &it.first {
AdtDecl::Struct(s) => &s.attributes,
AdtDecl::Enum(e) => &e.attributes,
};
let plugins = extract_derive_plugins(attrs);
if !plugins.is_empty() {
let facet_crate = {
let parsed_attrs = PAttrs::parse(attrs);
parsed_attrs.facet_crate()
};
if let Some(chain) = generate_plugin_chain(&input, &plugins, &facet_crate) {
return chain;
}
}
match it.first {
AdtDecl::Struct(parsed) => process_struct::process_struct(parsed),
AdtDecl::Enum(parsed) => process_enum::process_enum(parsed),
}
}
Err(err) => {
panic!("Could not parse type declaration: {input}\nError: {err}");
}
}
}
pub(crate) fn build_where_clauses(
where_clauses: Option<&WhereClauses>,
generics: Option<&GenericParams>,
opaque: bool,
facet_crate: &TokenStream,
custom_bounds: &[TokenStream],
) -> TokenStream {
let mut where_clause_tokens = TokenStream::new();
let mut has_clauses = false;
if let Some(wc) = where_clauses {
for c in wc.clauses.iter() {
if has_clauses {
where_clause_tokens.extend(quote! { , });
}
where_clause_tokens.extend(c.value.to_token_stream());
has_clauses = true;
}
}
if let Some(generics) = generics {
for p in generics.params.iter() {
match &p.value {
GenericParam::Lifetime { name, .. } => {
let facet_lifetime = LifetimeName(quote::format_ident!("{}", "Ê„"));
let lifetime = LifetimeName(name.name.clone());
if has_clauses {
where_clause_tokens.extend(quote! { , });
}
where_clause_tokens
.extend(quote! { #lifetime: #facet_lifetime, #facet_lifetime: #lifetime });
has_clauses = true;
}
GenericParam::Const { .. } => {
}
GenericParam::Type { name, .. } => {
if has_clauses {
where_clause_tokens.extend(quote! { , });
}
if opaque {
where_clause_tokens.extend(quote! { #name: 'Ê„ });
} else {
where_clause_tokens.extend(quote! { #name: #facet_crate::Facet<'Ê„> });
}
has_clauses = true;
}
}
}
}
for bound in custom_bounds {
if has_clauses {
where_clause_tokens.extend(quote! { , });
}
where_clause_tokens.extend(bound.clone());
has_clauses = true;
}
if !has_clauses {
quote! {}
} else {
quote! { where #where_clause_tokens }
}
}
pub(crate) fn build_type_params_call(
generics: Option<&GenericParams>,
opaque: bool,
facet_crate: &TokenStream,
) -> TokenStream {
if opaque {
return quote! {};
}
let mut type_params = Vec::new();
if let Some(generics) = generics {
for p in generics.params.iter() {
match &p.value {
GenericParam::Lifetime { .. } => {
}
GenericParam::Const { .. } => {
}
GenericParam::Type { name, .. } => {
let name_str = name.to_string();
type_params.push(quote! {
#facet_crate::TypeParam {
name: #name_str,
shape: <#name as #facet_crate::Facet>::SHAPE
}
});
}
}
}
}
if type_params.is_empty() {
quote! {}
} else {
quote! { .type_params(&[#(#type_params),*]) }
}
}
pub(crate) fn build_const_params_call(
generics: Option<&GenericParams>,
opaque: bool,
facet_crate: &TokenStream,
) -> TokenStream {
if opaque {
return quote! {};
}
let mut const_params = Vec::new();
if let Some(generics) = generics {
for p in generics.params.iter() {
if let GenericParam::Const { name, typ, .. } = &p.value {
let name_str = name.to_string();
let typ = typ.to_token_stream().to_string().replace(' ', "");
let primitive = typ.rsplit("::").next().unwrap_or(&typ);
let (kind, value) = match primitive {
"bool" => (
quote! { #facet_crate::ConstParamKind::Bool },
quote! { if #name { 1u64 } else { 0u64 } },
),
"char" => (
quote! { #facet_crate::ConstParamKind::Char },
quote! { #name as u32 as u64 },
),
"u8" => (
quote! { #facet_crate::ConstParamKind::U8 },
quote! { #name as u64 },
),
"u16" => (
quote! { #facet_crate::ConstParamKind::U16 },
quote! { #name as u64 },
),
"u32" => (
quote! { #facet_crate::ConstParamKind::U32 },
quote! { #name as u64 },
),
"u64" => (
quote! { #facet_crate::ConstParamKind::U64 },
quote! { #name as u64 },
),
"usize" => (
quote! { #facet_crate::ConstParamKind::Usize },
quote! { #name as u64 },
),
"i8" => (
quote! { #facet_crate::ConstParamKind::I8 },
quote! { (#name as i64) as u64 },
),
"i16" => (
quote! { #facet_crate::ConstParamKind::I16 },
quote! { (#name as i64) as u64 },
),
"i32" => (
quote! { #facet_crate::ConstParamKind::I32 },
quote! { (#name as i64) as u64 },
),
"i64" => (
quote! { #facet_crate::ConstParamKind::I64 },
quote! { (#name as i64) as u64 },
),
"isize" => (
quote! { #facet_crate::ConstParamKind::Isize },
quote! { (#name as i64) as u64 },
),
_ => continue,
};
const_params.push(quote! {
#facet_crate::ConstParam {
name: #name_str,
value: #value,
kind: #kind,
}
});
}
}
}
if const_params.is_empty() {
quote! {}
} else {
quote! { .const_params(&[#(#const_params),*]) }
}
}
pub(crate) fn generate_type_name_fn(
type_name: &Ident,
generics: Option<&GenericParams>,
opaque: bool,
facet_crate: &TokenStream,
) -> TokenStream {
let type_name_str = type_name.to_string();
let write_generics = (!opaque)
.then_some(generics)
.flatten()
.and_then(|generics| {
let params = generics.params.iter();
let write_each = params.filter_map(|param| match ¶m.value {
GenericParam::Lifetime { .. } => None,
GenericParam::Const { name, .. } => Some(quote! {
write!(f, "{:?}", #name)?;
}),
GenericParam::Type { name, .. } => Some(quote! {
<#name as #facet_crate::Facet>::SHAPE.write_type_name(f, opts)?;
}),
});
let mut tokens = TokenStream::new();
tokens.append_separated(write_each, quote! { write!(f, ", ")?; });
if tokens.is_empty() {
None
} else {
Some(tokens)
}
});
match write_generics {
Some(write_generics) => {
quote! {
|_shape, f, opts| {
write!(f, #type_name_str)?;
if let Some(opts) = opts.for_children() {
write!(f, "<")?;
#write_generics
write!(f, ">")?;
} else {
write!(f, "<…>")?;
}
Ok(())
}
}
}
None => quote! { |_shape, f, _opts| ::core::fmt::Write::write_str(f, #type_name_str) },
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_flatten_transparent_groups_simple() {
let input: TokenStream = quote::quote! { pub struct Foo; };
let flattened = flatten_transparent_groups(input.clone());
assert_eq!(flattened.to_string(), input.to_string());
}
#[test]
fn test_flatten_transparent_groups_with_none_delimiter() {
let pub_token: TokenStream = quote::quote! { pub };
let none_group = proc_macro2::Group::new(proc_macro2::Delimiter::None, pub_token.clone());
let mut input = TokenStream::new();
input.extend(std::iter::once(TokenTree::Group(none_group)));
input.extend(quote::quote! { struct Cat; });
let flattened = flatten_transparent_groups(input);
let expected: TokenStream = quote::quote! { pub struct Cat; };
assert_eq!(flattened.to_string(), expected.to_string());
}
#[test]
fn test_flatten_transparent_groups_preserves_braces() {
let input: TokenStream = quote::quote! { struct Foo { x: u32 } };
let flattened = flatten_transparent_groups(input.clone());
assert_eq!(flattened.to_string(), input.to_string());
}
#[test]
fn test_flatten_transparent_groups_nested() {
let inner: TokenStream = quote::quote! { pub };
let inner_group = proc_macro2::Group::new(proc_macro2::Delimiter::None, inner);
let outer_stream: TokenStream = std::iter::once(TokenTree::Group(inner_group)).collect();
let outer_group = proc_macro2::Group::new(proc_macro2::Delimiter::None, outer_stream);
let mut input = TokenStream::new();
input.extend(std::iter::once(TokenTree::Group(outer_group)));
input.extend(quote::quote! { struct Cat; });
let flattened = flatten_transparent_groups(input);
let expected: TokenStream = quote::quote! { pub struct Cat; };
assert_eq!(flattened.to_string(), expected.to_string());
}
#[test]
fn test_flatten_transparent_groups_inside_braces() {
let pub_token: TokenStream = quote::quote! { pub };
let none_group = proc_macro2::Group::new(proc_macro2::Delimiter::None, pub_token);
let mut brace_content = TokenStream::new();
brace_content.extend(std::iter::once(TokenTree::Group(none_group)));
brace_content.extend(quote::quote! { x: u32 });
let brace_group = proc_macro2::Group::new(proc_macro2::Delimiter::Brace, brace_content);
let mut input: TokenStream = quote::quote! { struct Foo };
input.extend(std::iter::once(TokenTree::Group(brace_group)));
let flattened = flatten_transparent_groups(input);
let expected: TokenStream = quote::quote! { struct Foo { pub x: u32 } };
assert_eq!(flattened.to_string(), expected.to_string());
}
#[test]
fn test_parse_struct_with_transparent_group_visibility() {
let pub_token: TokenStream = quote::quote! { pub };
let none_group = proc_macro2::Group::new(proc_macro2::Delimiter::None, pub_token);
let mut input = TokenStream::new();
input.extend(std::iter::once(TokenTree::Group(none_group)));
input.extend(quote::quote! { struct Cat; });
let flattened = flatten_transparent_groups(input);
let mut iter = flattened.to_token_iter();
let result = iter.parse::<Cons<AdtDecl, EndOfStream>>();
assert!(
result.is_ok(),
"Parsing should succeed after flattening transparent groups"
);
}
}