├── .gitignore ├── autotrait-sample ├── .gitignore ├── Cargo.toml ├── Cargo.lock └── src │ └── main.rs ├── .github └── FUNDING.yml ├── Cargo.toml ├── Justfile ├── autotrait ├── Cargo.toml ├── README.md ├── CHANGELOG.md └── src │ └── lib.rs ├── README.md ├── LICENSE-MIT ├── Cargo.lock └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /autotrait-sample/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [fasterthanlime] 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["autotrait", "autotrait-sample"] 3 | resolver = "3" 4 | -------------------------------------------------------------------------------- /Justfile: -------------------------------------------------------------------------------- 1 | 2 | list: 3 | just --list 4 | 5 | ship: 6 | release-plz update 7 | git add . 8 | git commit -m "Update version" 9 | git push 10 | just release 11 | 12 | release: 13 | release-plz release --backend github --git-token $(gh auth token) 14 | -------------------------------------------------------------------------------- /autotrait/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "autotrait" 3 | version = "0.2.3" 4 | edition = "2024" 5 | authors = ["Amos Wenger "] 6 | keywords = ["proc-macro", "derive", "trait", "dynamic-dispatch"] 7 | categories = ["rust-patterns"] 8 | repository = "https://github.com/bearcove/autotrait" 9 | documentation = "https://docs.rs/autotrait" 10 | license = "MIT OR Apache-2.0" 11 | description = "Reduces boilerplate by auto-generating trait definitions from impl blocks for dynamic dispatch." 12 | 13 | [lib] 14 | proc-macro = true 15 | 16 | [dependencies] 17 | quote = "1.0.40" 18 | unsynn = "0.3.0" 19 | -------------------------------------------------------------------------------- /autotrait-sample/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "autotrait-sample" 3 | publish = false 4 | version = "0.1.0" 5 | edition = "2024" 6 | authors = ["Amos Wenger "] 7 | description = "a demo for the autotrait crate" 8 | repository = "https://github.com/bearcove/autotrait" 9 | documentation = "https://docs.rs/autotrait-sample" 10 | license = "MIT OR Apache-2.0" 11 | keywords = ["proc-macro", "derive", "trait", "dynamic-dispatch"] 12 | categories = ["proc-macro", "derive", "trait", "dynamic-dispatch"] 13 | 14 | [dependencies] 15 | autotrait = { version = "0.2.3", path = "../autotrait" } 16 | bytes = "1.10.1" 17 | eyre = "0.6.12" 18 | futures-util = "0.3.31" 19 | http = "1.3.1" 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # autotrait 2 | 3 | Sometimes you want to do dynamic dispatch. First you'd have to define a trait: 4 | 5 | ```rust 6 | trait Trait { 7 | fn do_stuff(&self) -> String; 8 | } 9 | ``` 10 | 11 | And then implement it on something: 12 | 13 | ```rust 14 | struct Impl; 15 | 16 | impl Trait for Impl { 17 | fn do_stuff(&self) -> String { 18 | // do stuff here 19 | } 20 | } 21 | ``` 22 | 23 | We're repeating ourselves a bunch when doing that! What if we could just do: 24 | 25 | ```rust 26 | struct Impl; 27 | 28 | #[autotrait::autotrait] 29 | impl Trait for Impl { 30 | fn do_stuff(&self) -> String { 31 | // do stuff here 32 | } 33 | } 34 | ``` 35 | 36 | That way we wouldn't even have to define the trait! 37 | 38 | Well, that's what this crates does. 39 | -------------------------------------------------------------------------------- /autotrait/README.md: -------------------------------------------------------------------------------- 1 | # autotrait 2 | 3 | Sometimes you want to do dynamic dispatch. First you'd have to define a trait: 4 | 5 | ```rust 6 | trait Trait { 7 | fn do_stuff(&self) -> String; 8 | } 9 | ``` 10 | 11 | And then implement it on something: 12 | 13 | ```rust,ignore 14 | struct Impl; 15 | 16 | impl Trait for Impl { 17 | fn do_stuff(&self) -> String { 18 | // do stuff here 19 | todo!() 20 | } 21 | } 22 | ``` 23 | 24 | We're repeating ourselves a bunch when doing that! What if we could just do: 25 | 26 | ```rust 27 | struct Impl; 28 | 29 | #[autotrait::autotrait] 30 | impl Trait for Impl { 31 | fn do_stuff(&self) -> String { 32 | // do stuff here 33 | todo!() 34 | } 35 | } 36 | ``` 37 | 38 | That way we wouldn't even have to define the trait! 39 | 40 | Well, that's what this crates does. 41 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy 2 | of this software and associated documentation files (the "Software"), to deal 3 | in the Software without restriction, including without limitation the rights 4 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 5 | copies of the Software, and to permit persons to whom the Software is 6 | furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all 9 | copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. -------------------------------------------------------------------------------- /autotrait-sample/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "autotrait" 7 | version = "0.1.0" 8 | dependencies = [ 9 | "unsynn", 10 | ] 11 | 12 | [[package]] 13 | name = "autotrait-sample" 14 | version = "0.1.0" 15 | dependencies = [ 16 | "autotrait", 17 | ] 18 | 19 | [[package]] 20 | name = "mutants" 21 | version = "0.0.3" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | checksum = "bc0287524726960e07b119cebd01678f852f147742ae0d925e6a520dca956126" 24 | 25 | [[package]] 26 | name = "proc-macro2" 27 | version = "1.0.95" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 30 | dependencies = [ 31 | "unicode-ident", 32 | ] 33 | 34 | [[package]] 35 | name = "shadow_counted" 36 | version = "0.4.0" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "65da48d447333cebe1aadbdd3662f3ba56e76e67f53bc46f3dd5f67c74629d6b" 39 | 40 | [[package]] 41 | name = "unicode-ident" 42 | version = "1.0.18" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 45 | 46 | [[package]] 47 | name = "unsynn" 48 | version = "0.0.26" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "14013db4ac11f92d4df4b56edb431eab9c1d9f7aee4791f3205b6a1119b83f54" 51 | dependencies = [ 52 | "mutants", 53 | "proc-macro2", 54 | "shadow_counted", 55 | ] 56 | -------------------------------------------------------------------------------- /autotrait/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [0.2.3](https://github.com/bearcove/autotrait/compare/autotrait-v0.2.2...autotrait-v0.2.3) - 2025-12-16 11 | 12 | ### Other 13 | 14 | - Fix infinite loop bug 15 | 16 | ## [0.2.2](https://github.com/bearcove/autotrait/compare/autotrait-v0.2.1...autotrait-v0.2.2) - 2025-12-16 17 | 18 | ### Other 19 | 20 | - Upgrade unsynn to 0.3.0 21 | 22 | ## [0.2.1](https://github.com/bearcove/autotrait/compare/autotrait-v0.2.0...autotrait-v0.2.1) - 2025-05-08 23 | 24 | ### Dependencies 25 | 26 | - Upgrade unsynn from 0.0.26 to 0.1.0 27 | 28 | ## [0.1.12](https://github.com/bearcove/autotrait/compare/autotrait-v0.1.11...autotrait-v0.1.12) - 2025-04-24 29 | 30 | ### Other 31 | 32 | - Support async fn in trait 33 | 34 | ## [0.1.11](https://github.com/bearcove/autotrait/compare/autotrait-v0.1.10...autotrait-v0.1.11) - 2025-04-24 35 | 36 | ### Other 37 | 38 | - Support lifetimes in dyn traits 39 | 40 | ## [0.1.10](https://github.com/bearcove/autotrait/compare/autotrait-v0.1.9...autotrait-v0.1.10) - 2025-04-24 41 | 42 | ### Other 43 | 44 | - Update sample 45 | 46 | ## [0.1.9](https://github.com/bearcove/autotrait/compare/autotrait-v0.1.8...autotrait-v0.1.9) - 2025-04-24 47 | 48 | ### Other 49 | 50 | - Accept lifetimes in refs 51 | 52 | ## [0.1.8](https://github.com/bearcove/autotrait/compare/autotrait-v0.1.7...autotrait-v0.1.8) - 2025-04-24 53 | 54 | ### Other 55 | 56 | - Fix !Send / !Sync bounds to work on rustc as well as ra x) 57 | 58 | ## [0.1.7](https://github.com/bearcove/autotrait/compare/autotrait-v0.1.6...autotrait-v0.1.7) - 2025-04-24 59 | 60 | ### Other 61 | 62 | - Support non-sync (but still send) 63 | 64 | ## [0.1.6](https://github.com/bearcove/autotrait/compare/autotrait-v0.1.5...autotrait-v0.1.6) - 2025-04-24 65 | 66 | ### Other 67 | 68 | - Support '+' in trait objects 69 | 70 | ## [0.1.5](https://github.com/bearcove/autotrait/compare/autotrait-v0.1.4...autotrait-v0.1.5) - 2025-04-23 71 | 72 | ### Other 73 | 74 | - Parse function types and dyn/impl traits 75 | 76 | ## [0.1.4](https://github.com/bearcove/autotrait/compare/autotrait-v0.1.3...autotrait-v0.1.4) - 2025-04-23 77 | 78 | ### Other 79 | 80 | - Support more syntax 81 | 82 | ## [0.1.3](https://github.com/bearcove/autotrait/compare/autotrait-v0.1.2...autotrait-v0.1.3) - 2025-04-23 83 | 84 | ### Other 85 | 86 | - Make trait pub 87 | 88 | ## [0.1.2](https://github.com/bearcove/autotrait/compare/autotrait-v0.1.1...autotrait-v0.1.2) - 2025-04-23 89 | 90 | ### Other 91 | 92 | - Add Send bound by default 93 | 94 | ## [0.1.1](https://github.com/bearcove/autotrait/compare/autotrait-v0.1.0...autotrait-v0.1.1) - 2025-04-23 95 | 96 | ### Other 97 | 98 | - Support box 99 | - wip more complex implementations 100 | 101 | ## [0.1.0](https://github.com/bearcove/autotrait/releases/tag/autotrait-v0.1.0) - 2025-04-22 102 | 103 | ### Other 104 | 105 | - Add sample 106 | - Add metadata 107 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.4.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 10 | 11 | [[package]] 12 | name = "autotrait" 13 | version = "0.2.3" 14 | dependencies = [ 15 | "quote", 16 | "unsynn", 17 | ] 18 | 19 | [[package]] 20 | name = "autotrait-sample" 21 | version = "0.1.0" 22 | dependencies = [ 23 | "autotrait", 24 | "bytes", 25 | "eyre", 26 | "futures-util", 27 | "http", 28 | ] 29 | 30 | [[package]] 31 | name = "bytes" 32 | version = "1.10.1" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 35 | 36 | [[package]] 37 | name = "eyre" 38 | version = "0.6.12" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" 41 | dependencies = [ 42 | "indenter", 43 | "once_cell", 44 | ] 45 | 46 | [[package]] 47 | name = "fnv" 48 | version = "1.0.7" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 51 | 52 | [[package]] 53 | name = "futures-core" 54 | version = "0.3.31" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 57 | 58 | [[package]] 59 | name = "futures-macro" 60 | version = "0.3.31" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 63 | dependencies = [ 64 | "proc-macro2", 65 | "quote", 66 | "syn", 67 | ] 68 | 69 | [[package]] 70 | name = "futures-task" 71 | version = "0.3.31" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 74 | 75 | [[package]] 76 | name = "futures-util" 77 | version = "0.3.31" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 80 | dependencies = [ 81 | "futures-core", 82 | "futures-macro", 83 | "futures-task", 84 | "pin-project-lite", 85 | "pin-utils", 86 | "slab", 87 | ] 88 | 89 | [[package]] 90 | name = "http" 91 | version = "1.3.1" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" 94 | dependencies = [ 95 | "bytes", 96 | "fnv", 97 | "itoa", 98 | ] 99 | 100 | [[package]] 101 | name = "indenter" 102 | version = "0.3.3" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" 105 | 106 | [[package]] 107 | name = "itoa" 108 | version = "1.0.15" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 111 | 112 | [[package]] 113 | name = "mutants" 114 | version = "0.0.3" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "bc0287524726960e07b119cebd01678f852f147742ae0d925e6a520dca956126" 117 | 118 | [[package]] 119 | name = "once_cell" 120 | version = "1.21.3" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 123 | 124 | [[package]] 125 | name = "pin-project-lite" 126 | version = "0.2.16" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 129 | 130 | [[package]] 131 | name = "pin-utils" 132 | version = "0.1.0" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 135 | 136 | [[package]] 137 | name = "proc-macro2" 138 | version = "1.0.95" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 141 | dependencies = [ 142 | "unicode-ident", 143 | ] 144 | 145 | [[package]] 146 | name = "quote" 147 | version = "1.0.40" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 150 | dependencies = [ 151 | "proc-macro2", 152 | ] 153 | 154 | [[package]] 155 | name = "rustc-hash" 156 | version = "2.1.1" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" 159 | 160 | [[package]] 161 | name = "slab" 162 | version = "0.4.9" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 165 | dependencies = [ 166 | "autocfg", 167 | ] 168 | 169 | [[package]] 170 | name = "syn" 171 | version = "2.0.100" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 174 | dependencies = [ 175 | "proc-macro2", 176 | "quote", 177 | "unicode-ident", 178 | ] 179 | 180 | [[package]] 181 | name = "unicode-ident" 182 | version = "1.0.18" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 185 | 186 | [[package]] 187 | name = "unsynn" 188 | version = "0.3.0" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "501a7adf1a4bd9951501e5c66621e972ef8874d787628b7f90e64f936ef7ec0a" 191 | dependencies = [ 192 | "mutants", 193 | "proc-macro2", 194 | "rustc-hash", 195 | ] 196 | -------------------------------------------------------------------------------- /autotrait-sample/src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(warnings)] 2 | 3 | use std::{borrow::Cow, collections::HashMap}; 4 | 5 | use autotrait::autotrait; 6 | use bytes::Bytes; 7 | use futures_util::{future::BoxFuture, stream::BoxStream}; 8 | use http::{Error, HeaderName, HeaderValue, Method, StatusCode, Uri}; 9 | 10 | #[allow(dead_code)] 11 | struct ClientOpts; 12 | 13 | #[derive(Default)] 14 | struct ModImpl; 15 | 16 | #[autotrait] 17 | impl Foo for ModImpl { 18 | fn make_watcher_1(&self, on_event: Box) { 19 | todo!() 20 | } 21 | 22 | fn make_watcher_2(&self, on_event: Box) { 23 | todo!() 24 | } 25 | 26 | fn make_watcher_3(&self, on_event: Box u64>) { 27 | todo!() 28 | } 29 | 30 | fn make_watcher_4(&self, on_event: Box String + Send + Sync>) { 31 | todo!() 32 | } 33 | } 34 | 35 | #[autotrait(!Send)] 36 | impl Mod for ModImpl { 37 | fn box_self(self: Box) -> Result, Error> { 38 | todo!() 39 | } 40 | 41 | fn mut_box_self(mut self: Box) -> Result, Error> { 42 | self = todo!(); 43 | } 44 | 45 | fn mut_ref_self(&mut self) -> Result, Error> { 46 | *self = todo!(); 47 | } 48 | 49 | fn tuple_arg(&self, a: (u32, u64)) { 50 | todo!() 51 | } 52 | 53 | fn slice_arg(&self, s: &[u8]) { 54 | todo!() 55 | } 56 | 57 | fn and_str(&self, s: &str) { 58 | todo!() 59 | } 60 | 61 | fn and_string(&self, s: String) { 62 | todo!() 63 | } 64 | 65 | fn and_qualified_string(&self, s: std::string::String) { 66 | todo!() 67 | } 68 | 69 | fn and_dyn(&self, s: &dyn std::fmt::Debug) { 70 | todo!() 71 | } 72 | 73 | fn blah(&self) -> Result, Error> { 74 | todo!() 75 | } 76 | 77 | fn hashmap_string_string(&self) -> HashMap { 78 | todo!() 79 | } 80 | 81 | fn return_cow_str(&self) -> Cow<'static, str> { 82 | todo!() 83 | } 84 | 85 | fn client(&self) -> Box { 86 | todo!() 87 | } 88 | 89 | fn client_with_opts(&self, _opts: ClientOpts) -> Box { 90 | todo!() 91 | } 92 | 93 | fn has_lifetime<'fut>(&self) { 94 | todo!() 95 | } 96 | 97 | fn handle_oauth_callback<'fut>( 98 | &'fut self, 99 | tc: &'fut (), 100 | web: (), 101 | args: &'fut Cow<'_, str>, 102 | ) -> BoxFuture<'fut, Result>, ()>> { 103 | Box::pin(async move { todo!() }) 104 | } 105 | } 106 | 107 | #[allow(dead_code)] 108 | struct HttpClientImpl { 109 | // 110 | } 111 | 112 | #[autotrait] 113 | impl HttpClient for HttpClientImpl { 114 | fn request(&self, _method: Method, _uri: Uri) -> Box { 115 | Box::new(RequestBuilderImpl {}) 116 | } 117 | 118 | fn get(&self, uri: Uri) -> Box { 119 | self.request(Method::GET, uri) 120 | } 121 | 122 | fn post(&self, uri: Uri) -> Box { 123 | self.request(Method::POST, uri) 124 | } 125 | 126 | fn put(&self, uri: Uri) -> Box { 127 | self.request(Method::PUT, uri) 128 | } 129 | 130 | fn delete(&self, uri: Uri) -> Box { 131 | self.request(Method::DELETE, uri) 132 | } 133 | } 134 | 135 | #[allow(dead_code)] 136 | struct RequestBuilderImpl; 137 | 138 | fn main() { 139 | let m: Box = Box::new(ModImpl); 140 | m.client(); 141 | } 142 | 143 | struct ResponseImpl { 144 | response: (), 145 | } 146 | 147 | impl ResponseImpl { 148 | fn new(response: ()) -> Self { 149 | Self { response } 150 | } 151 | } 152 | 153 | #[autotrait] 154 | impl Response for ResponseImpl { 155 | fn status(&self) -> StatusCode { 156 | todo!() 157 | } 158 | 159 | fn headers_only_string_safe(&self) -> HashMap { 160 | todo!() 161 | } 162 | 163 | fn bytes(self: Box) -> BoxFuture<'static, Result, Error>> { 164 | todo!() 165 | } 166 | 167 | fn bytes_stream(self: Box) -> BoxStream<'static, Result> { 168 | todo!() 169 | } 170 | 171 | fn text(self: Box) -> BoxFuture<'static, Result> { 172 | todo!() 173 | } 174 | } 175 | 176 | #[autotrait] 177 | impl RequestBuilder for RequestBuilderImpl { 178 | fn body(mut self: Box, body: Bytes) -> Box { 179 | todo!() 180 | } 181 | 182 | fn form(mut self: Box, form: String) -> Box { 183 | todo!() 184 | } 185 | 186 | fn header(mut self: Box, key: HeaderName, value: HeaderValue) -> Box { 187 | todo!() 188 | } 189 | 190 | /// Sets a "polite" user agent, letting the server know where to reach us. 191 | fn polite_user_agent(mut self: Box) -> Box { 192 | todo!() 193 | } 194 | 195 | /// Sets a browser-like user Agent 196 | fn browser_like_user_agent(mut self: Box) -> Box { 197 | todo!() 198 | } 199 | 200 | fn basic_auth( 201 | mut self: Box, 202 | username: &str, 203 | password: Option<&str>, 204 | ) -> Box { 205 | todo!() 206 | } 207 | 208 | fn bearer_auth(mut self: Box, token: &str) -> Box { 209 | todo!() 210 | } 211 | 212 | fn send(self: Box) -> BoxFuture<'static, Result, Error>> { 213 | todo!() 214 | } 215 | 216 | fn send_and_expect_200( 217 | self: Box, 218 | ) -> BoxFuture<'static, Result, Error>> { 219 | todo!() 220 | } 221 | 222 | fn json( 223 | self: Box, 224 | body: &dyn std::fmt::Display, 225 | ) -> Result, Cow<'static, str>> { 226 | todo!() 227 | } 228 | 229 | fn query(self: Box, params: &[(&str, &str)]) -> Box { 230 | todo!() 231 | } 232 | } 233 | 234 | struct MultipartUploadWrapper {} 235 | 236 | #[autotrait(!Sync)] 237 | impl NotSync for MultipartUploadWrapper {} 238 | 239 | #[autotrait(!Send)] 240 | impl NotSend for MultipartUploadWrapper {} 241 | 242 | struct SvgImpl {} 243 | 244 | #[autotrait] 245 | impl Svg for SvgImpl { 246 | fn inject_font_faces<'future>( 247 | &'future self, 248 | input: &'future [u8], 249 | font_faces: &'future u8, 250 | ) -> BoxFuture<'future, Result, ()>> { 251 | todo!() 252 | } 253 | } 254 | 255 | struct MutDynImpl {} 256 | 257 | /// Here's a doc comment 258 | #[autotrait] 259 | impl MutDyn for MutDynImpl { 260 | fn render_math(&self, input: &str, mode: (), w: &mut dyn std::io::Write) -> eyre::Result<()> { 261 | todo!() 262 | } 263 | } 264 | 265 | struct MediaUploaderImpl {} 266 | 267 | trait ChunkReceiver {} 268 | 269 | #[autotrait] 270 | impl MediaUploader for MediaUploaderImpl { 271 | fn done_and_download_result<'a>( 272 | &self, 273 | mut chunk_receiver: Box, 274 | ) -> BoxFuture<'a, Result<(), ()>> { 275 | todo!() 276 | } 277 | } 278 | 279 | struct HasAsyncFnImpl; 280 | 281 | #[autotrait] 282 | impl HasAsyncFn for HasAsyncFnImpl { 283 | async fn async_fn(&self) -> Result<(), ()> { 284 | todo!() 285 | } 286 | } 287 | 288 | struct Bot; 289 | 290 | type Message = (); 291 | type RequestError = (); 292 | type Router = (T); 293 | type AsyncError = (); 294 | type Url = (); 295 | 296 | #[autotrait] 297 | impl BotExt for Bot { 298 | async fn reply(&self, message: &Message, text: &str) -> Result { 299 | todo!() 300 | } 301 | 302 | async fn try_reply(&self, message: &Message, text: &str) -> Result { 303 | todo!() 304 | } 305 | 306 | async fn try_reply_silent( 307 | &self, 308 | message: &Message, 309 | text: &str, 310 | ) -> Result { 311 | todo!() 312 | } 313 | 314 | async fn replace_chat_message( 315 | &self, 316 | message: &Message, 317 | text: &str, 318 | ) -> Result { 319 | todo!() 320 | } 321 | 322 | fn is_self_message(&self, message: &Message) -> bool { 323 | todo!() 324 | } 325 | 326 | async fn perform_replacement( 327 | &self, 328 | message: &Message, 329 | url_matcher: &Router<()>, 330 | preview_domain: &str, 331 | get_button_data: impl Fn(&Url) -> Option<(&str, Url)>, 332 | ) -> Result<(), AsyncError> { 333 | todo!() 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | https://www.apache.org/licenses/LICENSE-2.0 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | https://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /autotrait/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | use quote::{TokenStreamExt as _, format_ident, quote}; 4 | use unsynn::*; 5 | 6 | #[derive(Clone)] 7 | struct LifetimeName(pub Ident); 8 | 9 | impl quote::ToTokens for LifetimeName { 10 | fn to_tokens(&self, tokens: &mut TokenStream) { 11 | let punct = TokenTree::Punct(Punct::new('\'', Spacing::Joint)); 12 | let name = &self.0; 13 | tokens.append(punct); 14 | quote::ToTokens::to_tokens(&name, tokens); 15 | } 16 | } 17 | 18 | keyword! { 19 | /// The "pub" keyword. 20 | KPub = "pub"; 21 | /// The "impl" keyword. 22 | KImpl = "impl"; 23 | /// The "for" keyword. 24 | KFor = "for"; 25 | /// The "async" keyword 26 | KAsync = "async"; 27 | /// The "fn" keyword. 28 | KFn = "fn"; 29 | /// The "self" keyword. 30 | KSelf = "self"; 31 | /// The "dyn" keyword. 32 | KDyn = "dyn"; 33 | /// The "mut" keyword. 34 | KMut = "mut"; 35 | 36 | CapitalFn = "Fn"; 37 | CapitalFnMut = "FnMut"; 38 | CapitalFnOnce = "FnOnce"; 39 | 40 | KUpperSend = "Send"; 41 | KUpperSync = "Sync"; 42 | } 43 | 44 | operator! { 45 | /// The "->" operator. 46 | RightArrow = "->"; 47 | 48 | /// The "&" operator. 49 | And = "&"; 50 | 51 | /// The "'" operator. 52 | SingleQuote = "'"; 53 | 54 | /// The "#" operator. 55 | Pound = "#"; 56 | 57 | /// The "::" operator. 58 | DoubleColon = "::"; 59 | 60 | /// The "(" operator. 61 | LeftParen = "("; 62 | 63 | /// The ")" operator. 64 | RightParen = ")"; 65 | 66 | /// The "!" operator 67 | Not = "!"; 68 | } 69 | 70 | unsynn! { 71 | struct AttrBounds { 72 | bounds: CommaDelimitedVec 73 | } 74 | 75 | enum AttrBound { 76 | NotSend(Cons), 77 | NotSync(Cons), 78 | } 79 | 80 | struct ImplBlock { 81 | attrs: Vec, 82 | _impl: KImpl, 83 | trait_name: Ident, 84 | _for: KFor, 85 | typ_name: Ident, 86 | body: BraceGroupContaining>, 87 | } 88 | 89 | struct Function { 90 | attrs: Vec, 91 | _async: Option, 92 | _fn: KFn, 93 | name: Ident, 94 | generics: Option, 95 | params: ParenthesisGroupContaining, 96 | ret: Option>, 97 | body: BraceGroup, 98 | } 99 | 100 | struct FunctionGenericParams { 101 | _lt: Lt, 102 | params: CommaDelimitedVec, 103 | _gt: Gt, 104 | } 105 | 106 | struct Attr { 107 | _hash: Pound, 108 | group: BracketGroup, 109 | } 110 | 111 | struct Params { 112 | params: CommaDelimitedVec, 113 | } 114 | 115 | enum Param { 116 | ReceiverAndSelf(ReceiverAndSelf), 117 | NamedParam(NamedParam), 118 | } 119 | 120 | struct ReceiverAndSelf { 121 | _and: And, 122 | lifetime: Option, 123 | _mut: Option, 124 | _self: KSelf, 125 | } 126 | 127 | struct NamedParam { 128 | _mut: Option, 129 | ident: Ident, 130 | _colon: Colon, 131 | typ: Type, 132 | } 133 | 134 | struct SimpleType { 135 | ident: DelimitedVec, 136 | } 137 | 138 | enum Type { 139 | DynTrait(DynTrait), 140 | ImplTrait(ImplTrait), 141 | Reference(Reference), 142 | Slice(Slice), 143 | Tuple(TupleType), 144 | Fn(FnType), 145 | WithGenerics(WithGenerics), 146 | Simple(SimpleType), 147 | } 148 | 149 | struct DynTrait { 150 | _dyn: KDyn, 151 | traits: DelimitedVec, Plus, TrailingDelimiter::Forbidden, 1>, 152 | } 153 | 154 | enum TypeOrLifetime { 155 | Lifetime(Lifetime), 156 | Type(Type), 157 | } 158 | 159 | struct ImplTrait { 160 | _impl: KImpl, 161 | traits: DelimitedVec, Plus, TrailingDelimiter::Forbidden, 1>, 162 | } 163 | 164 | struct FnType { 165 | _fn: FnTypeWord, 166 | params: ParenthesisGroupContaining>, 167 | ret: Option>>, 168 | } 169 | 170 | enum FnTypeWord { 171 | CapitalFn(CapitalFn), 172 | CapitalFnMut(CapitalFnMut), 173 | CapitalFnOnce(CapitalFnOnce), 174 | } 175 | 176 | struct TupleType { 177 | types: ParenthesisGroupContaining>, 178 | } 179 | 180 | struct Slice { 181 | _and: And, 182 | lifetime: Option, 183 | element_type: BracketGroupContaining>, 184 | } 185 | 186 | struct Reference { 187 | _and: And, 188 | lifetime: Option, 189 | _mut: Option, 190 | typ: Box, 191 | } 192 | 193 | struct WithGenerics { 194 | typ: SimpleType, 195 | _lt: Lt, 196 | params: CommaDelimitedVec, 197 | _gt: Gt, 198 | } 199 | 200 | enum GenericParam { 201 | Lifetime(Lifetime), 202 | Type(Box), 203 | } 204 | 205 | struct Lifetime { 206 | _lifetime: SingleQuote, 207 | ident: Ident, 208 | } 209 | } 210 | 211 | impl quote::ToTokens for Function { 212 | fn to_tokens(&self, tokens: &mut TokenStream) { 213 | let name = &self.name; 214 | 215 | let generics = if let Some(generics) = &self.generics { 216 | let params = generics.params.iter().map(|param| ¶m.value); 217 | quote! { < #(#params),* > } 218 | } else { 219 | quote! {} 220 | }; 221 | 222 | let params = &self.params.content; 223 | 224 | let return_type = if let Some(ret) = &self.ret { 225 | let ret_type = &ret.second; 226 | quote! { -> #ret_type } 227 | } else { 228 | quote! {} 229 | }; 230 | 231 | quote::ToTokens::to_tokens( 232 | "e! { 233 | fn #name #generics (#params) #return_type { ... } 234 | }, 235 | tokens, 236 | ); 237 | } 238 | } 239 | 240 | impl quote::ToTokens for Params { 241 | fn to_tokens(&self, tokens: &mut TokenStream) { 242 | let params = self.params.iter().map(|param| ¶m.value); 243 | quote::ToTokens::to_tokens( 244 | "e! { 245 | #(#params),* 246 | }, 247 | tokens, 248 | ); 249 | } 250 | } 251 | 252 | impl quote::ToTokens for TypeOrLifetime { 253 | fn to_tokens(&self, tokens: &mut TokenStream) { 254 | match self { 255 | TypeOrLifetime::Type(typ) => quote::ToTokens::to_tokens(&typ, tokens), 256 | TypeOrLifetime::Lifetime(lifetime) => { 257 | LifetimeName(lifetime.ident.clone()).to_tokens(tokens); 258 | } 259 | } 260 | } 261 | } 262 | 263 | impl quote::ToTokens for Param { 264 | fn to_tokens(&self, tokens: &mut TokenStream) { 265 | match self { 266 | Param::ReceiverAndSelf(r) => { 267 | let lifetime = if let Some(lifetime) = &r.lifetime { 268 | let lifetime_token = LifetimeName(lifetime.ident.clone()); 269 | quote! { #lifetime_token } 270 | } else { 271 | quote! {} 272 | }; 273 | 274 | let mutability = if r._mut.is_some() { 275 | quote! { mut } 276 | } else { 277 | quote! {} 278 | }; 279 | 280 | quote::ToTokens::to_tokens("e! { &#lifetime #mutability self }, tokens); 281 | } 282 | Param::NamedParam(p) => { 283 | let ident = &p.ident; 284 | let typ = &p.typ; 285 | quote::ToTokens::to_tokens("e! { #ident: #typ }, tokens); 286 | } 287 | } 288 | } 289 | } 290 | 291 | impl quote::ToTokens for SimpleType { 292 | fn to_tokens(&self, tokens: &mut TokenStream) { 293 | let idents = self.ident.iter().map(|ident| &ident.value); 294 | quote::ToTokens::to_tokens( 295 | "e! { 296 | #(#idents)::* 297 | }, 298 | tokens, 299 | ); 300 | } 301 | } 302 | 303 | impl quote::ToTokens for DynTrait { 304 | fn to_tokens(&self, tokens: &mut TokenStream) { 305 | let traits = (&self.traits).iter().map(|trait_type| &trait_type.value); 306 | quote::ToTokens::to_tokens( 307 | "e! { 308 | dyn #(#traits)+* 309 | }, 310 | tokens, 311 | ); 312 | } 313 | } 314 | 315 | impl quote::ToTokens for ImplTrait { 316 | fn to_tokens(&self, tokens: &mut TokenStream) { 317 | let traits = (&self.traits).iter().map(|trait_type| &trait_type.value); 318 | quote::ToTokens::to_tokens( 319 | "e! { 320 | impl #(#traits)+* 321 | }, 322 | tokens, 323 | ); 324 | } 325 | } 326 | 327 | impl quote::ToTokens for Reference { 328 | fn to_tokens(&self, tokens: &mut TokenStream) { 329 | let lifetime = if let Some(lifetime) = &self.lifetime { 330 | let lifetime_token = LifetimeName(lifetime.ident.clone()); 331 | quote! { #lifetime_token } 332 | } else { 333 | quote! {} 334 | }; 335 | 336 | let mutability = if self._mut.is_some() { 337 | quote! { mut } 338 | } else { 339 | quote! {} 340 | }; 341 | 342 | let typ = &self.typ; 343 | 344 | quote::ToTokens::to_tokens( 345 | "e! { 346 | &#lifetime #mutability #typ 347 | }, 348 | tokens, 349 | ); 350 | } 351 | } 352 | 353 | impl quote::ToTokens for WithGenerics { 354 | fn to_tokens(&self, tokens: &mut TokenStream) { 355 | let typ = &self.typ; 356 | 357 | if (&self.params).iter().next().is_none() { 358 | quote::ToTokens::to_tokens(&typ, tokens); 359 | } else { 360 | let params = (&self.params).iter().map(|param| ¶m.value); 361 | quote::ToTokens::to_tokens( 362 | "e! { 363 | #typ < #(#params),* > 364 | }, 365 | tokens, 366 | ); 367 | } 368 | } 369 | } 370 | 371 | impl quote::ToTokens for TupleType { 372 | fn to_tokens(&self, tokens: &mut TokenStream) { 373 | let types = (&self.types.content).iter().map(|typ| &typ.value); 374 | quote::ToTokens::to_tokens( 375 | "e! { 376 | ( #(#types),* ) 377 | }, 378 | tokens, 379 | ); 380 | } 381 | } 382 | 383 | impl quote::ToTokens for Slice { 384 | fn to_tokens(&self, tokens: &mut TokenStream) { 385 | let lifetime = if let Some(lifetime) = &self.lifetime { 386 | let lifetime_token = LifetimeName(lifetime.ident.clone()); 387 | quote! { #lifetime_token } 388 | } else { 389 | quote! {} 390 | }; 391 | 392 | let element_type = &self.element_type.content; 393 | 394 | quote::ToTokens::to_tokens( 395 | "e! { 396 | &#lifetime [#element_type] 397 | }, 398 | tokens, 399 | ); 400 | } 401 | } 402 | 403 | impl quote::ToTokens for FnType { 404 | fn to_tokens(&self, tokens: &mut TokenStream) { 405 | let fn_type = match &self._fn { 406 | FnTypeWord::CapitalFn(_) => format_ident!("Fn"), 407 | FnTypeWord::CapitalFnMut(_) => format_ident!("FnMut"), 408 | FnTypeWord::CapitalFnOnce(_) => format_ident!("FnOnce"), 409 | }; 410 | 411 | let params = (&self.params.content).iter().map(|param| ¶m.value); 412 | 413 | let return_type = if let Some(ret) = &self.ret { 414 | let ret_type = &ret.second; 415 | quote! { -> #ret_type } 416 | } else { 417 | quote! {} 418 | }; 419 | 420 | quote::ToTokens::to_tokens( 421 | "e! { 422 | #fn_type ( #(#params),* ) #return_type 423 | }, 424 | tokens, 425 | ); 426 | } 427 | } 428 | 429 | impl quote::ToTokens for Type { 430 | fn to_tokens(&self, tokens: &mut TokenStream) { 431 | match self { 432 | Type::Simple(simple) => quote::ToTokens::to_tokens(simple, tokens), 433 | Type::Reference(reference) => quote::ToTokens::to_tokens(reference, tokens), 434 | Type::WithGenerics(with_generics) => quote::ToTokens::to_tokens(with_generics, tokens), 435 | Type::Tuple(tuple) => quote::ToTokens::to_tokens(tuple, tokens), 436 | Type::Slice(slice) => quote::ToTokens::to_tokens(slice, tokens), 437 | Type::Fn(fn_type) => quote::ToTokens::to_tokens(fn_type, tokens), 438 | Type::DynTrait(dyn_trait) => quote::ToTokens::to_tokens(dyn_trait, tokens), 439 | Type::ImplTrait(impl_trait) => quote::ToTokens::to_tokens(impl_trait, tokens), 440 | } 441 | } 442 | } 443 | 444 | impl quote::ToTokens for GenericParam { 445 | fn to_tokens(&self, tokens: &mut TokenStream) { 446 | match self { 447 | GenericParam::Type(typ) => quote::ToTokens::to_tokens(&typ, tokens), 448 | GenericParam::Lifetime(lifetime) => { 449 | let lifetime_token = LifetimeName(lifetime.ident.clone()); 450 | lifetime_token.to_tokens(tokens); 451 | } 452 | } 453 | } 454 | } 455 | 456 | #[proc_macro_attribute] 457 | pub fn autotrait( 458 | attr: proc_macro::TokenStream, 459 | item: proc_macro::TokenStream, 460 | ) -> proc_macro::TokenStream { 461 | let item_clone = item.clone(); 462 | 463 | let token_stream = TokenStream::from(item); 464 | let mut i = token_stream.to_token_iter(); 465 | let b = i.parse::().unwrap(); 466 | 467 | let attr_bounds = TokenStream::from(attr) 468 | .to_token_iter() 469 | .parse::() 470 | .expect("Failed to parse attribute bounds"); 471 | 472 | let mut has_not_send = false; 473 | let mut has_not_sync = false; 474 | 475 | for bound in attr_bounds.bounds.iter() { 476 | match &bound.value { 477 | AttrBound::NotSend(_) => has_not_send = true, 478 | AttrBound::NotSync(_) => has_not_sync = true, 479 | } 480 | } 481 | 482 | let bounds = if has_not_send { 483 | quote! {} 484 | } else if has_not_sync { 485 | quote! { : Send } 486 | } else { 487 | quote! { : Send + Sync } 488 | }; 489 | 490 | let trait_name = &b.trait_name; 491 | let attrs = b.attrs.iter().map(|attr| { 492 | quote! { #attr } 493 | }); 494 | 495 | let functions = b.body.content.iter().map(|f| { 496 | let async_kw = if f._async.is_some() { 497 | quote! { async } 498 | } else { 499 | quote! {} 500 | }; 501 | 502 | let fn_name = &f.name; 503 | 504 | let generics = if let Some(generics) = &f.generics { 505 | let params = (&generics.params).iter().map(|param| ¶m.value); 506 | quote! { < #(#params),* > } 507 | } else { 508 | quote! {} 509 | }; 510 | 511 | let params = &f.params.content; 512 | 513 | let return_type = if let Some(ret) = &f.ret { 514 | let ret_type = &ret.second; 515 | quote! { -> #ret_type } 516 | } else { 517 | quote! {} 518 | }; 519 | 520 | quote! { 521 | #async_kw fn #fn_name #generics (#params) #return_type; 522 | } 523 | }); 524 | 525 | let trait_def = quote! { 526 | #(#attrs)* 527 | pub trait #trait_name #bounds { 528 | #(#functions)* 529 | } 530 | }; 531 | 532 | let mut output = TokenStream::new(); 533 | trait_def.to_tokens(&mut output); 534 | 535 | let item_ts: TokenStream = item_clone.into(); 536 | output.extend(item_ts); 537 | 538 | output.into() 539 | } 540 | 541 | impl quote::ToTokens for Attr { 542 | fn to_tokens(&self, tokens: &mut TokenStream) { 543 | tokens.append(Punct::new('#', Spacing::Joint)); 544 | self.group.to_tokens(tokens); 545 | } 546 | } 547 | --------------------------------------------------------------------------------