diff --git a/Cargo.lock b/Cargo.lock index c3b448f..b614e32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,19 +4,13 @@ version = 4 [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] -[[package]] -name = "procmacro2" -version = "99.9.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302c525e16613efd9c9a684c096503d9dc662f7feb6b6c40e59303b4e2c4c113" - [[package]] name = "quote" version = "1.0.38" @@ -39,16 +33,16 @@ dependencies = [ [[package]] name = "ters" -version = "0.1.3" +version = "0.2.0" dependencies = [ "ters-macros", ] [[package]] name = "ters-macros" -version = "0.1.3" +version = "0.2.0" dependencies = [ - "procmacro2", + "proc-macro2", "quote", "syn", ] diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 28df205..7450770 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ters-macros" -version = "0.1.3" +version = "0.2.0" edition = "2021" description = "Supporting macros for the ters crate." license = "MIT" @@ -10,6 +10,6 @@ repository = "https://github.com/adinack/ters" proc-macro = true [dependencies] -procmacro2 = "99.9.99" +proc-macro2 = "1.0.103" quote = "1.0.38" syn = { version = "2.0.98", features = ["full"] } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 26663ab..7947393 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -2,15 +2,92 @@ use proc_macro::TokenStream; use quote::{format_ident, quote}; use syn::{parse_macro_input, ItemStruct}; +/// Generate getters and setters procedurally. +/// +/// Annotate fields with `#[get]` to generate a getter method. +/// ```ignore +/// use ters::ters; +/// +/// #[ters] +/// struct Foo { +/// a: i32, +/// #[get] +/// b: bool, +/// } +/// +/// fn getters() { +/// let foo = Foo { a: 42, b: true }; +/// assert_eq!(foo.b(), &true); +/// } +/// ``` +/// +/// Annotate fields with `#[set]` to generate a setter method. +/// ```ignore +/// use ters::ters; +/// +/// #[ters] +/// struct Foo { +/// #[set] +/// a: i32, +/// b: bool, +/// } +/// +/// fn setters() { +/// let mut foo = Foo { a: 42, b: true }; +/// foo.set_a(31); +/// } +/// ``` +/// +/// Annotate fields with `#[get]` and `#[set]` to generate both a getter and a setter method. +/// ```ignore +/// use ters::ters; +/// +/// #[ters] +/// struct Foo { +/// #[get] +/// #[set] +/// a: i32, +/// b: bool, +/// } +/// +/// fn getters_and_setters() { +/// let mut foo = Foo { a: 42, b: true }; +/// assert_eq!(foo.a(), &42); +/// foo.set_a(31); +/// +/// assert_eq!(foo.a(), &31); +/// } +/// ``` +/// +/// Unannotated fields will not have generated getters or setters. +/// ```ignore +/// use ters::ters; +/// +/// #[ters] +/// struct Foo { +/// a: i32, +/// #[get] +/// b: bool, +/// } +/// +/// fn getters_not_generated() { +/// let foo = Foo { a: 42, b: true }; +/// assert_eq!(foo.a(), &42); // this method doesn't exist +/// } +/// ``` #[proc_macro_attribute] -pub fn ters(_args: TokenStream, item: TokenStream) -> TokenStream { - let mut s = parse_macro_input!(item as ItemStruct); +pub fn ters(_args: TokenStream, tokens: TokenStream) -> TokenStream { + let item = parse_macro_input!(tokens as ItemStruct); - let (impl_generics, ty_generics, where_clause) = s.generics.split_for_impl(); + ters_inner(item).into() +} + +fn ters_inner(mut item: ItemStruct) -> proc_macro2::TokenStream { + let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl(); let mut fields = Vec::new(); - for field in s.fields.iter_mut() { + for field in item.fields.iter_mut() { let mut get = false; let mut set = false; @@ -26,45 +103,116 @@ pub fn ters(_args: TokenStream, item: TokenStream) -> TokenStream { } }); - fields.push((field.ident.clone().unwrap(), field.ty.clone(), get, set)); + fields.push(( + field.ident.clone().unwrap(), + field.ty.clone(), + get, + set, + field + .attrs + .iter() + .filter(|attr| { + attr.path() + .get_ident() + .map(|ident| ident == "doc") + .is_some_and(|is_doc| is_doc) + }) + .cloned() + .collect::>(), + )); } - let accessors = fields.iter().map(|(ident, ty, get, set)| { - let set_ident = format_ident!("set_{ident}"); + let accessors = fields + .iter() + .filter_map(|(ident, ty, get, set, docs)| { + let set_ident = format_ident!("set_{ident}"); + let str_ident = ident.to_string(); - let mut body = quote! {}; - - if *get { - body.extend(quote! { - #[inline] - pub fn #ident(&self) -> &#ty { - &self.#ident - } - }); - } + let mut body = quote! {}; - if *set { - body.extend(quote! { - #[inline] - pub fn #set_ident(&mut self, value: #ty) { - self.#ident = value; - } - }); - } + if *get { + body.extend(quote! { + #[doc = "Getter for `"] + #[doc = #str_ident] + #[doc = "`.\n\n"] + #(#docs)* + #[inline] + pub fn #ident(&self) -> &#ty { + &self.#ident + } + }); + } - body - }); + if *set { + body.extend(quote! { + #[doc = "Setter for `"] + #[doc = #str_ident] + #[doc = "`.\n\n"] + #(#docs)* + #[inline] + pub fn #set_ident(&mut self, value: #ty) { + self.#ident = value; + } + }); + } - let ident = &s.ident; + (!body.is_empty()).then_some(body) + }) + .collect::>(); - quote! { - #s + let ident = &item.ident; + let impl_ = (!accessors.is_empty()).then_some(quote! { impl #impl_generics #ident #ty_generics #where_clause { #( #accessors )* } + }); + + quote! { + #item + #impl_ + } +} + +#[cfg(test)] +mod tests { + use quote::quote; + use syn::parse_quote; + + use crate::ters_inner; + + #[test] + fn docs() { + let input = parse_quote! { + struct Foo { + /// Baz. + #[get] + bar: u8, + } + }; + + let expected = quote! { + struct Foo { + /// Baz. + bar: u8, + } + + impl Foo { + #[doc = "Getter for `"] + #[doc = "bar"] + #[doc = "`.\n\n"] + /// Baz. + #[inline] + pub fn bar(&self) -> &u8 { + &self.bar + } + } + }; + + let out: proc_macro2::TokenStream = ters_inner(input).into(); + + assert_eq!(out.to_string(), expected.to_string()); } - .into() } diff --git a/ters/Cargo.toml b/ters/Cargo.toml index 1d5f882..a0f015f 100644 --- a/ters/Cargo.toml +++ b/ters/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "ters" -version = "0.1.3" +version = "0.2.0" edition = "2021" description = "Generate getters and setters procedurally." license = "MIT" repository = "https://github.com/adinack/ters" [dependencies] -ters-macros = { path = "../macros", version = "0.1.3" } +ters-macros = { path = "../macros", version = "0.2.0" }