diff --git a/Cargo.toml b/Cargo.toml index 9f8f72c..2f80a93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,12 @@ [package] name = "virtue" -version = "0.1.0" +version = "0.0.1" edition = "2021" +description = "A sinless derive macro helper" +license-file = "LICENSE.md" +documentation = "https://docs.rs/virtue" +repository = "https://github.com/bincode-org/virtue" +readme = "README.md" [dependencies] diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..e753909 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2021 Victor Koenders + +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/README.md b/README.md new file mode 100644 index 0000000..a49dc98 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# Virtue, a sinless derive macro helper + +## Goals + +- Zero dependencies, so fast compile times +- No other dependencies needed +- Declarative code generation +- As much typesystem checking as possible +- Build for modern rust: 1.57 and up +- Build with popular crates in mind: + - [bincode](https://docs.rs/bincode) +- Will always respect semver. Minor releases will never have: + - Breaking API changes + - MSRV changes + +## Example + +```rust +use virtue::prelude::*; + +#[proc_macro_derive(YourDerive, attributes(some, attributes, go, here))] +pub fn derive_your_derive(input: TokenStream) -> TokenStream { + derive_your_derive_inner(input) + .unwrap_or_else(|error| error.into_token_stream()) +} + +fn derive_your_derive_inner(input: TokenStream) -> Result { + // Parse the struct or enum you want to implement a derive for + let parse = Parse::new(input)?; + // Get a reference to the generator + let (mut generator, body) = parse.into_generator(); + match body { + Body::Struct(body) => { + // Implement your struct body here + // See `Generator` for more information + generator.impl_for("YourTrait")? + .generate_fn("your_fn") + .with_self_arg(FnSelfArg::RefSelf) + .body(|fn_body| { + fn_body.push_parsed("println!(\"Hello world\");"); + })?; + }, + Body::Enum(body) => { + // Implement your enum body here + // See `Generator` for more information + generator.impl_for("YourTrait")? + .generate_fn("your_fn") + .with_self_arg(FnSelfArg::RefSelf) + .body(|fn_body| { + fn_body.push_parsed("println!(\"Hello world\");"); + })?; + }, + } + generator.finish() +} +``` + +Will generate + +```ignore +impl YourTrait for { + fn your_fn(&self) { // .generate_fn("your_fn").with_self_arg(FnSelfArg::RefSelf) + println!("Hello world"); // fn_body.push_parsed(...) + } +} +``` diff --git a/src/generate/generator.rs b/src/generate/generator.rs index 5687e55..062cf04 100644 --- a/src/generate/generator.rs +++ b/src/generate/generator.rs @@ -53,7 +53,7 @@ impl Generator { /// ```no_run /// # use virtue::prelude::*; /// # let mut generator: Generator = unsafe { std::mem::zeroed() }; - /// generator.impl_for_with_lifetime("Foo", &["a", "b"]); + /// generator.impl_for_with_lifetimes("Foo", &["a", "b"]); /// /// // will output: /// // impl<'a, 'b> Foo<'a, 'b> for StructOrEnum { } diff --git a/src/parse/attributes.rs b/src/parse/attributes.rs index cb88fd4..12852b1 100644 --- a/src/parse/attributes.rs +++ b/src/parse/attributes.rs @@ -100,6 +100,8 @@ fn test_attributes_try_take() { /// - [`IdentOrIndex::has_field_attribute`] /// /// This can be implemented on your own type to make parsing easier. +/// +/// [`IdentOrIndex::has_field_attribute`]: enum.IdentOrIndex.html#has_field_attribute pub trait FromAttribute: Sized { /// Try to parse the given group into your own type. Return `None` if the parsing failed or if the attribute was not this type. fn parse(group: &Group) -> Option; diff --git a/src/parse/body.rs b/src/parse/body.rs index 4b21f3d..9ec51bc 100644 --- a/src/parse/body.rs +++ b/src/parse/body.rs @@ -469,20 +469,37 @@ impl UnnamedField { } } +/// Reference to an enum variant's field. Either by index or by ident. +/// +/// ``` +/// enum Foo { +/// Bar(u32), // will be IdentOrIndex::Index { index: 0, .. } +/// Baz { +/// a: u32, // will be IdentOrIndex::Ident { ident: "a", .. } +/// }, +/// } #[derive(Debug)] pub enum IdentOrIndex<'a> { + /// The variant is a named field Ident { + /// The name of the field ident: &'a Ident, + /// The attributes of the field attributes: &'a Vec, }, + /// The variant is an unnamed field Index { + /// The field index index: usize, + /// The span of the field type span: Span, + /// The attributes of this field attributes: &'a Vec, }, } impl<'a> IdentOrIndex<'a> { + /// Get the ident. Will panic if this is an `IdentOrIndex::Index` pub fn unwrap_ident(&self) -> &'a Ident { match self { Self::Ident { ident, .. } => ident, @@ -490,6 +507,7 @@ impl<'a> IdentOrIndex<'a> { } } + /// Convert this ident into a TokenTree. If this is an `Index`, will return `prefix + index` instead. pub fn to_token_tree_with_prefix(&self, prefix: &str) -> TokenTree { TokenTree::Ident(match self { IdentOrIndex::Ident { ident, .. } => (*ident).clone(), @@ -499,6 +517,8 @@ impl<'a> IdentOrIndex<'a> { } }) } + + /// Return either the index or the ident of this field with a fixed prefix. The prefix will always be added. pub fn to_string_with_prefix(&self, prefix: &str) -> String { match self { IdentOrIndex::Ident { ident, .. } => ident.to_string(), @@ -508,6 +528,7 @@ impl<'a> IdentOrIndex<'a> { } } + /// Check to see if the field has the given attribute. See [`FromAttribute`] for more information. pub fn has_field_attribute>(&self, attrib: T) -> bool { let attributes = match self { Self::Ident { attributes, .. } => attributes, diff --git a/src/parse/mod.rs b/src/parse/mod.rs index e4bf3ef..6741f16 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -10,7 +10,7 @@ mod utils; mod visibility; pub use self::attributes::{Attribute, AttributeLocation, FromAttribute}; -pub use self::body::{EnumBody, EnumVariant, Fields, StructBody, UnnamedField}; +pub use self::body::{EnumBody, EnumVariant, Fields, IdentOrIndex, StructBody, UnnamedField}; pub(crate) use self::data_type::DataType; pub use self::generics::{Generic, GenericConstraints, Generics, Lifetime, SimpleGeneric}; pub use self::visibility::Visibility;