Skip to content

Commit

Permalink
Merge pull request #76 from pnevyk/skip-attribute
Browse files Browse the repository at this point in the history
Add option for skipping fields
  • Loading branch information
Hoverbear authored Aug 3, 2021
2 parents a4222c2 + 639c915 commit f081d39
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 5 deletions.
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,35 @@ fn main() {
let val = foo.get_field();
}
```

Skipping setters and getters generation for a field when struct level attribute is used
is possible with `#[getset(skip)]`.

```rust
use getset::{CopyGetters, Setters};

#[derive(CopyGetters, Setters)]
#[getset(get_copy, set)]
pub struct Foo {
// If the field was not skipped, the compiler would complain about moving
// a non-copyable type in copy getter.
#[getset(skip)]
skipped: String,

field1: usize,
field2: usize,
}

impl Foo {
// It is possible to write getters and setters manually,
// possibly with a custom logic.
fn skipped(&self) -> &str {
&self.skipped
}

fn set_skipped(&mut self, val: &str) -> &mut Self {
self.skipped = val.to_string();
self
}
}
```
2 changes: 2 additions & 0 deletions src/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ pub fn implement(field: &Field, params: &GenParams) -> TokenStream2 {

let visibility = parse_visibility(attr.as_ref(), params.mode.name());
match attr {
// Generate nothing for skipped field.
Some(meta) if meta.path().is_ident("skip") => quote! {},
Some(_) => match params.mode {
GenMode::Get => {
quote! {
Expand Down
74 changes: 69 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ foo.public();
```
For some purposes, it's useful to have the `get_` prefix on the getters for
either legacy of compatability reasons. It is done with `with_prefix`.
either legacy of compatibility reasons. It is done with `with_prefix`.
```rust
use getset::{Getters, MutGetters, CopyGetters, Setters};
Expand All @@ -141,6 +141,38 @@ pub struct Foo {
let mut foo = Foo::default();
let val = foo.get_field();
```
Skipping setters and getters generation for a field when struct level attribute is used
is possible with `#[getset(skip)]`.
```rust
use getset::{CopyGetters, Setters};
#[derive(CopyGetters, Setters)]
#[getset(get_copy, set)]
pub struct Foo {
// If the field was not skipped, the compiler would complain about moving
// a non-copyable type in copy getter.
#[getset(skip)]
skipped: String,
field1: usize,
field2: usize,
}
impl Foo {
// It is possible to write getters and setters manually,
// possibly with a custom logic.
fn skipped(&self) -> &str {
&self.skipped
}
fn set_skipped(&mut self, val: &str) -> &mut Self {
self.skipped = val.to_string();
self
}
}
```
*/

extern crate proc_macro;
Expand Down Expand Up @@ -235,20 +267,52 @@ fn parse_attr(attr: &syn::Attribute, mode: GenMode) -> Option<Meta> {
use syn::{punctuated::Punctuated, Token};

if attr.path.is_ident("getset") {
attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
let (last, skip, mut collected) = attr
.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
.unwrap_or_abort()
.into_iter()
.inspect(|meta| {
if !(meta.path().is_ident("get")
|| meta.path().is_ident("get_copy")
|| meta.path().is_ident("get_mut")
|| meta.path().is_ident("set"))
|| meta.path().is_ident("set")
|| meta.path().is_ident("skip"))
{
abort!(meta.path().span(), "unknown setter or getter")
}
})
.filter(|meta| meta.path().is_ident(mode.name()))
.last()
.fold(
(None, None, Vec::new()),
|(last, skip, mut collected), meta| {
if meta.path().is_ident(mode.name()) {
(Some(meta), skip, collected)
} else if meta.path().is_ident("skip") {
(last, Some(meta), collected)
} else {
// Store inapplicable item for potential error message
// if used with skip.
collected.push(meta);
(last, skip, collected)
}
},
);

if skip.is_some() {
// Check if there is any setter or getter used with skip, which is
// forbidden.
if last.is_none() && collected.is_empty() {
skip
} else {
abort!(
last.or_else(|| collected.pop()).unwrap().path().span(),
"use of setters and getters with skip is invalid"
);
}
} else {
// If skip is not used, return the last occurrence of matching
// setter/getter, if there is any.
last
}
} else {
attr.parse_meta()
.ok()
Expand Down
50 changes: 50 additions & 0 deletions tests/skip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#[macro_use]
extern crate getset;

#[derive(CopyGetters, Setters)]
#[getset(get_copy, set)]
pub struct Plain {
// If the field was not skipped, the compiler would complain about moving a
// non-copyable type.
#[getset(skip)]
non_copyable: String,

copyable: usize,
// Invalid use of skip -- compilation error.
// #[getset(skip, get_copy)]
// non_copyable2: String,

// Invalid use of skip -- compilation error.
// #[getset(get_copy, skip)]
// non_copyable2: String,
}

impl Plain {
fn custom_non_copyable(&self) -> &str {
&self.non_copyable
}

// If the field was not skipped, the compiler would complain about duplicate
// definitions of `set_non_copyable`.
fn set_non_copyable(&mut self, val: String) -> &mut Self {
self.non_copyable = val;
self
}
}

impl Default for Plain {
fn default() -> Self {
Plain {
non_copyable: "foo".to_string(),
copyable: 3,
}
}
}

#[test]
fn test_plain() {
let mut val = Plain::default();
val.copyable();
val.custom_non_copyable();
val.set_non_copyable("bar".to_string());
}

0 comments on commit f081d39

Please sign in to comment.