Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ members = [

[workspace.package]
authors = ["Antonio Yang <[email protected]>"]
version = "0.9.0"
version = "0.9.1"
edition = "2021"
categories = ["development-tools"]
keywords = ["struct", "patch", "macro", "derive", "overlay"]
Expand Down
125 changes: 43 additions & 82 deletions struct-patch-derive/src/filler.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
use std::str::FromStr;
use syn::{
meta::ParseNestedMeta, parenthesized, spanned::Spanned, DeriveInput, Error, LitStr, Result,
Type,
};
use syn::{parenthesized, DeriveInput, LitStr, Result, Type};

const FILLER: &str = "filler";
const NAME: &str = "name";
const ATTRIBUTE: &str = "attribute";

pub(crate) struct Filler {
Expand All @@ -19,11 +15,17 @@ pub(crate) struct Filler {
fields: Vec<Field>,
}

#[derive(Debug, PartialEq)]
enum FillerType {
Option,
Vec,
}

struct Field {
ident: Option<Ident>,
ty: Type,
attributes: Vec<TokenStream>,
retyped: bool,
fty: FillerType,
}

impl Filler {
Expand All @@ -42,17 +44,16 @@ impl Filler {
.iter()
.map(|f| f.to_token_stream())
.collect::<Result<Vec<_>>>()?;
let field_names = fields.iter().map(|f| f.ident.as_ref()).collect::<Vec<_>>();

let renamed_field_names = fields
let vector_field_names = fields
.iter()
.filter(|f| f.retyped)
.filter(|f| f.fty == FillerType::Vec)
.map(|f| f.ident.as_ref())
.collect::<Vec<_>>();

let original_field_names = fields
let option_field_names = fields
.iter()
.filter(|f| !f.retyped)
.filter(|f| f.fty == FillerType::Option)
.map(|f| f.ident.as_ref())
.collect::<Vec<_>>();

Expand All @@ -78,7 +79,12 @@ impl Filler {
impl #generics struct_patch::traits::Status for #name #generics #where_clause {
fn is_empty(&self) -> bool {
#(
if self.#field_names.is_some() {
if self.#option_field_names.is_some() {
return false
}
)*
#(
if !self.#vector_field_names.is_empty() {
return false
}
)*
Expand All @@ -93,26 +99,21 @@ impl Filler {
impl #generics struct_patch::traits::Filler< #name #generics > for #struct_name #generics #where_clause {
fn apply(&mut self, filler: #name #generics) {
#(
if let Some(v) = filler.#renamed_field_names {
if self.#renamed_field_names.is_none() {
self.#renamed_field_names.apply(v);
}
}
self.#vector_field_names.extend(filler.#vector_field_names.iter());
)*
#(
if let Some(v) = filler.#original_field_names {
if self.#original_field_names.is_none() {
self.#original_field_names = Some(v);
if let Some(v) = filler.#option_field_names {
if self.#option_field_names.is_none() {
self.#option_field_names = Some(v);
}
}
)*
}

fn new_empty_filler() -> #name #generics {
#name {
#(
#field_names: None,
)*
#(#option_field_names: None,)*
#(#vector_field_names: Vec::new(),)*
}
}
}
Expand Down Expand Up @@ -144,7 +145,6 @@ impl Filler {
));
};

let mut name = None;
let mut attributes = vec![];
let mut fields = vec![];

Expand All @@ -162,16 +162,6 @@ impl Filler {
attr.parse_nested_meta(|meta| {
let path = meta.path.to_string();
match path.as_str() {
NAME => {
// #[filler(name = "FillerStruct")]
if let Some(lit) = get_lit_str(path, &meta)? {
if name.is_some() {
return Err(meta
.error("The name attribute can't be defined more than once"));
}
name = Some(lit.parse()?);
}
}
ATTRIBUTE => {
// #[filler(attribute(derive(Deserialize)))]
// #[filler(attribute(derive(Deserialize, Debug), serde(rename = "foo"))]
Expand All @@ -196,14 +186,13 @@ impl Filler {
fields.push(f);
}
}
let ts = TokenStream::from_str(&format!("{}Filler", &ident,)).unwrap();
let lit = LitStr::new(&ts.to_string(), Span::call_site());
let filler_struct_name = lit.parse()?;

Ok(Filler {
visibility: vis,
filler_struct_name: name.unwrap_or({
let ts = TokenStream::from_str(&format!("{}Filler", &ident,)).unwrap();
let lit = LitStr::new(&ts.to_string(), Span::call_site());
lit.parse()?
}),
filler_struct_name,
struct_name: ident,
generics,
attributes,
Expand Down Expand Up @@ -248,12 +237,13 @@ impl Field {
ident, ty, attrs, ..
}: syn::Field,
) -> Result<Option<Field>> {
if !is_option(&ty) {
let fty = if let Some(fty) = filler_type(&ty) {
fty
} else {
return Ok(None);
}
};

let mut attributes = vec![];
let mut field_type = None;

for attr in attrs {
if attr.path().to_string().as_str() != FILLER {
Expand All @@ -276,11 +266,6 @@ impl Field {
let attribute: TokenStream = content.parse()?;
attributes.push(attribute);
}
NAME => {
// #[filler(name = "ItemFiller")]
let expr: LitStr = meta.value()?.parse()?;
field_type = Some(expr.parse()?)
}
_ => {
return Err(meta.error(format_args!(
"unknown filler field attribute `{}`",
Expand All @@ -294,9 +279,9 @@ impl Field {

Ok(Some(Field {
ident,
retyped: field_type.is_some(),
ty: field_type.unwrap_or(ty),
ty,
attributes,
fty,
}))
}
}
Expand All @@ -311,46 +296,22 @@ impl ToStr for syn::Path {
}
}

fn get_lit_str(attr_name: String, meta: &ParseNestedMeta) -> syn::Result<Option<syn::LitStr>> {
let expr: syn::Expr = meta.value()?.parse()?;
let mut value = &expr;
while let syn::Expr::Group(e) = value {
value = &e.expr;
}
if let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(lit),
..
}) = value
{
let suffix = lit.suffix();
if !suffix.is_empty() {
return Err(Error::new(
lit.span(),
format!("unexpected suffix `{}` on string literal", suffix),
));
}
Ok(Some(lit.clone()))
} else {
Err(Error::new(
expr.span(),
format!(
"expected serde {} attribute to be a string: `{} = \"...\"`",
attr_name, attr_name
),
))
}
}

fn is_option(ty: &Type) -> bool {
fn filler_type(ty: &Type) -> Option<FillerType> {
if let Type::Path(type_path) = ty {
let segments = &type_path.path.segments;
if segments.len() == 1 && segments[0].ident == "Option" {
if let syn::PathArguments::AngleBracketed(args) = &segments[0].arguments {
if args.args.len() == 1 {
return true;
return Some(FillerType::Option);
}
}
} else if segments.len() == 1 && segments[0].ident == "Vec" {
if let syn::PathArguments::AngleBracketed(args) = &segments[0].arguments {
if args.args.len() == 1 {
return Some(FillerType::Vec);
}
}
}
}
false
None
}
2 changes: 1 addition & 1 deletion struct-patch/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ license.workspace = true
readme.workspace = true

[dependencies]
struct-patch-derive = { version = "=0.9.0", path = "../struct-patch-derive" }
struct-patch-derive = { version = "=0.9.1", path = "../struct-patch-derive" }

[dev-dependencies]
serde_json = "1.0"
Expand Down
15 changes: 14 additions & 1 deletion struct-patch/examples/filler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ struct Item {
field_string: String,
maybe_field_int: Option<usize>,
maybe_field_string: Option<String>,
list: Vec<usize>,
}

// Generated by Filler derive macro
Expand All @@ -35,7 +36,7 @@ fn main() {

assert_eq!(
format!("{filler:?}"),
"ItemFiller { maybe_field_int: Some(7), maybe_field_string: None }"
"ItemFiller { maybe_field_int: Some(7), maybe_field_string: None, list: [] }"
);

item.apply(filler);
Expand All @@ -45,6 +46,7 @@ fn main() {
assert_eq!(item.field_string, "");
assert_eq!(item.maybe_field_int, Some(7));
assert_eq!(item.maybe_field_string, None);
assert_eq!(item.list.len(), 0);

let mut filler: ItemFiller = Item::new_empty_filler();
filler.maybe_field_int = Some(100);
Expand All @@ -57,4 +59,15 @@ fn main() {
assert_eq!(item.field_string, "");
assert_eq!(item.maybe_field_int, Some(7));
assert_eq!(item.maybe_field_string, Some("Something".into()));
assert_eq!(item.list.len(), 0);

let mut filler: ItemFiller = Item::new_empty_filler();
filler.list = vec![1, 2];
item.apply(filler);
assert_eq!(item.list, vec![1, 2]);

let mut filler: ItemFiller = Item::new_empty_filler();
filler.list = vec![3, 4];
item.apply(filler);
assert_eq!(item.list, vec![1, 2, 3, 4]);
}