summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Turner <jturner.usa@gmail.com>2023-04-20 23:17:09 -0400
committerJohn Turner <jturner.usa@gmail.com>2023-04-20 23:17:09 -0400
commit08595d40114b8d7b38f43d2b868f66abb37fd49a (patch)
treeac6027070597c50a6d3e7b7aff6128d423255527
parent42ffacb70095cad94c99832e6711414aa8c40fb2 (diff)
downloadget-08595d40114b8d7b38f43d2b868f66abb37fd49a.tar.gz
added "hide" attribute to disable getters on specific fields
-rw-r--r--src/lib.rs167
-rw-r--r--tests/get.rs1
-rw-r--r--tests/trybuild/hidden-field.rs18
-rw-r--r--tests/trybuild/hidden-field.stderr25
4 files changed, 150 insertions, 61 deletions
diff --git a/src/lib.rs b/src/lib.rs
index b43bff8..8f6ce4b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -100,11 +100,16 @@ mod get {
use quote::{format_ident, quote, ToTokens};
use syn::{
parse::Parser, punctuated::Punctuated, Attribute, Data, DeriveInput, Expr, Field, Fields,
- Ident, Index, Lit, Member, Meta, MetaNameValue, Token, Type,
+ Ident, Index, Lit, Member, MetaNameValue, Token, Type,
};
+ enum GetAttribute {
+ NameValueList(GetNameValueList),
+ IdentList(Vec<GetIdent>),
+ }
+
#[derive(Default)]
- struct GetAttribute {
+ struct GetNameValueList {
method: Option<String>,
}
@@ -113,6 +118,11 @@ mod get {
Method(String),
}
+ #[derive(Debug, Clone)]
+ enum GetIdent {
+ Hide,
+ }
+
pub fn expand(
input: &DeriveInput,
is_copy: bool,
@@ -141,24 +151,37 @@ mod get {
) -> Result<TokenStream, Box<dyn std::error::Error>> {
let mut tokens = TokenStream::new();
for field in fields {
- let method_name = match field.attrs.iter().find_map(|attr| {
+ match field.attrs.iter().find_map(|attr| {
attr.path()
.is_ident("get")
.then(|| GetAttribute::try_from(attr.clone()))
}) {
- Some(Ok(a)) if a.method.is_some() => {
- format_ident!("{}", a.method.unwrap().as_str())
+ Some(Ok(GetAttribute::IdentList(list)))
+ if list.iter().any(|i| matches!(i, GetIdent::Hide)) =>
+ {
+ continue
+ }
+ Some(Ok(GetAttribute::NameValueList(list))) => {
+ let method_name = list
+ .method
+ .map(|s| format_ident!("{s}"))
+ .unwrap_or(field.ident.as_ref().cloned().unwrap());
+ expand_getter(
+ field,
+ &Member::Named(field.ident.as_ref().cloned().unwrap()),
+ &method_name,
+ is_copy,
+ )
}
Some(Err(e)) => return Err(e),
- _ => field.ident.as_ref().cloned().unwrap(),
- };
- let getter = expand_getter(
- field,
- &Member::Named(field.ident.as_ref().unwrap().clone()),
- &method_name,
- is_copy,
- );
- getter.to_tokens(&mut tokens);
+ _ => expand_getter(
+ field,
+ &Member::Named(field.ident.as_ref().cloned().unwrap()),
+ field.ident.as_ref().unwrap(),
+ is_copy,
+ ),
+ }
+ .to_tokens(&mut tokens);
}
Ok(tokens)
}
@@ -169,37 +192,31 @@ mod get {
) -> Result<TokenStream, Box<dyn std::error::Error>> {
let mut tokens = TokenStream::new();
for (i, field) in fields.enumerate() {
- let method_name = match field.attrs.iter().find_map(|attr| {
+ match field.attrs.iter().find_map(|attr| {
attr.path()
.is_ident("get")
.then(|| GetAttribute::try_from(attr.clone()))
}) {
- Some(Ok(get_attr)) if get_attr.method.is_some() => {
- format_ident!("{}", get_attr.method.unwrap().as_str())
+ Some(Ok(GetAttribute::IdentList(list)))
+ if list.iter().any(|i| matches!(i, GetIdent::Hide)) =>
+ {
+ continue
}
- // Currently unreachable because an empty GetAttrubute is an error, and there is only
- // one field that can be set right now.
- Some(Ok(_)) => {
- return Err(concat!(
- r#"expected name value pair on tuple struct field"#,
- " ",
- r#"(e.g #[get("method" = method_name)])"#
+ Some(Ok(GetAttribute::NameValueList(list))) if list.method.is_some() => {
+ expand_getter(
+ field,
+ &Member::Unnamed(Index {
+ index: i.try_into().unwrap(),
+ span: Span::call_site(),
+ }),
+ &list.method.map(|s| format_ident!("{s}")).unwrap(),
+ is_copy,
)
- .into())
}
Some(Err(e)) => return Err(e),
- None => return Err(r#"expected attribute on tuple struct field"#.into()),
- };
- let getter = expand_getter(
- field,
- &Member::Unnamed(Index {
- index: i as u32,
- span: Span::call_site(),
- }),
- &method_name,
- is_copy,
- );
- getter.to_tokens(&mut tokens);
+ _ => return Err("expected attribute on tuple struct field".into()),
+ }
+ .to_tokens(&mut tokens)
}
Ok(tokens)
}
@@ -239,34 +256,62 @@ mod get {
}
}
- // This method might seem overly complicated but it will make it easy to add new fields to the
- // GetAttribute struct in the future!
-
impl TryFrom<Attribute> for GetAttribute {
type Error = Box<dyn std::error::Error>;
fn try_from(attr: Attribute) -> Result<Self, Self::Error> {
- if_chain! {
- if attr.path().is_ident("get");
- if let Meta::List(list) = &attr.meta;
- then {
- Ok(Punctuated::<MetaNameValue, Token![,]>::parse_terminated
- .parse(list.tokens.clone().into())?
- .into_iter()
- .map(|n| n.try_into())
- .collect::<Result<Vec<GetNameValue>, _>>()
- .map(|v| match v.len() {
- 1..=3 => Ok(v),
- _ => Err("expected at least 1 name value pair in attribute")
- })??
- .into_iter()
- .fold(Self::default(), |_, n| match n {
- GetNameValue::Method(s) => Self {
- method: Some(s),
- },
- }))
- } else {
- Err("failed to parse attribute".into())
+ let meta_list = attr
+ .meta
+ .require_list()
+ .map_err(|_| "failed to parse attribute")?;
+ let name_value_list = Punctuated::<MetaNameValue, Token![,]>::parse_terminated
+ .parse(meta_list.tokens.clone().into());
+ let ident_list = Punctuated::<Ident, Token![,]>::parse_terminated
+ .parse(meta_list.tokens.clone().into());
+ Ok(match (name_value_list, ident_list) {
+ (Ok(list), _) => Self::NameValueList(GetNameValueList::try_from(list)?),
+ (_, Ok(list)) => {
+ Self::IdentList(list.into_iter().map(GetIdent::try_from).collect::<Result<
+ Vec<GetIdent>,
+ _,
+ >>(
+ )?)
}
+ _ => return Err("failed to parse attribute".into()),
+ })
+ }
+ }
+
+ #[allow(clippy::needless_update)]
+ impl TryFrom<Punctuated<MetaNameValue, Token![,]>> for GetNameValueList {
+ type Error = Box<dyn std::error::Error>;
+ fn try_from(punct: Punctuated<MetaNameValue, Token![,]>) -> Result<Self, Self::Error> {
+ Ok(punct
+ .into_iter()
+ .map(GetNameValue::try_from)
+ .collect::<Result<Vec<GetNameValue>, _>>()
+ .map(|v| {
+ if !v.is_empty() {
+ Ok::<Vec<GetNameValue>, Box<dyn std::error::Error>>(v)
+ } else {
+ Err("expected at least 1 name value pair in attribute".into())
+ }
+ })??
+ .into_iter()
+ .fold(Self::default(), |acc, n| match n {
+ GetNameValue::Method(m) => Self {
+ method: Some(m),
+ ..acc
+ },
+ }))
+ }
+ }
+
+ impl TryFrom<Ident> for GetIdent {
+ type Error = Box<dyn std::error::Error>;
+ fn try_from(i: Ident) -> Result<Self, Self::Error> {
+ match i.to_string().as_str() {
+ "hide" => Ok(Self::Hide),
+ _ => Err(r#"expected the following ident in meta list: "hide""#.into()),
}
}
}
diff --git a/tests/get.rs b/tests/get.rs
index 937ddf5..8ff2c24 100644
--- a/tests/get.rs
+++ b/tests/get.rs
@@ -10,6 +10,7 @@ fn trybuild() {
tests.compile_fail(testcase!("unit-struct.rs"));
tests.compile_fail(testcase!("tuple-struct-without-attribute.rs"));
tests.compile_fail(testcase!("invalid-attribute.rs"));
+ tests.compile_fail(testcase!("hidden-field.rs"));
}
mod get {
diff --git a/tests/trybuild/hidden-field.rs b/tests/trybuild/hidden-field.rs
new file mode 100644
index 0000000..52675fb
--- /dev/null
+++ b/tests/trybuild/hidden-field.rs
@@ -0,0 +1,18 @@
+use get::Get;
+
+#[derive(Get)]
+struct Crab {
+ #[get(hide)]
+ name: String,
+ age: u64
+}
+
+fn crab() {
+ let ferris = Crab {
+ name: "ferris".to_string(),
+ 1
+ };
+ assert_eq!(ferris.name().as_str(), "ferris");
+}
+
+pub fn main() {}
diff --git a/tests/trybuild/hidden-field.stderr b/tests/trybuild/hidden-field.stderr
new file mode 100644
index 0000000..1b2adf0
--- /dev/null
+++ b/tests/trybuild/hidden-field.stderr
@@ -0,0 +1,25 @@
+error: expected identifier, found `1`
+ --> tests/trybuild/hidden-field.rs:13:9
+ |
+11 | let ferris = Crab {
+ | ---- while parsing this struct
+12 | name: "ferris".to_string(),
+13 | 1
+ | ^ expected identifier
+
+error[E0063]: missing field `age` in initializer of `Crab`
+ --> tests/trybuild/hidden-field.rs:11:18
+ |
+11 | let ferris = Crab {
+ | ^^^^ missing `age`
+
+error[E0599]: no method named `name` found for struct `Crab` in the current scope
+ --> tests/trybuild/hidden-field.rs:15:23
+ |
+4 | struct Crab {
+ | ----------- method `name` not found for this struct
+...
+15 | assert_eq!(ferris.name().as_str(), "ferris");
+ | ^^^^-- help: remove the arguments
+ | |
+ | field, not a method