diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 21f9f1c3c0da4bd347bf568a65f3bfc53df3b030..71b24288d578fa091b19511661cf940a93372c2a 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -18,6 +18,7 @@ name = "amalib" version = "0.1.0" dependencies = [ "commons", + "getset", "serde", "tokio", ] @@ -259,6 +260,16 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "getset" +version = "0.1.2" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "h2" version = "0.3.15" diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 0b4b217d6d6e822811b900c682c12ce0a1fec4d1..f8363c81dc645e9c6c0afcf521c0822eb7734fd5 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -11,6 +11,7 @@ members = [ # Common things "kurisu_api", "commons", + "getset", # Clients "amadeus", diff --git a/src/rust/amalib/Cargo.toml b/src/rust/amalib/Cargo.toml index 6def1de2e7e71aa0e950abd6a0cad47115f265f2..9cee7e7860571c0b82aba6cbbb2d4008c7c42ff8 100644 --- a/src/rust/amalib/Cargo.toml +++ b/src/rust/amalib/Cargo.toml @@ -10,3 +10,4 @@ serde.workspace = true tokio.workspace = true commons = { path = "../commons" } +getset = { path = "../getset" } diff --git a/src/rust/amalib/src/response.rs b/src/rust/amalib/src/response.rs index 460cd0ce40f6092db44cd36509e3c1ddd245faa5..ecd326c307e7960faa2117b2da2ad0790f616285 100644 --- a/src/rust/amalib/src/response.rs +++ b/src/rust/amalib/src/response.rs @@ -7,8 +7,8 @@ use crate::*; /// spaces from the keys and the values. The keys are always in lowercase. #[derive(Debug)] pub struct LektorFormatedResponse { - content: Vec<(String, String)>, - raw_content: Vec<String>, + dict: Vec<(String, String)>, + raw: Vec<String>, } impl LektorFormatedResponse { @@ -16,12 +16,12 @@ impl LektorFormatedResponse { /// found. If multiple keys are found, only the first found is returned. pub fn pop(&mut self, key: &str) -> Result<String, String> { match self - .content + .dict .iter() .enumerate() .find_map(|(index, (what, _))| (what == key).then_some(index)) { - Some(index) => Ok(self.content.remove(index).1), + Some(index) => Ok(self.dict.remove(index).1), None => Err(format!("no key {key} was found in formated response")), } } @@ -30,7 +30,7 @@ impl LektorFormatedResponse { /// vector is returned. This function can't fail. pub fn pop_all(&mut self, key: &str) -> Vec<String> { let mut ret: Vec<String> = Vec::new(); - self.content.retain(|(what, field)| { + self.dict.retain(|(what, field)| { if *what == key { ret.push(field.clone()); false @@ -45,12 +45,12 @@ impl LektorFormatedResponse { /// [`LektorFormatedResponse::pop_raw`] function, but we splited each line /// on the `: ` patern, and trimed all the things. pub fn pop_dict(self) -> Vec<(String, String)> { - self.content + self.dict } /// Get the raw content of the response. pub fn pop_raw(self) -> Vec<String> { - self.raw_content + self.raw } } @@ -60,7 +60,7 @@ impl IntoIterator for LektorFormatedResponse { <std::vec::Vec<(std::string::String, std::string::String)> as IntoIterator>::IntoIter; fn into_iter(self) -> Self::IntoIter { - self.content.into_iter() + self.dict.into_iter() } } @@ -76,8 +76,8 @@ impl TryFrom<Vec<String>> for LektorFormatedResponse { } } Ok(Self { - content, - raw_content, + dict: content, + raw: raw_content, }) } } @@ -116,21 +116,8 @@ mod private { impl Sealed for LektorEmptyResponse {} } -macro_rules! getter { - ($name: ident: ref $type: ty) => { - pub fn $name(&self) -> &$type { - &self.$name - } - }; - - ($name: ident: $type: ty) => { - pub fn $name(&self) -> $type { - self.$name - } - }; -} - -#[derive(Debug)] +#[derive(Debug, getset::CopyGetters)] +#[getset(get_copy = "pub")] pub struct LektorPlaybackStatusResponse { elapsed: usize, songid: Option<usize>, @@ -151,15 +138,18 @@ pub struct LektorPlaylistSetResponse { playlists: Vec<String>, } -#[derive(Debug)] +#[derive(Debug, getset::Getters)] +#[getset(get = "pub")] pub struct LektorCurrentKaraInnerResponse { title: String, author: String, source: String, song_type: String, - song_number: Option<usize>, category: String, language: String, + + #[getset(get_copy = "pub")] + song_number: Option<usize>, } #[derive(Debug)] @@ -177,7 +167,8 @@ pub struct LektorIntegerResponse { content: usize, } -#[derive(Debug)] +#[derive(Debug, getset::Getters)] +#[getset(get = "pub")] pub struct LektorCurrentKaraResponse { content: Option<LektorCurrentKaraInnerResponse>, } @@ -186,8 +177,6 @@ pub struct LektorCurrentKaraResponse { pub struct LektorEmptyResponse; impl LektorCurrentKaraResponse { - getter!(content: ref Option<LektorCurrentKaraInnerResponse>); - pub fn maybe_into_inner(self) -> Option<LektorCurrentKaraInnerResponse> { self.content } @@ -197,30 +186,7 @@ impl LektorCurrentKaraResponse { } } -impl LektorCurrentKaraInnerResponse { - getter!(title: ref String); - getter!(author: ref String); - getter!(source: ref String); - getter!(song_type: ref String); - getter!(song_number: Option<usize>); - getter!(category: ref String); - getter!(language: ref String); -} - impl LektorPlaybackStatusResponse { - getter!(elapsed: usize); - getter!(songid: Option<usize>); - getter!(song: Option<usize>); - getter!(volume: usize); - getter!(state: LektorState); - getter!(duration: usize); - getter!(updating_db: usize); - getter!(playlistlength: usize); - getter!(random: bool); - getter!(consume: bool); - getter!(single: bool); - getter!(repeat: bool); - pub fn verify(&self) -> bool { self.elapsed() <= self.duration() } diff --git a/src/rust/getset/.github/actions-rs/grcov.yml b/src/rust/getset/.github/actions-rs/grcov.yml new file mode 100644 index 0000000000000000000000000000000000000000..581dfdd4cd0891ad605bb478352923cc46f57dc8 --- /dev/null +++ b/src/rust/getset/.github/actions-rs/grcov.yml @@ -0,0 +1,10 @@ +branch: true +ignore-not-existing: true +llvm: true +filter: covered +output-type: lcov +output-file: /tmp/lcov.info +ignore: + - "/*" + - "C:/*" + - "../*" \ No newline at end of file diff --git a/src/rust/getset/.github/workflows/audit.yml b/src/rust/getset/.github/workflows/audit.yml new file mode 100644 index 0000000000000000000000000000000000000000..1fbe6239ccccd2dcd4ae64838cbed983d9ec27da --- /dev/null +++ b/src/rust/getset/.github/workflows/audit.yml @@ -0,0 +1,21 @@ +name: audit + +on: + schedule: + - cron: '0 0 * * *' + push: + +jobs: + audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Cache target + id: cache-target + uses: actions/cache@v1 + with: + path: target + key: ${{ runner.os }}-audit-target-${{ hashFiles('**/Cargo.toml') }} + - uses: actions-rs/audit-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/src/rust/getset/.github/workflows/test.yml b/src/rust/getset/.github/workflows/test.yml new file mode 100644 index 0000000000000000000000000000000000000000..8c20e2c79153bda9d4dda8f66f58da3d0918060f --- /dev/null +++ b/src/rust/getset/.github/workflows/test.yml @@ -0,0 +1,72 @@ +name: test + +on: + push: + pull_request: + +env: + RUSTFLAGS: "--deny=warnings" + +jobs: + test: + strategy: + matrix: + os: [macos-latest, windows-latest, ubuntu-latest] + rust: [stable, nightly] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v1 + - name: Cache target + id: cache-target + uses: actions/cache@v1 + with: + path: target + key: ${{ runner.os }}-${{ matrix.rust }}-test-target-${{ hashFiles('**/Cargo.toml') }} + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + - name: Test + uses: actions-rs/cargo@v1 + with: + command: test + args: -- --nocapture + + clippy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Cache target + id: cache-target + uses: actions/cache@v1 + with: + path: target + key: ${{ runner.os }}-clippy-target-${{ hashFiles('**/Cargo.toml') }} + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + - run: rustup component add clippy + - uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Cache target + id: cache-target + uses: actions/cache@v1 + with: + path: target + key: ${{ runner.os }}-format-target-${{ hashFiles('**/Cargo.toml') }} + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + - run: rustup component add rustfmt + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: -- --check diff --git a/src/rust/getset/Cargo.toml b/src/rust/getset/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..b6b65079c2a35d3d37619ccd1c79c4e6539957f4 --- /dev/null +++ b/src/rust/getset/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "getset" +description = """ +Getset, we're ready to go! + +A procedural macro for generating the most basic getters and setters on fields. +""" +version = "0.1.2" +authors = ["Ana Hobden <ana@hoverbear.org>"] +license = "MIT" +edition = "2018" + +categories = ["development-tools::procedural-macro-helpers"] +keywords = ["macro", "getter", "setter", "getters", "setters"] +readme = "README.md" +repository = "https://github.com/Hoverbear/getset" + +[lib] +proc-macro = true + +[dependencies] +quote = "1" +syn = "1" +proc-macro2 = { version = "1", default-features = false } +proc-macro-error = "1.0" diff --git a/src/rust/getset/LICENSE b/src/rust/getset/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..76f4968430db086691c4f8cf47fdd65dc908373b --- /dev/null +++ b/src/rust/getset/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Ana Hobden <ana@hoverbear.org> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/rust/getset/examples/simple.rs b/src/rust/getset/examples/simple.rs new file mode 100644 index 0000000000000000000000000000000000000000..62f072f37afeb751e6bcd63a01867524beb7c110 --- /dev/null +++ b/src/rust/getset/examples/simple.rs @@ -0,0 +1,24 @@ +use getset::{CopyGetters, Getters, MutGetters, Setters}; + +#[derive(Getters, Setters, MutGetters, CopyGetters, Default)] +pub struct Foo<T> +where + T: Copy + Clone + Default, +{ + /// Doc comments are supported! + /// Multiline, even. + #[getset(get, set, get_mut)] + private: T, + + /// Doc comments are supported! + /// Multiline, even. + #[getset(get_copy = "pub", set = "pub", get_mut = "pub")] + public: T, +} + +fn main() { + let mut foobar = Foo::default(); + foobar.set_private(1); + (*foobar.private_mut()) += 1; + assert_eq!(*foobar.private(), 2); +} diff --git a/src/rust/getset/src/generate.rs b/src/rust/getset/src/generate.rs new file mode 100644 index 0000000000000000000000000000000000000000..c4069617cbd2a7efdc4a2ca5a2fc3fb92d5f36e2 --- /dev/null +++ b/src/rust/getset/src/generate.rs @@ -0,0 +1,197 @@ +use self::GenMode::*; +use super::{parse_attr, quote}; +use proc_macro2::TokenStream as TokenStream2; +use proc_macro2::{Ident, Span}; +use proc_macro_error::{abort, ResultExt}; +use syn::{self, ext::IdentExt, spanned::Spanned, Field, Lit, Meta, MetaNameValue, Visibility}; + +pub struct GenParams { + pub mode: GenMode, + pub global_attr: Option<Meta>, +} + +#[derive(PartialEq, Eq, Copy, Clone)] +pub enum GenMode { + Get, + GetCopy, + Set, + GetMut, +} + +impl GenMode { + pub fn name(self) -> &'static str { + match self { + Get => "get", + GetCopy => "get_copy", + Set => "set", + GetMut => "get_mut", + } + } + + pub fn prefix(self) -> &'static str { + match self { + Get | GetCopy | GetMut => "", + Set => "set_", + } + } + + pub fn suffix(self) -> &'static str { + match self { + Get | GetCopy | Set => "", + GetMut => "_mut", + } + } + + fn is_get(self) -> bool { + match self { + GenMode::Get | GenMode::GetCopy | GenMode::GetMut => true, + GenMode::Set => false, + } + } +} + +pub fn parse_visibility(attr: Option<&Meta>, meta_name: &str) -> Option<Visibility> { + match attr { + // `#[get = "pub"]` or `#[set = "pub"]` + Some(Meta::NameValue(MetaNameValue { + lit: Lit::Str(ref s), + path, + .. + })) => { + if path.is_ident(meta_name) { + s.value().split(' ').find(|v| *v != "with_prefix").map(|v| { + syn::parse_str(v) + .map_err(|e| syn::Error::new(s.span(), e)) + .expect_or_abort("invalid visibility found") + }) + } else { + None + } + } + _ => None, + } +} + +/// Some users want legacy/compatability. +/// (Getters are often prefixed with `get_`) +fn has_prefix_attr(f: &Field, params: &GenParams) -> bool { + let inner = f + .attrs + .iter() + .filter_map(|v| parse_attr(v, params.mode)) + .filter(|meta| { + ["get", "get_copy"] + .iter() + .any(|ident| meta.path().is_ident(ident)) + }) + .last(); + + // Check it the attr includes `with_prefix` + let wants_prefix = |possible_meta: &Option<Meta>| -> bool { + match possible_meta { + Some(Meta::NameValue(meta)) => { + if let Lit::Str(lit_str) = &meta.lit { + // Naive tokenization to avoid a possible visibility mod named `with_prefix`. + lit_str.value().split(' ').any(|v| v == "with_prefix") + } else { + false + } + } + _ => false, + } + }; + + // `with_prefix` can either be on the local or global attr + wants_prefix(&inner) || wants_prefix(¶ms.global_attr) +} + +pub fn implement(field: &Field, params: &GenParams) -> TokenStream2 { + let field_name = field + .clone() + .ident + .unwrap_or_else(|| abort!(field.span(), "Expected the field to have a name")); + + let fn_name = if !has_prefix_attr(field, params) + && (params.mode.is_get()) + && params.mode.suffix().is_empty() + && field_name.to_string().starts_with("r#") + { + field_name.clone() + } else { + Ident::new( + &format!( + "{}{}{}{}", + if has_prefix_attr(field, params) && (params.mode.is_get()) { + "get_" + } else { + "" + }, + params.mode.prefix(), + field_name.unraw(), + params.mode.suffix() + ), + Span::call_site(), + ) + }; + let ty = field.ty.clone(); + + let doc = field.attrs.iter().filter(|v| { + v.parse_meta() + .map(|meta| meta.path().is_ident("doc")) + .unwrap_or(false) + }); + + let attr = field + .attrs + .iter() + .filter_map(|v| parse_attr(v, params.mode)) + .last() + .or_else(|| params.global_attr.clone()); + + 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! { + #(#doc)* + #[inline(always)] + #visibility fn #fn_name(&self) -> &#ty { + &self.#field_name + } + } + } + GenMode::GetCopy => { + quote! { + #(#doc)* + #[inline(always)] + #visibility fn #fn_name(&self) -> #ty { + self.#field_name + } + } + } + GenMode::Set => { + quote! { + #(#doc)* + #[inline(always)] + #visibility fn #fn_name(&mut self, val: #ty) -> &mut Self { + self.#field_name = val; + self + } + } + } + GenMode::GetMut => { + quote! { + #(#doc)* + #[inline(always)] + #visibility fn #fn_name(&mut self) -> &mut #ty { + &mut self.#field_name + } + } + } + }, + // Don't need to do anything. + None => quote! {}, + } +} diff --git a/src/rust/getset/src/lib.rs b/src/rust/getset/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..9ab860177cbfdf4ed035eaa5f9562991b4497e96 --- /dev/null +++ b/src/rust/getset/src/lib.rs @@ -0,0 +1,336 @@ +/*! +Getset, we're ready to go! + +A procedural macro for generating the most basic getters and setters on fields. + +Getters are generated as `fn field(&self) -> &type`, while setters are generated as `fn field(&mut self, val: type)`. + +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}; + +#[derive(Getters, Setters, MutGetters, CopyGetters, Default)] +pub struct Foo<T> +where + T: Copy + Clone + Default, +{ + /// Doc comments are supported! + /// Multiline, even. + #[getset(get, set, get_mut)] + private: T, + + /// Doc comments are supported! + /// Multiline, even. + #[getset(get_copy = "pub", set = "pub", get_mut = "pub")] + public: T, +} + +let mut foo = Foo::default(); +foo.set_private(1); +(*foo.private_mut()) += 1; +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}; +pub struct Foo<T> +where + T: Copy + Clone + Default, +{ + /// Doc comments are supported! + /// Multiline, even. + #[getset(get, get, get_mut)] + private: T, + /// Doc comments are supported! + /// Multiline, even. + #[getset(get_copy = "pub", set = "pub", get_mut = "pub")] + public: T, +} +impl<T> Foo<T> +where + T: Copy + Clone + Default, +{ + /// Doc comments are supported! + /// Multiline, even. + #[inline(always)] + fn private(&self) -> &T { + &self.private + } +} +impl<T> Foo<T> +where + T: Copy + Clone + Default, +{ + /// Doc comments are supported! + /// Multiline, even. + #[inline(always)] + pub fn set_public(&mut self, val: T) -> &mut Self { + self.public = val; + self + } +} +impl<T> Foo<T> +where + T: Copy + Clone + Default, +{ + /// Doc comments are supported! + /// Multiline, even. + #[inline(always)] + fn private_mut(&mut self) -> &mut T { + &mut self.private + } + /// Doc comments are supported! + /// Multiline, even. + #[inline(always)] + pub fn public_mut(&mut self) -> &mut T { + &mut self.public + } +} +impl<T> Foo<T> +where + T: Copy + Clone + Default, +{ + /// Doc comments are supported! + /// Multiline, even. + #[inline(always)] + pub fn public(&self) -> T { + self.public + } +} +``` + +Attributes can be set on struct level for all fields in struct as well. Field level attributes take +precedence. + +```rust +mod submodule { + use getset::{Getters, MutGetters, CopyGetters, Setters}; + #[derive(Getters, CopyGetters, Default)] + #[getset(get_copy = "pub")] // By default add a pub getting for all fields. + pub struct Foo { + public: i32, + #[getset(get_copy)] // Override as private + private: i32, + } + fn demo() { + let mut foo = Foo::default(); + foo.private(); + } +} + +let mut foo = submodule::Foo::default(); +foo.public(); +``` + +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}; + +#[derive(Getters, Default)] +pub struct Foo { + #[getset(get = "pub with_prefix")] + field: bool, +} + + +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 + } +} +``` +*/ + +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use proc_macro_error::{abort, abort_call_site, proc_macro_error, ResultExt}; +use quote::quote; +use syn::{spanned::Spanned, DataStruct, DeriveInput, Meta}; + +mod generate; +use crate::generate::{GenMode, GenParams}; + +#[proc_macro_derive(Getters, attributes(get, with_prefix, getset))] +#[proc_macro_error] +pub fn getters(input: TokenStream) -> TokenStream { + // Parse the string representation + let ast: DeriveInput = syn::parse(input).expect_or_abort("Couldn't parse for getters"); + let params = GenParams { + mode: GenMode::Get, + global_attr: parse_global_attr(&ast.attrs, GenMode::Get), + }; + + // Build the impl + let gen = produce(&ast, ¶ms); + + // Return the generated impl + gen.into() +} + +#[proc_macro_derive(CopyGetters, attributes(get_copy, with_prefix, getset))] +#[proc_macro_error] +pub fn copy_getters(input: TokenStream) -> TokenStream { + // Parse the string representation + let ast: DeriveInput = syn::parse(input).expect_or_abort("Couldn't parse for getters"); + let params = GenParams { + mode: GenMode::GetCopy, + global_attr: parse_global_attr(&ast.attrs, GenMode::GetCopy), + }; + + // Build the impl + let gen = produce(&ast, ¶ms); + + // Return the generated impl + gen.into() +} + +#[proc_macro_derive(MutGetters, attributes(get_mut, getset))] +#[proc_macro_error] +pub fn mut_getters(input: TokenStream) -> TokenStream { + // Parse the string representation + let ast: DeriveInput = syn::parse(input).expect_or_abort("Couldn't parse for getters"); + let params = GenParams { + mode: GenMode::GetMut, + global_attr: parse_global_attr(&ast.attrs, GenMode::GetMut), + }; + + // Build the impl + let gen = produce(&ast, ¶ms); + // Return the generated impl + gen.into() +} + +#[proc_macro_derive(Setters, attributes(set, getset))] +#[proc_macro_error] +pub fn setters(input: TokenStream) -> TokenStream { + // Parse the string representation + let ast: DeriveInput = syn::parse(input).expect_or_abort("Couldn't parse for setters"); + let params = GenParams { + mode: GenMode::Set, + global_attr: parse_global_attr(&ast.attrs, GenMode::Set), + }; + + // Build the impl + let gen = produce(&ast, ¶ms); + + // Return the generated impl + gen.into() +} + +fn parse_global_attr(attrs: &[syn::Attribute], mode: GenMode) -> Option<Meta> { + attrs + .iter() + .filter_map(|v| parse_attr(v, mode)) // non "meta" attributes are not our concern + .last() +} + +fn parse_attr(attr: &syn::Attribute, mode: GenMode) -> Option<Meta> { + use syn::{punctuated::Punctuated, Token}; + + if attr.path.is_ident("getset") { + 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("skip")) + { + abort!(meta.path().span(), "unknown setter or getter") + } + }) + .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() + .filter(|meta| meta.path().is_ident(mode.name())) + } +} + +fn produce(ast: &DeriveInput, params: &GenParams) -> TokenStream2 { + let name = &ast.ident; + let generics = &ast.generics; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + // Is it a struct? + if let syn::Data::Struct(DataStruct { ref fields, .. }) = ast.data { + let generated = fields.iter().map(|f| generate::implement(f, params)); + + quote! { + impl #impl_generics #name #ty_generics #where_clause { + #(#generated)* + } + } + } else { + // Nope. This is an Enum. We cannot handle these! + abort_call_site!("#[derive(Getters)] is only defined for structs, not for enums!"); + } +} diff --git a/src/rust/getset/tests/copy_getters.rs b/src/rust/getset/tests/copy_getters.rs new file mode 100644 index 0000000000000000000000000000000000000000..c43f7c2665639a2459151337662e573995eac18a --- /dev/null +++ b/src/rust/getset/tests/copy_getters.rs @@ -0,0 +1,149 @@ +#[macro_use] +extern crate getset; + +use crate::submodule::other::{Generic, Plain, Where}; + +// For testing `pub(super)` +mod submodule { + // For testing `pub(super::other)` + pub mod other { + #[derive(CopyGetters)] + #[get_copy] + pub struct Plain { + /// A doc comment. + /// Multiple lines, even. + private_accessible: usize, + + /// A doc comment. + #[get_copy = "pub"] + public_accessible: usize, + // /// A doc comment. + // #[get_copy = "pub(crate)"] + // crate_accessible: usize, + + // /// A doc comment. + // #[get_copy = "pub(super)"] + // super_accessible: usize, + + // /// A doc comment. + // #[get_copy = "pub(super::other)"] + // scope_accessible: usize, + + // Prefixed getter. + #[get_copy = "with_prefix"] + private_prefixed: usize, + + // Prefixed getter. + #[get_copy = "pub with_prefix"] + public_prefixed: usize, + } + + impl Default for Plain { + fn default() -> Plain { + Plain { + private_accessible: 17, + public_accessible: 18, + private_prefixed: 19, + public_prefixed: 20, + } + } + } + + #[derive(CopyGetters, Default)] + #[get_copy] + pub struct Generic<T: Copy + Clone + Default> { + /// A doc comment. + /// Multiple lines, even. + private_accessible: T, + + /// A doc comment. + #[get_copy = "pub"] + public_accessible: T, + // /// A doc comment. + // #[get_copy = "pub(crate)"] + // crate_accessible: usize, + + // /// A doc comment. + // #[get_copy = "pub(super)"] + // super_accessible: usize, + + // /// A doc comment. + // #[get_copy = "pub(super::other)"] + // scope_accessible: usize, + } + + #[derive(CopyGetters, Getters, Default)] + #[get_copy] + pub struct Where<T> + where + T: Copy + Clone + Default, + { + /// A doc comment. + /// Multiple lines, even. + private_accessible: T, + + /// A doc comment. + #[get_copy = "pub"] + public_accessible: T, + // /// A doc comment. + // #[get_copy = "pub(crate)"] + // crate_accessible: usize, + + // /// A doc comment. + // #[get_copy = "pub(super)"] + // super_accessible: usize, + + // /// A doc comment. + // #[get_copy = "pub(super::other)"] + // scope_accessible: usize, + } + + #[test] + fn test_plain() { + let val = Plain::default(); + val.private_accessible(); + } + + #[test] + fn test_generic() { + let val = Generic::<usize>::default(); + val.private_accessible(); + } + + #[test] + fn test_where() { + let val = Where::<usize>::default(); + val.private_accessible(); + } + + #[test] + fn test_prefixed_plain() { + let val = Plain::default(); + assert_eq!(19, val.get_private_prefixed()); + } + } +} + +#[test] +fn test_plain() { + let val = Plain::default(); + assert_eq!(18, val.public_accessible()); +} + +#[test] +fn test_generic() { + let val = Generic::<usize>::default(); + assert_eq!(usize::default(), val.public_accessible()); +} + +#[test] +fn test_where() { + let val = Where::<usize>::default(); + assert_eq!(usize::default(), val.public_accessible()); +} + +#[test] +fn test_prefixed_plain() { + let val = Plain::default(); + assert_eq!(20, val.get_public_prefixed()); +} diff --git a/src/rust/getset/tests/getset.rs b/src/rust/getset/tests/getset.rs new file mode 100644 index 0000000000000000000000000000000000000000..9985078072de14cdd6adb6307683c63b1605e7bf --- /dev/null +++ b/src/rust/getset/tests/getset.rs @@ -0,0 +1,121 @@ +#[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(MutGetters, Getters, Default)] + #[getset(get_mut)] + pub struct Plain { + /// A doc comment. + /// Multiple lines, even. + private_accessible: usize, + + /// A doc comment. + #[getset(get = "pub", get_mut = "pub")] + public_accessible: usize, + // /// A doc comment. + // #[get_mut = "pub(crate)"] + // crate_accessible: usize, + + // /// A doc comment. + // #[get_mut = "pub(super)"] + // super_accessible: usize, + + // /// A doc comment. + // #[get_mut = "pub(in super::other)"] + // scope_accessible: usize, + } + + #[derive(MutGetters, Getters, Default)] + #[getset(get_mut)] + pub struct Generic<T: Copy + Clone + Default> { + /// A doc comment. + /// Multiple lines, even. + private_accessible: T, + + /// A doc comment. + #[getset(get = "pub", get_mut = "pub")] + public_accessible: T, + // /// A doc comment. + // #[get_mut = "pub(crate)"] + // crate_accessible: usize, + + // /// A doc comment. + // #[get_mut = "pub(super)"] + // super_accessible: usize, + + // /// A doc comment. + // #[get_mut = "pub(in super::other)"] + // scope_accessible: usize, + } + + #[derive(MutGetters, Getters, Default)] + #[getset(get_mut)] + pub struct Where<T> + where + T: Copy + Clone + Default, + { + /// A doc comment. + /// Multiple lines, even. + private_accessible: T, + + /// A doc comment. + #[getset(get = "pub", get_mut = "pub")] + public_accessible: T, + // /// A doc comment. + // #[get_mut = "pub(crate)"] + // crate_accessible: usize, + + // /// A doc comment. + // #[get_mut = "pub(super)"] + // super_accessible: usize, + + // /// A doc comment. + // #[get_mut = "pub(in super::other)"] + // scope_accessible: usize, + } + + #[test] + fn test_plain() { + let mut val = Plain::default(); + (*val.private_accessible_mut()) += 1; + } + + #[test] + fn test_generic() { + let mut val = Generic::<usize>::default(); + (*val.private_accessible_mut()) += 1; + } + + #[test] + fn test_where() { + let mut val = Where::<usize>::default(); + (*val.private_accessible_mut()) += 1; + } + } +} + +#[test] +fn test_plain() { + let mut val = Plain::default(); + let _ = val.public_accessible(); + (*val.public_accessible_mut()) += 1; +} + +#[test] +fn test_generic() { + let mut val = Generic::<usize>::default(); + let _ = val.public_accessible(); + (*val.public_accessible_mut()) += 1; +} + +#[test] +fn test_where() { + let mut val = Where::<usize>::default(); + let _ = val.public_accessible(); + (*val.public_accessible_mut()) += 1; +} diff --git a/src/rust/getset/tests/getters.rs b/src/rust/getset/tests/getters.rs new file mode 100644 index 0000000000000000000000000000000000000000..91debf3f0d30d0c399156627d5b6cb0c7fe38d4f --- /dev/null +++ b/src/rust/getset/tests/getters.rs @@ -0,0 +1,149 @@ +#[macro_use] +extern crate getset; + +use crate::submodule::other::{Generic, Plain, Where}; + +// For testing `pub(super)` +mod submodule { + // For testing `pub(super::other)` + pub mod other { + #[derive(Getters)] + #[get] + pub struct Plain { + /// A doc comment. + /// Multiple lines, even. + private_accessible: usize, + + /// A doc comment. + #[get = "pub"] + public_accessible: usize, + // /// A doc comment. + // #[get = "pub(crate)"] + // crate_accessible: usize, + + // /// A doc comment. + // #[get = "pub(super)"] + // super_accessible: usize, + + // /// A doc comment. + // #[get = "pub(super::other)"] + // scope_accessible: usize, + + // Prefixed getter. + #[get = "with_prefix"] + private_prefixed: usize, + + // Prefixed getter. + #[get = "pub with_prefix"] + public_prefixed: usize, + } + + impl Default for Plain { + fn default() -> Plain { + Plain { + private_accessible: 17, + public_accessible: 18, + private_prefixed: 19, + public_prefixed: 20, + } + } + } + + #[derive(Getters, Default)] + #[get] + pub struct Generic<T: Copy + Clone + Default> { + /// A doc comment. + /// Multiple lines, even. + private_accessible: T, + + /// A doc comment. + #[get = "pub(crate)"] + public_accessible: T, + // /// A doc comment. + // #[get = "pub(crate)"] + // crate_accessible: usize, + + // /// A doc comment. + // #[get = "pub(super)"] + // super_accessible: usize, + + // /// A doc comment. + // #[get = "pub(super::other)"] + // scope_accessible: usize, + } + + #[derive(Getters, Default)] + #[get] + pub struct Where<T> + where + T: Copy + Clone + Default, + { + /// A doc comment. + /// Multiple lines, even. + private_accessible: T, + + /// A doc comment. + #[get = "pub"] + public_accessible: T, + // /// A doc comment. + // #[get = "pub(crate)"] + // crate_accessible: usize, + + // /// A doc comment. + // #[get = "pub(super)"] + // super_accessible: usize, + + // /// A doc comment. + // #[get = "pub(super::other)"] + // scope_accessible: usize, + } + + #[test] + fn test_plain() { + let val = Plain::default(); + val.private_accessible(); + } + + #[test] + fn test_generic() { + let val = Generic::<usize>::default(); + val.private_accessible(); + } + + #[test] + fn test_where() { + let val = Where::<usize>::default(); + val.private_accessible(); + } + + #[test] + fn test_prefixed_plain() { + let val = Plain::default(); + assert_eq!(19, *val.get_private_prefixed()); + } + } +} + +#[test] +fn test_plain() { + let val = Plain::default(); + assert_eq!(18, *val.public_accessible()); +} + +#[test] +fn test_generic() { + let val = Generic::<usize>::default(); + assert_eq!(usize::default(), *val.public_accessible()); +} + +#[test] +fn test_where() { + let val = Where::<usize>::default(); + assert_eq!(usize::default(), *val.public_accessible()); +} + +#[test] +fn test_prefixed_plain() { + let val = Plain::default(); + assert_eq!(20, *val.get_public_prefixed()); +} diff --git a/src/rust/getset/tests/mut_getters.rs b/src/rust/getset/tests/mut_getters.rs new file mode 100644 index 0000000000000000000000000000000000000000..c536f5c34cdb7c8f44f32e8d0e460e6d9140fa62 --- /dev/null +++ b/src/rust/getset/tests/mut_getters.rs @@ -0,0 +1,118 @@ +#[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(MutGetters, Default)] + #[get_mut] + pub struct Plain { + /// A doc comment. + /// Multiple lines, even. + private_accessible: usize, + + /// A doc comment. + #[get_mut = "pub"] + public_accessible: usize, + // /// A doc comment. + // #[get_mut = "pub(crate)"] + // crate_accessible: usize, + + // /// A doc comment. + // #[get_mut = "pub(super)"] + // super_accessible: usize, + + // /// A doc comment. + // #[get_mut = "pub(in super::other)"] + // scope_accessible: usize, + } + + #[derive(MutGetters, Default)] + #[get_mut] + pub struct Generic<T: Copy + Clone + Default> { + /// A doc comment. + /// Multiple lines, even. + private_accessible: T, + + /// A doc comment. + #[get_mut = "pub"] + public_accessible: T, + // /// A doc comment. + // #[get_mut = "pub(crate)"] + // crate_accessible: usize, + + // /// A doc comment. + // #[get_mut = "pub(super)"] + // super_accessible: usize, + + // /// A doc comment. + // #[get_mut = "pub(in super::other)"] + // scope_accessible: usize, + } + + #[derive(MutGetters, Default)] + #[get_mut] + pub struct Where<T> + where + T: Copy + Clone + Default, + { + /// A doc comment. + /// Multiple lines, even. + private_accessible: T, + + /// A doc comment. + #[get_mut = "pub"] + public_accessible: T, + // /// A doc comment. + // #[get_mut = "pub(crate)"] + // crate_accessible: usize, + + // /// A doc comment. + // #[get_mut = "pub(super)"] + // super_accessible: usize, + + // /// A doc comment. + // #[get_mut = "pub(in super::other)"] + // scope_accessible: usize, + } + + #[test] + fn test_plain() { + let mut val = Plain::default(); + (*val.private_accessible_mut()) += 1; + } + + #[test] + fn test_generic() { + let mut val = Generic::<usize>::default(); + (*val.private_accessible_mut()) += 1; + } + + #[test] + fn test_where() { + let mut val = Where::<usize>::default(); + (*val.private_accessible_mut()) += 1; + } + } +} + +#[test] +fn test_plain() { + let mut val = Plain::default(); + (*val.public_accessible_mut()) += 1; +} + +#[test] +fn test_generic() { + let mut val = Generic::<usize>::default(); + (*val.public_accessible_mut()) += 1; +} + +#[test] +fn test_where() { + let mut val = Where::<usize>::default(); + (*val.public_accessible_mut()) += 1; +} diff --git a/src/rust/getset/tests/raw_identifiers.rs b/src/rust/getset/tests/raw_identifiers.rs new file mode 100644 index 0000000000000000000000000000000000000000..7e67c4240b67113a8b69890f30f963794222c031 --- /dev/null +++ b/src/rust/getset/tests/raw_identifiers.rs @@ -0,0 +1,57 @@ +#[macro_use] +extern crate getset; + +#[derive(CopyGetters, Default, Getters, MutGetters, Setters)] +struct RawIdentifiers { + #[get] + r#type: usize, + #[get_copy] + r#move: usize, + #[get_mut] + r#union: usize, + #[set] + r#enum: usize, + #[get = "with_prefix"] + r#const: usize, + #[get_copy = "with_prefix"] + r#if: usize, + // Ensure having no gen mode doesn't break things. + #[allow(dead_code)] + r#loop: usize, +} + +#[test] +fn test_get() { + let val = RawIdentifiers::default(); + let _ = val.r#type(); +} + +#[test] +fn test_get_copy() { + let val = RawIdentifiers::default(); + let _ = val.r#move(); +} + +#[test] +fn test_get_mut() { + let mut val = RawIdentifiers::default(); + let _ = val.union_mut(); +} + +#[test] +fn test_set() { + let mut val = RawIdentifiers::default(); + val.set_enum(42); +} + +#[test] +fn test_get_with_prefix() { + let val = RawIdentifiers::default(); + let _ = val.get_const(); +} + +#[test] +fn test_get_copy_with_prefix() { + let val = RawIdentifiers::default(); + let _ = val.get_if(); +} diff --git a/src/rust/getset/tests/setters.rs b/src/rust/getset/tests/setters.rs new file mode 100644 index 0000000000000000000000000000000000000000..ba5998d2e8db8426fa01b0977d404224c32eb39f --- /dev/null +++ b/src/rust/getset/tests/setters.rs @@ -0,0 +1,129 @@ +#[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(Setters, Default)] + #[set] + pub struct Plain { + /// A doc comment. + /// Multiple lines, even. + private_accessible: usize, + + /// A doc comment. + #[set = "pub"] + public_accessible: usize, + + /// This field is used for testing chaining. + #[set = "pub"] + second_public_accessible: bool, + // /// A doc comment. + // #[set = "pub(crate)"] + // crate_accessible: usize, + + // /// A doc comment. + // #[set = "pub(super)"] + // super_accessible: usize, + + // /// A doc comment. + // #[set = "pub(in super::other)"] + // scope_accessible: usize, + } + + #[derive(Setters, Default)] + #[set] + pub struct Generic<T: Copy + Clone + Default> { + /// A doc comment. + /// Multiple lines, even. + private_accessible: T, + + /// A doc comment. + #[set = "pub"] + public_accessible: T, + // /// A doc comment. + // #[set = "pub(crate)"] + // crate_accessible: usize, + + // /// A doc comment. + // #[set = "pub(super)"] + // super_accessible: usize, + + // /// A doc comment. + // #[set = "pub(in super::other)"] + // scope_accessible: usize, + } + + #[derive(Setters, Default)] + #[set] + pub struct Where<T> + where + T: Copy + Clone + Default, + { + /// A doc comment. + /// Multiple lines, even. + private_accessible: T, + + /// A doc comment. + #[set = "pub"] + public_accessible: T, + // /// A doc comment. + // #[set = "pub(crate)"] + // crate_accessible: usize, + + // /// A doc comment. + // #[set = "pub(super)"] + // super_accessible: usize, + + // /// A doc comment. + // #[set = "pub(in super::other)"] + // scope_accessible: usize, + } + + #[test] + fn test_plain() { + let mut val = Plain::default(); + val.set_private_accessible(1); + } + + #[test] + fn test_generic() { + let mut val = Generic::default(); + val.set_private_accessible(1); + } + + #[test] + fn test_where() { + let mut val = Where::default(); + val.set_private_accessible(1); + } + } +} + +#[test] +fn test_plain() { + let mut val = Plain::default(); + val.set_public_accessible(1); +} + +#[test] +fn test_generic() { + let mut val = Generic::default(); + val.set_public_accessible(1); +} + +#[test] +fn test_where() { + let mut val = Where::default(); + val.set_public_accessible(1); +} + +#[test] +fn test_chaining() { + let mut val = Plain::default(); + val.set_public_accessible(1) + .set_second_public_accessible(true); +} diff --git a/src/rust/getset/tests/skip.rs b/src/rust/getset/tests/skip.rs new file mode 100644 index 0000000000000000000000000000000000000000..93f40e4f72ae2220b0364acd72f4e6d3e68fe551 --- /dev/null +++ b/src/rust/getset/tests/skip.rs @@ -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()); +}