Skip to content

Commit 68abda5

Browse files
authored
Merge pull request #88 from JRRudy1/fields
Implemented delegation to fields
2 parents 36a89f4 + ed862ca commit 68abda5

File tree

9 files changed

+365
-22
lines changed

9 files changed

+365
-22
lines changed

.github/workflows/test.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ jobs:
2626
override: true
2727
components: rustfmt, clippy
2828

29+
- name: Install cargo-expand
30+
uses: actions-rs/cargo@v1
31+
with:
32+
command: install
33+
args: --locked --version 1.0.118 cargo-expand
34+
2935
- name: Build
3036
uses: actions-rs/cargo@v1
3137
with:

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ proc-macro = true
2525
async-trait = "0.1.50"
2626
futures = "0.3.16"
2727
tokio = { version = "1.16.1", features = ["sync"] }
28+
macrotest = "1.0.12"

README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,58 @@ impl Enum {
456456
assert_eq!(Enum::A(A).get_toto(), <A as WithConst>::TOTO);
457457
```
458458

459+
### Delegate to fields
460+
```rust
461+
use delegate::delegate;
462+
463+
struct Datum {
464+
value: u32,
465+
error: u32,
466+
}
467+
468+
struct DatumWrapper(Datum);
469+
470+
impl DatumWrapper {
471+
delegate! {
472+
to self.0 {
473+
/// Get the value of a nested field with the same name
474+
#[field]
475+
fn value(&self) -> u32;
476+
477+
/// Get the value of a nested field with a different name
478+
#[field(value)]
479+
fn renamed_value(&self) -> u32;
480+
481+
/// Get shared reference to a nested field
482+
#[field(&value)]
483+
fn value_ref(&self) -> &u32;
484+
485+
/// Get mutable reference to a nested field
486+
#[field(&mut value)]
487+
fn value_ref_mut(&mut self) -> &mut u32;
488+
489+
/// Get mutable reference to a nested field with the same name
490+
#[field(&)]
491+
fn error(&self) -> &u32;
492+
}
493+
}
494+
}
495+
```
496+
497+
## Development
498+
499+
This project uses a standard test suite for quality control, as well as a set of
500+
"expansion" tests that utilize the `macrotest` crate to ensure the macro expands
501+
as expected. PRs implementing new features should add both standard and expansion
502+
tests where appropriate.
503+
504+
To add an expansion test, place a Rust source file in the `tests/expand/` directory
505+
with methods demonstrating the new feature. Next, run `cargo test` to run the test
506+
suite and generate a `*.expanded.rs` file in the same directory. Next, carefully
507+
inspect the contents of the generated file to confirm that all methods expanded as
508+
expected. Finally, commit both files to the git repository. Future test suite runs
509+
will now include expanding the source file and comparing it to the expanded file.
510+
459511
## License
460512

461513
Licensed under either of

src/attributes.rs

Lines changed: 76 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
use std::collections::VecDeque;
2-
31
use proc_macro2::{Delimiter, TokenStream, TokenTree};
42
use quote::ToTokens;
3+
use std::collections::VecDeque;
4+
use std::ops::Not;
55
use syn::parse::ParseStream;
6-
use syn::{Attribute, Error, Meta, Path, PathSegment, TypePath};
6+
use syn::{Attribute, Error, Meta, Path, PathSegment, Token, TypePath};
77

8-
struct CallMethodAttribute {
8+
pub struct CallMethodAttribute {
99
name: syn::Ident,
1010
}
1111

@@ -17,6 +17,35 @@ impl syn::parse::Parse for CallMethodAttribute {
1717
}
1818
}
1919

20+
#[derive(Default, Clone)]
21+
pub struct GetFieldAttribute {
22+
reference: Option<(Token![&], Option<Token![mut]>)>,
23+
member: Option<syn::Member>,
24+
}
25+
26+
impl GetFieldAttribute {
27+
pub fn reference_tokens(&self) -> Option<TokenStream> {
28+
let (ref_, mut_) = self.reference.as_ref()?;
29+
let mut tokens = ref_.to_token_stream();
30+
mut_.to_tokens(&mut tokens);
31+
Some(tokens)
32+
}
33+
}
34+
35+
impl syn::parse::Parse for GetFieldAttribute {
36+
fn parse(input: ParseStream) -> Result<Self, Error> {
37+
let mut reference = None;
38+
if let Ok(ref_) = input.parse::<syn::Token![&]>() {
39+
reference = Some((ref_, None));
40+
}
41+
if let Some((_, mut_)) = &mut reference {
42+
*mut_ = input.parse::<syn::Token![mut]>().ok();
43+
}
44+
let member = input.is_empty().not().then(|| input.parse()).transpose()?;
45+
Ok(GetFieldAttribute { reference, member })
46+
}
47+
}
48+
2049
struct GenerateAwaitAttribute {
2150
literal: syn::LitBool,
2251
}
@@ -202,10 +231,28 @@ pub enum ReturnExpression {
202231
Unwrap,
203232
}
204233

234+
pub enum TargetSpecifier {
235+
Field(GetFieldAttribute),
236+
Method(CallMethodAttribute),
237+
}
238+
239+
impl TargetSpecifier {
240+
pub fn get_member(&self, default: &syn::Ident) -> syn::Member {
241+
match self {
242+
Self::Field(GetFieldAttribute {
243+
member: Some(member),
244+
..
245+
}) => member.clone(),
246+
Self::Field(_) => default.clone().into(),
247+
Self::Method(method) => method.name.clone().into(),
248+
}
249+
}
250+
}
251+
205252
enum ParsedAttribute {
206253
ReturnExpression(ReturnExpression),
207254
Await(bool),
208-
TargetMethod(syn::Ident),
255+
TargetSpecifier(TargetSpecifier),
209256
ThroughTrait(TraitTarget),
210257
ConstantAccess(AssociatedConstant),
211258
Expr(TemplateExpr),
@@ -231,7 +278,19 @@ fn parse_attributes(
231278
let target = attribute
232279
.parse_args::<CallMethodAttribute>()
233280
.expect("Cannot parse `call` attribute");
234-
Some(ParsedAttribute::TargetMethod(target.name))
281+
let spec = TargetSpecifier::Method(target);
282+
Some(ParsedAttribute::TargetSpecifier(spec))
283+
}
284+
"field" => {
285+
let target = if let syn::Meta::Path(_) = &attribute.meta {
286+
GetFieldAttribute::default()
287+
} else {
288+
attribute
289+
.parse_args::<GetFieldAttribute>()
290+
.expect("Cannot parse `field` attribute")
291+
};
292+
let spec = TargetSpecifier::Field(target);
293+
Some(ParsedAttribute::TargetSpecifier(spec))
235294
}
236295
"into" => {
237296
let into = match &attribute.meta {
@@ -300,7 +359,7 @@ fn parse_attributes(
300359

301360
pub struct MethodAttributes<'a> {
302361
pub attributes: Vec<&'a Attribute>,
303-
pub target_method: Option<syn::Ident>,
362+
pub target_specifier: Option<TargetSpecifier>,
304363
pub expressions: VecDeque<ReturnExpression>,
305364
pub generate_await: Option<bool>,
306365
pub target_trait: Option<TypePath>,
@@ -320,7 +379,7 @@ pub fn parse_method_attributes<'a>(
320379
attrs: &'a [Attribute],
321380
method: &syn::TraitItemFn,
322381
) -> MethodAttributes<'a> {
323-
let mut target_method: Option<syn::Ident> = None;
382+
let mut target_spec: Option<TargetSpecifier> = None;
324383
let mut expressions: Vec<ReturnExpression> = vec![];
325384
let mut generate_await: Option<bool> = None;
326385
let mut target_trait: Option<TraitTarget> = None;
@@ -340,14 +399,14 @@ pub fn parse_method_attributes<'a>(
340399
}
341400
generate_await = Some(value);
342401
}
343-
ParsedAttribute::TargetMethod(target) => {
344-
if target_method.is_some() {
402+
ParsedAttribute::TargetSpecifier(spec) => {
403+
if target_spec.is_some() {
345404
panic!(
346-
"Multiple call attributes specified for {}",
405+
"Multiple field/call attributes specified for {}",
347406
method.sig.ident
348407
)
349408
}
350-
target_method = Some(target);
409+
target_spec = Some(spec);
351410
}
352411
ParsedAttribute::ThroughTrait(target) => {
353412
if target_trait.is_some() {
@@ -379,13 +438,13 @@ pub fn parse_method_attributes<'a>(
379438
}
380439
}
381440

382-
if associated_constant.is_some() && target_method.is_some() {
383-
panic!("Cannot use both `call` and `const` attributes.");
441+
if associated_constant.is_some() && target_spec.is_some() {
442+
panic!("Cannot use both `call`/`field` and `const` attributes.");
384443
}
385444

386445
MethodAttributes {
387446
attributes: other.into_iter().collect(),
388-
target_method,
447+
target_specifier: target_spec,
389448
generate_await,
390449
expressions: expressions.into(),
391450
target_trait: target_trait.map(|t| t.type_path),
@@ -425,8 +484,8 @@ pub fn parse_segment_attributes(attrs: &[Attribute]) -> SegmentAttributes {
425484
}
426485
target_trait = Some(target);
427486
}
428-
ParsedAttribute::TargetMethod(_) => {
429-
panic!("Call attribute cannot be specified on a `to <expr>` segment.");
487+
ParsedAttribute::TargetSpecifier(_) => {
488+
panic!("Field/call attribute cannot be specified on a `to <expr>` segment.");
430489
}
431490
ParsedAttribute::ConstantAccess(_) => {
432491
panic!("Const attribute cannot be specified on a `to <expr>` segment.");

src/lib.rs

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,44 @@
433433
//! }
434434
//!
435435
//! assert_eq!(Enum::A(A).get_toto(), <A as WithConst>::TOTO);
436+
//! ```
437+
//!
438+
//! - Delegate to fields
439+
//! ```rust
440+
//! use delegate::delegate;
441+
//!
442+
//! struct Datum {
443+
//! value: u32,
444+
//! error: u32,
445+
//! }
446+
//!
447+
//! struct DatumWrapper(Datum);
448+
//!
449+
//! impl DatumWrapper {
450+
//! delegate! {
451+
//! to self.0 {
452+
//! /// Get the value of a nested field with the same name
453+
//! #[field]
454+
//! fn value(&self) -> u32;
455+
//!
456+
//! /// Get the value of a nested field with a different name
457+
//! #[field(value)]
458+
//! fn renamed_value(&self) -> u32;
436459
//!
460+
//! /// Get shared reference to a nested field
461+
//! #[field(&value)]
462+
//! fn value_ref(&self) -> &u32;
463+
//!
464+
//! /// Get mutable reference to a nested field
465+
//! #[field(&mut value)]
466+
//! fn value_ref_mut(&mut self) -> &mut u32;
467+
//!
468+
//! /// Get mutable reference to a nested field with the same name
469+
//! #[field(&)]
470+
//! fn error(&self) -> &u32;
471+
//! }
472+
//! }
473+
//! }
437474
//! ```
438475
439476
extern crate proc_macro;
@@ -451,7 +488,7 @@ use syn::{parse_quote, Error, Expr, ExprField, ExprMethodCall, FnArg, GenericPar
451488

452489
use crate::attributes::{
453490
combine_attributes, parse_method_attributes, parse_segment_attributes, ReturnExpression,
454-
SegmentAttributes,
491+
SegmentAttributes, TargetSpecifier,
455492
};
456493

457494
mod attributes;
@@ -895,10 +932,13 @@ pub fn delegate(tokens: TokenStream) -> TokenStream {
895932

896933
// Generate an argument vector from Punctuated list.
897934
let args: Vec<Expr> = method.arguments.clone().into_iter().collect();
898-
let name = match &attributes.target_method {
899-
Some(n) => n,
900-
None => &input.sig.ident,
935+
936+
// Get name (or index) of the target method or field
937+
let name = match &attributes.target_specifier {
938+
Some(target) => target.get_member(&input.sig.ident),
939+
None => input.sig.ident.clone().into(),
901940
};
941+
902942
let inline = if has_inline_attribute(&attributes.attributes) {
903943
quote!()
904944
} else {
@@ -972,7 +1012,15 @@ pub fn delegate(tokens: TokenStream) -> TokenStream {
9721012
get_const(#expr)
9731013
}}
9741014
} else if is_method {
975-
quote::quote! { #expr.#name#generics(#(#args),*) }
1015+
match &attributes.target_specifier {
1016+
None | Some(TargetSpecifier::Method(_)) => {
1017+
quote::quote! { #expr.#name#generics(#(#args),*) }
1018+
}
1019+
Some(TargetSpecifier::Field(target)) => {
1020+
let reference = target.reference_tokens();
1021+
quote::quote! { #reference#expr.#name }
1022+
}
1023+
}
9761024
} else {
9771025
quote::quote! { #expr::#name#generics(#(#args),*) }
9781026
};

tests/delegation.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
use delegate::delegate;
22

3+
#[test]
4+
fn test_expansions() {
5+
macrotest::expand("tests/expand/*.rs");
6+
}
7+
38
#[test]
49
fn test_delegation() {
510
struct Inner;

tests/expand/fields.expanded.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use delegate::delegate;
2+
struct Datum {
3+
value: u32,
4+
error: u32,
5+
xy: (f32, f32),
6+
}
7+
struct DatumWrapper(Datum);
8+
impl DatumWrapper {
9+
fn get_inner(&self) -> &Datum {
10+
&self.0
11+
}
12+
/// Expands to `self.0.value`
13+
#[inline]
14+
fn value(&self) -> u32 {
15+
self.0.value
16+
}
17+
/// Expands to `self.0.value`
18+
#[inline]
19+
fn renamed_value(&self) -> u32 {
20+
self.0.value
21+
}
22+
/// Expands to `&self.0.value`
23+
#[inline]
24+
fn renamed_value_ref(&self) -> &u32 {
25+
&self.0.value
26+
}
27+
/// Expands to `&mut self.0.value`
28+
#[inline]
29+
fn renamed_value_ref_mut(&mut self) -> &mut u32 {
30+
&mut self.0.value
31+
}
32+
/// Expands to `&self.0.error` (demonstrates `&` without a field name)
33+
#[inline]
34+
fn error(&self) -> &u32 {
35+
&self.0.error
36+
}
37+
/// Expands to `self.0.xy.0` (demonstrates unnamed field access by value)
38+
#[inline]
39+
fn x(&self) -> f32 {
40+
self.0.xy.0
41+
}
42+
/// Expands to `&self.0.xy.1` (demonstrates unnamed field access by reference)
43+
#[inline]
44+
fn y(&self) -> &f32 {
45+
&self.0.xy.1
46+
}
47+
/// Expands to `&self.get_inner().value`
48+
#[inline]
49+
fn value_ref_via_get_inner(&self) -> &u32 {
50+
&self.get_inner().value
51+
}
52+
}

0 commit comments

Comments
 (0)