use crate::{Attribute, AttributeInner, FacetInner, IParse, Ident, ToTokenIter, TokenStream};
use quote::quote;
#[derive(Debug, Clone)]
pub enum PluginRef {
Simple(String),
Path {
crate_name: String,
plugin_name: String,
},
}
impl PluginRef {
pub fn crate_path(&self) -> TokenStream {
match self {
PluginRef::Simple(name) => {
let snake_case = to_snake_case(name);
let crate_name = format!("facet_{snake_case}");
let crate_ident = quote::format_ident!("{}", crate_name);
quote! { ::#crate_ident }
}
PluginRef::Path { crate_name, .. } => {
let crate_ident = quote::format_ident!("{}", crate_name);
quote! { ::#crate_ident }
}
}
}
}
pub fn extract_derive_plugins(attrs: &[Attribute]) -> Vec<PluginRef> {
let mut plugins = Vec::new();
for attr in attrs {
if let AttributeInner::Facet(facet_attr) = &attr.body.content {
for inner in facet_attr.inner.content.iter().map(|d| &d.value) {
if let FacetInner::Simple(simple) = inner
&& simple.key == "derive"
{
if let Some(ref args) = simple.args {
match args {
crate::AttrArgs::Parens(parens) => {
plugins.extend(parse_plugin_list(&parens.content));
}
crate::AttrArgs::Equals(_) => {
}
}
}
}
}
}
}
plugins
}
fn parse_plugin_list(tokens: &[crate::TokenTree]) -> Vec<PluginRef> {
let mut plugins = Vec::new();
let mut iter = tokens.iter().cloned().peekable();
while iter.peek().is_some() {
let mut item_tokens = Vec::new();
while let Some(tt) = iter.peek() {
if let proc_macro2::TokenTree::Punct(p) = tt
&& p.as_char() == ','
{
iter.next(); break;
}
item_tokens.push(iter.next().unwrap());
}
if let Some(plugin_ref) = parse_plugin_ref(&item_tokens) {
plugins.push(plugin_ref);
}
}
plugins
}
fn parse_plugin_ref(tokens: &[proc_macro2::TokenTree]) -> Option<PluginRef> {
if tokens.is_empty() {
return None;
}
let has_path_sep = tokens.windows(2).any(|w| {
matches!((&w[0], &w[1]),
(proc_macro2::TokenTree::Punct(p1), proc_macro2::TokenTree::Punct(p2))
if p1.as_char() == ':' && p2.as_char() == ':')
});
if has_path_sep {
let mut iter = tokens.iter();
let crate_name = match iter.next() {
Some(proc_macro2::TokenTree::Ident(id)) => id.to_string(),
_ => return None,
};
match (iter.next(), iter.next()) {
(Some(proc_macro2::TokenTree::Punct(p1)), Some(proc_macro2::TokenTree::Punct(p2)))
if p1.as_char() == ':' && p2.as_char() == ':' => {}
_ => return None,
}
let plugin_name = match iter.next() {
Some(proc_macro2::TokenTree::Ident(id)) => id.to_string(),
_ => return None,
};
Some(PluginRef::Path {
crate_name,
plugin_name,
})
} else {
match tokens.first() {
Some(proc_macro2::TokenTree::Ident(id)) => Some(PluginRef::Simple(id.to_string())),
_ => None,
}
}
}
pub fn plugin_to_crate_path(plugin_name: &str) -> TokenStream {
let snake_case = to_snake_case(plugin_name);
let crate_name = format!("facet_{snake_case}");
let crate_ident = quote::format_ident!("{}", crate_name);
quote! { ::#crate_ident }
}
fn to_snake_case(s: &str) -> String {
let mut result = String::new();
for (i, c) in s.chars().enumerate() {
if c.is_uppercase() {
if i > 0 {
result.push('_');
}
result.push(c.to_ascii_lowercase());
} else {
result.push(c);
}
}
result
}
fn strip_derive_attrs(tokens: TokenStream) -> TokenStream {
let mut result = TokenStream::new();
let mut iter = tokens.into_iter().peekable();
while let Some(tt) = iter.next() {
if let proc_macro2::TokenTree::Punct(p) = &tt
&& p.as_char() == '#'
&& let Some(proc_macro2::TokenTree::Group(g)) = iter.peek()
&& g.delimiter() == proc_macro2::Delimiter::Bracket
{
let inner = g.stream();
if let Some(filtered) = strip_plugin_items_from_facet_attr(&inner) {
if filtered.is_empty() {
iter.next(); continue;
} else {
result.extend(std::iter::once(tt));
iter.next(); result.extend(std::iter::once(proc_macro2::TokenTree::Group(
proc_macro2::Group::new(proc_macro2::Delimiter::Bracket, filtered),
)));
continue;
}
}
}
result.extend(std::iter::once(tt));
}
result
}
fn strip_plugin_items_from_facet_attr(inner: &TokenStream) -> Option<TokenStream> {
let mut iter = inner.clone().into_iter().peekable();
let facet_ident = match iter.next() {
Some(proc_macro2::TokenTree::Ident(id)) if id == "facet" => id,
_ => return None,
};
let group = match iter.next() {
Some(proc_macro2::TokenTree::Group(g))
if g.delimiter() == proc_macro2::Delimiter::Parenthesis =>
{
g
}
_ => return None,
};
let filtered_content = strip_plugin_items_from_content(group.stream());
let mut result = TokenStream::new();
result.extend(std::iter::once(proc_macro2::TokenTree::Ident(facet_ident)));
result.extend(std::iter::once(proc_macro2::TokenTree::Group(
proc_macro2::Group::new(proc_macro2::Delimiter::Parenthesis, filtered_content),
)));
Some(result)
}
fn strip_plugin_items_from_content(content: TokenStream) -> TokenStream {
let mut items: Vec<TokenStream> = Vec::new();
let mut current_item = TokenStream::new();
let tokens: Vec<proc_macro2::TokenTree> = content.into_iter().collect();
for tt in &tokens {
if let proc_macro2::TokenTree::Punct(p) = tt
&& p.as_char() == ','
{
if !current_item.is_empty() && !is_plugin_item(¤t_item) {
items.push(current_item);
}
current_item = TokenStream::new();
continue;
}
current_item.extend(std::iter::once(tt.clone()));
}
if !current_item.is_empty() && !is_plugin_item(¤t_item) {
items.push(current_item);
}
let mut result = TokenStream::new();
for (idx, item) in items.iter().enumerate() {
if idx > 0 {
result.extend(std::iter::once(proc_macro2::TokenTree::Punct(
proc_macro2::Punct::new(',', proc_macro2::Spacing::Alone),
)));
}
result.extend(item.clone());
}
result
}
fn is_plugin_item(item: &TokenStream) -> bool {
let mut iter = item.clone().into_iter();
if let Some(proc_macro2::TokenTree::Ident(id)) = iter.next() {
let name = id.to_string();
if name == "derive" {
return true;
}
}
false
}
pub fn generate_plugin_chain(
input_tokens: &TokenStream,
plugins: &[PluginRef],
facet_crate: &TokenStream,
) -> Option<TokenStream> {
if plugins.is_empty() {
return None;
}
let plugin_paths: Vec<TokenStream> = plugins
.iter()
.map(|p| {
let crate_path = p.crate_path();
quote! { #crate_path::__facet_invoke }
})
.collect();
let first = &plugin_paths[0];
let rest: Vec<_> = plugin_paths[1..].iter().collect();
let remaining = if rest.is_empty() {
quote! {}
} else {
quote! { #(#rest),* }
};
Some(quote! {
#first! {
@tokens { #input_tokens }
@remaining { #remaining }
@plugins { }
@facet_crate { #facet_crate }
}
})
}
pub fn facet_finalize(input: TokenStream) -> TokenStream {
let mut iter = input.to_token_iter();
let mut tokens: Option<TokenStream> = None;
let mut plugins_section: Option<TokenStream> = None;
let mut facet_crate: Option<TokenStream> = None;
while let Ok(section) = iter.parse::<FinalizeSection>() {
match section.marker.name.to_string().as_str() {
"tokens" => {
tokens = Some(section.content.content);
}
"plugins" => {
plugins_section = Some(section.content.content);
}
"facet_crate" => {
facet_crate = Some(section.content.content);
}
other => {
let msg = format!("unknown section in __facet_finalize: @{other}");
return quote! { compile_error!(#msg); };
}
}
}
let tokens = match tokens {
Some(t) => t,
None => {
return quote! { compile_error!("__facet_finalize: missing @tokens section"); };
}
};
let facet_crate = facet_crate.unwrap_or_else(|| quote! { ::facet });
let filtered_tokens = strip_derive_attrs(tokens.clone());
let mut type_iter = filtered_tokens.clone().to_token_iter();
let facet_impl = match type_iter.parse::<crate::Cons<crate::AdtDecl, crate::EndOfStream>>() {
Ok(it) => match it.first {
crate::AdtDecl::Struct(parsed) => crate::process_struct::process_struct(parsed),
crate::AdtDecl::Enum(parsed) => crate::process_enum::process_enum(parsed),
},
Err(err) => {
let msg = format!("__facet_finalize: could not parse type: {err}");
return quote! { compile_error!(#msg); };
}
};
let plugin_impls = if let Some(plugins_tokens) = plugins_section {
extract_plugin_templates(plugins_tokens, &filtered_tokens, &facet_crate)
} else {
vec![]
};
quote! {
#facet_impl
#(#plugin_impls)*
}
}
struct PluginTemplate {
#[allow(dead_code)] name: String,
template: TokenStream,
}
fn extract_plugin_templates(
plugins_tokens: TokenStream,
type_tokens: &TokenStream,
facet_crate: &TokenStream,
) -> Vec<TokenStream> {
let plugins = parse_plugin_sections(plugins_tokens);
let parsed_type = match facet_macro_parse::parse_type(type_tokens.clone()) {
Ok(ty) => ty,
Err(e) => {
let msg = format!("failed to parse type for plugin templates: {e}");
return vec![quote! { compile_error!(#msg); }];
}
};
plugins
.into_iter()
.map(|plugin| evaluate_template(plugin.template, &parsed_type, facet_crate))
.collect()
}
fn parse_plugin_sections(tokens: TokenStream) -> Vec<PluginTemplate> {
let mut plugins = Vec::new();
let mut iter = tokens.into_iter().peekable();
while let Some(tt) = iter.next() {
if let proc_macro2::TokenTree::Punct(p) = &tt
&& p.as_char() == '@'
{
if let Some(proc_macro2::TokenTree::Ident(id)) = iter.peek()
&& *id == "plugin"
{
iter.next();
if let Some(proc_macro2::TokenTree::Group(g)) = iter.next()
&& g.delimiter() == proc_macro2::Delimiter::Brace
&& let Some(plugin) = parse_plugin_content(g.stream())
{
plugins.push(plugin);
}
}
}
}
plugins
}
fn parse_plugin_content(tokens: TokenStream) -> Option<PluginTemplate> {
let mut name: Option<String> = None;
let mut template: Option<TokenStream> = None;
let mut iter = tokens.into_iter().peekable();
while let Some(tt) = iter.next() {
if let proc_macro2::TokenTree::Punct(p) = &tt
&& p.as_char() == '@'
&& let Some(proc_macro2::TokenTree::Ident(id)) = iter.peek()
{
let key = id.to_string();
iter.next();
if let Some(proc_macro2::TokenTree::Group(g)) = iter.next()
&& g.delimiter() == proc_macro2::Delimiter::Brace
{
match key.as_str() {
"name" => {
let content = g.stream().into_iter().collect::<Vec<_>>();
if let Some(proc_macro2::TokenTree::Literal(lit)) = content.first() {
let s = lit.to_string();
name = Some(s.trim_matches('"').to_string());
}
}
"template" => {
template = Some(g.stream());
}
_ => {}
}
}
}
}
match (name, template) {
(Some(n), Some(t)) => Some(PluginTemplate {
name: n,
template: t,
}),
_ => None,
}
}
fn evaluate_template(
template: TokenStream,
parsed_type: &facet_macro_parse::PType,
_facet_crate: &TokenStream,
) -> TokenStream {
let mut ctx = EvalContext::new(parsed_type);
evaluate_with_context(template, &mut ctx)
}
struct EvalContext<'a> {
parsed_type: &'a facet_macro_parse::PType,
stack: Vec<ContextFrame<'a>>,
}
enum ContextFrame<'a> {
Variant {
variant: &'a facet_macro_parse::PVariant,
},
Field {
field: &'a facet_macro_parse::PStructField,
index: usize,
},
Attr {
attr: &'a facet_macro_parse::PFacetAttr,
},
}
impl<'a> EvalContext<'a> {
const fn new(parsed_type: &'a facet_macro_parse::PType) -> Self {
Self {
parsed_type,
stack: Vec::new(),
}
}
fn push(&mut self, frame: ContextFrame<'a>) {
self.stack.push(frame);
}
fn pop(&mut self) {
self.stack.pop();
}
fn current_variant(&self) -> Option<&'a facet_macro_parse::PVariant> {
self.stack.iter().rev().find_map(|f| match f {
ContextFrame::Variant { variant } => Some(*variant),
_ => None,
})
}
fn current_field(&self) -> Option<(&'a facet_macro_parse::PStructField, usize)> {
self.stack.iter().rev().find_map(|f| match f {
ContextFrame::Field { field, index } => Some((*field, *index)),
_ => None,
})
}
fn current_attr(&self) -> Option<&'a facet_macro_parse::PFacetAttr> {
self.stack.iter().rev().find_map(|f| match f {
ContextFrame::Attr { attr } => Some(*attr),
_ => None,
})
}
fn current_fields(&self) -> Option<&'a [facet_macro_parse::PStructField]> {
if let Some(variant) = self.current_variant() {
return match &variant.kind {
facet_macro_parse::PVariantKind::Tuple { fields } => Some(fields),
facet_macro_parse::PVariantKind::Struct { fields } => Some(fields),
facet_macro_parse::PVariantKind::Unit => None,
};
}
if let facet_macro_parse::PType::Struct(s) = self.parsed_type {
return match &s.kind {
facet_macro_parse::PStructKind::Struct { fields } => Some(fields),
facet_macro_parse::PStructKind::TupleStruct { fields } => Some(fields),
facet_macro_parse::PStructKind::UnitStruct => None,
};
}
None
}
fn current_attrs(&self) -> &'a facet_macro_parse::PAttrs {
if let Some((field, _)) = self.current_field() {
return &field.attrs;
}
if let Some(variant) = self.current_variant() {
return &variant.attrs;
}
match self.parsed_type {
facet_macro_parse::PType::Struct(s) => &s.container.attrs,
facet_macro_parse::PType::Enum(e) => &e.container.attrs,
}
}
}
struct AttrQuery {
ns: String,
key: String,
}
impl AttrQuery {
fn parse(tokens: TokenStream) -> Option<Self> {
let mut iter = tokens.into_iter();
let ns = match iter.next() {
Some(proc_macro2::TokenTree::Ident(id)) => id.to_string(),
_ => return None,
};
match (iter.next(), iter.next()) {
(Some(proc_macro2::TokenTree::Punct(p1)), Some(proc_macro2::TokenTree::Punct(p2)))
if p1.as_char() == ':' && p2.as_char() == ':' => {}
_ => return None,
}
let key = match iter.next() {
Some(proc_macro2::TokenTree::Ident(id)) => id.to_string(),
_ => return None,
};
Some(AttrQuery { ns, key })
}
fn matches(&self, attr: &facet_macro_parse::PFacetAttr) -> bool {
if let Some(ref ns) = attr.ns {
*ns == self.ns && attr.key == self.key
} else {
false
}
}
fn find_in<'a>(
&self,
attrs: &'a [facet_macro_parse::PFacetAttr],
) -> Option<&'a facet_macro_parse::PFacetAttr> {
attrs.iter().find(|a| self.matches(a))
}
}
fn evaluate_with_context(template: TokenStream, ctx: &mut EvalContext<'_>) -> TokenStream {
let mut output = TokenStream::new();
let mut iter = template.into_iter().peekable();
while let Some(tt) = iter.next() {
match &tt {
proc_macro2::TokenTree::Punct(p) if p.as_char() == '@' => {
handle_directive(&mut iter, ctx, &mut output);
}
proc_macro2::TokenTree::Group(g) => {
let inner = evaluate_with_context(g.stream(), ctx);
let new_group = proc_macro2::Group::new(g.delimiter(), inner);
output.extend(std::iter::once(proc_macro2::TokenTree::Group(new_group)));
}
_ => {
output.extend(std::iter::once(tt));
}
}
}
output
}
fn handle_directive(
iter: &mut std::iter::Peekable<proc_macro2::token_stream::IntoIter>,
ctx: &mut EvalContext<'_>,
output: &mut TokenStream,
) {
let Some(next) = iter.next() else {
output.extend(quote! { @ });
return;
};
let proc_macro2::TokenTree::Ident(directive_ident) = &next else {
output.extend(quote! { @ });
output.extend(std::iter::once(next));
return;
};
let directive = directive_ident.to_string();
match directive.as_str() {
"Self" => emit_self_type(ctx, output),
"for_variant" => handle_for_variant(iter, ctx, output),
"for_field" => handle_for_field(iter, ctx, output),
"if_attr" => handle_if_attr(iter, ctx, output),
"if_field_attr" => handle_if_field_attr(iter, ctx, output),
"if_any_field_attr" => handle_if_any_field_attr(iter, ctx, output),
"if_struct" => handle_if_struct(iter, ctx, output),
"if_enum" => handle_if_enum(iter, ctx, output),
"if_unit_variant" => handle_if_unit_variant(iter, ctx, output),
"if_tuple_variant" => handle_if_tuple_variant(iter, ctx, output),
"if_struct_variant" => handle_if_struct_variant(iter, ctx, output),
"variant_name" => emit_variant_name(ctx, output),
"variant_pattern" => emit_variant_pattern(ctx, output),
"variant_pattern_only" => handle_variant_pattern_only(iter, ctx, output),
"field_name" => emit_field_name(ctx, output),
"field_type" => emit_field_type(ctx, output),
"field_expr" => emit_field_expr(ctx, output),
"attr_args" => emit_attr_args(ctx, output),
"doc" => emit_doc(ctx, output),
"field_default_expr" => emit_field_default_expr(ctx, output),
"variant_default_construction" => emit_variant_default_construction(ctx, output),
"format_doc_comment" => emit_format_doc_comment(ctx, output),
_ => {
output.extend(quote! { @ });
output.extend(std::iter::once(next.clone()));
}
}
}
fn emit_self_type(ctx: &EvalContext<'_>, output: &mut TokenStream) {
let name = ctx.parsed_type.name();
output.extend(quote! { #name });
}
fn handle_for_variant(
iter: &mut std::iter::Peekable<proc_macro2::token_stream::IntoIter>,
ctx: &mut EvalContext<'_>,
output: &mut TokenStream,
) {
let Some(proc_macro2::TokenTree::Group(body_group)) = iter.next() else {
return; };
let body = body_group.stream();
let facet_macro_parse::PType::Enum(e) = ctx.parsed_type else {
return;
};
for variant in &e.variants {
ctx.push(ContextFrame::Variant { variant });
let expanded = evaluate_with_context(body.clone(), ctx);
output.extend(expanded);
ctx.pop();
}
}
fn handle_for_field(
iter: &mut std::iter::Peekable<proc_macro2::token_stream::IntoIter>,
ctx: &mut EvalContext<'_>,
output: &mut TokenStream,
) {
let Some(proc_macro2::TokenTree::Group(body_group)) = iter.next() else {
return;
};
let body = body_group.stream();
let Some(fields) = ctx.current_fields() else {
return;
};
let fields: Vec<_> = fields.iter().enumerate().collect();
for (index, field) in fields {
ctx.push(ContextFrame::Field { field, index });
let expanded = evaluate_with_context(body.clone(), ctx);
output.extend(expanded);
ctx.pop();
}
}
fn handle_if_attr(
iter: &mut std::iter::Peekable<proc_macro2::token_stream::IntoIter>,
ctx: &mut EvalContext<'_>,
output: &mut TokenStream,
) {
let Some(proc_macro2::TokenTree::Group(query_group)) = iter.next() else {
return;
};
let Some(proc_macro2::TokenTree::Group(body_group)) = iter.next() else {
return;
};
let Some(query) = AttrQuery::parse(query_group.stream()) else {
return;
};
let attrs = ctx.current_attrs();
if let Some(matched_attr) = query.find_in(&attrs.facet) {
ctx.push(ContextFrame::Attr { attr: matched_attr });
let expanded = evaluate_with_context(body_group.stream(), ctx);
output.extend(expanded);
ctx.pop();
}
}
fn handle_if_field_attr(
iter: &mut std::iter::Peekable<proc_macro2::token_stream::IntoIter>,
ctx: &mut EvalContext<'_>,
output: &mut TokenStream,
) {
let Some(proc_macro2::TokenTree::Group(query_group)) = iter.next() else {
return;
};
let Some(proc_macro2::TokenTree::Group(body_group)) = iter.next() else {
return;
};
let Some(query) = AttrQuery::parse(query_group.stream()) else {
return;
};
let Some(fields) = ctx.current_fields() else {
return;
};
let fields: Vec<_> = fields.iter().enumerate().collect();
for (index, field) in fields {
if let Some(matched_attr) = query.find_in(&field.attrs.facet) {
ctx.push(ContextFrame::Field { field, index });
ctx.push(ContextFrame::Attr { attr: matched_attr });
let expanded = evaluate_with_context(body_group.stream(), ctx);
output.extend(expanded);
ctx.pop(); ctx.pop(); return; }
}
}
fn handle_if_any_field_attr(
iter: &mut std::iter::Peekable<proc_macro2::token_stream::IntoIter>,
ctx: &mut EvalContext<'_>,
output: &mut TokenStream,
) {
let Some(proc_macro2::TokenTree::Group(query_group)) = iter.next() else {
return;
};
let Some(proc_macro2::TokenTree::Group(body_group)) = iter.next() else {
return;
};
let Some(query) = AttrQuery::parse(query_group.stream()) else {
return;
};
let Some(fields) = ctx.current_fields() else {
return;
};
let has_any = fields
.iter()
.any(|f| query.find_in(&f.attrs.facet).is_some());
if has_any {
let expanded = evaluate_with_context(body_group.stream(), ctx);
output.extend(expanded);
}
}
fn handle_if_struct(
iter: &mut std::iter::Peekable<proc_macro2::token_stream::IntoIter>,
ctx: &mut EvalContext<'_>,
output: &mut TokenStream,
) {
let Some(proc_macro2::TokenTree::Group(body_group)) = iter.next() else {
return;
};
if matches!(ctx.parsed_type, facet_macro_parse::PType::Struct(_)) {
let expanded = evaluate_with_context(body_group.stream(), ctx);
output.extend(expanded);
}
}
fn handle_if_enum(
iter: &mut std::iter::Peekable<proc_macro2::token_stream::IntoIter>,
ctx: &mut EvalContext<'_>,
output: &mut TokenStream,
) {
let Some(proc_macro2::TokenTree::Group(body_group)) = iter.next() else {
return;
};
if matches!(ctx.parsed_type, facet_macro_parse::PType::Enum(_)) {
let expanded = evaluate_with_context(body_group.stream(), ctx);
output.extend(expanded);
}
}
fn handle_if_unit_variant(
iter: &mut std::iter::Peekable<proc_macro2::token_stream::IntoIter>,
ctx: &mut EvalContext<'_>,
output: &mut TokenStream,
) {
let Some(proc_macro2::TokenTree::Group(body_group)) = iter.next() else {
return;
};
let Some(variant) = ctx.current_variant() else {
return;
};
if matches!(variant.kind, facet_macro_parse::PVariantKind::Unit) {
let expanded = evaluate_with_context(body_group.stream(), ctx);
output.extend(expanded);
}
}
fn handle_if_tuple_variant(
iter: &mut std::iter::Peekable<proc_macro2::token_stream::IntoIter>,
ctx: &mut EvalContext<'_>,
output: &mut TokenStream,
) {
let Some(proc_macro2::TokenTree::Group(body_group)) = iter.next() else {
return;
};
let Some(variant) = ctx.current_variant() else {
return;
};
if matches!(variant.kind, facet_macro_parse::PVariantKind::Tuple { .. }) {
let expanded = evaluate_with_context(body_group.stream(), ctx);
output.extend(expanded);
}
}
fn handle_if_struct_variant(
iter: &mut std::iter::Peekable<proc_macro2::token_stream::IntoIter>,
ctx: &mut EvalContext<'_>,
output: &mut TokenStream,
) {
let Some(proc_macro2::TokenTree::Group(body_group)) = iter.next() else {
return;
};
let Some(variant) = ctx.current_variant() else {
return;
};
if matches!(variant.kind, facet_macro_parse::PVariantKind::Struct { .. }) {
let expanded = evaluate_with_context(body_group.stream(), ctx);
output.extend(expanded);
}
}
fn emit_variant_name(ctx: &EvalContext<'_>, output: &mut TokenStream) {
if let Some(variant) = ctx.current_variant()
&& let facet_macro_parse::IdentOrLiteral::Ident(name) = &variant.name.raw
{
output.extend(quote! { #name });
}
}
fn emit_variant_pattern(ctx: &EvalContext<'_>, output: &mut TokenStream) {
let Some(variant) = ctx.current_variant() else {
return;
};
use facet_macro_parse::{IdentOrLiteral, PVariantKind};
match &variant.kind {
PVariantKind::Unit => {
}
PVariantKind::Tuple { fields } => {
let names: Vec<_> = (0..fields.len())
.map(|i| quote::format_ident!("v{}", i))
.collect();
output.extend(quote! { ( #(#names),* ) });
}
PVariantKind::Struct { fields } => {
let names: Vec<_> = fields
.iter()
.filter_map(|f| {
if let IdentOrLiteral::Ident(id) = &f.name.raw {
Some(id.clone())
} else {
None
}
})
.collect();
output.extend(quote! { { #(#names),* } });
}
}
}
fn handle_variant_pattern_only(
iter: &mut std::iter::Peekable<proc_macro2::token_stream::IntoIter>,
ctx: &EvalContext<'_>,
output: &mut TokenStream,
) {
let Some(proc_macro2::TokenTree::Group(query_group)) = iter.next() else {
return;
};
let Some(query) = AttrQuery::parse(query_group.stream()) else {
return;
};
let Some(variant) = ctx.current_variant() else {
return;
};
use facet_macro_parse::{IdentOrLiteral, PVariantKind};
match &variant.kind {
PVariantKind::Unit => {
}
PVariantKind::Tuple { fields } => {
let patterns: Vec<_> = fields
.iter()
.enumerate()
.map(|(i, f)| {
if query.find_in(&f.attrs.facet).is_some() {
let name = quote::format_ident!("v{}", i);
quote! { #name }
} else {
quote! { _ }
}
})
.collect();
output.extend(quote! { ( #(#patterns),* ) });
}
PVariantKind::Struct { fields } => {
let bindings: Vec<_> = fields
.iter()
.filter_map(|f| {
if let IdentOrLiteral::Ident(id) = &f.name.raw {
if query.find_in(&f.attrs.facet).is_some() {
Some(quote! { #id })
} else {
Some(quote! { #id: _ })
}
} else {
None
}
})
.collect();
output.extend(quote! { { #(#bindings),* } });
}
}
}
fn emit_field_name(ctx: &EvalContext<'_>, output: &mut TokenStream) {
let Some((field, index)) = ctx.current_field() else {
return;
};
use facet_macro_parse::IdentOrLiteral;
match &field.name.raw {
IdentOrLiteral::Ident(id) => {
output.extend(quote! { #id });
}
IdentOrLiteral::Literal(_) => {
let name = quote::format_ident!("v{}", index);
output.extend(quote! { #name });
}
}
}
fn emit_field_type(ctx: &EvalContext<'_>, output: &mut TokenStream) {
if let Some((field, _)) = ctx.current_field() {
let ty = &field.ty;
output.extend(quote! { #ty });
}
}
fn emit_field_expr(ctx: &EvalContext<'_>, output: &mut TokenStream) {
emit_field_name(ctx, output);
}
fn emit_attr_args(ctx: &EvalContext<'_>, output: &mut TokenStream) {
if let Some(attr) = ctx.current_attr() {
let args = &attr.args;
output.extend(args.clone());
}
}
fn emit_doc(ctx: &EvalContext<'_>, output: &mut TokenStream) {
let attrs = ctx.current_attrs();
let doc = attrs.doc.join(" ").trim().to_string();
if !doc.is_empty() {
output.extend(quote! { #doc });
}
}
fn emit_field_default_expr(ctx: &EvalContext<'_>, output: &mut TokenStream) {
let Some((field, _)) = ctx.current_field() else {
return;
};
if let Some(attr) = field
.attrs
.facet
.iter()
.find(|a| a.ns.is_none() && a.key == "default")
{
let args = &attr.args;
if args.is_empty() {
output.extend(quote! { ::core::default::Default::default() });
} else {
output.extend(quote! { #args });
}
return;
}
output.extend(quote! { ::core::default::Default::default() });
}
fn emit_variant_default_construction(ctx: &EvalContext<'_>, output: &mut TokenStream) {
use facet_macro_parse::{IdentOrLiteral, PVariantKind};
let Some(variant) = ctx.current_variant() else {
return;
};
match &variant.kind {
PVariantKind::Unit => {
}
PVariantKind::Tuple { fields } => {
let defaults: Vec<_> = fields.iter().map(field_default_tokens).collect();
output.extend(quote! { ( #(#defaults),* ) });
}
PVariantKind::Struct { fields } => {
let field_inits: Vec<_> = fields
.iter()
.filter_map(|f| {
if let IdentOrLiteral::Ident(name) = &f.name.raw {
let default_expr = field_default_tokens(f);
Some(quote! { #name: #default_expr })
} else {
None
}
})
.collect();
output.extend(quote! { { #(#field_inits),* } });
}
}
}
fn field_default_tokens(field: &facet_macro_parse::PStructField) -> TokenStream {
if let Some(attr) = field
.attrs
.facet
.iter()
.find(|a| a.ns.is_none() && a.key == "default")
{
let args = &attr.args;
if args.is_empty() {
return quote! { ::core::default::Default::default() };
} else {
return quote! { #args };
}
}
quote! { ::core::default::Default::default() }
}
fn emit_format_doc_comment(ctx: &EvalContext<'_>, output: &mut TokenStream) {
use facet_macro_parse::PVariantKind;
let Some(variant) = ctx.current_variant() else {
return;
};
let doc = variant.attrs.doc.join(" ").trim().to_string();
let format_str = if doc.is_empty() {
variant.name.original.clone()
} else {
doc
};
match &variant.kind {
PVariantKind::Unit => {
output.extend(quote! { #format_str });
}
PVariantKind::Tuple { fields } => {
if format_str.contains("{0}") {
let field_names: Vec<_> = (0..fields.len())
.map(|i| quote::format_ident!("v{}", i))
.collect();
output.extend(quote! { #format_str, #(#field_names),* });
} else {
output.extend(quote! { #format_str });
}
}
PVariantKind::Struct { fields: _ } => {
output.extend(quote! { #format_str });
}
}
}
crate::unsynn! {
struct FinalizeSectionMarker {
_at: crate::At,
name: Ident,
}
struct FinalizeSection {
marker: FinalizeSectionMarker,
content: crate::BraceGroupContaining<TokenStream>,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::IParse;
use quote::quote;
#[test]
fn test_to_snake_case() {
assert_eq!(to_snake_case("Error"), "error");
assert_eq!(to_snake_case("Display"), "display");
assert_eq!(to_snake_case("PartialEq"), "partial_eq");
assert_eq!(to_snake_case("FromStr"), "from_str");
}
#[test]
fn test_extract_derive_plugins() {
let input = quote! {
#[derive(Facet, Debug)]
#[facet(derive(Error))]
#[repr(u8)]
pub enum MyError {
Disconnect(u32),
}
};
let mut iter = input.to_token_iter();
let parsed = iter.parse::<crate::Enum>().expect("Failed to parse enum");
let plugins = extract_derive_plugins(&parsed.attributes);
assert_eq!(plugins.len(), 1);
assert!(matches!(&plugins[0], PluginRef::Simple(name) if name == "Error"));
}
#[test]
fn test_extract_multiple_plugins() {
let input = quote! {
#[facet(derive(Error, Display))]
pub enum MyError {
Unknown,
}
};
let mut iter = input.to_token_iter();
let parsed = iter.parse::<crate::Enum>().expect("Failed to parse enum");
let plugins = extract_derive_plugins(&parsed.attributes);
assert_eq!(plugins.len(), 2);
assert!(matches!(&plugins[0], PluginRef::Simple(name) if name == "Error"));
assert!(matches!(&plugins[1], PluginRef::Simple(name) if name == "Display"));
}
#[test]
fn test_extract_path_plugins() {
let input = quote! {
#[facet(derive(Error, facet_default::Default))]
pub enum MyError {
Unknown,
}
};
let mut iter = input.to_token_iter();
let parsed = iter.parse::<crate::Enum>().expect("Failed to parse enum");
let plugins = extract_derive_plugins(&parsed.attributes);
assert_eq!(plugins.len(), 2);
assert!(matches!(&plugins[0], PluginRef::Simple(name) if name == "Error"));
assert!(
matches!(&plugins[1], PluginRef::Path { crate_name, plugin_name } if crate_name == "facet_default" && plugin_name == "Default")
);
}
#[test]
fn test_plugin_ref_crate_path() {
let simple = PluginRef::Simple("Error".to_string());
assert_eq!(simple.crate_path().to_string(), ":: facet_error");
let path = PluginRef::Path {
crate_name: "facet_default".to_string(),
plugin_name: "Default".to_string(),
};
assert_eq!(path.crate_path().to_string(), ":: facet_default");
}
#[test]
fn test_extract_derive_plugins_combined_attrs() {
let input = quote! {
#[derive(Debug, Facet)]
#[facet(rename_all = "kebab-case", derive(Default))]
struct PreCommitConfig {
generate_readmes: bool,
}
};
let mut iter = input.to_token_iter();
let parsed = iter
.parse::<crate::Struct>()
.expect("Failed to parse struct");
let plugins = extract_derive_plugins(&parsed.attributes);
assert_eq!(
plugins.len(),
1,
"should extract derive(Default) even when combined with other attrs"
);
assert!(matches!(&plugins[0], PluginRef::Simple(name) if name == "Default"));
}
#[test]
fn test_strip_derive_attrs_combined() {
let input = quote! {
#[derive(Debug, Facet)]
#[facet(rename_all = "kebab-case", derive(Default))]
struct PreCommitConfig {
generate_readmes: bool,
}
};
let stripped = strip_derive_attrs(input);
let stripped_str = stripped.to_string();
assert!(
stripped_str.contains("derive"),
"should keep #[derive(Debug, Facet)]"
);
assert!(
stripped_str.contains("rename_all"),
"should keep rename_all attribute"
);
assert!(
!stripped_str.contains("facet (rename_all = \"kebab-case\" , derive (Default))"),
"should strip derive(Default) from combined attribute"
);
}
#[test]
fn test_strip_derive_attrs_only_derive() {
let input = quote! {
#[facet(derive(Default))]
struct Foo {}
};
let stripped = strip_derive_attrs(input);
let stripped_str = stripped.to_string();
assert!(
!stripped_str.contains("derive (Default)"),
"derive(Default) should be stripped"
);
}
}