diff --git a/Cargo.toml b/Cargo.toml index bedec54..eba125f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,10 @@ Getset, we're ready to go! A procedural macro for generating the most basic getters and setters on fields. """ version = "0.1.3" -authors = ["Ana Hobden ", "John Baublitz ", + "John Baublitz &type`, while setters are generated These macros are not intended to be used on fields which require custom logic inside of their setters and getters. Just write your own in that case! ```rust -use getset::{CopyGetters, Getters, MutGetters, Setters}; +use getset::{CopyGetters, Getters, MutGetters, Setters, WithSetters}; -#[derive(Getters, Setters, MutGetters, CopyGetters, Default)] +#[derive(Getters, Setters, MutGetters, CopyGetters, WithSetters, Default)] pub struct Foo where T: Copy + Clone + Default, { /// Doc comments are supported! /// Multiline, even. - #[getset(get, set, get_mut)] + #[getset(get, set, get_mut, set_with)] private: T, /// Doc comments are supported! /// Multiline, even. - #[getset(get_copy = "pub", set = "pub", get_mut = "pub")] + #[getset(get_copy = "pub", set = "pub", get_mut = "pub", set_with = "pub")] public: T, } @@ -37,13 +37,15 @@ fn main() { foo.set_private(1); (*foo.private_mut()) += 1; assert_eq!(*foo.private(), 2); + foo = foo.with_private(3); + assert_eq!(*foo.private(), 3); } ``` You can use `cargo-expand` to generate the output. Here are the functions that the above generates (Replicate with `cargo expand --example simple`): ```rust -use getset::{Getters, MutGetters, CopyGetters, Setters}; +use getset::{CopyGetters, Getters, MutGetters, Setters, WithSetters}; pub struct Foo where T: Copy + Clone + Default, @@ -54,7 +56,7 @@ where private: T, /// Doc comments are supported! /// Multiline, even. - #[getset(get_copy = "pub", set = "pub", get_mut = "pub")] + #[getset(get_copy = "pub", set = "pub", get_mut = "pub", set_with = "pub")] public: T, } impl Foo @@ -108,6 +110,18 @@ where self.public } } +impl Foo +where + T: Copy + Clone + Default, +{ + /// Doc comments are supported! + /// Multiline, even. + #[inline(always)] + pub fn with_public(mut self, val: T) -> Self { + self.public = val; + self + } +} ``` Attributes can be set on struct level for all fields in struct as well. Field level attributes take @@ -162,7 +176,7 @@ is possible with `#[getset(skip)]`. use getset::{CopyGetters, Setters}; #[derive(CopyGetters, Setters)] -#[getset(get_copy, set)] +#[getset(get_copy, set, set_with)] pub struct Foo { // If the field was not skipped, the compiler would complain about moving // a non-copyable type in copy getter. @@ -184,5 +198,10 @@ impl Foo { self.skipped = val.to_string(); self } + + fn with_skipped(mut self, val: &str) -> Self { + self.skipped = val.to_string(); + self + } } ``` diff --git a/examples/simple.rs b/examples/simple.rs index e4c2d18..8814693 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,18 +1,18 @@ -use getset::{CopyGetters, Getters, MutGetters, Setters}; +use getset::{CopyGetters, Getters, MutGetters, Setters, WithSetters}; -#[derive(Getters, Setters, MutGetters, CopyGetters, Default)] +#[derive(Getters, Setters, WithSetters, MutGetters, CopyGetters, Default)] pub struct Foo where T: Copy + Clone + Default, { /// Doc comments are supported! /// Multiline, even. - #[getset(get, set, get_mut)] + #[getset(get, set, get_mut, set_with)] private: T, /// Doc comments are supported! /// Multiline, even. - #[getset(get_copy = "pub", set = "pub", get_mut = "pub")] + #[getset(get_copy = "pub", set = "pub", get_mut = "pub", set_with = "pub")] public: T, } @@ -21,4 +21,6 @@ fn main() { foo.set_private(1); (*foo.private_mut()) += 1; assert_eq!(*foo.private(), 2); + foo = foo.with_private(3); + assert_eq!(*foo.private(), 3); } diff --git a/src/generate.rs b/src/generate.rs index 055f0e4..ea7540f 100644 --- a/src/generate.rs +++ b/src/generate.rs @@ -4,7 +4,7 @@ use syn::{ self, ext::IdentExt, spanned::Spanned, Expr, Field, Lit, Meta, MetaNameValue, Visibility, }; -use self::GenMode::{Get, GetCopy, GetMut, Set}; +use self::GenMode::{Get, GetCopy, GetMut, Set, SetWith}; use super::parse_attr; pub struct GenParams { @@ -16,8 +16,9 @@ pub struct GenParams { pub enum GenMode { Get, GetCopy, - Set, GetMut, + Set, + SetWith, } impl GenMode { @@ -25,8 +26,9 @@ impl GenMode { match self { Get => "get", GetCopy => "get_copy", - Set => "set", GetMut => "get_mut", + Set => "set", + SetWith => "set_with", } } @@ -34,12 +36,13 @@ impl GenMode { match self { Get | GetCopy | GetMut => "", Set => "set_", + SetWith => "with_", } } pub fn suffix(self) -> &'static str { match self { - Get | GetCopy | Set => "", + Get | GetCopy | Set | SetWith => "", GetMut => "_mut", } } @@ -47,7 +50,7 @@ impl GenMode { fn is_get(self) -> bool { match self { GenMode::Get | GenMode::GetCopy | GenMode::GetMut => true, - GenMode::Set => false, + GenMode::Set | GenMode::SetWith => false, } } } @@ -197,6 +200,16 @@ pub fn implement(field: &Field, params: &GenParams) -> TokenStream2 { } } } + GenMode::SetWith => { + quote! { + #(#doc)* + #[inline(always)] + #visibility fn #fn_name(mut self, val: #ty) -> Self { + self.#field_name = val; + self + } + } + } }, None => quote! {}, } @@ -258,6 +271,17 @@ pub fn implement_for_unnamed(field: &Field, params: &GenParams) -> TokenStream2 } } } + GenMode::SetWith => { + let fn_name = Ident::new("set_with", Span::call_site()); + quote! { + #(#doc)* + #[inline(always)] + #visibility fn #fn_name(mut self, val: #ty) -> Self { + self.0 = val; + self + } + } + } }, None => quote! {}, } diff --git a/src/lib.rs b/src/lib.rs index 7bca668..30949e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,7 +35,7 @@ assert_eq!(*foo.private(), 2); You can use `cargo-expand` to generate the output. Here are the functions that the above generates (Replicate with `cargo expand --example simple`): ```rust,ignore -use getset::{Getters, MutGetters, CopyGetters, Setters}; +use getset::{Getters, MutGetters, CopyGetters, Setters, WithSetters}; pub struct Foo where T: Copy + Clone + Default, @@ -107,7 +107,7 @@ precedence. ```rust mod submodule { - use getset::{Getters, MutGetters, CopyGetters, Setters}; + use getset::{Getters, MutGetters, CopyGetters, Setters, WithSetters}; #[derive(Getters, CopyGetters, Default)] #[getset(get_copy = "pub")] // By default add a pub getting for all fields. pub struct Foo { @@ -129,7 +129,7 @@ For some purposes, it's useful to have the `get_` prefix on the getters for either legacy of compatibility reasons. It is done with `with_prefix`. ```rust -use getset::{Getters, MutGetters, CopyGetters, Setters}; +use getset::{Getters, MutGetters, CopyGetters, Setters, WithSetters}; #[derive(Getters, Default)] pub struct Foo { @@ -146,10 +146,10 @@ Skipping setters and getters generation for a field when struct level attribute is possible with `#[getset(skip)]`. ```rust -use getset::{CopyGetters, Setters}; +use getset::{CopyGetters, Setters, WithSetters}; -#[derive(CopyGetters, Setters)] -#[getset(get_copy, set)] +#[derive(CopyGetters, Setters, WithSetters)] +#[getset(get_copy, set, set_with)] pub struct Foo { // If the field was not skipped, the compiler would complain about moving // a non-copyable type in copy getter. @@ -171,6 +171,11 @@ impl Foo { self.skipped = val.to_string(); self } + + fn with_skipped(mut self, val: &str) -> Self { + self.skipped = val.to_string(); + self + } } ``` @@ -257,6 +262,18 @@ pub fn setters(input: TokenStream) -> TokenStream { produce(&ast, ¶ms).into() } +#[proc_macro_derive(WithSetters, attributes(set_with, getset))] +#[proc_macro_error] +pub fn with_setters(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + let params = GenParams { + mode: GenMode::SetWith, + global_attr: parse_global_attr(&ast.attrs, GenMode::SetWith), + }; + + produce(&ast, ¶ms).into() +} + fn parse_global_attr(attrs: &[syn::Attribute], mode: GenMode) -> Option { attrs.iter().filter_map(|v| parse_attr(v, mode)).last() } @@ -278,6 +295,7 @@ fn parse_attr(attr: &syn::Attribute, mode: GenMode) -> Option { || meta.path().is_ident("get_copy") || meta.path().is_ident("get_mut") || meta.path().is_ident("set") + || meta.path().is_ident("set_with") || meta.path().is_ident("skip")) { abort!(meta.path().span(), "unknown setter or getter") diff --git a/tests/skip.rs b/tests/skip.rs index 93f40e4..71de00a 100644 --- a/tests/skip.rs +++ b/tests/skip.rs @@ -1,8 +1,8 @@ #[macro_use] extern crate getset; -#[derive(CopyGetters, Setters)] -#[getset(get_copy, set)] +#[derive(CopyGetters, Setters, WithSetters)] +#[getset(get_copy, set, set_with)] pub struct Plain { // If the field was not skipped, the compiler would complain about moving a // non-copyable type. @@ -30,6 +30,13 @@ impl Plain { self.non_copyable = val; self } + + // If the field was not skipped, the compiler would complain about duplicate + // definitions of `with_non_copyable`. + fn with_non_copyable(mut self, val: String) -> Self { + self.non_copyable = val; + self + } } impl Default for Plain { @@ -47,4 +54,6 @@ fn test_plain() { val.copyable(); val.custom_non_copyable(); val.set_non_copyable("bar".to_string()); + val = val.with_non_copyable("foo".to_string()); + let _ = val; } diff --git a/tests/unary_tuple.rs b/tests/unary_tuple.rs index 2c90bd0..89ae223 100644 --- a/tests/unary_tuple.rs +++ b/tests/unary_tuple.rs @@ -1,15 +1,17 @@ -use getset::{CopyGetters, Getters, MutGetters, Setters}; +use getset::{CopyGetters, Getters, MutGetters, Setters, WithSetters}; #[test] fn test_unary_tuple() { - #[derive(Setters, Getters, MutGetters)] - struct UnaryTuple(#[getset(set, get, get_mut)] i32); + #[derive(Setters, Getters, MutGetters, WithSetters)] + struct UnaryTuple(#[getset(set, get, get_mut, set_with)] i32); let mut tup = UnaryTuple(42); assert_eq!(tup.get(), &42); assert_eq!(tup.get_mut(), &mut 42); tup.set(43); assert_eq!(tup.get(), &43); + tup = tup.set_with(44); + assert_eq!(tup.get(), &44); #[derive(CopyGetters)] struct CopyUnaryTuple(#[getset(get_copy)] i32); @@ -20,8 +22,8 @@ fn test_unary_tuple() { #[test] fn test_unary_tuple_with_attrs() { - #[derive(Setters, Getters, MutGetters)] - #[getset(set, get, get_mut)] + #[derive(Setters, Getters, MutGetters, WithSetters)] + #[getset(set, get, get_mut, set_with)] struct UnaryTuple(i32); let mut tup = UnaryTuple(42); @@ -29,6 +31,8 @@ fn test_unary_tuple_with_attrs() { assert_eq!(tup.get_mut(), &mut 42); tup.set(43); assert_eq!(tup.get(), &43); + tup = tup.set_with(44); + assert_eq!(tup.get(), &44); #[derive(CopyGetters)] #[getset(get_copy)] diff --git a/tests/with_setters.rs b/tests/with_setters.rs new file mode 100644 index 0000000..1ebeb48 --- /dev/null +++ b/tests/with_setters.rs @@ -0,0 +1,130 @@ +#[macro_use] +extern crate getset; + +use crate::submodule::other::{Generic, Plain, Where}; + +// For testing `pub(super)` +mod submodule { + // For testing `pub(in super::other)` + pub mod other { + #[derive(WithSetters, Default)] + #[set_with] + pub struct Plain { + /// A doc comment. + /// Multiple lines, even. + private_accessible: usize, + + /// A doc comment. + #[set_with = "pub"] + public_accessible: usize, + + /// This field is used for testing chaining. + #[set_with = "pub"] + second_public_accessible: bool, + // /// A doc comment. + // #[set_with = "pub(crate)"] + // crate_accessible: usize, + + // /// A doc comment. + // #[set_with = "pub(super)"] + // super_accessible: usize, + + // /// A doc comment. + // #[set_with = "pub(in super::other)"] + // scope_accessible: usize, + } + + #[derive(WithSetters, Default)] + #[set_with] + pub struct Generic { + /// A doc comment. + /// Multiple lines, even. + private_accessible: T, + + /// A doc comment. + #[set_with = "pub"] + public_accessible: T, + // /// A doc comment. + // #[set_with = "pub(crate)"] + // crate_accessible: usize, + + // /// A doc comment. + // #[set_with = "pub(super)"] + // super_accessible: usize, + + // /// A doc comment. + // #[set_with = "pub(in super::other)"] + // scope_accessible: usize, + } + + #[derive(WithSetters, Default)] + #[set_with] + pub struct Where + where + T: Copy + Clone + Default, + { + /// A doc comment. + /// Multiple lines, even. + private_accessible: T, + + /// A doc comment. + #[set_with = "pub"] + public_accessible: T, + // /// A doc comment. + // #[set_with = "pub(crate)"] + // crate_accessible: usize, + + // /// A doc comment. + // #[set_with = "pub(super)"] + // super_accessible: usize, + + // /// A doc comment. + // #[set_with = "pub(in super::other)"] + // scope_accessible: usize, + } + + #[test] + fn test_plain() { + let val: Plain = Plain::default(); + let _: Plain = val.with_private_accessible(1); + } + + #[test] + fn test_generic() { + let val: Generic = Generic::default(); + let _: Generic = val.with_private_accessible(1); + } + + #[test] + fn test_where() { + let val: Where = Where::default(); + let _: Where = val.with_private_accessible(1); + } + } +} + +#[test] +fn test_plain() { + let val: Plain = Plain::default(); + let _: Plain = val.with_public_accessible(1); +} + +#[test] +fn test_generic() { + let val: Generic = Generic::default(); + let _: Generic = val.with_public_accessible(1); +} + +#[test] +fn test_where() { + let val: Where = Where::default(); + let _: Where = val.with_public_accessible(1); +} + +#[test] +fn test_chaining() { + let val: Plain = Plain::default(); + let _: Plain = val + .with_public_accessible(1) + .with_second_public_accessible(true); +}