Skip to content

Commit

Permalink
Merge pull request #81 from vic1707/constant-delegation
Browse files Browse the repository at this point in the history
Constant delegation
  • Loading branch information
Kobzol authored Feb 9, 2025
2 parents 930f0e4 + 5273fb4 commit f7506ee
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Dev

- Add `#[const(path::to::Trait::CONST)]` attribute to delegate associated constants via a getter.

# 0.13.2 (14. 1. 2025)

- Correctly parse attributes with segmented paths (e.g. `#[a::b::c]`) (https://github.com/Kobzol/rust-delegate/issues/77).
Expand Down
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,52 @@ impl<T> Stack<T> {
assert_eq!(B::foo(1), 2);
```

- Delegate associated constants

```rust
use delegate::delegate;

trait WithConst {
const TOTO: u8;
}

struct A;
impl WithConst for A {
const TOTO: u8 = 1;
}

struct B;
impl WithConst for B {
const TOTO: u8 = 2;
}
struct C;
impl WithConst for C {
const TOTO: u8 = 2;
}

enum Enum {
A(A),
B(B),
C(C),
}

impl Enum {
delegate! {
to match self {
Self::A(a) => a,
Self::B(b) => b,
Self::C(c) => { println!("hello from c"); c },
} {
#[const(WithConst::TOTO)]
fn get_toto(&self) -> u8;
}
}
}

assert_eq!(Enum::A(A).get_toto(), <A as WithConst>::TOTO);

```

## License

Licensed under either of
Expand Down
65 changes: 64 additions & 1 deletion src/attributes.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::collections::VecDeque;

use syn::parse::ParseStream;
use syn::{Attribute, Error, Meta, TypePath};
use syn::{Attribute, Error, Meta, Path, PathSegment, TypePath};

struct CallMethodAttribute {
name: syn::Ident,
Expand Down Expand Up @@ -46,6 +46,43 @@ impl syn::parse::Parse for IntoAttribute {
}
}

pub struct AssociatedConstant {
pub const_name: PathSegment,
pub trait_path: Path,
}

impl syn::parse::Parse for AssociatedConstant {
fn parse(input: ParseStream) -> Result<Self, Error> {
let mut path = input.parse::<syn::Path>().map_err(|error| {
Error::new(
input.span(),
format!(
"{error}\nExpected const path, e.g. #[const(path::to::MyTrait::CONST_NAME)]"
),
)
})?;

let const_name = path.segments.pop().ok_or_else(|| {
Error::new_spanned(
&path,
"Expected a path. e.g. #[const(path::to::MyTrait::CONST_NAME)]",
)
})?;
// poping a segment leads to trailing `::`
path.segments.pop_punct().ok_or_else(|| {
Error::new_spanned(
&path,
"Expected a multipart path. e.g. #[const(path::to::MyTrait::CONST_NAME)]",
)
})?;

Ok(Self {
const_name: const_name.into_value(),
trait_path: path,
})
}
}

pub struct TraitTarget {
type_path: TypePath,
}
Expand Down Expand Up @@ -75,6 +112,7 @@ enum ParsedAttribute {
Await(bool),
TargetMethod(syn::Ident),
ThroughTrait(TraitTarget),
ConstantAccess(AssociatedConstant),
}

fn parse_attributes(
Expand Down Expand Up @@ -139,6 +177,11 @@ fn parse_attributes(
.parse_args::<TraitTarget>()
.expect("Cannot parse `through` attribute"),
)),
"const" => Some(ParsedAttribute::ConstantAccess(
attribute
.parse_args::<AssociatedConstant>()
.expect("Cannot parse `const` attribute"),
)),
_ => None,
}
} else {
Expand All @@ -160,6 +203,7 @@ pub struct MethodAttributes<'a> {
pub expressions: VecDeque<ReturnExpression>,
pub generate_await: Option<bool>,
pub target_trait: Option<TypePath>,
pub associated_constant: Option<AssociatedConstant>,
}

/// Iterates through the attributes of a method and filters special attributes.
Expand All @@ -169,6 +213,7 @@ pub struct MethodAttributes<'a> {
/// - await => generates an `.await` expression after the delegated expression
/// - unwrap => generates a `unwrap()` call after the delegated expression
/// - through => generates a UFCS call (`Target::method(&<expr>, ...)`) around the delegated expression
/// - const => generates a getter to a trait associated constant
pub fn parse_method_attributes<'a>(
attrs: &'a [Attribute],
method: &syn::TraitItemFn,
Expand All @@ -177,6 +222,7 @@ pub fn parse_method_attributes<'a>(
let mut expressions: Vec<ReturnExpression> = vec![];
let mut generate_await: Option<bool> = None;
let mut target_trait: Option<TraitTarget> = None;
let mut associated_constant: Option<AssociatedConstant> = None;

let (parsed, other) = parse_attributes(attrs);
for attr in parsed {
Expand Down Expand Up @@ -209,15 +255,29 @@ pub fn parse_method_attributes<'a>(
}
target_trait = Some(target);
}
ParsedAttribute::ConstantAccess(const_attr) => {
if associated_constant.is_some() {
panic!(
"Multiple const attributes specified for {}",
method.sig.ident
)
}
associated_constant = Some(const_attr);
}
}
}

if associated_constant.is_some() && target_method.is_some() {
panic!("Cannot use both `call` and `const` attributes.");
}

MethodAttributes {
attributes: other.into_iter().collect(),
target_method,
generate_await,
expressions: expressions.into(),
target_trait: target_trait.map(|t| t.type_path),
associated_constant,
}
}

Expand Down Expand Up @@ -253,6 +313,9 @@ pub fn parse_segment_attributes(attrs: &[Attribute]) -> SegmentAttributes {
ParsedAttribute::TargetMethod(_) => {
panic!("Call attribute cannot be specified on a `to <expr>` segment.");
}
ParsedAttribute::ConstantAccess(_) => {
panic!("Const attribute cannot be specified on a `to <expr>` segment.");
}
}
}
SegmentAttributes {
Expand Down
59 changes: 59 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,10 +362,56 @@
//!
//! assert_eq!(B::foo(1), 2);
//! ```
//! - Delegate associated constants
//!
//! ```rust
//! use delegate::delegate;
//!
//! trait WithConst {
//! const TOTO: u8;
//! }
//!
//! struct A;
//! impl WithConst for A {
//! const TOTO: u8 = 1;
//! }
//!
//! struct B;
//! impl WithConst for B {
//! const TOTO: u8 = 2;
//! }
//! struct C;
//! impl WithConst for C {
//! const TOTO: u8 = 2;
//! }
//!
//! enum Enum {
//! A(A),
//! B(B),
//! C(C),
//! }
//!
//! impl Enum {
//! delegate! {
//! to match self {
//! Self::A(a) => a,
//! Self::B(b) => b,
//! Self::C(c) => { println!("hello from c"); c },
//! } {
//! #[const(WithConst::TOTO)]
//! fn get_toto(&self) -> u8;
//! }
//! }
//! }
//!
//! assert_eq!(Enum::A(A).get_toto(), <A as WithConst>::TOTO);
//!
//! ```
extern crate proc_macro;
use std::mem;

use attributes::AssociatedConstant;
use proc_macro::TokenStream;

use proc_macro2::Ident;
Expand Down Expand Up @@ -833,6 +879,7 @@ pub fn delegate(tokens: TokenStream) -> TokenStream {
let visibility = &method.visibility;

let is_method = method.method.sig.receiver().is_some();
let associated_const = &attributes.associated_constant;

// Use the body of a closure (like `|k: u32| <body>`) as the delegation expression
let delegated_body = if let Expr::Closure(closure) = delegated_expr {
Expand Down Expand Up @@ -882,6 +929,18 @@ pub fn delegate(tokens: TokenStream) -> TokenStream {
let modify_expr = |expr: &Expr| {
let body = if let Some(target_trait) = &attributes.target_trait {
quote::quote! { #target_trait::#name#generics(#expr, #(#args),*) }
} else if let Some(AssociatedConstant {
const_name,
trait_path,
}) = associated_const
{
let return_type = &signature.output;
quote::quote! {{
const fn get_const<T: #trait_path>(t: &T) #return_type {
<T as #trait_path>::#const_name
}
get_const(#expr)
}}
} else if is_method {
quote::quote! { #expr.#name#generics(#(#args),*) }
} else {
Expand Down
80 changes: 80 additions & 0 deletions tests/associated_const_delegation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
extern crate delegate;
use delegate::delegate;

#[test]
fn test_delegate_constant() {
trait WithConst {
const TOTO: u8;
}

struct A;
impl WithConst for A {
const TOTO: u8 = 1;
}

struct B;
impl WithConst for B {
const TOTO: u8 = 2;
}
struct C;
impl WithConst for C {
const TOTO: u8 = 2;
}

enum Enum {
A(A),
B(B),
C(C),
}

impl Enum {
delegate! {
to match self {
Self::A(a) => a,
Self::B(b) => b,
Self::C(c) => { println!("hello from c"); c },
} {
#[const(WithConst::TOTO)]
fn get_toto(&self) -> u8;
}
}
}

let a = Enum::A(A);
assert_eq!(a.get_toto(), <A as WithConst>::TOTO);
let b = Enum::B(B);
assert_eq!(b.get_toto(), <B as WithConst>::TOTO);
let c = Enum::C(C);
assert_eq!(c.get_toto(), <C as WithConst>::TOTO);
}

#[test]
fn multiple_consts() {
trait Foo {
const A: u32;
const B: u32;
}

struct A;
impl Foo for A {
const A: u32 = 1;
const B: u32 = 2;
}

struct Wrapper(A);
impl Wrapper {
delegate! {
to &self.0 {
#[const(Foo::A)]
fn a(&self) -> u32;

#[const(Foo::B)]
fn b(&self) -> u32;
}
}
}

let wrapper = Wrapper(A);
assert_eq!(wrapper.a(), <A as Foo>::A);
assert_eq!(wrapper.b(), <A as Foo>::B);
}

0 comments on commit f7506ee

Please sign in to comment.