Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for renaming types when generating optionals struct #2

Closed
wants to merge 3 commits into from
Closed
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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ struct Opt<T> {
}
```

`optfield` supports defining visibility, documentation, attributes and merge
methods. For more details and examples check its [documentation].
`optfield` supports defining visibility, documentation, attributes, renaming
types and merge methods. For more details and examples check its
[documentation].

### License
Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE)
Expand Down
86 changes: 86 additions & 0 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mod kw {
syn::custom_keyword!(field_doc);
syn::custom_keyword!(field_attrs);
syn::custom_keyword!(from);
syn::custom_keyword!(renames);

pub mod attrs_sub {
syn::custom_keyword!(add);
Expand All @@ -28,6 +29,7 @@ pub struct Args {
pub field_doc: bool,
pub field_attrs: Option<Attrs>,
pub from: bool,
pub renames: Option<Renames>,
}

enum Arg {
Expand All @@ -38,6 +40,7 @@ enum Arg {
FieldDocs(bool),
FieldAttrs(Attrs),
From(bool),
Renames(Renames),
}

#[cfg_attr(test, derive(PartialEq))]
Expand Down Expand Up @@ -88,8 +91,15 @@ struct ArgList {
field_attrs: Option<Span>,
from: Option<Span>,
list: Vec<Arg>,
renames: Option<Span>,
}

#[cfg_attr(test, derive(Debug, PartialEq))]
pub struct Renames(pub Vec<(Ident, Ident)>);

#[cfg_attr(test, derive(Debug, PartialEq))]
pub struct RenameList(Vec<(Ident, Ident)>);

impl Parse for Args {
fn parse(input: ParseStream) -> Result<Self> {
let arg_list: ArgList = input.parse()?;
Expand Down Expand Up @@ -135,6 +145,8 @@ impl Parse for ArgList {
arg_list.parse_field_attrs(input)?;
} else if lookahead.peek(kw::from) {
arg_list.parse_from(input)?;
} else if lookahead.peek(kw::renames) {
arg_list.parse_renames(input)?;
} else {
return Err(lookahead.error());
}
Expand All @@ -155,6 +167,7 @@ impl Args {
field_doc: false,
field_attrs: None,
from: false,
renames: None,
}
}
}
Expand All @@ -171,6 +184,7 @@ impl ArgList {
field_attrs: None,
from: None,
list: Vec::with_capacity(6),
renames: None,
}
}

Expand All @@ -182,6 +196,7 @@ impl ArgList {
|| input.peek(kw::field_attrs)
|| input.peek(kw::attrs)
|| input.peek(kw::from)
|| input.peek(kw::renames)
}

fn parse_doc(&mut self, input: ParseStream) -> Result<()> {
Expand Down Expand Up @@ -286,6 +301,21 @@ impl ArgList {
Ok(())
}

fn parse_renames(&mut self, input: ParseStream) -> Result<()> {
if let Some(renames_span) = self.renames {
return ArgList::already_defined_error(input, "renames", renames_span);
}

let span = input.span();
input.parse::<kw::renames>()?;
let renames: Renames = input.parse()?;

self.renames = Some(span);
self.list.push(Arg::Renames(renames));

Ok(())
}

fn already_defined_error(
input: ParseStream,
arg_name: &'static str,
Expand Down Expand Up @@ -417,6 +447,44 @@ impl Parse for AttrList {
}
}

impl Renames {
fn parse_rename_list(input: ParseStream) -> Result<Vec<(Ident, Ident)>> {
let group: Group = input.parse()?;

let pairs: RenameList = parse2(group.stream())?;
Ok(pairs.0)
}
}

impl Parse for Renames {
fn parse(input: ParseStream) -> Result<Self> {
input.parse::<Eq>()?;
let list = Renames::parse_rename_list(input)?;

Ok(Self(list))
}
}

impl Parse for RenameList {
fn parse(input: ParseStream) -> Result<Self> {
let mut pairs = Vec::new();

while !input.is_empty() {
let from: Ident = input.parse()?;
input.parse::<Eq>()?;
let to: Ident = input.parse()?;

pairs.push((from, to));

if input.peek(Comma) {
input.parse::<Comma>()?;
}
}

Ok(Self(pairs))
}
}

impl From<ArgList> for Args {
fn from(arg_list: ArgList) -> Args {
use Arg::*;
Expand All @@ -432,6 +500,7 @@ impl From<ArgList> for Args {
FieldDocs(field_doc) => args.field_doc = field_doc,
FieldAttrs(field_attrs) => args.field_attrs = Some(field_attrs),
From(from) => args.from = from,
Renames(pairs) => args.renames = Some(pairs),
}
}

Expand Down Expand Up @@ -735,4 +804,21 @@ mod tests {

assert!(args.from);
}

#[test]
fn parse_rewrite() {
let parser = Renames::parse_rename_list;

let args = parse_args(quote! {
Opt,
renames = (Substruct = OptSubstruct, Substruct2 = OptSubstruct2)
});
let renames = Renames(
parser
.parse2(quote! {(Substruct = OptSubstruct, Substruct2 = OptSubstruct2)})
.unwrap(),
);

assert_eq!(args.renames, Some(renames));
}
}
71 changes: 67 additions & 4 deletions src/fields/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use quote::quote;
use syn::{parse2, Field, Fields, ItemStruct, Path, Type, TypePath};
use syn::{parse2, Field, Fields, Ident, ItemStruct, Path, Type, TypePath};

use crate::args::Args;
use crate::error::unexpected;
Expand All @@ -8,7 +8,8 @@ mod attrs;

const OPTION: &str = "Option";

/// Wraps item fields in Option.
/// Wraps item fields in Option. If rewrite options are set it also performs
/// that change.
pub fn generate(item: &ItemStruct, args: &Args) -> Fields {
let item_name = item.ident.clone();

Expand All @@ -22,10 +23,15 @@ pub fn generate(item: &ItemStruct, args: &Args) -> Fields {
continue;
}

let rename = find_rename(field, args);
let ty = &field.ty;

let opt_type = quote! {
Option<#ty>
let opt_type = if let Some(r) = rename {
quote! { #r }
} else {
quote! {
Option<#ty>
}
};

field.ty = parse2(opt_type).unwrap_or_else(|e| {
Expand Down Expand Up @@ -55,6 +61,34 @@ pub fn is_option(field: &Field) -> bool {
}
}

pub fn find_rename(field: &Field, args: &Args) -> Option<Ident> {
let rewrites = if let Some(r) = &args.renames {
&r.0
} else {
return None;
};

match &field.ty {
Type::Path(TypePath {
path: Path { segments, .. },
..
}) => {
if let Some(segment) = segments.first() {
for (from, to) in rewrites {
if segment.ident == *from {
return Some(to.clone());
}
}

None
} else {
None
}
}
_ => None,
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -135,4 +169,33 @@ mod tests {

assert_eq!(field_types(generated), expected_types);
}

#[test]
fn with_renames() {
let (item, args) = parse_item_and_args(
quote! {
struct S<T> {
text: AnotherStruct,
number: Option<i128>,
generic: T,
optional_generic: Option<T>
}
},
quote! {
Opt,
renames = (AnotherStruct = OptAnotherStruct)
},
);

let expected_types = parse_types(vec![
quote! {OptAnotherStruct},
quote! {Option<i128>},
quote! {Option<T>},
quote! {Option<T>},
]);

let generated = generate(&item, &args);

assert_eq!(field_types(generated), expected_types);
}
}
40 changes: 40 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,43 @@
//! }
//! ```
//!
//! # Field Type Renaming
//! You can change which types will be used in the output in order to support
//! embedded structures which also go through `optfield`.
//!
//! ```
//! # use optfield::*;
//!
//! #[optfield(OptAnotherStruct)]
//! struct AnotherStruct(String);
//! #[optfield(OptYetAnotherStruct)]
//! struct YetAnotherStruct(u32);
//!
//! #[optfield(
//! Opt,
//! renames = (
//! AnotherStruct = OptAnotherStruct,
//! YetAnotherStruct = OptYetAnotherStruct
//! )
//! )]
//! struct MyStruct {
//! other_struct: AnotherStruct,
//! yet_another_struct: YetAnotherStruct,
//! my_number: i32
//! }
//! ```
//! Will generate:
//! ```
//! struct OptAnotherStruct(String);
//! struct OptYetAnotherStruct(u32);
//!
//! struct Opt {
//! other_struct: OptAnotherStruct,
//! yet_another_struct: OptYetAnotherStruct,
//! my_number: Option<i32>
//! }
//! ```
//!
//! # Merging
//! When the `merge_fn` argument is used `optfield` will add a method to the
//! original struct that merges an opt struct back into the original.
Expand Down Expand Up @@ -402,6 +439,9 @@
//! * custom visibility (default is private): `merge_fn = pub(crate)`
//! * both: `merge_fn = pub my_merge_fn`
//!
//! Note that if you use `merge_fn` in combination with `renames`, all the merge
//! functions for those types must have the same name.
//!
//! # From
//! When the `from` argument is used, `From<MyStruct>` is implemented for `Opt`.
//! ```
Expand Down
8 changes: 6 additions & 2 deletions src/merge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub fn generate(item: &ItemStruct, opt_item: &ItemStruct, args: &Args) -> TokenS
let opt_name = &opt_item.ident;
let opt_generics = &opt_item.generics;

let fields = field_bindings(&item.fields, args);
let fields = field_bindings(&item.fields, args, &fn_name);

quote! {
impl#item_generics #item_name#item_generics {
Expand All @@ -36,7 +36,7 @@ pub fn generate(item: &ItemStruct, opt_item: &ItemStruct, args: &Args) -> TokenS
}
}

fn field_bindings(fields: &Fields, args: &Args) -> TokenStream {
fn field_bindings(fields: &Fields, args: &Args, fn_name: &Ident) -> TokenStream {
let mut tokens = TokenStream::new();

for (i, field) in fields.iter().enumerate() {
Expand All @@ -56,6 +56,10 @@ fn field_bindings(fields: &Fields, args: &Args) -> TokenStream {
self.#field_name = opt.#field_name;
}
}
} else if fields::find_rename(field, args).is_some() {
quote! {
self.#field_name.#fn_name(opt.#field_name);
}
} else {
quote! {
if let Some(value) = opt.#field_name {
Expand Down
Loading