Skip to content

Commit

Permalink
Merge 54f90a0 into a304ff9
Browse files Browse the repository at this point in the history
  • Loading branch information
Alorel committed Sep 28, 2023
2 parents a304ff9 + 54f90a0 commit 83e5ba5
Show file tree
Hide file tree
Showing 7 changed files with 257 additions and 32 deletions.
37 changes: 37 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,45 @@ jobs:
- name: Clippy
run: cargo clippy --workspace --tests

- name: Doc
run: cargo doc --workspace --no-deps && rm -rf target/doc

- name: cargo-rdme
uses: alorel-actions/cargo/rdme@v1

nightly-test:
name: Rust nightly
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Init toolchain
uses: alorel-actions/cargo/init@v1
with:
toolchain: nightly-2023-09-18
cache-prefix: test
local: true
components: llvm-tools-preview

- name: Examples & unit tests
uses: alorel-actions/cargo/llvm-cov@v1
with:
output: unit.lcov
args: --lcov --examples --tests --workspace

- name: Doc tests
uses: alorel-actions/cargo/llvm-cov@v1
with:
output: doc.lcov
args: --lcov --doctests --workspace

- name: Upload coverage
uses: coverallsapp/github-action@v2
continue-on-error: true
with:
files: unit.lcov doc.lcov

test:
name: Rust ${{matrix.toolchain}} on ${{matrix.os}}
runs-on: ${{matrix.os}}
Expand Down Expand Up @@ -88,6 +124,7 @@ jobs:
needs:
- lint
- test
- nightly-test
permissions:
contents: write
steps:
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/target/
/*.lcov
/examples/adhoc.rs
1 change: 1 addition & 0 deletions .idea/fancy_constructor.iml

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

65 changes: 61 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ Derive a highly configurable constructor for your struct
[![dependencies badge](https://img.shields.io/librariesio/release/cargo/fancy_constructor)](https://libraries.io/cargo/fancy_constructor)

# Examples
## Basic

<details><summary>Basic</summary>

```rust
use fancy_constructor::new;
Expand All @@ -32,7 +33,8 @@ impl MyStruct {
}
````

## Options showcase
</details>
<details><summary>Options showcase</summary>

```rust
#[derive(new, PartialEq, Eq, Debug)]
Expand Down Expand Up @@ -73,7 +75,8 @@ impl<T> MyStruct<T> {
}
````

## Private const fn
</details>
<details><summary>Private const fn</summary>

```rust
#[derive(new, PartialEq, Eq, Debug)]
Expand All @@ -94,7 +97,8 @@ impl Foo {
}
````

## Computed values
</details>
<details><summary>Computed values</summary>

```rust
#[derive(new)]
Expand All @@ -108,4 +112,57 @@ assert_eq!(Foo::new(true).barness_level, 100);
assert_eq!(Foo::new(false).barness_level, 5);
```

</details>
<details><summary>Enums</summary>

```rust
#[derive(new, Eq, PartialEq, Debug)]
enum MyEnum {
#[new]
Foo { #[new(into)] bar: u8 },
Qux,
}

assert_eq!(MyEnum::new(5), MyEnum::Foo { bar: 5 });
```

Outputs:

```rust
impl MyEnum {
pub fn new(bar: Into<u8>) -> Self {
Self::Foo { bar: bar.into() }
}
}
````

</details>

<details><summary>Invalid inputs</summary>

```rust
#[derive(fancy_constructor::new]
enum Foo {
Bar, // no variants marked with `#[new]`
}
```

```rust
#[derive(fancy_constructor::new]
enum Foo {
#[new] Bar, // multiple variants marked with `#[new]`
#[new] Qux,
}
```

```rust
#[derive(fancy_constructor::new]
union Foo { // Unions not supported
bar: u8,
qux: u8,
}
```

</details>

<!-- cargo-rdme end -->
77 changes: 72 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
//! [![dependencies badge](https://img.shields.io/librariesio/release/cargo/fancy_constructor)](https://libraries.io/cargo/fancy_constructor)
//!
//! # Examples
//! ## Basic
//!
//! <details><summary>Basic</summary>
//!
//! ```
//! use fancy_constructor::new;
Expand All @@ -31,7 +32,8 @@
//! }
//! ````
//!
//! ## Options showcase
//! </details>
//! <details><summary>Options showcase</summary>
//!
//! ```
//! # use fancy_constructor::new;
Expand Down Expand Up @@ -77,7 +79,8 @@
//! }
//! ````
//!
//! ## Private const fn
//! </details>
//! <details><summary>Private const fn</summary>
//!
//! ```
//! # use fancy_constructor::new;
Expand All @@ -100,7 +103,8 @@
//! }
//! ````
//!
//! ## Computed values
//! </details>
//! <details><summary>Computed values</summary>
//!
//! ```
//! # use fancy_constructor::new;
Expand All @@ -114,6 +118,61 @@
//! assert_eq!(Foo::new(true).barness_level, 100);
//! assert_eq!(Foo::new(false).barness_level, 5);
//! ```
//!
//! </details>
//! <details><summary>Enums</summary>
//!
//! ```
//! # use fancy_constructor::new;
//! #[derive(new, Eq, PartialEq, Debug)]
//! enum MyEnum {
//! #[new]
//! Foo { #[new(into)] bar: u8 },
//! Qux,
//! }
//!
//! assert_eq!(MyEnum::new(5), MyEnum::Foo { bar: 5 });
//! ```
//!
//! Outputs:
//!
#![cfg_attr(doctest, doc = " ````no_test")]
//! ```
//! impl MyEnum {
//! pub fn new(bar: Into<u8>) -> Self {
//! Self::Foo { bar: bar.into() }
//! }
//! }
//! ````
//!
//! </details>
//!
//! <details><summary>Invalid inputs</summary>
//!
//! ```compile_fail
//! #[derive(fancy_constructor::new]
//! enum Foo {
//! Bar, // no variants marked with `#[new]`
//! }
//! ```
//!
//! ```compile_fail
//! #[derive(fancy_constructor::new]
//! enum Foo {
//! #[new] Bar, // multiple variants marked with `#[new]`
//! #[new] Qux,
//! }
//! ```
//!
//! ```compile_fail
//! #[derive(fancy_constructor::new]
//! union Foo { // Unions not supported
//! bar: u8,
//! qux: u8,
//! }
//! ```
//!
//! </details>

#![deny(clippy::correctness, clippy::suspicious)]
#![warn(clippy::complexity, clippy::perf, clippy::style, clippy::pedantic)]
Expand Down Expand Up @@ -162,10 +221,18 @@ enum Fields {
Unnamed(Vec<Field>),
}

enum FieldsSource {
Struct(Fields),
Enum {
variant: proc_macro2::Ident,
fields: Fields,
},
}

struct FancyConstructor {
struct_name: proc_macro2::Ident,
generics: syn::Generics,
fields: Fields,
fields: FieldsSource,
opts: options::ContainerOptions,
}

Expand Down
71 changes: 55 additions & 16 deletions src/parse.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use crate::options::{ContainerOptions, FieldOptions};
use crate::{FancyConstructor, Field, Fields, ATTR_NAME};
use macroific::attr_parse::__private::try_collect;
use macroific::extract_fields::Rejection;
use macroific::prelude::*;
use proc_macro2::{Ident, Span};
use quote::format_ident;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{Attribute, Data, DeriveInput, Error, Fields as SFields, Type};
use syn::{Attribute, Data, DeriveInput, Error, Fields as SFields, Type, Variant};

use crate::options::{ContainerOptions, FieldOptions};
use crate::{FancyConstructor, Field, Fields, FieldsSource, ATTR_NAME};

impl Parse for FancyConstructor {
fn parse(input: ParseStream) -> syn::Result<Self> {
Expand All @@ -20,10 +21,19 @@ impl Parse for FancyConstructor {
} = input.parse()?;

Ok(Self {
fields: match get_fields(data)? {
SFields::Unit => Fields::Unit,
SFields::Named(f) => Fields::Named(collect_fields(fmt_named(f.named))?),
SFields::Unnamed(f) => Fields::Unnamed(collect_fields(fmt_unnamed(f.unnamed))?),
fields: match data {
Data::Struct(s) => FieldsSource::Struct(s.fields.try_into()?),
Data::Enum(e) => {
let variant = find_variant(&e.enum_token, e.variants)?;

FieldsSource::Enum {
fields: variant.fields.try_into()?,
variant: variant.ident,
}
}
Data::Union(u) => {
return Err(Error::new_spanned(u.union_token, "Unions not supported"));
}
},
opts: if attrs.is_empty() {
ContainerOptions::default()
Expand Down Expand Up @@ -86,13 +96,42 @@ fn fmt_unnamed(fields: impl IntoIterator<Item = syn::Field>) -> impl Iterator<It
.map(move |(i, syn::Field { attrs, ty, .. })| (attrs, Err(format_ident!("f{}", i + 1)), ty))
}

#[inline]
fn get_fields(data: Data) -> syn::Result<SFields> {
let span = match data.extract_struct() {
Ok(data) => return Ok(data.fields),
Err(Rejection::A(a)) => a.enum_token.span,
Err(Rejection::B(b)) => b.union_token.span,
};
fn find_variant<P>(span: &impl Spanned, variants: Punctuated<Variant, P>) -> syn::Result<Variant> {
let mut out = None;
for variant in variants {
if variant
.attrs
.iter()
.any(move |a| a.path().is_ident(ATTR_NAME))
{
if out.is_some() {
return Err(Error::new_spanned(
variant.ident,
"Multiple variants marked with `#[new]`",
));
}
out = Some(variant);
}
}

Err(Error::new(span, "Expected a struct"))
if let Some(out) = out {
Ok(out)
} else {
Err(Error::new(
span.span(),
"Expected a variant marked with `#[new]`",
))
}
}

impl TryFrom<SFields> for Fields {
type Error = Error;

fn try_from(fields: SFields) -> Result<Self, Self::Error> {
Ok(match fields {
SFields::Unit => Fields::Unit,
SFields::Named(f) => Fields::Named(collect_fields(fmt_named(f.named))?),
SFields::Unnamed(f) => Fields::Unnamed(collect_fields(fmt_unnamed(f.unnamed))?),
})
}
}

0 comments on commit 83e5ba5

Please sign in to comment.