commit bf08cdc5dc315e80a0743143c0f219a6990da00e Author: Dominic Grimm Date: Thu Nov 23 21:45:47 2023 +0100 Init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..18d8ef0 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,46 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "none-guard" +version = "0.1.0" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0a2753b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "none-guard" +version = "0.1.0" +edition = "2021" +authors = ["Dominic Grimm "] +repository = "https://git.dergrimm.net/dergrimm/none-guard" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +proc-macro = true + +[dependencies] +quote = "1.0.33" +syn = "2.0.39" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cbd8642 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Dominic Grimm + +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/examples/example.rs b/examples/example.rs new file mode 100644 index 0000000..543ccf9 --- /dev/null +++ b/examples/example.rs @@ -0,0 +1,31 @@ +use none_guard::NoneGuard; + +#[derive(NoneGuard)] +struct Test { + x: Option, + y: Option, + z: Option, +} + +fn main() { + let x = Test { + x: Some(1), + y: None, + z: None, + }; + dbg!(x.is_all_none()); + + let y = Test { + x: None, + y: Some("dd".to_string()), + z: Some(2.0), + }; + dbg!(y.is_all_none()); + + let z = Test { + x: None, + y: None, + z: None, + }; + dbg!(z.is_all_none()); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..071e57e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,73 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, DeriveInput}; + +#[proc_macro_derive(NoneGuard)] +pub fn none_guard(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + let struct_name = &ast.ident; + + if let syn::Data::Struct(s) = &ast.data { + let field_checks = s.fields.iter().map(|field| { + let name = &field.ident; + if let syn::Type::Path(type_path) = &field.ty { + if type_path.path.segments.len() == 1 { + let segment = &type_path.path.segments.iter().next().unwrap(); + if segment.ident != "Option" { + return quote! { + compile_error!("All fields must be of type Option!"); + }; + } + } else { + return quote! { + compile_error!("All fields must be of type Option!"); + }; + } + } else { + return quote! { + compile_error!("All fields must be of type Option!"); + }; + } + + quote! { self.#name.is_some() } + }); + + let is_some_checks = field_checks + .clone() + .reduce(|acc, e| { + dbg!(&acc, &e); + + quote! { #acc && #e } + }) + .unwrap(); + + let is_none_checks = field_checks + .map(|tok| quote! { !(#tok) }) + .reduce(|acc, e| { + dbg!(&acc, &e); + + quote! { #acc && #e } + }) + .unwrap(); + + quote! { + impl #struct_name { + pub fn is_all_some(&self) -> bool { + #is_some_checks + } + + pub fn is_all_none(&self) -> bool { + #is_none_checks + } + } + } + .into() + } else { + quote! { + compile_error!("CheckAllNone macro can only be used with structs!"); + } + .into() + } +}