1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
//! Inherit methods from a field automatically (via procedural macros).
//!
//! # Motivation
//!
//! While Rust is partially inspired by the object-oriented programming (OOP) paradigm
//! and has some typical OOP features (like objects, encapsulation, and polymorphism),
//! it is not an OOP language. One piece of evidence is the lack of _inheritance_, which an
//! important pillar of OOP. But don't take me wrong: this lack of inheritance is actually a
//! good thing since it promotes the practice of
//! [_composition over inheritance_](https://en.wikipedia.org/wiki/Composition_over_inheritance)
//! in Rust programs. Despite all the benefits of composition, Rust programmers
//! have to write trivial [fowarding methods](https://en.wikipedia.org/wiki/Forwarding_(object-oriented_programming)),
//! which is a tedious task, especially when you have to write many of them.
//!
//! To address this pain point of using composition in Rust, the crate provides a convenient
//! procedure macro that generates forwarding methods automatically for you. In other words,
//! your structs can now "inherit" methods from their fields, enjoying the best of both worlds:
//! the convenience of inheritance and the flexibility of composition.
//!
//! # Examples
//!
//! ## Implementing the new type idiom
//!
//! Suppose that you want to create a new struct named `Stack<T>`, which can be implemented by
//! simply wrapping around `Vec<T>` and exposing only a subset of the APIs of `Vec`. Here is
//! how this crate can help you do it easily.
//!
//! ```rust
//! use inherit_methods_macro::inherit_methods;
//!
//! pub struct Stack<T>(Vec<T>);
//!
//! // Annotate an impl block with #[inherit_methods(from = "...")] to enable automatically
//! // inheriting methods from a field, which is specifiedd by the from attribute.
//! #[inherit_methods(from = "self.0")]
//! // This prevent cargo-fmt from issuing false alarms due to the way that this crate extends
//! // the Rust syntax (i.e., allowing method definitions without code blocks).
//! #[rustfmt::skip]
//! impl<T> Stack<T> {
//! // Normal methods can be implemented with inherited methods in the same impl block.
//! pub fn new() -> Self {
//! Self(Vec::new())
//! }
//!
//! // All methods without code blocks will "inherit" the implementation of Vec by
//! // forwarding their method calls to self.0.
//! pub fn push(&mut self, value: T);
//! pub fn pop(&mut self) -> Option<T>;
//! pub fn len(&self) -> usize;
//! }
//! ```
//!
//! If you want to derive common traits (like `AsRef` and `Deref`) for a wrapper type, check out
//! the [shrinkwraprs](https://crates.io/crates/shrinkwraprs) crate.
//!
//! ## Emulating the classic OOP inheritance
//!
//! In many OOP frameworks or applications, it is useful to have a base class from which all objects
//! inherit. In this example, we would like to do the same thing, creating a base class
//! (the `Object` trait for the interface and the `ObjectBase` struct for the implementation).
//! that all objects should "inherit".
//!
//! ```rust
//! use std::sync::atomic::{AtomicU64, Ordering};
//! use std::sync::Mutex;
//!
//! use inherit_methods_macro::inherit_methods;
//!
//! pub trait Object {
//! fn type_name(&self) -> &'static str;
//! fn object_id(&self) -> u64;
//! fn name(&self) -> String;
//! fn set_name(&self, new_name: String);
//! }
//!
//! struct ObjectBase {
//! object_id: u64,
//! name: Mutex<String>,
//! }
//!
//! impl ObjectBase {
//! pub fn new() -> Self {
//! static NEXT_ID: AtomicU64 = AtomicU64::new(0);
//! Self {
//! object_id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
//! name: Mutex::new(String::new()),
//! }
//! }
//!
//! pub fn object_id(&self) -> u64 {
//! self.object_id
//! }
//!
//! pub fn name(&self) -> String {
//! self.name.lock().unwrap().clone()
//! }
//!
//! pub fn set_name(&self, new_name: String) {
//! *self.name.lock().unwrap() = new_name;
//! }
//! }
//!
//! struct DummyObject {
//! base: ObjectBase,
//! }
//!
//! impl DummyObject {
//! pub fn new() -> Self {
//! Self {
//! base: ObjectBase::new(),
//! }
//! }
//! }
//!
//! #[inherit_methods(from = "self.base")]
//! #[rustfmt::skip]
//! impl Object for DummyObject {
//! // Give this method an implementation specific to this type
//! fn type_name(&self) -> &'static str {
//! "DummyObject"
//! }
//!
//! // Inherit methods from the base class
//! fn object_id(&self) -> u64;
//! fn name(&self) -> String;
//! fn set_name(&self, new_name: String);
//! }
//! ```
// TODO: fix the compatibility issue with cargo-fmt.
extern crate proc_macro;
use darling::FromMeta;
use proc_macro2::{Punct, Spacing, TokenStream};
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
AttributeArgs, Block, Expr, FnArg, Ident, ImplItem, ImplItemMethod, Item, ItemImpl, Pat, Stmt,
};
#[derive(Debug, FromMeta)]
struct MacroAttr {
#[darling(default = "default_from_val")]
from: String,
}
fn default_from_val() -> String {
"self.0".to_string()
}
#[proc_macro_attribute]
pub fn inherit_methods(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let attr = {
let attr_tokens = syn::parse_macro_input!(attr as AttributeArgs);
match MacroAttr::from_list(&attr_tokens) {
Ok(attr) => attr,
Err(e) => {
return e.write_errors().into();
}
}
};
let item_impl = syn::parse_macro_input!(item as syn::ItemImpl);
do_inherit_methods(attr, item_impl).into()
}
fn do_inherit_methods(attr: MacroAttr, mut item_impl: ItemImpl) -> TokenStream {
// Parse the field to which we will forward method calls
let field: Expr = syn::parse_str(&attr.from).unwrap();
// Transform this impl item by adding method forwarding code to inherited methods.
for impl_item in &mut item_impl.items {
let impl_item_method = match is_method_missing_fn_block(impl_item) {
Some(method) => method,
None => continue,
};
add_fn_block(impl_item_method, &field);
}
item_impl.into_token_stream()
}
// Returns whether an item inside `impl XXX { ... }` is a method without code block.
fn is_method_missing_fn_block(impl_item: &mut ImplItem) -> Option<&mut ImplItemMethod> {
// We only care about method items.
let impl_item_method = if let ImplItem::Method(method) = impl_item {
method
} else {
return None;
};
// We only care about methods without a code block.
if !impl_item_method.block.is_empty() {
return None;
}
Some(impl_item_method)
}
// Add a code block of method forwarding for the method item.
fn add_fn_block(impl_item_method: &mut ImplItemMethod, field: &Expr) {
let fn_sig = &impl_item_method.sig;
let fn_name = &fn_sig.ident;
let fn_arg_tokens = {
// Extract all argument idents (except self) from the signature
let fn_arg_idents: Vec<&Ident> = fn_sig
.inputs
.iter()
.filter_map(|fn_arg| match fn_arg {
FnArg::Receiver(_) => None,
FnArg::Typed(pat_type) => Some(pat_type),
})
.filter_map(|pat_type| match &*pat_type.pat {
Pat::Ident(pat_ident) => Some(&pat_ident.ident),
_ => None,
})
.collect();
// Combine all arguments into a comma-separated token stream
let mut fn_arg_tokens = TokenStream::new();
for fn_arg_ident in fn_arg_idents {
let fn_arg_ident = fn_arg_ident.clone();
fn_arg_tokens.append(fn_arg_ident);
fn_arg_tokens.append(Punct::new(',', Spacing::Alone));
}
fn_arg_tokens
};
let new_fn_block: Block = {
let new_fn_tokens = quote! {
// This is the code block added to the incomplete method, which
// is just forwarding the function call to the field.
{
#field.#fn_name(#fn_arg_tokens)
}
};
syn::parse(new_fn_tokens.into()).unwrap()
};
impl_item_method.block = new_fn_block;
}
trait BlockExt {
/// Check if a block is empty, which means only contains a ";".
fn is_empty(&self) -> bool;
}
impl BlockExt for Block {
fn is_empty(&self) -> bool {
if self.stmts.len() == 0 {
return true;
}
if self.stmts.len() > 1 {
return false;
}
if let Stmt::Item(item) = &self.stmts[0] {
if let Item::Verbatim(token_stream) = item {
token_stream.to_string().trim() == ";"
} else {
false
}
} else {
false
}
}
}