Skip to content

Commit

Permalink
Merge pull request #3 from ImaMapleTree/feature/remove-method-property
Browse files Browse the repository at this point in the history
Remove Method Property
  • Loading branch information
ImaMapleTree authored May 28, 2024
2 parents e08d334 + ed4ef15 commit 21a993c
Show file tree
Hide file tree
Showing 13 changed files with 372 additions and 287 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "derive-ctor"
version = "0.1.0"
version = "0.1.1"
description = "Adds `#[derive(ctor)]` which allows for the auto-generation of a constructor."
keywords = ["derive", "macro", "trait", "procedural"]
authors = ["Evan Cowin"]
Expand All @@ -12,6 +12,8 @@ exclude = [".github/*", ".gitignore"]
[lib]
proc-macro = true

[features]
no-std = []

[dependencies]
syn = { version = "2.0.*" }
Expand Down
63 changes: 27 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@
- Customize the name and visibility of the auto-generated constructor using `#[ctor(visibility method_name)]`.
- Provide a list of names to generate multiple constructors.
- Customize field behavior in the constructor with the following attributes:
- `#[ctor(default)]` - Exclude the field from the generated method and use its default value.
- `#[ctor(value(EXPRESSION))]` - Exclude the field from the generated method and use the defined expression as its default value.
- `#[ctor(method(METHOD_NAME))]` - Exclude the field from the generated method and call the defined method for its default value.
- `#[ctor(impl)]` - Change the parameter type for the generated method to `impl Into<Type>`.
- `#[ctor(default)]` - Exclude the field from the generated method and use its default value.
- `#[ctor(expr(EXPRESSION))]` - Exclude the field from the generated method and use the defined expression as its default value.
- `#[ctor(impl)]` - Change the parameter type for the generated method to `impl Into<Type>`.

## Basic Usage

Expand All @@ -30,6 +29,8 @@ use derive_ctor::ctor;
Annotate your struct with `#[derive(ctor)]` to automatically generate a `new` constructor:

```rust
use derive_ctor::ctor;

#[derive(ctor)]
struct MyStruct {
field1: i32,
Expand All @@ -48,6 +49,8 @@ In the following example, three constructor methods are created: `new`, `with_de
These methods all inherit their respective visibilities defined within the `#[ctor]` attribute.

```rust
use derive_ctor::ctor;

#[derive(ctor)]
#[ctor(pub new, pub(crate) with_defaults, internal)]
struct MyStruct {
Expand All @@ -63,6 +66,8 @@ The following are the available properties that can be used with the field-attri

`#[ctor(default)]` - This property excludes the annotated field from the constructor and uses its default value.
```rust
use derive_ctor::ctor;

#[derive(ctor)]
struct MyStruct {
field1: i32,
Expand All @@ -76,6 +81,8 @@ let my_struct = MyStruct::new(100);
`#[ctor(impl)]` - This property modifies the parameter type of the annotated field for the generated method
converting it from `Type` -> `impl Into<Type>`.
```rust
use derive_ctor::ctor;

#[derive(ctor)]
struct MyStruct {
field1: i32,
Expand All @@ -86,34 +93,19 @@ struct MyStruct {
let my_struct = MyStruct::new(100, "Foo");
```

`#[ctor(value(VALUE))]` - This property excludes the annotated field from the constructor and utilizes the defined expression
`#[ctor(expr(VALUE))]` - This property excludes the annotated field from the constructor and utilizes the defined expression
to generate its value.
```rust
#[derive(ctor)]
struct MyStruct {
field1: i32,
#[ctor(value(String::from("Foo")))]
field2: String
}

let my_struct = MyStruct::new(100); // generates MyStruct { field1: 100, field2: "foo" }
```

`#[ctor(method(METHOD_NAME)]` - This property exludes the annotated field from the constructor and invokes the provided
method to generate its value.
```rust
fn generate_struct_value() -> String {
String::from("Foo")
}
use derive_ctor::ctor;

#[derive(ctor)]
struct MyStruct {
field1: i32,
#[ctor(method(generate_struct_value))]
#[ctor(expr(String::from("Foo")))]
field2: String
}

let my_struct = MyStruct::new(100);
let my_struct = MyStruct::new(100); // generates MyStruct { field1: 100, field2: "foo" }
```

### Advanced Configuration
Expand All @@ -122,23 +114,22 @@ Field attributes can additionally be configured with a list of indices correspon
value for. This allows for the creation of multiple functions with different parameter requirements.

```rust
use derive_ctor::ctor;

#[derive(ctor)]
#[ctor(new, with_defaults)]
struct MyStruct {
field1: i32,
#[ctor(default) = [1]]
field2: String,
#[ctor(default) = 1] // brackets can be removed if specifying only 1 index
field3: bool,
#[ctor(default) = [0, 1]]
field4: u64,
#[ctor(default)] // this is the same as specifying all indices
field5: u64
field1: i32,
#[ctor(default = [1])]
field2: String,
#[ctor(default = 1)] // brackets can be removed if specifying only 1 index
field3: bool,
#[ctor(default = [0, 1])]
field4: u64,
#[ctor(default)] // this is the same as specifying all indices
field5: u64
}

let my_struct1 = MyStruct::new(100, "Foo".to_string(), true);
let my_struct2 = MyStruct::with_defaults(100);
```



```
51 changes: 34 additions & 17 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
use proc_macro::TokenStream;
#![cfg_attr(feature = "no-std", no_std, doc = "Test")]
#![doc = include_str!("../README.md")]

#[cfg(feature = "no-std")]
extern crate alloc;
#[cfg(feature = "no-std")]
use alloc::vec;
#[cfg(feature = "no-std")]
use alloc::vec::Vec;
#[cfg(feature = "no-std")]
use alloc::collections::BTreeSet as HashSet;
#[cfg(feature = "no-std")]
use alloc::string::ToString;

#[cfg(not(feature = "no-std"))]
use std::vec;
#[cfg(not(feature = "no-std"))]
use std::vec::Vec;
#[cfg(not(feature = "no-std"))]
use std::collections::HashSet;


use proc_macro::TokenStream;

use proc_macro2::{Delimiter, Punct, Span};
use proc_macro2::Spacing::Alone;
use quote::{quote, TokenStreamExt, ToTokens};
Expand All @@ -10,9 +31,11 @@ use syn::parse::ParseStream;
use syn::spanned::Spanned;
use syn::token::{Comma, Impl, Pub};



static FIELD_CONFIG_ERR_MSG: &str =
"Unexpected property: \"{prop}\" (must be one of the following:\
\"default\", \"method\", \"value\", \"impl\")";
\"default\", \"expr\", \"impl\")";

struct CtorTypeConfiguration {
definitions: Vec<(Visibility, Ident)>
Expand All @@ -37,6 +60,7 @@ impl Default for CtorTypeConfiguration {
/// field: i16
/// }
/// ```

#[derive(Clone)]
struct FieldConfig {
property: FieldConfigProperty,
Expand All @@ -47,8 +71,7 @@ struct FieldConfig {
enum FieldConfigProperty {
Default,
Impl,
Method { ident: Ident },
Value { expression: proc_macro2::TokenStream }
Expression { expression: proc_macro2::TokenStream }
}

struct RequiredStructField {
Expand Down Expand Up @@ -126,27 +149,22 @@ impl Parse for FieldConfigProperty {
let property_name = property.to_string();
match property_name.as_str() {
"default" => Ok(FieldConfigProperty::Default),
"method" => {
"expr" => {
let (delimiter, span, buffer) = input.parse_any_delimiter()?;

if delimiter != Delimiter::Parenthesis {
return Err(Error::new(span.span(), "Expected enclosing parenthesis"))
}

Ok(FieldConfigProperty::Method { ident: buffer.parse()? })
},
"value" => {
let (delimiter, span, buffer) = input.parse_any_delimiter()?;

if delimiter != Delimiter::Parenthesis {
return Err(Error::new(span.span(), "Expected enclosing parenthesis"))
}

Ok(FieldConfigProperty::Value {
Ok(FieldConfigProperty::Expression {
expression: proc_macro2::TokenStream::parse(&buffer)
.expect("Unable to convert buffer back into TokenStream")
})
},
"method" => Err(Error::new(
property.span(),
"\"method\" property has been removed. Please refer to documentation for a list of valid properties."
)),
_ => Err(Error::new(
property.span(),
FIELD_CONFIG_ERR_MSG.replace("{prop}", &property_name)
Expand Down Expand Up @@ -175,8 +193,7 @@ impl ToTokens for GeneratedStructField {

tokens.extend(match &self.configuration {
FieldConfigProperty::Default => quote! { Default::default() },
FieldConfigProperty::Method { ident } => quote! { #ident() },
FieldConfigProperty::Value { expression } => expression.clone(),
FieldConfigProperty::Expression { expression } => expression.clone(),
FieldConfigProperty::Impl => quote! { #ident.into() }
});

Expand Down
42 changes: 42 additions & 0 deletions tests/struct_base.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use derive_ctor::ctor;

#[derive(ctor, Debug, PartialEq)]
pub struct Unit;

#[test]
fn test_unit_struct() {
let unit = Unit::new();
assert_eq!(Unit, unit);
}

#[derive(ctor, Debug, PartialEq)]
pub struct Empty {}

#[test]
fn test_empty_struct_no_config() {
let empty = Empty::new();
assert_eq!(Empty { }, empty)
}

#[derive(ctor, Debug, PartialEq)]
pub struct OneFieldStruct {
value: u32
}

#[test]
fn test_struct_with_field() {
let ofs = OneFieldStruct::new(300);
assert_eq!(OneFieldStruct { value: 300 }, ofs)
}

#[derive(ctor, Debug, PartialEq)]
pub struct ManyFieldStruct {
value1: u32,
value2: bool
}

#[test]
fn test_struct_with_many_fields() {
let mfs = ManyFieldStruct::new(400, true);
assert_eq!(ManyFieldStruct { value1: 400, value2: true }, mfs);
}
19 changes: 19 additions & 0 deletions tests/struct_field_all.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use derive_ctor::ctor;

#[derive(ctor, Debug, PartialEq)]
struct MixedStruct {
provided1: i16,
provided2: bool,
#[ctor(impl)]
provided3: String,
#[ctor(expr("Foo"))]
generated1: &'static str,
#[ctor(default)]
generated2: u32
}

#[test]
fn test_struct_with_multiple_generated_fields() {
let multi = MixedStruct::new(41, false, "Test");
assert_eq!(MixedStruct { provided1: 41, provided2: false, provided3: String::from("Test"), generated1: "Foo", generated2: Default::default() }, multi)
}
29 changes: 29 additions & 0 deletions tests/struct_field_default.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use derive_ctor::ctor;

#[derive(ctor, Debug, PartialEq)]
struct StructDefault {
provided: String,
#[ctor(default)]
generated: u32
}

#[derive(ctor, Debug, PartialEq)]
struct StructManyDefault {
provided: String,
#[ctor(default)]
generated1: u32,
#[ctor(default)]
generated2: String
}

#[test]
fn test_struct_with_default_field() {
let test = StructDefault::new(String::from("ABC"));
assert_eq!(StructDefault { provided: String::from("ABC"), generated: Default::default() }, test);
}

#[test]
fn test_struct_with_multiple_default_fields() {
let test = StructManyDefault::new(String::from("ABC"));
assert_eq!(StructManyDefault { provided: String::from("ABC"), generated1: Default::default(), generated2: Default::default() }, test);
}
Loading

0 comments on commit 21a993c

Please sign in to comment.