├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples └── main.rs └── src ├── lib.rs └── proc_macro ├── Cargo.toml ├── macros.rs └── mod.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | 13 | #Added by cargo 14 | # 15 | #already existing elements are commented out 16 | 17 | /target 18 | #**/*.rs.bk 19 | #Cargo.lock 20 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "inheritance" 3 | version = "0.0.1-alpha.2" 4 | authors = ["Daniel Henry-Mantilla "] 5 | edition = "2018" 6 | 7 | documentation = "https://docs.rs/inheritance" 8 | homepage = "https://crates.io/crates/inheritance" 9 | repository = "https://github.com/danielhenrymantilla/inheritance-rs" 10 | 11 | description = "Avoiding code repetition in Rust with OOP inheritance" 12 | 13 | license = "MIT" 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [dependencies] 18 | 19 | [dependencies.proc_macro] 20 | package = "inheritance-proc-macro" 21 | version = "0.0.1-alpha.2" 22 | path = "src/proc_macro" 23 | 24 | [features] 25 | specialization = ["proc_macro/specialization"] 26 | verbose-expansions = ["proc_macro/verbose-expansions", ] 27 | 28 | external_doc = [] 29 | 30 | default = [] 31 | 32 | [package.metadata.docs.rs] 33 | features = ["external_doc"] 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Daniel Henry-Mantilla 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `::inheritance` 2 | 3 | This (experimental) crate provides procedural macros to get "inheritance-like" 4 | behavior by **deriving delegation from composition**. 5 | 6 | [![Repository](https://img.shields.io/badge/repository-GitHub-brightgreen.svg)](https://github.com/danielhenrymantilla/inheritance-rs) 7 | [![Latest version](https://img.shields.io/crates/v/inheritance.svg)](https://crates.io/crates/inheritance) 8 | [![Documentation](https://docs.rs/inheritance/badge.svg)](https://docs.rs/inheritance) 9 | [![License](https://img.shields.io/crates/l/inheritance.svg)](https://github.com/danielhenrymantilla/inheritance-rs/blob/master/LICENSE) 10 | 11 | 12 | ## Presentation 13 | 14 | Imagine having some `Point` type and some behavior / associated methods: 15 | 16 | ```rust 17 | #[derive(Debug, Clone, Copy, PartialEq)] 18 | struct Point { 19 | x: f32, 20 | 21 | y: f32, 22 | } 23 | 24 | impl Point { 25 | fn x (self: &'_ Self) -> f32 26 | { 27 | self.x 28 | } 29 | 30 | fn y (self: &'_ Self) -> f32 31 | { 32 | self.y 33 | } 34 | 35 | fn name (self: &'_ Self) -> Option<&'_ str> 36 | { 37 | Some("Point") 38 | } 39 | } 40 | ``` 41 | 42 | Now imagine you want to have a new "`Point`-like" type, but with extra 43 | attributes (and maybe some overriden behavior): 44 | 45 | ```rust 46 | # struct Point; 47 | struct NamedPoint { 48 | name: String, 49 | 50 | point: Point, 51 | } 52 | ``` 53 | 54 | First of all, being "`Point`-like" requires abstracting the inherent methods 55 | under a trait: 56 | 57 | ```rust 58 | #[derive(Debug, Clone, Copy, PartialEq)] 59 | struct Point { 60 | x: f32, 61 | 62 | y: f32, 63 | } 64 | 65 | trait IsPoint { 66 | fn x (self: &'_ Self) -> f32; 67 | 68 | fn y (self: &'_ Self) -> f32; 69 | 70 | fn name (self: &'_ Self) -> Option<&'_ str>; 71 | } 72 | 73 | impl IsPoint for Point { 74 | fn x (self: &'_ Self) -> f32 75 | { 76 | self.x 77 | } 78 | 79 | fn y (self: &'_ Self) -> f32 80 | { 81 | self.y 82 | } 83 | 84 | fn name (self: &'_ Self) -> Option<&'_ str> 85 | { 86 | Some("Point") 87 | } 88 | } 89 | ``` 90 | 91 | Now we'd like `NamedPoint` to implement `IsPoint`: 92 | 93 | ```rust 94 | # #[derive(Debug,Clone,Copy,PartialEq)]struct Point{x:f32, y:f32}trait IsPoint{fn x(&self)->f32;fn y(&self)->f32;fn name(&self)->Option<&str>;}impl IsPoint for Point{fn x(&self)->f32{self.x}fn y(&self)->f32{self.y}fn name(&self)->Option<&str>{Some("Point")}} 95 | struct NamedPoint { 96 | name: String, 97 | 98 | point: Point, 99 | } 100 | 101 | impl IsPoint for NamedPoint { 102 | #[inline] 103 | fn x (self: &'_ Self) -> f32 104 | { 105 | self.point.x() 106 | } 107 | 108 | #[inline] 109 | fn y (self: &'_ Self) -> f32 110 | { 111 | self.point.y() 112 | } 113 | 114 | #[inline] 115 | fn name (self: &'_ Self) -> Option<&'_ str> 116 | { 117 | self.point.name() 118 | } 119 | } 120 | ``` 121 | 122 | This leads to writing very repetitive and uninteresting (delegation) code... 123 | 124 | ## Enter `::inheritance` 125 | 126 | This last implementation can be completely skipped by slapping a 127 | `#[inheritable]` attribute on the `IsPoint` trait, and a `Inheritance` derive 128 | on the `NamedPoint` struct: 129 | 130 | ```rust 131 | use ::inheritance::{inheritable, Inheritance}; 132 | 133 | #[derive(Debug, Clone, Copy, PartialEq)] 134 | struct Point { 135 | x: f32, 136 | 137 | y: f32, 138 | } 139 | 140 | #[inheritable] 141 | trait IsPoint { 142 | fn x (self: &'_ Self) -> f32; 143 | 144 | fn y (self: &'_ Self) -> f32; 145 | 146 | fn name (self: &'_ Self) -> Option<&'_ str>; 147 | } 148 | 149 | impl IsPoint for Point { 150 | fn x (self: &'_ Self) -> f32 151 | { 152 | self.x 153 | } 154 | 155 | fn y (self: &'_ Self) -> f32 156 | { 157 | self.y 158 | } 159 | 160 | fn name (self: &'_ Self) -> Option<&'_ str> 161 | { 162 | Some("Point") 163 | } 164 | } 165 | 166 | #[derive(Inheritance)] 167 | struct NamedPoint { 168 | name: String, 169 | 170 | #[inherits(IsPoint)] 171 | point: Point, 172 | } 173 | ``` 174 | 175 | ### `nightly` Rust 176 | 177 | This feature is especially interesting with the `specialization` feature, which 178 | you can already use in nightly. 179 | 180 | When depending on the `inheritance` crate in your `Cargo.toml`, you can specify 181 | that you want to use this feature: 182 | 183 | ```toml 184 | [dependencies] 185 | inheritance = { version = "...", features = ["specialization"] } 186 | ``` 187 | 188 | You will then be able to override some of the auto-generated delegation methods: 189 | 190 | ```rust 191 | # use::inheritance::{inheritable, Inheritance};#[derive(Debug,Clone,Copy,PartialEq)]struct Point{x:f32, y:f32}#[inheritable]trait IsPoint{fn x(&self)->f32;fn y(&self)->f32;fn name(&self)->Option<&str>;}impl IsPoint for Point{fn x(&self)->f32{self.x}fn y(&self)->f32{self.y}fn name(&self)->Option<&str>{Some("Point")}} 192 | #[derive(Inheritance)] 193 | struct NamedPoint { 194 | name: String, 195 | 196 | #[inherits(IsPoint)] 197 | point: Point, 198 | } 199 | 200 | # #[cfg(feature = "specialization")] 201 | impl IsPoint for NamedPoint { 202 | fn name (self: &'_ Self) -> Option<&'_ str> 203 | { 204 | Some(&*self.name) 205 | } 206 | } 207 | ``` 208 | 209 | ## Going further 210 | 211 | #### Newtypes 212 | 213 | The `#[inherits(...)]` field attribute can be used on tuple structs fields, 214 | which makes it the perfect tool for newtypes: 215 | 216 | ```rust 217 | # use::inheritance::{inheritable, Inheritance};#[derive(Debug,Clone,Copy,PartialEq)]struct Point{x:f32, y:f32}#[inheritable]trait IsPoint{fn x(&self)->f32;fn y(&self)->f32;fn name(&self)->Option<&str>;}impl IsPoint for Point{fn x(&self)->f32{self.x}fn y(&self)->f32{self.y}fn name(&self)->Option<&str>{Some("Point")}} 218 | #[derive(Inheritance)] 219 | struct AnonymousPoint ( 220 | #[inherits(IsPoint)] 221 | Point, 222 | ); 223 | 224 | # #[cfg(feature = "specialization")] 225 | impl IsPoint for AnonymousPoint { 226 | fn name (self: &'_ Self) -> Option<&'_ str> 227 | { 228 | None 229 | } 230 | } 231 | ``` 232 | 233 | #### Multiple "inheritance" 234 | 235 | A single struct with a `#[derive(Inheritance)]` on it can have multiple 236 | `#[inherits(...)]` on either the same field or multiple distinct fields, since 237 | the implementation of each "inherited" trait will just be delegating the 238 | methods of that trait onto the decorated field. If you try to "inherit" the same 239 | trait from multiple fields, then classic coherence rules will prevent it: 240 | 241 | ```rust 242 | # use::inheritance::{inheritable, Inheritance};#[derive(Debug,Clone,Copy,PartialEq)]struct Point{x:f32, y:f32}#[inheritable]trait IsPoint{fn x(&self)->f32;fn y(&self)->f32;fn name(&self)->Option<&str>;}impl IsPoint for Point{fn x(&self)->f32{self.x}fn y(&self)->f32{self.y}fn name(&self)->Option<&str>{Some("Point")}} 243 | #[derive(Debug, Clone, Copy, PartialEq)] 244 | enum Color { 245 | Red, 246 | Green, 247 | Blue, 248 | } 249 | 250 | #[inheritable] 251 | trait Colored { 252 | fn color (self: &'_ Self) -> Color; 253 | } 254 | 255 | impl Colored for Color { 256 | #[inline] 257 | fn color (self: &'_ Self) -> Color 258 | { 259 | *self 260 | } 261 | } 262 | 263 | #[derive(Inheritance)] 264 | struct ColoredPoint { 265 | #[inherits(IsPoint)] 266 | point: Point, 267 | 268 | #[inherits(Colored)] 269 | pigments: Color, 270 | } 271 | ``` 272 | 273 | #### Virtual dispatch 274 | 275 | The idea behind "virtual" methods, _à la_ C++, is that objects _always_ always 276 | carry a vtable with _some_ methods in it, the ones marked `virtual` (and with 277 | the other methods _never_ in it). In Rust, however, "objects" _sometimes_ carry 278 | a vtable (_i.e._, `struct`s and `enum`s do not, but trait objects (`dyn Trait`) 279 | do), and this vtable contains _all_ the methods of the trait and its 280 | supertraits: 281 | 282 | Imagine having a `.present()` method whose body calls `.name()`, and where the 283 | behavior / value returned by the `.name()` call changes for each different 284 | `Point`. 285 | 286 | 287 | ```rust,ignore 288 | /// somewhere in the code 289 | fn present (self: &'_ Self) -> Cow<'static, str> 290 | // where Self : IsPoint 291 | { 292 | if let Some(name) = self.name() { 293 | Cow::from(format!("{}({}, {})", name, self.x(), self.y())) 294 | } else { 295 | Cow::from("") 296 | } 297 | } 298 | 299 | let point = Point { x: 42., y: 27. }; 300 | assert_eq!( 301 | &point.present() as &str, 302 | "Point(42.0, 27.0)" 303 | ); 304 | 305 | let anonymous_point = AnonymousPoint(point); 306 | assert_eq!( 307 | &anonymous_point.present() as &str, 308 | "" 309 | ); 310 | 311 | let named_point = NamedPoint { name: "Carré", point }; 312 | assert_eq!( 313 | &named_point.present() as &str, 314 | "Carré(42.0, 27.0)" 315 | ); 316 | ``` 317 | 318 | - In a language such as C++, this can be achieved by tagging the `.name()` 319 | method as `virtual`, and by then having `.present()` be a method of the 320 | base class (not necessarily `virtual` itself). 321 | 322 | - In Rust the above strategy cannot be done; one must use a function / method 323 | polymorphic over implementors of the `IsPoint` type, using: 324 | 325 | - dynamic dispatch (the mechanism in C++ behind `virtual` methods): 326 | 327 | ```rust 328 | # use::inheritance::{inheritable, Inheritance};#[derive(Debug,Clone,Copy,PartialEq)]struct Point{x:f32, y:f32}#[inheritable]trait IsPoint{fn x(&self)->f32;fn y(&self)->f32;fn name(&self)->Option<&str>;}impl IsPoint for Point{fn x(&self)->f32{self.x}fn y(&self)->f32{self.y}fn name(&self)->Option<&str>{Some("Point")}}use::std::borrow::Cow; 329 | impl dyn IsPoint + '_ { 330 | fn present (self: &'_ Self) -> Cow<'static, str> 331 | { 332 | if let Some(name) = self.name() { 333 | Cow::from(format!("{}({}, {})", name, self.x(), self.y())) 334 | } else { 335 | Cow::from("") 336 | } 337 | } 338 | } 339 | ``` 340 | 341 | - static dispatch; then getting method-like syntax requires a helper 342 | trait: 343 | 344 | ```rust 345 | # use::inheritance::{inheritable, Inheritance};#[derive(Debug,Clone,Copy,PartialEq)]struct Point{x:f32, y:f32}#[inheritable]trait IsPoint{fn x(&self)->f32;fn y(&self)->f32;fn name(&self)->Option<&str>;}impl IsPoint for Point{fn x(&self)->f32{self.x}fn y(&self)->f32{self.y}fn name(&self)->Option<&str>{Some("Point")}}use::std::borrow::Cow; 346 | trait Present 347 | where 348 | Self : IsPoint, 349 | { 350 | fn present (self: &'_ Self) -> Cow<'static, str> 351 | { 352 | if let Some(name) = self.name() { 353 | Cow::from(format!("{}({}, {})", name, self.x(), self.y())) 354 | } else { 355 | Cow::from("") 356 | } 357 | } 358 | } 359 | impl Present for T 360 | where 361 | T : IsPoint, 362 | {} 363 | ``` 364 | 365 | Currently the macro does not target full-featured "virtual" methods, so 366 | _overriding_ `.present()` itself is not yet possible. 367 | 368 | Since this is an experimental crate, I will try to explore that possibility... 369 | -------------------------------------------------------------------------------- /examples/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "specialization", 2 | feature(specialization), 3 | )] 4 | #![allow(unused)]#![warn(unused_must_use)] 5 | 6 | #[macro_use] 7 | extern crate inheritance; 8 | 9 | #[inheritable] 10 | trait IsPoint { 11 | fn x (self: &'_ Self) 12 | -> f32 13 | ; 14 | 15 | fn y (self: &'_ Self) 16 | -> f32 17 | ; 18 | 19 | fn name (self: &'_ Self) 20 | -> String 21 | { 22 | format!("Point({x}, {y})", x = self.x(), y = self.y()) 23 | } 24 | } 25 | 26 | struct Point { 27 | x: f32, 28 | y: f32, 29 | } 30 | 31 | impl IsPoint for Point { 32 | #[inline] 33 | fn x (self: &'_ Self) -> f32 34 | { 35 | self.x 36 | } 37 | 38 | #[inline] 39 | fn y (self: &'_ Self) -> f32 40 | { 41 | self.y 42 | } 43 | } 44 | 45 | #[derive(Inheritance)] 46 | struct NewPoint (#[inherits(IsPoint)] Point); 47 | 48 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 49 | enum Color { 50 | Red, 51 | Green, 52 | Blue, 53 | } 54 | 55 | #[inheritable] 56 | trait Colored { 57 | fn color (self: &'_ Self) 58 | -> Color 59 | ; 60 | } 61 | 62 | impl Colored for Color { 63 | #[inline] 64 | fn color (self: &'_ Self) 65 | -> Color 66 | { 67 | *self 68 | } 69 | } 70 | 71 | #[derive(Inheritance)] 72 | struct NamedPoint { 73 | #[inherits(IsPoint)] 74 | point: Point, 75 | 76 | name: &'static str, 77 | 78 | #[inherits(Colored)] 79 | pigments: Color, 80 | } 81 | 82 | fn main () 83 | { 84 | let point = NamedPoint { 85 | point: Point { x: 42., y: 27. }, 86 | name: "Carré", 87 | pigments: Color::Red, 88 | }; 89 | dbg!(point.color()); 90 | 91 | #[cfg(not(feature = "specialization"))] { 92 | dbg!(point.name()); 93 | } 94 | #[cfg(feature = "specialization")] { 95 | // With the (nightly) specialization feature we can override the 96 | // 97 | impl IsPoint for NamedPoint { 98 | fn name (self: &'_ Self) -> String 99 | { 100 | format!("{} named {}", self.point.name(), self.name) 101 | } 102 | 103 | /// This, however, does not change the value returned by 104 | /// `self.point.name()` (obviously?) 105 | fn x (self: &'_ Self) -> f32 106 | { 107 | -1. 108 | } 109 | } 110 | dbg!(point.name()); 111 | // Access the "parent" / non-specialized `impl` through an explicit delegation 112 | dbg!(point.point.name()); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "external_doc", 2 | feature(external_doc), 3 | doc(include = "../README.md"), 4 | )] 5 | 6 | pub use proc_macro::*; #[cfg(any())] 7 | mod proc_macro; 8 | -------------------------------------------------------------------------------- /src/proc_macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [lib] 2 | proc-macro = true 3 | path = "mod.rs" 4 | 5 | [package] 6 | name = "inheritance-proc-macro" 7 | version = "0.0.1-alpha.2" 8 | authors = ["Daniel Henry-Mantilla "] 9 | edition = "2018" 10 | 11 | description = "Avoiding code repetition in Rust with OOP inheritance" 12 | 13 | license = "MIT" 14 | 15 | [dependencies] 16 | fstrings = "0.1.4" 17 | proc-macro2 = "1.0.5" 18 | proc-macro-crate = "0.1.4" 19 | quote = "1.0.2" 20 | syn = { version = "1.0.5", features = ["full", ] } 21 | 22 | [features] 23 | specialization = [] 24 | verbose-expansions = ["syn/extra-traits", ] 25 | 26 | default = [] 27 | -------------------------------------------------------------------------------- /src/proc_macro/macros.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "verbose-expansions"))] 2 | macro_rules! debug {( 3 | $($tt:tt)* 4 | ) => ( 5 | )} 6 | #[cfg(feature = "verbose-expansions")] 7 | macro_rules! debug {( 8 | $($tt:tt)* 9 | ) => ( 10 | eprintln!($($tt)*) 11 | )} 12 | 13 | macro_rules! error { 14 | ( 15 | $span:expr, $msg:expr $(,)? 16 | ) => ({ 17 | let span: Span = $span; 18 | let msg = LitStr::new(&$msg, span); 19 | return TokenStream::from(quote_spanned! {span => 20 | compile_error!(#msg); 21 | }); 22 | }); 23 | 24 | ( 25 | $msg:expr $(,)? 26 | ) => ( 27 | error! { Span::call_site()=> 28 | $msg 29 | } 30 | ); 31 | } 32 | 33 | macro_rules! parse_error { 34 | ( 35 | $span:expr, $msg:expr $(,)? 36 | ) => ( 37 | return Err(Error::new($span, $msg)); 38 | ); 39 | 40 | ( 41 | $msg:expr $(,)? 42 | ) => ( 43 | parse_error! { Span::call_site()=> 44 | $msg 45 | } 46 | ); 47 | } 48 | 49 | macro_rules! set_output { 50 | (@with_dollar![$dol:tt] 51 | $render:ident => $ret:ident 52 | ) => ( 53 | #[allow(unused_mut)] 54 | let mut $ret = TokenStream::new(); 55 | 56 | macro_rules! $render { 57 | ($dol span:expr => 58 | $dol($dol tt:tt)* 59 | ) => ({ 60 | let span: Span = $dol span; 61 | $ret.extend(TokenStream::from(quote_spanned! { span=> 62 | $dol($dol tt)* 63 | })); 64 | }); 65 | 66 | ( 67 | $dol($dol tt:tt)* 68 | ) => ($render! { Span::call_site() => 69 | $dol($dol tt)* 70 | }); 71 | } 72 | 73 | ); 74 | 75 | ( 76 | $($tt:tt)* 77 | ) => (set_output!(@with_dollar![$] 78 | $($tt)* 79 | )); 80 | } 81 | -------------------------------------------------------------------------------- /src/proc_macro/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case, unused_imports)] 2 | 3 | extern crate proc_macro; 4 | 5 | #[macro_use] 6 | extern crate fstrings; 7 | 8 | use ::proc_macro::{ 9 | TokenStream, 10 | }; 11 | use ::proc_macro2::{ 12 | Span, 13 | TokenStream as TokenStream2, 14 | }; 15 | use ::quote::{ 16 | quote, 17 | quote_spanned, 18 | }; 19 | use ::syn::{*, 20 | parse::{ 21 | Parse, 22 | ParseStream, 23 | }, 24 | spanned::Spanned, 25 | }; 26 | use ::std::{*, 27 | convert::{TryInto, TryFrom}, 28 | iter::FromIterator, 29 | result::Result, 30 | }; 31 | 32 | #[macro_use] 33 | mod macros; 34 | 35 | #[inline] 36 | fn take (x: &'_ mut T) 37 | -> T 38 | { 39 | mem::replace(x, T::default()) 40 | } 41 | 42 | #[proc_macro_attribute] pub 43 | fn inheritable (params: TokenStream, input: TokenStream) 44 | -> TokenStream 45 | { 46 | // === Parse / extraction logic === 47 | #[cfg_attr(feature = "verbose-expansions", 48 | derive(Debug), 49 | )] 50 | #[allow(dead_code)] // dumb compiler... 51 | struct Trait { 52 | ident: Ident, 53 | methods: Vec, 54 | } 55 | 56 | impl Parse for Trait { 57 | fn parse (input: ParseStream) -> syn::Result 58 | {Ok({ 59 | let ItemTrait { 60 | ident, 61 | items, 62 | generics, 63 | .. 64 | } = input.parse()? 65 | ; 66 | match ( 67 | generics.type_params().next(), 68 | generics.lifetimes().next(), 69 | generics.const_params().next(), 70 | ) 71 | { 72 | | (None, None, None) => {}, 73 | 74 | | _ => parse_error!( 75 | generics.span(), 76 | "Trait generics are not supported (yet)", 77 | ), 78 | } 79 | let methods: Vec = 80 | items 81 | .into_iter() 82 | // error on non-function items 83 | .map(|trait_item| match trait_item { 84 | | TraitItem::Method(method) => Ok(method), 85 | | _ => parse_error!( 86 | trait_item.span(), 87 | "`#[inheritable]` currently only supports methods" 88 | ), 89 | }) 90 | // error on non-method functions 91 | .map(|x| x.and_then(|method| { 92 | let ref sig = method.sig; 93 | let mut span = sig.ident.span(); 94 | match sig.inputs.iter().next() { 95 | // & [mut] self 96 | | Some(&FnArg::Receiver(Receiver { 97 | reference: Some(_), 98 | .. 99 | })) 100 | => {}, 101 | 102 | // self: & [mut] _ 103 | | Some(&FnArg::Typed(PatType { 104 | ref pat, 105 | ref ty, 106 | .. 107 | })) 108 | if match (&**pat, &**ty) { 109 | | ( 110 | &Pat::Ident(PatIdent { ref ident, .. }), 111 | &Type::Reference(_), 112 | ) => { 113 | ident == "self" 114 | }, 115 | 116 | | _ => false, 117 | } 118 | => {}, 119 | 120 | // otherwise 121 | | opt_arg => { 122 | if let Some(arg) = opt_arg { 123 | span = arg.span(); 124 | } 125 | parse_error!(span, concat!( 126 | "associated function requires a ", 127 | "`&self` or `&mut self` receiver", 128 | )); 129 | }, 130 | } 131 | Ok(method) 132 | })) 133 | .collect::>()? 134 | ; 135 | Self { 136 | ident, 137 | methods, 138 | } 139 | })} 140 | } 141 | 142 | 143 | set_output!( render => ret ); 144 | 145 | debug!(concat!( 146 | "-------------------------\n", 147 | "#[inheritable({params})]\n", 148 | "{input}\n", 149 | ), params=params, input=input); 150 | 151 | 152 | // === This macro does not expect params === 153 | let params = TokenStream2::from(params); 154 | if params.clone().into_iter().next().is_some() { 155 | error!(params.span(), "Unexpected parameter(s)"); 156 | } 157 | 158 | // === Parse the input === 159 | let Trait { 160 | ident: Trait, 161 | mut methods, 162 | } = { 163 | let input = input.clone(); 164 | parse_macro_input!(input) 165 | }; 166 | 167 | 168 | // === Render the decorated trait itself (as is) === 169 | ret.extend(input); 170 | 171 | 172 | // === Render the helper `Inherits#Trait` trait === 173 | let InheritsTrait = Ident::new(&f!( 174 | "__Inherits{Trait}__" 175 | ), Span::call_site()); 176 | 177 | ret.extend({ 178 | // Due to a bug in `quote!`, we need to render 179 | // `#[doc(hidden)]` 180 | // manually 181 | use ::proc_macro::*; 182 | iter::once(TokenTree::Punct(Punct::new( 183 | '#', 184 | Spacing::Alone))).chain(TokenStream::from(::quote::quote! { 185 | [doc(hidden)] 186 | })) 187 | }); 188 | render! { 189 | pub(in crate) 190 | trait #InheritsTrait { 191 | type __Parent__ 192 | : #Trait 193 | ; 194 | fn __parent__ (self: &'_ Self) 195 | -> &'_ Self::__Parent__ 196 | ; 197 | fn __parent_mut__ (self: &'_ mut Self) 198 | -> &'_ mut Self::__Parent__ 199 | ; 200 | } 201 | }; 202 | 203 | 204 | // === Render the default impl of `Trait` for `InheritsTrait` implemetors === 205 | methods 206 | .iter_mut() 207 | .for_each(|method| { 208 | let &mut TraitItemMethod { 209 | sig: Signature { 210 | ref ident, 211 | ref generics, 212 | ref mut inputs, 213 | .. 214 | }, 215 | ref mut default, 216 | ref mut semi_token, 217 | ref mut attrs, 218 | } = method; 219 | *attrs = vec![]; 220 | *semi_token = None; 221 | let mut args: Vec = 222 | Vec::with_capacity( 223 | inputs 224 | .len() 225 | .saturating_sub(1) 226 | ) 227 | ; 228 | let mut inputs_iter = take(inputs).into_iter(); 229 | let mut parent_mb_mut = TokenStream2::default(); 230 | *inputs = 231 | inputs_iter 232 | .next() 233 | .map(|first_arg| { 234 | if match first_arg { 235 | | FnArg::Receiver(Receiver { 236 | ref mutability, 237 | .. 238 | }) => { 239 | mutability.is_some() 240 | }, 241 | // with box patterns we'd be able to merge both cases... 242 | | FnArg::Typed(PatType { ref ty, .. }) => { 243 | match &**ty { 244 | | &Type::Reference(TypeReference { 245 | ref mutability, 246 | .. 247 | }) => { 248 | mutability.is_some() 249 | }, 250 | 251 | | _ => unreachable!(), 252 | } 253 | }, 254 | } { 255 | parent_mb_mut = quote!( __parent_mut__ ); 256 | } else { 257 | parent_mb_mut = quote!( __parent__ ); 258 | } 259 | first_arg 260 | }) 261 | .into_iter() 262 | .chain( 263 | inputs_iter 264 | .zip(1 ..) 265 | .map(|(mut arg, i)| match arg { 266 | | FnArg::Typed(PatType { ref mut pat, .. }) => { 267 | let ident = Ident::new(&f!( 268 | "arg_{i}" 269 | ), Span::call_site()); 270 | *pat = parse_quote! { 271 | #ident 272 | }; 273 | args.push(ident); 274 | arg 275 | }, 276 | 277 | | _ => unreachable!("Invalid method signature"), 278 | }) 279 | ) 280 | .collect() 281 | ; 282 | let generics = generics.split_for_impl().1; 283 | let generics = generics.as_turbofish(); 284 | // method body 285 | *default = Some(parse_quote! { 286 | { 287 | /* 100% guaranteed unambiguous version */ 288 | // < 289 | // ::__Parent__ 290 | // as #Trait 291 | // >::#ident #generics ( 292 | // #InheritsTrait :: #parent_mb_mut(self), 293 | // #(#args),* 294 | // ) 295 | /* This should nevertheless also be unambiguous */ 296 | self.#parent_mb_mut() 297 | .#ident #generics ( 298 | #(#args),* 299 | ) 300 | } 301 | }); 302 | }) 303 | ; 304 | let default_if_specialization = 305 | if cfg!(feature = "specialization") { 306 | quote!( default ) 307 | } else { 308 | TokenStream2::new() 309 | } 310 | ; 311 | render! { 312 | impl<__inheritable_T__ : #InheritsTrait> #Trait 313 | for __inheritable_T__ 314 | { 315 | #( 316 | #[inline] 317 | #default_if_specialization 318 | #methods 319 | )* 320 | } 321 | } 322 | 323 | 324 | debug!("=> becomes =>\n\n{}\n-------------------------\n", ret); 325 | 326 | 327 | ret 328 | } 329 | 330 | #[proc_macro_derive(Inheritance, attributes(inherits))] pub 331 | fn derive_Inheritance (input: TokenStream) 332 | -> TokenStream 333 | { 334 | debug!(concat!( 335 | "-------------------------\n", 336 | "#[derive(Inheritance)]\n", 337 | "{input}\n", 338 | "\n", 339 | ), input=input); 340 | 341 | set_output!( render => ret ); 342 | 343 | let DeriveInput { 344 | ident: Struct, 345 | generics, 346 | data, 347 | .. 348 | } = parse_macro_input!(input) 349 | ; 350 | let fields = match data { 351 | | Data::Struct(DataStruct { fields, .. }) => fields, 352 | | Data::Enum(r#enum) => { 353 | error!(r#enum.enum_token.span(), 354 | "enums are not supported" 355 | ); 356 | }, 357 | | Data::Union(r#union) => { 358 | error!(r#union.union_token.span(), 359 | "unions are not supported" 360 | ); 361 | }, 362 | }; 363 | let (mut iter1, mut iter2); 364 | let fields: &mut dyn Iterator = match fields { 365 | | Fields::Unit => { 366 | iter1 = iter::empty(); 367 | &mut iter1 368 | }, 369 | | Fields::Unnamed(fields) => { 370 | iter2 = fields.unnamed.into_iter(); 371 | &mut iter2 372 | }, 373 | | Fields::Named(fields) => { 374 | iter2 = fields.named.into_iter(); 375 | &mut iter2 376 | }, 377 | }; 378 | let ref inherits: Ident = parse_quote! { 379 | inherits 380 | }; 381 | for (i, mut field) in fields.enumerate() { 382 | let (path_to_InheritsTrait, span) = 383 | match take(&mut field.attrs) 384 | .into_iter() 385 | .find_map(|attr| if attr.path.is_ident(inherits) { Some({ 386 | let span = attr.span(); 387 | attr.parse_args_with(Path::parse_mod_style) 388 | .map(|mut path| { 389 | let last = 390 | path.segments 391 | .iter_mut() 392 | .last() 393 | .expect("path has at least one segment") 394 | ; 395 | let ref Trait = last.ident; 396 | let InheritsTrait = Ident::new(&f!( 397 | "__Inherits{Trait}__" 398 | ), span); 399 | *last = parse_quote! { 400 | #InheritsTrait 401 | }; 402 | (path, span) 403 | }) 404 | })} else { 405 | None 406 | }) 407 | { 408 | | None => continue, 409 | | Some(Err(err)) => return err.to_compile_error().into(), 410 | | Some(Ok(inner)) => inner, 411 | } 412 | ; 413 | let field_name = 414 | if let Some(ref ident) = field.ident { 415 | quote! { 416 | #ident 417 | } 418 | } else { 419 | let i: Index = i.into(); 420 | quote! { 421 | #i 422 | } 423 | } 424 | ; 425 | let ref FieldType = field.ty; 426 | let (impl_generics, ty_generics, where_clause) = 427 | generics.split_for_impl() 428 | ; 429 | render! { span => 430 | impl #impl_generics #path_to_InheritsTrait 431 | for #Struct #ty_generics 432 | #where_clause 433 | { 434 | type __Parent__ = #FieldType; 435 | 436 | #[inline] 437 | fn __parent__ (self: &'_ Self) 438 | -> &'_ Self::__Parent__ 439 | { 440 | &self.#field_name 441 | } 442 | 443 | #[inline] 444 | fn __parent_mut__ (self: &'_ mut Self) 445 | -> &'_ mut Self::__Parent__ 446 | { 447 | &mut self.#field_name 448 | } 449 | } 450 | } 451 | } 452 | debug!("=> generates =>\n\n{}\n-------------------------\n", ret); 453 | ret 454 | } 455 | --------------------------------------------------------------------------------