1178 lines
40 KiB
Rust
1178 lines
40 KiB
Rust
//! This crate removes some boilerplate for structs that simply delegate
|
|
//! some of their methods to one or more of their fields.
|
|
//!
|
|
//! It gives you the `delegate!` macro, which delegates method calls to selected expressions (usually inner fields).
|
|
//!
|
|
//! ## Features:
|
|
//! - Delegate to a method with a different name
|
|
//! ```rust
|
|
//! use delegate::delegate;
|
|
//!
|
|
//! struct Stack { inner: Vec<u32> }
|
|
//! impl Stack {
|
|
//! delegate! {
|
|
//! to self.inner {
|
|
//! #[call(push)]
|
|
//! pub fn add(&mut self, value: u32);
|
|
//! }
|
|
//! }
|
|
//! }
|
|
//! ```
|
|
//! - Use an arbitrary inner field expression
|
|
//! ```rust
|
|
//! use delegate::delegate;
|
|
//!
|
|
//! use std::rc::Rc;
|
|
//! use std::cell::RefCell;
|
|
//! use std::ops::Deref;
|
|
//!
|
|
//! struct Wrapper { inner: Rc<RefCell<Vec<u32>>> }
|
|
//! impl Wrapper {
|
|
//! delegate! {
|
|
//! to self.inner.deref().borrow_mut() {
|
|
//! pub fn push(&mut self, val: u32);
|
|
//! }
|
|
//! }
|
|
//! }
|
|
//! ```
|
|
//!
|
|
//! - Delegate to enum variants
|
|
//!
|
|
//! ```rust
|
|
//! use delegate::delegate;
|
|
//!
|
|
//! enum Enum {
|
|
//! A(A),
|
|
//! B(B),
|
|
//! C { v: C },
|
|
//! }
|
|
//!
|
|
//! struct A {
|
|
//! val: usize,
|
|
//! }
|
|
//!
|
|
//! impl A {
|
|
//! fn dbg_inner(&self) -> usize {
|
|
//! dbg!(self.val);
|
|
//! 1
|
|
//! }
|
|
//! }
|
|
//! struct B {
|
|
//! val_a: String,
|
|
//! }
|
|
//!
|
|
//! impl B {
|
|
//! fn dbg_inner(&self) -> usize {
|
|
//! dbg!(self.val_a.clone());
|
|
//! 2
|
|
//! }
|
|
//! }
|
|
//!
|
|
//! struct C {
|
|
//! val_c: f64,
|
|
//! }
|
|
//!
|
|
//! impl C {
|
|
//! fn dbg_inner(&self) -> usize {
|
|
//! dbg!(self.val_c);
|
|
//! 3
|
|
//! }
|
|
//! }
|
|
//!
|
|
//! impl Enum {
|
|
//! delegate! {
|
|
//! // transformed to
|
|
//! //
|
|
//! // ```rust
|
|
//! // match self {
|
|
//! // Enum::A(a) => a.dbg_inner(),
|
|
//! // Enum::B(b) => { println!("i am b"); b }.dbg_inner(),
|
|
//! // Enum::C { v: c } => { c }.dbg_inner(),
|
|
//! // }
|
|
//! // ```
|
|
//! to match self {
|
|
//! Enum::A(a) => a,
|
|
//! Enum::B(b) => { println!("i am b"); b },
|
|
//! Enum::C { v: c } => { c },
|
|
//! } {
|
|
//! fn dbg_inner(&self) -> usize;
|
|
//! }
|
|
//! }
|
|
//! }
|
|
//! ```
|
|
//!
|
|
//! - Use modifiers that alter the generated method body
|
|
//! ```rust
|
|
//! use delegate::delegate;
|
|
//! struct Inner;
|
|
//! impl Inner {
|
|
//! pub fn method(&self, num: u32) -> u32 { num }
|
|
//! pub fn method_res(&self, num: u32) -> Result<u32, ()> { Ok(num) }
|
|
//! }
|
|
//! struct Wrapper { inner: Inner }
|
|
//! impl Wrapper {
|
|
//! delegate! {
|
|
//! to self.inner {
|
|
//! // calls method, converts result to u64 using `From`
|
|
//! #[into]
|
|
//! pub fn method(&self, num: u32) -> u64;
|
|
//!
|
|
//! // calls method, returns ()
|
|
//! #[call(method)]
|
|
//! pub fn method_noreturn(&self, num: u32);
|
|
//!
|
|
//! // calls method, converts result to i6 using `TryFrom`
|
|
//! #[try_into]
|
|
//! #[call(method)]
|
|
//! pub fn method2(&self, num: u32) -> Result<u16, std::num::TryFromIntError>;
|
|
//!
|
|
//! // calls method_res, unwraps the result
|
|
//! #[unwrap]
|
|
//! pub fn method_res(&self, num: u32) -> u32;
|
|
//!
|
|
//! // calls method_res, unwraps the result, then calls into
|
|
//! #[unwrap]
|
|
//! #[into]
|
|
//! #[call(method_res)]
|
|
//! pub fn method_res_into(&self, num: u32) -> u64;
|
|
//!
|
|
//! // specify explicit type for into
|
|
//! #[into(u64)]
|
|
//! #[call(method)]
|
|
//! pub fn method_into_explicit(&self, num: u32) -> u64;
|
|
//! }
|
|
//! }
|
|
//! }
|
|
//! ```
|
|
//!
|
|
//! - Custom called expression
|
|
//!
|
|
//! The `#[expr()]` attribute can be used to modify the delegated call. You can use the `$` sigil as a placeholder for what delegate would normally expand to, and wrap that expression with custom code.
|
|
//!
|
|
//! _Note:_ the `$` placeholder isn't required and can be present multiple times if you want.
|
|
//!
|
|
//! ```rs
|
|
//! struct A(Vec<u8>);
|
|
//!
|
|
//! impl A {
|
|
//! delegate! {
|
|
//! to self.0 {
|
|
//! #[expr(*$.unwrap())]
|
|
//! /// Here `$` == `self.0.get(idx)`
|
|
//! /// Will expand to `*self.0.get(idx).unwrap()`
|
|
//! fn get(&self, idx: usize) -> u8;
|
|
//!
|
|
//! #[call(get)]
|
|
//! #[expr($?.checked_pow(2))]
|
|
//! /// Here `$` == `self.0.get(idx)`
|
|
//! /// Will expand to `self.0.get(idx)?.checked_pow(2)`
|
|
//! fn get_checked_pow_2(&self, idx: usize) -> Option<u8>;
|
|
//! }
|
|
//! }
|
|
//! }
|
|
//! ```
|
|
//!
|
|
//! - Call `await` on async functions
|
|
//! ```rust
|
|
//! use delegate::delegate;
|
|
//!
|
|
//! struct Inner;
|
|
//! impl Inner {
|
|
//! pub async fn method(&self, num: u32) -> u32 { num }
|
|
//! }
|
|
//! struct Wrapper { inner: Inner }
|
|
//! impl Wrapper {
|
|
//! delegate! {
|
|
//! to self.inner {
|
|
//! // calls method(num).await, returns impl Future<Output = u32>
|
|
//! pub async fn method(&self, num: u32) -> u32;
|
|
//!
|
|
//! // calls method(num).await.into(), returns impl Future<Output = u64>
|
|
//! #[into]
|
|
//! #[call(method)]
|
|
//! pub async fn method_into(&self, num: u32) -> u64;
|
|
//! }
|
|
//! }
|
|
//! }
|
|
//! ```
|
|
//! You can use the `#[await(true/false)]` attribute on delegated methods to specify if `.await` should
|
|
//! be generated after the delegated expression. It will be generated by default if the delegated
|
|
//! method is `async`.
|
|
//! - Delegate to multiple fields
|
|
//! ```rust
|
|
//! use delegate::delegate;
|
|
//!
|
|
//! struct MultiStack {
|
|
//! left: Vec<u32>,
|
|
//! right: Vec<u32>,
|
|
//! }
|
|
//! impl MultiStack {
|
|
//! delegate! {
|
|
//! to self.left {
|
|
//! // Push an item to the top of the left stack
|
|
//! #[call(push)]
|
|
//! pub fn push_left(&mut self, value: u32);
|
|
//! }
|
|
//! to self.right {
|
|
//! // Push an item to the top of the right stack
|
|
//! #[call(push)]
|
|
//! pub fn push_right(&mut self, value: u32);
|
|
//! }
|
|
//! }
|
|
//! }
|
|
//! ```
|
|
//! - Inserts `#[inline(always)]` automatically (unless you specify `#[inline]` manually on the method)
|
|
//! - You can use an attribute on a whole segment to automatically apply it to all methods in that
|
|
//! segment:
|
|
//! ```rust
|
|
//! use delegate::delegate;
|
|
//!
|
|
//! struct Inner;
|
|
//!
|
|
//! impl Inner {
|
|
//! fn foo(&self) -> Result<u32, ()> { Ok(0) }
|
|
//! fn bar(&self) -> Result<u32, ()> { Ok(1) }
|
|
//! }
|
|
//!
|
|
//! struct Wrapper { inner: Inner }
|
|
//!
|
|
//! impl Wrapper {
|
|
//! delegate! {
|
|
//! #[unwrap]
|
|
//! to self.inner {
|
|
//! fn foo(&self) -> u32; // calls self.inner.foo().unwrap()
|
|
//! fn bar(&self) -> u32; // calls self.inner.bar().unwrap()
|
|
//! }
|
|
//! }
|
|
//! }
|
|
//! ```
|
|
//! - Specify expressions in the signature that will be used as delegated arguments
|
|
//! ```rust
|
|
//! use delegate::delegate;
|
|
//! struct Inner;
|
|
//! impl Inner {
|
|
//! pub fn polynomial(&self, a: i32, x: i32, b: i32, y: i32, c: i32) -> i32 {
|
|
//! a + x * x + b * y + c
|
|
//! }
|
|
//! }
|
|
//! struct Wrapper { inner: Inner, a: i32, b: i32, c: i32 }
|
|
//! impl Wrapper {
|
|
//! delegate! {
|
|
//! to self.inner {
|
|
//! // Calls `polynomial` on `inner` with `self.a`, `self.b` and
|
|
//! // `self.c` passed as arguments `a`, `b`, and `c`, effectively
|
|
//! // calling `polynomial(self.a, x, self.b, y, self.c)`.
|
|
//! pub fn polynomial(&self, [ self.a ], x: i32, [ self.b ], y: i32, [ self.c ]) -> i32 ;
|
|
//! // Calls `polynomial` on `inner` with `0`s passed for arguments
|
|
//! // `a` and `x`, and `self.b` and `self.c` for `b` and `c`,
|
|
//! // effectively calling `polynomial(0, 0, self.b, y, self.c)`.
|
|
//! #[call(polynomial)]
|
|
//! pub fn linear(&self, [ 0 ], [ 0 ], [ self.b ], y: i32, [ self.c ]) -> i32 ;
|
|
//! }
|
|
//! }
|
|
//! }
|
|
//! ```
|
|
//! - Modify how will an input parameter be passed to the delegated method with parameter attribute modifiers.
|
|
//! Currently, the following modifiers are supported:
|
|
//! - `#[into]`: Calls `.into()` on the parameter passed to the delegated method.
|
|
//! - `#[as_ref]`: Calls `.as_ref()` on the parameter passed to the delegated method.
|
|
//! - `#[newtype]`: Calls `.0` on the parameter passed to the delegated method.
|
|
//! ```rust
|
|
//! use delegate::delegate;
|
|
//!
|
|
//! struct InnerType {}
|
|
//! impl InnerType {
|
|
//! fn foo(&self, other: Self) {}
|
|
//! }
|
|
//!
|
|
//! impl From<Wrapper> for InnerType {
|
|
//! fn from(wrapper: Wrapper) -> Self {
|
|
//! wrapper.0
|
|
//! }
|
|
//! }
|
|
//!
|
|
//! struct Wrapper(InnerType);
|
|
//! impl Wrapper {
|
|
//! delegate! {
|
|
//! to self.0 {
|
|
//! // Calls `self.0.foo(other.into());`
|
|
//! pub fn foo(&self, #[into] other: Self);
|
|
//! }
|
|
//! }
|
|
//! }
|
|
//! ```
|
|
//! - Specify a trait through which will the delegated method be called
|
|
//! (using [UFCS](https://doc.rust-lang.org/reference/expressions/call-expr.html#disambiguating-function-calls).
|
|
//! ```rust
|
|
//! use delegate::delegate;
|
|
//!
|
|
//! struct InnerType {}
|
|
//! impl InnerType {
|
|
//!
|
|
//! }
|
|
//!
|
|
//! trait MyTrait {
|
|
//! fn foo(&self);
|
|
//! }
|
|
//! impl MyTrait for InnerType {
|
|
//! fn foo(&self) {}
|
|
//! }
|
|
//!
|
|
//! struct Wrapper(InnerType);
|
|
//! impl Wrapper {
|
|
//! delegate! {
|
|
//! to &self.0 {
|
|
//! // Calls `MyTrait::foo(&self.0)`
|
|
//! #[through(MyTrait)]
|
|
//! pub fn foo(&self);
|
|
//! }
|
|
//! }
|
|
//! }
|
|
//! ```
|
|
//!
|
|
//! - Add additional arguments to method
|
|
//!
|
|
//! ```rust
|
|
//! use delegate::delegate;
|
|
//! use std::cell::OnceCell;
|
|
//! struct Inner(u32);
|
|
//! impl Inner {
|
|
//! pub fn new(m: u32) -> Self {
|
|
//! // some "very complex" constructing work
|
|
//! Self(m)
|
|
//! }
|
|
//! pub fn method(&self, n: u32) -> u32 {
|
|
//! self.0 + n
|
|
//! }
|
|
//! }
|
|
//!
|
|
//! struct Wrapper {
|
|
//! inner: OnceCell<Inner>,
|
|
//! }
|
|
//!
|
|
//! impl Wrapper {
|
|
//! pub fn new() -> Self {
|
|
//! Self {
|
|
//! inner: OnceCell::new(),
|
|
//! }
|
|
//! }
|
|
//! fn content(&self, val: u32) -> &Inner {
|
|
//! self.inner.get_or_init(|| Inner(val))
|
|
//! }
|
|
//! delegate! {
|
|
//! to |k: u32| self.content(k) {
|
|
//! // `wrapper.method(k, num)` will call `self.content(k).method(num)`
|
|
//! pub fn method(&self, num: u32) -> u32;
|
|
//! }
|
|
//! }
|
|
//! }
|
|
//! ```
|
|
//! - Delegate associated functions
|
|
//! ```rust
|
|
//! use delegate::delegate;
|
|
//!
|
|
//! struct A {}
|
|
//! impl A {
|
|
//! fn foo(a: u32) -> u32 {
|
|
//! a + 1
|
|
//! }
|
|
//! }
|
|
//!
|
|
//! struct B;
|
|
//!
|
|
//! impl B {
|
|
//! delegate! {
|
|
//! to A {
|
|
//! fn foo(a: u32) -> u32;
|
|
//! }
|
|
//! }
|
|
//! }
|
|
//!
|
|
//! 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);
|
|
//! ```
|
|
//!
|
|
//! - Delegate to fields
|
|
//! ```rust
|
|
//! use delegate::delegate;
|
|
//!
|
|
//! struct Datum {
|
|
//! value: u32,
|
|
//! error: u32,
|
|
//! }
|
|
//!
|
|
//! struct DatumWrapper(Datum);
|
|
//!
|
|
//! impl DatumWrapper {
|
|
//! delegate! {
|
|
//! to self.0 {
|
|
//! /// Get the value of a nested field with the same name
|
|
//! #[field]
|
|
//! fn value(&self) -> u32;
|
|
//!
|
|
//! /// Get the value of a nested field with a different name
|
|
//! #[field(value)]
|
|
//! fn renamed_value(&self) -> u32;
|
|
//!
|
|
//! /// Get shared reference to a nested field
|
|
//! #[field(&value)]
|
|
//! fn value_ref(&self) -> &u32;
|
|
//!
|
|
//! /// Get mutable reference to a nested field
|
|
//! #[field(&mut value)]
|
|
//! fn value_ref_mut(&mut self) -> &mut u32;
|
|
//!
|
|
//! /// Get mutable reference to a nested field with the same name
|
|
//! #[field(&)]
|
|
//! fn error(&self) -> &u32;
|
|
//! }
|
|
//! }
|
|
//! }
|
|
//! ```
|
|
|
|
extern crate proc_macro;
|
|
use std::mem;
|
|
|
|
use attributes::AssociatedConstant;
|
|
use proc_macro::TokenStream;
|
|
|
|
use proc_macro2::Ident;
|
|
use quote::{quote, ToTokens};
|
|
use syn::parse::{Parse, ParseStream};
|
|
use syn::spanned::Spanned;
|
|
use syn::visit_mut::VisitMut;
|
|
use syn::{parse_quote, Error, Expr, ExprField, ExprMethodCall, FnArg, GenericParam, Meta};
|
|
|
|
use crate::attributes::{
|
|
combine_attributes, parse_method_attributes, parse_segment_attributes, ReturnExpression,
|
|
SegmentAttributes, TargetSpecifier,
|
|
};
|
|
|
|
mod attributes;
|
|
|
|
mod kw {
|
|
syn::custom_keyword!(to);
|
|
syn::custom_keyword!(target);
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
enum ArgumentModifier {
|
|
Into,
|
|
AsRef,
|
|
Newtype,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
enum DelegatedInput {
|
|
Input {
|
|
parameter: syn::FnArg,
|
|
modifier: Option<ArgumentModifier>,
|
|
},
|
|
Argument(syn::Expr),
|
|
}
|
|
|
|
fn get_argument_modifier(attribute: syn::Attribute) -> Result<ArgumentModifier, Error> {
|
|
if let Meta::Path(mut path) = attribute.meta {
|
|
if path.segments.len() == 1 {
|
|
let segment = path.segments.pop().unwrap();
|
|
if segment.value().arguments.is_empty() {
|
|
let ident = segment.value().ident.to_string();
|
|
let ident = ident.as_str();
|
|
|
|
match ident {
|
|
"into" => return Ok(ArgumentModifier::Into),
|
|
"as_ref" => return Ok(ArgumentModifier::AsRef),
|
|
"newtype" => return Ok(ArgumentModifier::Newtype),
|
|
_ => (),
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
panic!("The attribute argument has to be `into` or `as_ref`, like this: `#[into] a: u32`.")
|
|
}
|
|
|
|
impl syn::parse::Parse for DelegatedInput {
|
|
fn parse(input: ParseStream) -> Result<Self, Error> {
|
|
let lookahead = input.lookahead1();
|
|
if lookahead.peek(syn::token::Bracket) {
|
|
let content;
|
|
let _bracket_token = syn::bracketed!(content in input);
|
|
let expression: syn::Expr = content.parse()?;
|
|
Ok(Self::Argument(expression))
|
|
} else {
|
|
let (input, modifier) = if lookahead.peek(syn::token::Pound) {
|
|
let mut attributes = input.call(tolerant_outer_attributes)?;
|
|
if attributes.len() > 1 {
|
|
panic!("You can specify at most a single attribute for each parameter in a delegated method");
|
|
}
|
|
let modifier = get_argument_modifier(attributes.pop().unwrap())
|
|
.expect("Could not parse argument modifier attribute");
|
|
|
|
let input: syn::FnArg = input.parse()?;
|
|
(input, Some(modifier))
|
|
} else {
|
|
(input.parse()?, None)
|
|
};
|
|
|
|
Ok(Self::Input {
|
|
parameter: input,
|
|
modifier,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
struct DelegatedMethod {
|
|
method: syn::TraitItemFn,
|
|
attributes: Vec<syn::Attribute>,
|
|
visibility: syn::Visibility,
|
|
arguments: syn::punctuated::Punctuated<syn::Expr, syn::Token![,]>,
|
|
}
|
|
|
|
// Given an input parameter from a function signature, create a function
|
|
// argument used to call the delegate function: omit receiver, extract an
|
|
// identifier from a typed input parameter (and wrap it in an `Expr`).
|
|
fn parse_input_into_argument_expression(
|
|
function_name: &Ident,
|
|
input: &syn::FnArg,
|
|
) -> Option<syn::Expr> {
|
|
match input {
|
|
// Parse inputs of the form `x: T` to retrieve their identifiers.
|
|
syn::FnArg::Typed(typed) => {
|
|
match &*typed.pat {
|
|
// This should not happen, I think. If it does,
|
|
// it will be ignored as if it were the
|
|
// receiver.
|
|
syn::Pat::Ident(ident) if ident.ident == "self" => None,
|
|
// Expression in the form `x: T`. Extract the
|
|
// identifier, wrap it in Expr for type compatibility with bracketed expressions,
|
|
// and append it
|
|
// to the argument list.
|
|
syn::Pat::Ident(ident) => {
|
|
let path_segment = syn::PathSegment {
|
|
ident: ident.ident.clone(),
|
|
arguments: syn::PathArguments::None,
|
|
};
|
|
let mut segments = syn::punctuated::Punctuated::new();
|
|
segments.push(path_segment);
|
|
let path = syn::Path {
|
|
leading_colon: None,
|
|
segments,
|
|
};
|
|
let ident_as_expr = syn::Expr::from(syn::ExprPath {
|
|
attrs: Vec::new(),
|
|
qself: None,
|
|
path,
|
|
});
|
|
Some(ident_as_expr)
|
|
}
|
|
// Other more complex argument expressions are not covered.
|
|
_ => panic!(
|
|
"You have to use simple identifiers for delegated method parameters ({})",
|
|
function_name // The signature is not constructed yet. We make due.
|
|
),
|
|
}
|
|
}
|
|
// Skip any `self`/`&self`/`&mut self` argument, since
|
|
// it does not appear in the argument list and it's
|
|
// already added to the parameter list.
|
|
syn::FnArg::Receiver(_receiver) => None,
|
|
}
|
|
}
|
|
|
|
impl syn::parse::Parse for DelegatedMethod {
|
|
fn parse(input: ParseStream) -> Result<Self, Error> {
|
|
let attributes = input.call(tolerant_outer_attributes)?;
|
|
let visibility = input.call(syn::Visibility::parse)?;
|
|
|
|
// Unchanged from Parse from TraitItemMethod
|
|
let constness: Option<syn::Token![const]> = input.parse()?;
|
|
let asyncness: Option<syn::Token![async]> = input.parse()?;
|
|
let unsafety: Option<syn::Token![unsafe]> = input.parse()?;
|
|
let abi: Option<syn::Abi> = input.parse()?;
|
|
let fn_token: syn::Token![fn] = input.parse()?;
|
|
let ident: Ident = input.parse()?;
|
|
let generics: syn::Generics = input.parse()?;
|
|
|
|
let content;
|
|
let paren_token = syn::parenthesized!(content in input);
|
|
|
|
// Parse inputs (method parameters) and arguments. The parameters
|
|
// constitute the parameter list of the signature of the delegating
|
|
// method so it must include all inputs, except bracketed expressions.
|
|
// The argument list constitutes the list of arguments used to call the
|
|
// delegated function. It must include all inputs, excluding the
|
|
// receiver (self-type) input. The arguments must all be parsed to
|
|
// retrieve the expressions inside of the brackets as well as variable
|
|
// identifiers of ordinary inputs. The arguments must preserve the order
|
|
// of the inputs.
|
|
let delegated_inputs = content.parse_terminated(DelegatedInput::parse, syn::Token![,])?;
|
|
let mut inputs: syn::punctuated::Punctuated<syn::FnArg, syn::Token![,]> =
|
|
syn::punctuated::Punctuated::new();
|
|
let mut arguments: syn::punctuated::Punctuated<syn::Expr, syn::Token![,]> =
|
|
syn::punctuated::Punctuated::new();
|
|
|
|
// First, combine the cases for pairs with cases for end, to remove
|
|
// redundancy below.
|
|
delegated_inputs
|
|
.into_pairs()
|
|
.map(|punctuated_pair| match punctuated_pair {
|
|
syn::punctuated::Pair::Punctuated(item, comma) => (item, Some(comma)),
|
|
syn::punctuated::Pair::End(item) => (item, None),
|
|
})
|
|
.for_each(|pair| match pair {
|
|
// This input is a bracketed argument (eg. `[ self.x ]`). It
|
|
// is omitted in the signature of the delegator, but the
|
|
// expression inside the brackets is used in the body of the
|
|
// delegator, as an arugnment to the delegated function (eg.
|
|
// `self.x`). The argument needs to be generated in the
|
|
// appropriate position with respect other arguments and non-
|
|
// argument inputs. As long as inputs are added to the
|
|
// `arguments` vector in order of occurance, this is trivial.
|
|
(DelegatedInput::Argument(argument), maybe_comma) => {
|
|
arguments.push_value(argument);
|
|
if let Some(comma) = maybe_comma {
|
|
arguments.push_punct(comma)
|
|
}
|
|
}
|
|
// The input is a standard function parameter with a name and
|
|
// a type (eg. `x: T`). This input needs to be reflected in
|
|
// the delegator signature as is (eg. `x: T`). The identifier
|
|
// also needs to be included in the argument list in part
|
|
// (eg. `x`). The argument list needs to preserve the order of
|
|
// the inputs with relation to arguments (see above), so the
|
|
// parsing is best done here (previously it was done at
|
|
// generation).
|
|
(
|
|
DelegatedInput::Input {
|
|
parameter,
|
|
modifier,
|
|
},
|
|
maybe_comma,
|
|
) => {
|
|
inputs.push_value(parameter.clone());
|
|
if let Some(comma) = maybe_comma {
|
|
inputs.push_punct(comma);
|
|
}
|
|
let maybe_argument = parse_input_into_argument_expression(&ident, ¶meter);
|
|
if let Some(mut argument) = maybe_argument {
|
|
let span = argument.span();
|
|
|
|
if let Some(modifier) = modifier {
|
|
let method_call = |name: &str| {
|
|
syn::Expr::from(ExprMethodCall {
|
|
attrs: vec![],
|
|
receiver: Box::new(argument.clone()),
|
|
dot_token: Default::default(),
|
|
method: Ident::new(name, span),
|
|
turbofish: None,
|
|
paren_token,
|
|
args: Default::default(),
|
|
})
|
|
};
|
|
|
|
let field_call = || {
|
|
syn::Expr::from(ExprField {
|
|
attrs: vec![],
|
|
base: Box::new(argument.clone()),
|
|
dot_token: Default::default(),
|
|
member: syn::Member::Unnamed(0.into()),
|
|
})
|
|
};
|
|
|
|
match modifier {
|
|
ArgumentModifier::Into => {
|
|
argument = method_call("into");
|
|
}
|
|
ArgumentModifier::AsRef => {
|
|
argument = method_call("as_ref");
|
|
}
|
|
ArgumentModifier::Newtype => argument = field_call(),
|
|
}
|
|
}
|
|
|
|
arguments.push(argument);
|
|
if let Some(comma) = maybe_comma {
|
|
arguments.push_punct(comma);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Unchanged from Parse from TraitItemMethod
|
|
let output: syn::ReturnType = input.parse()?;
|
|
let where_clause: Option<syn::WhereClause> = input.parse()?;
|
|
|
|
// This needs to be generated manually, because inputs need to be
|
|
// separated into actual inputs that go in the signature (the
|
|
// parameters) and the additional expressions in square brackets which
|
|
// go into the arguments vector (artguments of the call on the method
|
|
// on the inner object).
|
|
let signature = syn::Signature {
|
|
constness,
|
|
asyncness,
|
|
unsafety,
|
|
abi,
|
|
fn_token,
|
|
ident,
|
|
paren_token,
|
|
inputs,
|
|
output,
|
|
variadic: None,
|
|
generics: syn::Generics {
|
|
where_clause,
|
|
..generics
|
|
},
|
|
};
|
|
|
|
// Check if the input contains a semicolon or a brace. If it contains
|
|
// a semicolon, we parse it (to retain token location information) and
|
|
// continue. However, if it contains a brace, this indicates that
|
|
// there is a default definition of the method. This is not supported,
|
|
// so in that case we error out.
|
|
let lookahead = input.lookahead1();
|
|
let semi_token: Option<syn::Token![;]> = if lookahead.peek(syn::Token![;]) {
|
|
Some(input.parse()?)
|
|
} else {
|
|
panic!(
|
|
"Do not include implementation of delegated functions ({})",
|
|
signature.ident
|
|
);
|
|
};
|
|
|
|
// This needs to be populated from scratch because of the signature above.
|
|
let method = syn::TraitItemFn {
|
|
// All attributes are attached to `DelegatedMethod`, since they
|
|
// presumably pertain to the process of delegation, not the
|
|
// signature of the delegator.
|
|
attrs: Vec::new(),
|
|
sig: signature,
|
|
default: None,
|
|
semi_token,
|
|
};
|
|
|
|
Ok(DelegatedMethod {
|
|
method,
|
|
attributes,
|
|
visibility,
|
|
arguments,
|
|
})
|
|
}
|
|
}
|
|
|
|
struct DelegatedSegment {
|
|
delegator: syn::Expr,
|
|
methods: Vec<DelegatedMethod>,
|
|
segment_attrs: SegmentAttributes,
|
|
}
|
|
|
|
impl syn::parse::Parse for DelegatedSegment {
|
|
fn parse(input: ParseStream) -> Result<Self, Error> {
|
|
let attributes = input.call(tolerant_outer_attributes)?;
|
|
let segment_attrs = parse_segment_attributes(&attributes);
|
|
|
|
if let Ok(keyword) = input.parse::<kw::target>() {
|
|
return Err(Error::new(keyword.span(), "You are using the old `target` expression, which is deprecated. Please replace `target` with `to`."));
|
|
} else {
|
|
input.parse::<kw::to>()?;
|
|
}
|
|
|
|
syn::Expr::parse_without_eager_brace(input).and_then(|delegator| {
|
|
let content;
|
|
syn::braced!(content in input);
|
|
|
|
let mut methods = vec![];
|
|
while !content.is_empty() {
|
|
methods.push(
|
|
content
|
|
.parse::<DelegatedMethod>()
|
|
.expect("Cannot parse delegated method"),
|
|
);
|
|
}
|
|
|
|
Ok(DelegatedSegment {
|
|
delegator,
|
|
methods,
|
|
segment_attrs,
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
struct DelegationBlock {
|
|
segments: Vec<DelegatedSegment>,
|
|
}
|
|
|
|
impl syn::parse::Parse for DelegationBlock {
|
|
fn parse(input: ParseStream) -> Result<Self, Error> {
|
|
let mut segments = vec![];
|
|
while !input.is_empty() {
|
|
segments.push(input.parse()?);
|
|
}
|
|
|
|
Ok(DelegationBlock { segments })
|
|
}
|
|
}
|
|
|
|
/// Returns true if there are any `inline` attributes in the input.
|
|
fn has_inline_attribute(attrs: &[&syn::Attribute]) -> bool {
|
|
attrs.iter().any(|attr| {
|
|
if let syn::AttrStyle::Outer = attr.style {
|
|
attr.path().is_ident("inline")
|
|
} else {
|
|
false
|
|
}
|
|
})
|
|
}
|
|
|
|
struct MatchVisitor<F>(F);
|
|
|
|
impl<F: Fn(&Expr) -> proc_macro2::TokenStream> VisitMut for MatchVisitor<F> {
|
|
fn visit_arm_mut(&mut self, arm: &mut syn::Arm) {
|
|
let transformed = self.0(&arm.body);
|
|
arm.body = parse_quote!(#transformed);
|
|
}
|
|
}
|
|
|
|
#[proc_macro]
|
|
pub fn delegate(tokens: TokenStream) -> TokenStream {
|
|
let block: DelegationBlock = syn::parse_macro_input!(tokens);
|
|
let sections = block.segments.iter().map(|delegator| {
|
|
let delegated_expr = &delegator.delegator;
|
|
let functions = delegator.methods.iter().map(|method| {
|
|
let input = &method.method;
|
|
let mut signature = input.sig.clone();
|
|
if let Expr::Closure(closure) = delegated_expr {
|
|
let additional_inputs: Vec<FnArg> = closure
|
|
.inputs
|
|
.iter()
|
|
.map(|input| {
|
|
if let syn::Pat::Type(pat_type) = input {
|
|
syn::parse_quote!(#pat_type)
|
|
} else {
|
|
panic!(
|
|
"Use a type pattern (`a: u32`) for delegation closure arguments"
|
|
);
|
|
}
|
|
})
|
|
.collect();
|
|
let mut origin_inputs = mem::take(&mut signature.inputs).into_iter();
|
|
// When delegating methods, `first_input` should be self or similar receivers
|
|
// Then we need to move it to first
|
|
// When delegating associated methods, it may be a trivial argument or does not even exist
|
|
// We just keep the origin order.
|
|
let first_input = origin_inputs.next();
|
|
match first_input {
|
|
Some(FnArg::Receiver(receiver)) => {
|
|
signature.inputs.push(FnArg::Receiver(receiver));
|
|
signature.inputs.extend(additional_inputs);
|
|
}
|
|
Some(first_input) => {
|
|
signature.inputs.extend(additional_inputs);
|
|
signature.inputs.push(first_input);
|
|
}
|
|
_ => {
|
|
signature.inputs.extend(additional_inputs);
|
|
}
|
|
}
|
|
signature.inputs.extend(origin_inputs);
|
|
}
|
|
let attributes = parse_method_attributes(&method.attributes, input);
|
|
let attributes = combine_attributes(attributes, &delegator.segment_attrs);
|
|
if input.default.is_some() {
|
|
panic!(
|
|
"Do not include implementation of delegated functions ({})",
|
|
signature.ident
|
|
);
|
|
}
|
|
|
|
// Generate an argument vector from Punctuated list.
|
|
let args: Vec<Expr> = method.arguments.clone().into_iter().collect();
|
|
|
|
// Get name (or index) of the target method or field
|
|
let name = match &attributes.target_specifier {
|
|
Some(target) => target.get_member(&input.sig.ident),
|
|
None => input.sig.ident.clone().into(),
|
|
};
|
|
|
|
let inline = if has_inline_attribute(&attributes.attributes) {
|
|
quote!()
|
|
} else {
|
|
quote! { #[inline] }
|
|
};
|
|
let visibility = &method.visibility;
|
|
|
|
let is_method = method.method.sig.receiver().is_some();
|
|
let associated_const = &attributes.associated_constant;
|
|
let expr_attr = &attributes.expr_attr;
|
|
|
|
// Use the body of a closure (like `|k: u32| <body>`) as the delegation expression
|
|
let delegated_body = if let Expr::Closure(closure) = delegated_expr {
|
|
&closure.body
|
|
} else {
|
|
delegated_expr
|
|
};
|
|
|
|
let span = input.span();
|
|
let generate_await = attributes
|
|
.generate_await
|
|
.unwrap_or_else(|| method.method.sig.asyncness.is_some());
|
|
|
|
// fn method<'a, A, B> -> method::<A, B>
|
|
let generic_params = &method.method.sig.generics.params;
|
|
let generics = if generic_params.is_empty() {
|
|
quote::quote! {}
|
|
} else {
|
|
let span = generic_params.span();
|
|
let mut params: syn::punctuated::Punctuated<
|
|
proc_macro2::TokenStream,
|
|
syn::Token![,],
|
|
> = syn::punctuated::Punctuated::new();
|
|
for param in generic_params.iter() {
|
|
let token = match param {
|
|
GenericParam::Lifetime(_) => {
|
|
// Do not pass lifetimes to generic arguments explicitly to avoid
|
|
// things like https://doc.rust-lang.org/error_codes/E0794.html
|
|
// See https://github.com/Kobzol/rust-delegate/issues/85.
|
|
continue;
|
|
}
|
|
GenericParam::Type(t) => {
|
|
let token = &t.ident;
|
|
let span = t.span();
|
|
quote::quote_spanned! {span=> #token }
|
|
}
|
|
GenericParam::Const(c) => {
|
|
let token = &c.ident;
|
|
let span = c.span();
|
|
quote::quote_spanned! {span=> #token }
|
|
}
|
|
};
|
|
params.push(token);
|
|
}
|
|
quote::quote_spanned! {span=> ::<#params> }
|
|
};
|
|
|
|
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 {
|
|
match &attributes.target_specifier {
|
|
None | Some(TargetSpecifier::Method(_)) => {
|
|
quote::quote! { #expr.#name#generics(#(#args),*) }
|
|
}
|
|
Some(TargetSpecifier::Field(target)) => {
|
|
let reference = target.reference_tokens();
|
|
quote::quote! { #reference#expr.#name }
|
|
}
|
|
}
|
|
} else {
|
|
quote::quote! { #expr::#name#generics(#(#args),*) }
|
|
};
|
|
|
|
let mut body = if generate_await {
|
|
quote::quote! { #body.await }
|
|
} else {
|
|
body
|
|
};
|
|
|
|
for expression in &attributes.expressions {
|
|
match expression {
|
|
ReturnExpression::Into(type_name) => {
|
|
body = match type_name {
|
|
Some(name) => {
|
|
quote::quote! { ::core::convert::Into::<#name>::into(#body) }
|
|
}
|
|
None => quote::quote! { ::core::convert::Into::into(#body) },
|
|
};
|
|
}
|
|
ReturnExpression::TryInto => {
|
|
body = quote::quote! { ::core::convert::TryInto::try_into(#body) };
|
|
}
|
|
ReturnExpression::Unwrap => {
|
|
body = quote::quote! { #body.unwrap() };
|
|
}
|
|
}
|
|
}
|
|
body
|
|
};
|
|
let mut body = if let Expr::Match(expr_match) = delegated_body {
|
|
let mut expr_match = expr_match.clone();
|
|
MatchVisitor(modify_expr).visit_expr_match_mut(&mut expr_match);
|
|
expr_match.into_token_stream()
|
|
} else {
|
|
modify_expr(delegated_body)
|
|
};
|
|
|
|
if let syn::ReturnType::Default = &signature.output {
|
|
body = quote::quote! { #body; };
|
|
};
|
|
|
|
if let Some(expr_template) = expr_attr {
|
|
body = expr_template.expand_template(&body);
|
|
}
|
|
|
|
let attrs = &attributes.attributes;
|
|
quote::quote_spanned! {span=>
|
|
#(#attrs)*
|
|
#inline
|
|
#visibility #signature {
|
|
#body
|
|
}
|
|
}
|
|
});
|
|
|
|
quote! { #(#functions)* }
|
|
});
|
|
|
|
let result = quote! {
|
|
#(#sections)*
|
|
};
|
|
result.into()
|
|
}
|
|
|
|
// we cannot use `Attributes::parse_outer` directly, because it does not allow keywords to appear
|
|
// in meta path positions, i.e., it does not accept `#[await(true)]`.
|
|
// related issue: https://github.com/dtolnay/syn/issues/1458
|
|
fn tolerant_outer_attributes(input: ParseStream) -> syn::Result<Vec<syn::Attribute>> {
|
|
use proc_macro2::{Delimiter, TokenTree};
|
|
use syn::{
|
|
bracketed,
|
|
ext::IdentExt,
|
|
parse::discouraged::Speculative,
|
|
token::{Brace, Bracket, Paren},
|
|
AttrStyle, Attribute, ExprLit, Lit, MacroDelimiter, MetaList, MetaNameValue, Path, Result,
|
|
Token,
|
|
};
|
|
|
|
fn tolerant_attr(input: ParseStream) -> Result<Attribute> {
|
|
let content;
|
|
Ok(Attribute {
|
|
pound_token: input.parse()?,
|
|
style: AttrStyle::Outer,
|
|
bracket_token: bracketed!(content in input),
|
|
meta: content.call(tolerant_meta)?,
|
|
})
|
|
}
|
|
|
|
// adapted from `impl Parse for Meta`
|
|
fn tolerant_meta(input: ParseStream) -> Result<Meta> {
|
|
// Try to parse as Meta
|
|
if let Ok(meta) = input.call(Meta::parse) {
|
|
Ok(meta)
|
|
} else {
|
|
// If it's not possible, try to parse it as any identifier, to support #[await]
|
|
let path = Path::from(input.call(Ident::parse_any)?);
|
|
if input.peek(Paren) || input.peek(Bracket) || input.peek(Brace) {
|
|
// adapted from the private `syn::attr::parse_meta_after_path`
|
|
input.step(|cursor| {
|
|
if let Some((TokenTree::Group(g), rest)) = cursor.token_tree() {
|
|
let span = g.delim_span();
|
|
let delimiter = match g.delimiter() {
|
|
Delimiter::Parenthesis => MacroDelimiter::Paren(Paren(span)),
|
|
Delimiter::Brace => MacroDelimiter::Brace(Brace(span)),
|
|
Delimiter::Bracket => MacroDelimiter::Bracket(Bracket(span)),
|
|
Delimiter::None => {
|
|
return Err(cursor.error("expected delimiter"));
|
|
}
|
|
};
|
|
Ok((
|
|
Meta::List(MetaList {
|
|
path,
|
|
delimiter,
|
|
tokens: g.stream(),
|
|
}),
|
|
rest,
|
|
))
|
|
} else {
|
|
Err(cursor.error("expected delimiter"))
|
|
}
|
|
})
|
|
} else if input.peek(Token![=]) {
|
|
// adapted from the private `syn::attr::parse_meta_name_value_after_path`
|
|
let eq_token = input.parse()?;
|
|
let ahead = input.fork();
|
|
let value = match ahead.parse::<Option<Lit>>()? {
|
|
// this branch is probably for speeding up the parsing for doc comments etc.
|
|
Some(lit) if ahead.is_empty() => {
|
|
input.advance_to(&ahead);
|
|
Expr::Lit(ExprLit {
|
|
attrs: Vec::new(),
|
|
lit,
|
|
})
|
|
}
|
|
_ => input.parse()?,
|
|
};
|
|
Ok(Meta::NameValue(MetaNameValue {
|
|
path,
|
|
eq_token,
|
|
value,
|
|
}))
|
|
} else {
|
|
Ok(Meta::Path(path))
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut attrs = Vec::new();
|
|
while input.peek(Token![#]) {
|
|
attrs.push(input.call(tolerant_attr)?);
|
|
}
|
|
Ok(attrs)
|
|
}
|