Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make accessor alias differentiate accessors to the same attribute name #8

Draft
wants to merge 17 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ doctest = false

[dependencies]
proc-macro2 = "1.0.43"
syn = { version = "1.0.99", features = ["full"] }
syn = { version = "1.0.109", features = ["full"] }
quote = "1.0.21"
either = "1.8.0"

[dev-dependencies]
rust-format = "0.3.4"
trybuild = {version = "1.0.80", features = ["diff"]}
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@

<!-- TOC -->
* [Usage](#usage)
* [SortBy](#sortby)
* [EnumAccessor](#enumaccessor)
* [Field accessor](#field-accessor)
* [EnumSequence](#enumsequence)
* [SortBy](#sortby)
* [EnumAccessor](#enumaccessor)
* [Field accessor](#field-accessor)
* [EnumSequence](#enumsequence)
* [Example](#example)
<!-- TOC -->

Expand Down Expand Up @@ -53,16 +53,17 @@ This is typical for events - they represent a state change and are generally con

#### Field accessor

After adding `derive(EnumAccessor)` to the enum, fields are declared as `accessor(field: type)` attributes:
After adding `derive(EnumAccessor)` to the enum, fields are declared as `accessor(field: type)` attributes.
Aliases can be created to rename the accessor in order to avoid name clashes with the `as` keyword: `accessor(field as alias: type)`.

This will derive the accessor methods `fn name(&self) -> &type;` and`fn name_mut(&mut self) -> &mut type;`, and return a reference to the field of the same name on any variant.
This will derive the accessor methods `fn name(&self) -> &type;` and`fn name_mut(&mut self) -> &mut type;`, and return a reference to the field of the same name or alias on any variant.

```rust
use sort_by_derive::EnumAccessor;

#[derive(EnumAccessor)]
#[accessor(a: u16)]
#[accessor(b: u16)]
#[accessor(b as my_b: u16)]
enum E {
Variant1{a: u16, b: u16},
Variant2{a: u16, b: u16, c: u32},
Expand All @@ -73,14 +74,14 @@ let mut v2 = E::Variant2{a: 1, b: 1, c: 2};

// Accessor methods are generated for the specified members
assert_eq!(*v1.a(), 1);
assert_eq!(*v2.b(), 1);
assert_eq!(*v2.my_b(), 1);

// Mutable accessors are also generated
*v2.a_mut() = 2;
assert_eq!(*v2.a(), 2);
```

So you can take any `E`, all variants will have `a`, `a_mut`, `b`, `b_mut`
So you can take any `E`, all variants will have `a`, `a_mut`, `my_b`, `my_b_mut`

### EnumSequence

Expand Down
15 changes: 10 additions & 5 deletions src/enum_variant_accessor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,12 +459,17 @@ pub fn impl_enum_accessor(input: DeriveInput) -> TokenStream {
{
match parse_attr(attr) {
Ok(accessor) => {
if accessors.iter().any(|a| a.ident == accessor.ident) {
return syn::Error::new(
if let Some(conflicting) = accessors.iter().find(|a| a.alias == accessor.alias) {
let mut error = syn::Error::new(
accessor.span,
format!("Duplicate accessor {}", accessor.ident),
)
.into_compile_error();
format!("Duplicate accessor {}", accessor.alias),
);
error.combine(syn::Error::new(
// This isn't quite as pretty as compiler diagnostics, but close.
conflicting.span,
format!("{} is previously defined here", conflicting.alias),
));
return error.into_compile_error();
}
accessors.push(accessor)
}
Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,12 +300,12 @@ pub fn sort_by_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream
///
/// **Caveat**: Aliasing doesn't prevent *Duplicate accessor* error:
///
/// ```compile_fail
/// ```rust
/// # use sort_by_derive::EnumAccessor;
/// #
/// #[derive(EnumAccessor)]
/// #[accessor(a: u16, except(Variant3))]
/// #[accessor(a as a_big: u32, except(Variant1,Variant2))] // error: Duplicate accessor a
/// #[accessor(a as a_big: u32, except(Variant1,Variant2))]
/// enum E {
/// Variant1 { a: u16 },
/// Variant2 { a: u16, c: u32 },
Expand Down
26 changes: 26 additions & 0 deletions tests/enum_variant_accessor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use sort_by_derive::EnumAccessor;

#[test]
fn test_alias() {
#[derive(EnumAccessor)]
#[accessor(a: u16, except(Variant3))]
#[accessor(a as a_big: u32, except(Variant1,Variant2))]
enum E {
Variant1 { a: u16 },
Variant2 { a: u16, c: u32 },

Check warning on line 10 in tests/enum_variant_accessor.rs

View workflow job for this annotation

GitHub Actions / Test Suite

field `c` is never read
Variant3 { a: u32 },
}

assert_eq!(E::Variant1 { a: 1 }.a(), Some(&1));
assert_eq!(E::Variant2 { a: 1, c: 0 }.a(), Some(&1));
assert_eq!(E::Variant3 { a: 0 }.a(), None);
assert_eq!(E::Variant3 { a: 2 }.a_big(), Some(&2));
assert_eq!(E::Variant1 { a: 0 }.a_big(), None);
}

#[test]
fn ui() {
let t = trybuild::TestCases::new();
t.pass("tests/ui/pass_*.rs");
t.compile_fail("tests/ui/fail_*.rs");
}
12 changes: 12 additions & 0 deletions tests/ui/fail_alias_conflicting_field_ident.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use sort_by_derive::EnumAccessor;

#[derive(EnumAccessor)]
#[accessor(b: u16, except(Variant3))]

Check failure on line 4 in tests/ui/fail_alias_conflicting_field_ident.rs

View workflow job for this annotation

GitHub Actions / Test Suite

b is previously defined here

Check failure on line 4 in tests/ui/fail_alias_conflicting_field_ident.rs

View workflow job for this annotation

GitHub Actions / Test Suite

b is previously defined here
#[accessor(a as b: u32, except(Variant1,Variant2))]

Check failure on line 5 in tests/ui/fail_alias_conflicting_field_ident.rs

View workflow job for this annotation

GitHub Actions / Test Suite

Duplicate accessor b

Check failure on line 5 in tests/ui/fail_alias_conflicting_field_ident.rs

View workflow job for this annotation

GitHub Actions / Test Suite

Duplicate accessor b
enum E {
Variant1 { b: u16 },
Variant2 { b: u16, c: u32 },
Variant3 { a: u32 },
}

fn main() {}
11 changes: 11 additions & 0 deletions tests/ui/fail_alias_conflicting_field_ident.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
error: Duplicate accessor b
--> tests/ui/fail_alias_conflicting_field_ident.rs:5:12
|
5 | #[accessor(a as b: u32, except(Variant1,Variant2))]
| ^^^^^^

error: b is previously defined here
--> tests/ui/fail_alias_conflicting_field_ident.rs:4:12
|
4 | #[accessor(b: u16, except(Variant3))]
| ^^^^^^
12 changes: 12 additions & 0 deletions tests/ui/fail_field_conflicting_alias.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use sort_by_derive::EnumAccessor;

#[derive(EnumAccessor)]
#[accessor(a as b: u32, except(Variant1,Variant2))]

Check failure on line 4 in tests/ui/fail_field_conflicting_alias.rs

View workflow job for this annotation

GitHub Actions / Test Suite

b is previously defined here

Check failure on line 4 in tests/ui/fail_field_conflicting_alias.rs

View workflow job for this annotation

GitHub Actions / Test Suite

b is previously defined here
#[accessor(b: u16, except(Variant3))]

Check failure on line 5 in tests/ui/fail_field_conflicting_alias.rs

View workflow job for this annotation

GitHub Actions / Test Suite

Duplicate accessor b

Check failure on line 5 in tests/ui/fail_field_conflicting_alias.rs

View workflow job for this annotation

GitHub Actions / Test Suite

Duplicate accessor b
enum E {
Variant1 { b: u16 },
Variant2 { b: u16, c: u32 },
Variant3 { a: u32 },
}

fn main() {}
11 changes: 11 additions & 0 deletions tests/ui/fail_field_conflicting_alias.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
error: Duplicate accessor b
--> tests/ui/fail_field_conflicting_alias.rs:5:12
|
5 | #[accessor(b: u16, except(Variant3))]
| ^^^^^^

error: b is previously defined here
--> tests/ui/fail_field_conflicting_alias.rs:4:12
|
4 | #[accessor(a as b: u32, except(Variant1,Variant2))]
| ^^^^^^
24 changes: 24 additions & 0 deletions tests/ui/fail_field_conflicting_method.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use sort_by_derive::EnumAccessor;

#[derive(EnumAccessor)]
#[accessor(a: u16, except(Variant3))]
#[accessor(b: u32, except(Variant1,Variant2))]
enum E {
Variant1 { a: u16 },
Variant2 { a: u16, c: u32 },
Variant3 { b: u32 },
}

impl E {
fn b(&self) {}
}

trait B {
fn b(&self);
}

impl B for E {
fn b(&self) {}
}

fn main() {}
12 changes: 12 additions & 0 deletions tests/ui/pass_alias_avoiding_field_ident.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use sort_by_derive::EnumAccessor;

#[derive(EnumAccessor)]
#[accessor(a: u16, except(Variant3))]
#[accessor(a as a_big: u32, except(Variant1,Variant2))]
enum E {
Variant1 { a: u16 },
Variant2 { a: u16, c: u32 },
Variant3 { a: u32 },
}

fn main() {}
16 changes: 16 additions & 0 deletions tests/ui/pass_alias_avoiding_method.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use sort_by_derive::EnumAccessor;

#[derive(EnumAccessor)]
#[accessor(a: u16, except(Variant3))]
#[accessor(b as b_field: u32, except(Variant1,Variant2))]
enum E {
Variant1 { a: u16 },
Variant2 { a: u16, c: u32 },
Variant3 { b: u32 },
}

impl E {
fn b(&self) {}
}

fn main() {}
Loading