Skip to content

Commit

Permalink
Add support for tuple structs (#2)
Browse files Browse the repository at this point in the history
* Add support for tuple structs

* Document tuple struct support

---------

Co-authored-by: tlaferriere <[email protected]>
  • Loading branch information
tlaferriere and tlaferriere authored Jun 17, 2023
1 parent 31bd69a commit 8e59c1f
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 16 deletions.
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Fields that should be used for sorting are marked with the attribute `#[sort_by]
Alternatively, or in combination with, a struct-level or enum-level `#[sort_by(method1(),method2(),attr1,nested.attr)]` can be declared. This top-level declaration takes precedence,
fields comparison will be considered if top-level comparisons are all `eq`. The top-level `sort_by` attribute takes a list of attributes or method calls; items will be prepended with `self.`.

#### Example
#### Examples

```rust
#[derive(SortBy)]
Expand Down Expand Up @@ -75,6 +75,21 @@ impl core::cmp::Ord for Something {
}
```

You can use it the same way with tuple structs:

```rust
#[derive(SortBy)]
#[sort_by(somemethod())]
struct Something (
#[sort_by]
u16,
#[sort_by]
u32,
f32
)
```
This will expand the same way as a normal struct, with the proper numerical fields.

### EnumAccessor

This derive macro is similar to [enum_dispatch](https://crates.io/crates/enum_dispatch). `enum_dispatch` requires structs to implement a common trait, which can be useful if a common set of functions applies to all variants . `EnumAccessor` takes the opposite approach: common fields and methods are declared at enum level, and you can have variants that don't have a given field or method. This may be more practical if there is a large amount of variants and your only concern is accessing fields, because individual structs just hold data. This is typical for events - they represent a state change and are generally consumed as a whole, individual structs have no code of their own.
Expand Down
61 changes: 46 additions & 15 deletions src/sort_by.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use proc_macro2::{Span, TokenStream};
use proc_macro2::{Literal, Span, TokenStream};
use quote::{quote_spanned, ToTokens};

use syn::{
self, spanned::Spanned, Attribute, Data, DataStruct, DeriveInput, Error, Expr, ExprLit, Fields,
FieldsNamed, GenericParam, Lit, Meta, NestedMeta,
FieldsNamed, FieldsUnnamed, GenericParam, Lit, Meta, NestedMeta,
};

const HELP_SORTBY: &str = r#"SortBy: invalid sort_by attribute, expected list form i.e #[sort_by(attr1, attr2, methodcall())]"#;
Expand Down Expand Up @@ -34,34 +34,38 @@ pub fn impl_sort_by_derive(input: DeriveInput) -> TokenStream {
Data::Struct(DataStruct {
fields: Fields::Named(fields),
..
}) => match parse_fields(fields) {
}) => match parse_named_fields(fields) {
Ok(mut result) => sortable_expressions.append(&mut result),
Err(e) => return e.into_compile_error(),
},
Data::Struct(DataStruct {
fields: Fields::Unnamed(fields),
..
}) => match parse_unnamed_fields(fields) {
Ok(mut result) => sortable_expressions.append(&mut result),
Err(e) => return e.into_compile_error(),
},
Data::Enum(_) => (),
_ => {
return Error::new(
input_span,
r#"SortBy: expected an enum or a struct with named fields"#,
)
.into_compile_error();
return Error::new(input_span, r#"SortBy: expected an enum or a struct"#)
.into_compile_error();
}
};

let mut iter_sort_expressions = sortable_expressions.iter();
let ord_statement = if let Some(sort_expression) = iter_sort_expressions.next() {
quote_spanned! { sort_expression.span() =>
core::cmp::Ord::cmp(&self.#sort_expression, &other.#sort_expression)
let ord_statement = if let Some(field) = iter_sort_expressions.next() {
quote_spanned! { field.span() =>
core::cmp::Ord::cmp(&self.#field, &other.#field)
}
} else {
return quote::quote! {
compile_error!(r#"SortBy: no field to sort on. Mark fields to sort on with #[sort_by]"#);
};
};

let ord_statement = iter_sort_expressions.fold(ord_statement, |ord_statement, field_name| {
syn::parse_quote_spanned! {field_name.span() =>
#ord_statement.then_with(|| self.#field_name.cmp(&other.#field_name))
let ord_statement = iter_sort_expressions.fold(ord_statement, |ord_statement, field| {
syn::parse_quote_spanned! {field.span() =>
#ord_statement.then_with(|| self.#field.cmp(&other.#field))
}
});

Expand Down Expand Up @@ -112,7 +116,34 @@ pub fn impl_sort_by_derive(input: DeriveInput) -> TokenStream {
}
}

fn parse_fields(fields: FieldsNamed) -> Result<Vec<Expr>, Error> {
fn parse_unnamed_fields(fields: FieldsUnnamed) -> Result<Vec<Expr>, Error> {
let mut sortable_expressions = vec![];

for (index, field) in fields.unnamed.iter().enumerate() {
let span = field.span();
let mut attrs = field
.attrs
.iter()
.filter(|i| i.path.get_ident().map_or(false, |i| i == "sort_by"));

if attrs.next().is_none() {
continue;
}

let expr: Expr = syn::parse2(Literal::usize_unsuffixed(index).to_token_stream())?;
sortable_expressions.push(expr);

if attrs.next().is_some() {
return Err(Error::new(
span,
r#"SortBy: expected at most one `sort_by` attribute"#,
));
}
}
Ok(sortable_expressions)
}

fn parse_named_fields(fields: FieldsNamed) -> Result<Vec<Expr>, Error> {
let mut sortable_expressions = vec![];

for field in fields.named {
Expand Down

0 comments on commit 8e59c1f

Please sign in to comment.