Skip to content
Open
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
35 changes: 35 additions & 0 deletions derive-ex-tests/tests/deref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,41 @@ fn single_field() {
let _: &u8 = &X { x: 10u8 };
let _: &mut u8 = &mut X { x: 10u8 };
}
#[test]
fn multiple_fields_tuple() {
#[derive_ex(Deref, DerefMut)]
#[expect(unused, reason = "testing with additional fields")]
struct X(#[deref] u8, u32);

let _: &u8 = &X(10u8, 1u32);
let _: &mut u8 = &mut X(10u8, 1u32);
}
#[test]
fn multiple_fields_named() {
#[derive_ex(Deref, DerefMut)]
#[expect(unused, reason = "testing with additional fields")]
struct X {
#[deref]
x: u8,
y: u32,
}

let _: &u8 = &X { x: 10u8, y: 1u32 };
let _: &mut u8 = &mut X { x: 10u8, y: 1u32 };
}
#[test]
fn not_first_field() {
#[derive_ex(Deref, DerefMut)]
#[expect(unused, reason = "testing with additional fields")]
struct X {
x: u8,
#[deref]
y: u32,
}

let _: &u32 = &X { x: 10u8, y: 1u32 };
let _: &mut u32 = &mut X { x: 10u8, y: 1u32 };
}

#[test]
fn with_where() {
Expand Down
90 changes: 80 additions & 10 deletions derive-ex/src/item_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -747,16 +747,49 @@ fn build_deref_for_struct(
let mut wcb = WhereClauseBuilder::new(&item.generics);
e.push_bounds_to(&mut wcb);

if fields.len() != 1 {
bail!(
Span::call_site(),
"`#[deirve_ex({})]` supports only single field struct.",
kind
);
}
let target_ty = &fields[0].field.ty;
let member = fields[0].member();
let targets: Vec<_> = fields
.iter()
.filter(|&field| field.hattrs.deref.deref.value())
.collect();
let field_idx = match targets.len() {
0 => {
if fields.len() == 1 {
0
} else {
bail!(
Span::call_site(),
"field with `#[deref]` does not exist but struct has more than one field.",
);
}
}
1 => targets[0].index,
_ => {
let names: Vec<String> = targets
.iter()
.map(|&field| {
field
.field
.ident
.as_ref()
.map(Ident::to_string)
.unwrap_or_else(move || field.index.to_string())
})
.collect();
bail!(
targets[0]
.hattrs
.deref
.deref
.span
.unwrap_or(targets[0].field.span()),
"there are multiple variants with `#[deref]` ({})",
names.join(", "),
)
}
};

let target_ty = &fields[field_idx].field.ty;
let member = fields[field_idx].member();
let content = match kind {
DeriveItemKind::Deref => {
quote! {
Expand Down Expand Up @@ -1193,6 +1226,7 @@ struct HelperAttributeKinds {
derive_ex: bool,
default: bool,
debug: bool,
deref: bool,
ord: bool,
partial_ord: bool,
eq: bool,
Expand All @@ -1212,6 +1246,7 @@ impl HelperAttributeKinds {
match e.kind {
DeriveItemKind::Default => self.default = true,
DeriveItemKind::Debug => self.debug = true,
DeriveItemKind::Deref => self.deref = true,
DeriveItemKind::CompareOp(op) => match op {
CompareOp::Ord => self.ord = true,
CompareOp::PartialOrd => self.partial_ord = true,
Expand Down Expand Up @@ -1248,6 +1283,7 @@ impl HelperAttributeKinds {
"derive_ex" => self.derive_ex,
"default" => self.default,
"debug" => self.debug,
"deref" => self.deref,
"ord" => self.is_match_cmp_attr(CompareOp::Ord),
"partial_ord" => self.is_match_cmp_attr(CompareOp::PartialOrd),
"eq" => self.is_match_cmp_attr(CompareOp::Eq),
Expand All @@ -1269,6 +1305,7 @@ struct HelperAttributes {
items: HashMap<DeriveItemKind, DeriveEntry>,
default: Option<HelperAttributeForDefault>,
debug: HelperAttributeForDebug,
deref: HelperAttributeForDeref,
cmp: HelperAttributesForCompareOp,
}

Expand Down Expand Up @@ -1296,11 +1333,17 @@ impl HelperAttributes {
} else {
HelperAttributeForDebug::default()
};
let deref = if kinds.deref {
HelperAttributeForDeref::from_attrs(attrs)?
} else {
HelperAttributeForDeref::default()
};
let cmp = HelperAttributesForCompareOp::from_attrs(attrs, kinds)?;
let this = Self {
items,
default,
debug,
deref,
cmp,
};
this.verify(target)?;
Expand Down Expand Up @@ -1387,7 +1430,9 @@ impl HelperAttributeForDebug {
Ok(Self {
transparent: args.transparent,
skip: if args.skip.value() || args.ignore.value() {
Flag { span: args.skip.span.or(args.ignore.span) }
Flag {
span: args.skip.span.or(args.ignore.span),
}
} else {
Flag::NONE
},
Expand Down Expand Up @@ -1458,6 +1503,31 @@ impl HelperAttributeForDefault {
}
}

#[derive(StructMeta, Debug, Default)]
struct ArgsForDeref {}

#[derive(Default)]
struct HelperAttributeForDeref {
deref: Flag,
}

impl HelperAttributeForDeref {
fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
if parse_single::<ArgsForDeref>(attrs, "deref")?.is_some() {
let span = attrs
.iter()
.find(|&attr| attr.path().is_ident("deref"))
.expect("parse_single already found a deref attribute")
.span();
Ok(Self {
deref: Flag { span: Some(span) },
})
} else {
Ok(Self { deref: Flag::NONE })
}
}
}

#[derive(StructMeta, Default, Debug)]
struct ArgsForCompareOp {
skip: Flag,
Expand Down
30 changes: 30 additions & 0 deletions doc/derive_ex.md
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,20 @@ struct X(u8);
let _: &u8 = &X(10u8);
```

For structs with multiple fields, you can use `#[deref]` to select the target for the `Deref` implementation.

```rust
use derive_ex::derive_ex;
#[derive(Debug, Default)]
#[derive_ex(Deref)]
struct X {
a: u8,
#[deref]
b: u32,
}
assert_eq!(X::default().leading_zeros(), 32);
```

# Derive `DerefMut`

You can use `#[derive(DerefMut)]` for struct with a single field to implement `DerefMut`.
Expand All @@ -559,6 +573,22 @@ struct X(u8);
let _: &mut u8 = &mut X(10u8);
```

For structs with multiple fields, you can use `#[deref]` to select the target for the `Deref` implementation.

```rust
use derive_ex::derive_ex;

#[derive(Debug, Default)]
#[derive_ex(Deref, DerefMut)]
struct X {
a: u8,
#[deref]
b: u32,
}

let _: &mut u32 = &mut X::default();
```

# Derive operators

## `Add`-like
Expand Down