├── .github └── workflows │ └── CI.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── deny.toml ├── src └── lib.rs └── tests ├── compile_fail ├── double_vis.rs ├── double_vis.stderr ├── supertraits_are_actually_included.rs └── supertraits_are_actually_included.stderr └── compile_pass ├── associated_constants.rs ├── async_trait.rs ├── changing_extension_trait_name.rs ├── complex_trait_name.rs ├── destructure.rs ├── double_ext_on_same_type.rs ├── extension_on_complex_types.rs ├── generics.rs ├── hello_world.rs ├── issue_2.rs ├── more_than_one_extension.rs ├── multiple_config.rs ├── multiple_generic_params.rs ├── pub_impl.rs ├── ref_and_ref_mut.rs ├── sized.rs ├── super_trait.rs └── visibility_config.rs /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: {} 8 | 9 | jobs: 10 | check: 11 | # Run `cargo check` first to ensure that the pushed code at least compiles. 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | rust: [stable, 1.40.0] 16 | steps: 17 | - uses: actions/checkout@master 18 | - uses: actions-rs/toolchain@v1 19 | with: 20 | toolchain: ${{ matrix.rust }} 21 | profile: minimal 22 | - name: Check 23 | uses: actions-rs/cargo@v1 24 | with: 25 | command: clippy 26 | args: --all --all-targets --all-features 27 | - name: rustfmt 28 | uses: actions-rs/cargo@v1 29 | with: 30 | command: fmt 31 | args: --all -- --check 32 | 33 | cargo-hack: 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/checkout@master 37 | - uses: actions-rs/toolchain@v1 38 | with: 39 | toolchain: stable 40 | profile: minimal 41 | - name: Install cargo-hack 42 | run: | 43 | curl -LsSf https://github.com/taiki-e/cargo-hack/releases/latest/download/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xzf - -C ~/.cargo/bin 44 | - name: cargo hack check 45 | working-directory: ${{ matrix.subcrate }} 46 | run: cargo hack check --each-feature --no-dev-deps --all 47 | 48 | test-versions: 49 | # Test against the stable, beta, and nightly Rust toolchains on ubuntu-latest. 50 | needs: check 51 | runs-on: ubuntu-latest 52 | strategy: 53 | matrix: 54 | rust: [stable, beta, nightly, 1.40.0] 55 | steps: 56 | - uses: actions/checkout@master 57 | - uses: actions-rs/toolchain@v1 58 | with: 59 | toolchain: ${{ matrix.rust }} 60 | profile: minimal 61 | - name: Run tests 62 | uses: actions-rs/cargo@v1 63 | with: 64 | command: test 65 | args: --all --all-features 66 | 67 | deny-check: 68 | name: cargo-deny check 69 | runs-on: ubuntu-latest 70 | steps: 71 | - uses: actions/checkout@v1 72 | - uses: EmbarkStudios/cargo-deny-action@v1 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All user visible changes to this project will be documented in this file. 4 | This project adheres to [Semantic Versioning](http://semver.org/), as described 5 | for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md) 6 | 7 | ## Unreleased 8 | 9 | - None. 10 | 11 | ### Breaking changes 12 | 13 | - None. 14 | 15 | ## 1.2.0 - 2023-03-18 16 | 17 | - Support destructuring function arguments 18 | 19 | ## 1.1.3 - 2023-03-18 20 | 21 | - Update to syn 2.0 22 | 23 | ## 1.1.2 - 2021-09-02 24 | 25 | - Fix using `pub impl` with `#[async_trait]`. 26 | 27 | ## 1.1.1 - 2021-06-12 28 | 29 | - Fix name collision for extensions on `&T` and `&mut T`. The generated traits 30 | now get different names. 31 | 32 | ## 1.1.0 - 2021-06-12 33 | 34 | - Support setting visibility of the generated trait directly on the `impl` 35 | block. For example: `pub impl i32 { ... }`. 36 | - Add `#[ext_sized]` for adding `Sized` supertrait. 37 | 38 | ## 1.0.1 - 2021-02-14 39 | 40 | - Update maintenance status. 41 | 42 | ## 1.0.0 - 2021-01-30 43 | 44 | - Support extensions on bare functions types (things like `fn(i32) -> bool`). 45 | - Support extensions on trait objects (things like `dyn Send + Sync`). 46 | 47 | ## 0.3.0 - 2020-08-31 48 | 49 | - Add async-trait compatibility. 50 | 51 | ### Breaking changes 52 | 53 | - Other attributes put on the `impl` would previously only be included on the generated trait. They're now included on both the trait and the implementation. 54 | 55 | ## 0.2.1 - 2020-08-29 56 | 57 | - Fix documentation link in Cargo.toml. 58 | - Use more correct repository URL in Cargo.toml. 59 | 60 | ## 0.2.0 - 2020-08-29 61 | 62 | - Handle unnamed extensions on the same generic type with different type parameters. For example `Option` and `Option`. Previously we would generate the same name of both hidden traits which wouldn't compile. 63 | - Support associated constants in extension impls. 64 | 65 | ### Breaking changes 66 | 67 | - Generated traits are no longer sealed and the `sealed` argument previously supported by `#[ext]` has been removed. Making the traits sealed lead to lots of complexity that we didn't think brought much value. 68 | 69 | ## 0.1.1 - 2020-02-22 70 | 71 | - Add support for specifying supertraits of the generated trait [#4](https://github.com/davidpdrsn/extend/pull/4). 72 | 73 | ## 0.1.0 74 | 75 | - Support adding extensions to the ["never type"](https://doc.rust-lang.org/std/primitive.never.html). 76 | 77 | ### Breaking changes 78 | 79 | - Simplify names of traits generates for complex types. 80 | 81 | ## 0.0.2 82 | 83 | - Move "trybuild" to dev-dependency. 84 | 85 | ## 0.0.1 86 | 87 | Initial release. 88 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "extend" 3 | version = "1.2.0" 4 | authors = ["David Pedersen "] 5 | edition = "2018" 6 | categories = ["rust-patterns"] 7 | description = "Create extensions for types you don't own with extension traits but without the boilerplate." 8 | homepage = "https://github.com/davidpdrsn/extend" 9 | keywords = ["extension", "trait"] 10 | license = "MIT" 11 | readme = "README.md" 12 | repository = "https://github.com/davidpdrsn/extend" 13 | 14 | [dependencies] 15 | syn = { version = "2", features = ["full", "extra-traits", "visit"] } 16 | quote = "1" 17 | proc-macro2 = "1" 18 | 19 | [dev-dependencies] 20 | trybuild = "1.0.17" 21 | async-trait = "0.1.40" 22 | 23 | [lib] 24 | proc-macro = true 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License Copyright (c) 2020 David Pedersen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice (including the next 11 | paragraph) shall be included in all copies or substantial portions of the 12 | Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 17 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF 19 | OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # extend 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/extend.svg)](https://crates.io/crates/extend) 4 | [![Docs](https://docs.rs/extend/badge.svg)](https://docs.rs/extend) 5 | [![dependency status](https://deps.rs/repo/github/davidpdrsn/extend/status.svg)](https://deps.rs/repo/github/davidpdrsn/extend) 6 | [![Build status](https://github.com/davidpdrsn/extend/workflows/CI/badge.svg)](https://github.com/davidpdrsn/extend/actions) 7 | ![maintenance-status](https://img.shields.io/badge/maintenance-passively--maintained-yellowgreen.svg) 8 | 9 | Create extensions for types you don't own with [extension traits] but without the boilerplate. 10 | 11 | Example: 12 | 13 | ```rust 14 | use extend::ext; 15 | 16 | #[ext] 17 | impl Vec { 18 | fn sorted(mut self) -> Self { 19 | self.sort(); 20 | self 21 | } 22 | } 23 | 24 | fn main() { 25 | assert_eq!( 26 | vec![1, 2, 3], 27 | vec![2, 3, 1].sorted(), 28 | ); 29 | } 30 | ``` 31 | 32 | [extension traits]: https://dev.to/matsimitsu/extending-existing-functionality-in-rust-with-traits-in-rust-3622 33 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | [advisories] 2 | vulnerability = "deny" 3 | unmaintained = "warn" 4 | notice = "warn" 5 | ignore = [] 6 | 7 | [licenses] 8 | unlicensed = "warn" 9 | allow = [] 10 | deny = [] 11 | copyleft = "warn" 12 | allow-osi-fsf-free = "either" 13 | confidence-threshold = 0.8 14 | 15 | [bans] 16 | multiple-versions = "deny" 17 | highlight = "all" 18 | skip-tree = [] 19 | 20 | [sources] 21 | unknown-registry = "warn" 22 | unknown-git = "warn" 23 | allow-git = [] 24 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Create extensions for types you don't own with [extension traits] but without the boilerplate. 2 | //! 3 | //! Example: 4 | //! 5 | //! ```rust 6 | //! use extend::ext; 7 | //! 8 | //! #[ext] 9 | //! impl Vec { 10 | //! fn sorted(mut self) -> Self { 11 | //! self.sort(); 12 | //! self 13 | //! } 14 | //! } 15 | //! 16 | //! assert_eq!( 17 | //! vec![1, 2, 3], 18 | //! vec![2, 3, 1].sorted(), 19 | //! ); 20 | //! ``` 21 | //! 22 | //! # How does it work? 23 | //! 24 | //! Under the hood it generates a trait with methods in your `impl` and implements those for the 25 | //! type you specify. The code shown above expands roughly to: 26 | //! 27 | //! ```rust 28 | //! trait VecExt { 29 | //! fn sorted(self) -> Self; 30 | //! } 31 | //! 32 | //! impl VecExt for Vec { 33 | //! fn sorted(mut self) -> Self { 34 | //! self.sort(); 35 | //! self 36 | //! } 37 | //! } 38 | //! ``` 39 | //! 40 | //! # Supported items 41 | //! 42 | //! Extensions can contain methods or associated constants: 43 | //! 44 | //! ```rust 45 | //! use extend::ext; 46 | //! 47 | //! #[ext] 48 | //! impl String { 49 | //! const CONSTANT: &'static str = "FOO"; 50 | //! 51 | //! fn method() { 52 | //! // ... 53 | //! # todo!() 54 | //! } 55 | //! } 56 | //! ``` 57 | //! 58 | //! # Configuration 59 | //! 60 | //! You can configure: 61 | //! 62 | //! - The visibility of the trait. Use `pub impl ...` to generate `pub trait ...`. The default 63 | //! visibility is private. 64 | //! - The name of the generated extension trait. Example: `#[ext(name = MyExt)]`. By default we 65 | //! generate a name based on what you extend. 66 | //! - Which supertraits the generated extension trait should have. Default is no supertraits. 67 | //! Example: `#[ext(supertraits = Default + Clone)]`. 68 | //! 69 | //! More examples: 70 | //! 71 | //! ```rust 72 | //! use extend::ext; 73 | //! 74 | //! #[ext(name = SortedVecExt)] 75 | //! impl Vec { 76 | //! fn sorted(mut self) -> Self { 77 | //! self.sort(); 78 | //! self 79 | //! } 80 | //! } 81 | //! 82 | //! #[ext] 83 | //! pub(crate) impl i32 { 84 | //! fn double(self) -> i32 { 85 | //! self * 2 86 | //! } 87 | //! } 88 | //! 89 | //! #[ext(name = ResultSafeUnwrapExt)] 90 | //! pub impl Result { 91 | //! fn safe_unwrap(self) -> T { 92 | //! match self { 93 | //! Ok(t) => t, 94 | //! Err(_) => unreachable!(), 95 | //! } 96 | //! } 97 | //! } 98 | //! 99 | //! #[ext(supertraits = Default + Clone)] 100 | //! impl String { 101 | //! fn my_length(self) -> usize { 102 | //! self.len() 103 | //! } 104 | //! } 105 | //! ``` 106 | //! 107 | //! For backwards compatibility you can also declare the visibility as the first argument to `#[ext]`: 108 | //! 109 | //! ``` 110 | //! use extend::ext; 111 | //! 112 | //! #[ext(pub)] 113 | //! impl i32 { 114 | //! fn double(self) -> i32 { 115 | //! self * 2 116 | //! } 117 | //! } 118 | //! ``` 119 | //! 120 | //! # async-trait compatibility 121 | //! 122 | //! Async extensions are supported via [async-trait](https://crates.io/crates/async-trait). 123 | //! 124 | //! Be aware that you need to add `#[async_trait]` _below_ `#[ext]`. Otherwise the `ext` macro 125 | //! cannot see the `#[async_trait]` attribute and pass it along in the generated code. 126 | //! 127 | //! Example: 128 | //! 129 | //! ``` 130 | //! use extend::ext; 131 | //! use async_trait::async_trait; 132 | //! 133 | //! #[ext] 134 | //! #[async_trait] 135 | //! impl String { 136 | //! async fn read_file() -> String { 137 | //! // ... 138 | //! # todo!() 139 | //! } 140 | //! } 141 | //! ``` 142 | //! 143 | //! # Other attributes 144 | //! 145 | //! Other attributes provided _below_ `#[ext]` will be passed along to both the generated trait and 146 | //! the implementation. See [async-trait compatibility](#async-trait-compatibility) above for an 147 | //! example. 148 | //! 149 | //! [extension traits]: https://dev.to/matsimitsu/extending-existing-functionality-in-rust-with-traits-in-rust-3622 150 | 151 | #![allow(clippy::let_and_return)] 152 | #![deny(unused_variables, dead_code, unused_must_use, unused_imports)] 153 | 154 | use proc_macro2::TokenStream; 155 | use quote::{format_ident, quote, ToTokens}; 156 | use std::convert::{TryFrom, TryInto}; 157 | use syn::{ 158 | parse::{self, Parse, ParseStream}, 159 | parse_macro_input, parse_quote, 160 | punctuated::Punctuated, 161 | spanned::Spanned, 162 | token::{Plus, Semi}, 163 | Ident, ImplItem, ItemImpl, Result, Token, TraitItemConst, TraitItemFn, Type, TypeArray, 164 | TypeBareFn, TypeGroup, TypeNever, TypeParamBound, TypeParen, TypePath, TypePtr, TypeReference, 165 | TypeSlice, TypeTraitObject, TypeTuple, Visibility, 166 | }; 167 | 168 | #[derive(Debug)] 169 | struct Input { 170 | item_impl: ItemImpl, 171 | vis: Option, 172 | } 173 | 174 | impl Parse for Input { 175 | fn parse(input: ParseStream) -> syn::Result { 176 | let mut attributes = Vec::new(); 177 | if input.peek(syn::Token![#]) { 178 | attributes.extend(syn::Attribute::parse_outer(input)?); 179 | } 180 | 181 | let vis = input 182 | .parse::() 183 | .ok() 184 | .filter(|vis| vis != &Visibility::Inherited); 185 | 186 | let mut item_impl = input.parse::()?; 187 | item_impl.attrs.extend(attributes); 188 | 189 | Ok(Self { item_impl, vis }) 190 | } 191 | } 192 | 193 | /// See crate docs for more info. 194 | #[proc_macro_attribute] 195 | #[allow(clippy::unneeded_field_pattern)] 196 | pub fn ext( 197 | attr: proc_macro::TokenStream, 198 | item: proc_macro::TokenStream, 199 | ) -> proc_macro::TokenStream { 200 | let item = parse_macro_input!(item as Input); 201 | let config = parse_macro_input!(attr as Config); 202 | match go(item, config) { 203 | Ok(tokens) => tokens, 204 | Err(err) => err.into_compile_error().into(), 205 | } 206 | } 207 | 208 | /// Like [`ext`](macro@crate::ext) but always add `Sized` as a supertrait. 209 | /// 210 | /// This is provided as a convenience for generating extension traits that require `Self: Sized` 211 | /// such as: 212 | /// 213 | /// ``` 214 | /// use extend::ext_sized; 215 | /// 216 | /// #[ext_sized] 217 | /// impl i32 { 218 | /// fn requires_sized(self) -> Option { 219 | /// Some(self) 220 | /// } 221 | /// } 222 | /// ``` 223 | #[proc_macro_attribute] 224 | #[allow(clippy::unneeded_field_pattern)] 225 | pub fn ext_sized( 226 | attr: proc_macro::TokenStream, 227 | item: proc_macro::TokenStream, 228 | ) -> proc_macro::TokenStream { 229 | let item = parse_macro_input!(item as Input); 230 | let mut config: Config = parse_macro_input!(attr as Config); 231 | 232 | config.supertraits = if let Some(supertraits) = config.supertraits.take() { 233 | Some(parse_quote!(#supertraits + Sized)) 234 | } else { 235 | Some(parse_quote!(Sized)) 236 | }; 237 | 238 | match go(item, config) { 239 | Ok(tokens) => tokens, 240 | Err(err) => err.into_compile_error().into(), 241 | } 242 | } 243 | 244 | fn go(item: Input, mut config: Config) -> Result { 245 | if let Some(vis) = item.vis { 246 | if config.visibility != Visibility::Inherited { 247 | return Err(syn::Error::new( 248 | config.visibility.span(), 249 | "Cannot set visibility on `#[ext]` and `impl` block", 250 | )); 251 | } 252 | 253 | config.visibility = vis; 254 | } 255 | 256 | let ItemImpl { 257 | attrs, 258 | unsafety, 259 | generics, 260 | trait_, 261 | self_ty, 262 | items, 263 | // What is defaultness? 264 | defaultness: _, 265 | impl_token: _, 266 | brace_token: _, 267 | } = item.item_impl; 268 | 269 | if let Some((_, path, _)) = trait_ { 270 | return Err(syn::Error::new( 271 | path.span(), 272 | "Trait impls cannot be used for #[ext]", 273 | )); 274 | } 275 | 276 | let self_ty = parse_self_ty(&self_ty)?; 277 | 278 | let ext_trait_name = if let Some(ext_trait_name) = config.ext_trait_name { 279 | ext_trait_name 280 | } else { 281 | ext_trait_name(&self_ty)? 282 | }; 283 | 284 | let MethodsAndConsts { 285 | trait_methods, 286 | trait_consts, 287 | } = extract_allowed_items(&items)?; 288 | 289 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 290 | 291 | let visibility = &config.visibility; 292 | 293 | let mut all_supertraits = Vec::::new(); 294 | 295 | if let Some(supertraits_from_config) = config.supertraits { 296 | all_supertraits.extend(supertraits_from_config); 297 | } 298 | 299 | let supertraits_quoted = if all_supertraits.is_empty() { 300 | quote! {} 301 | } else { 302 | let supertraits_quoted = punctuated_from_iter::<_, _, Plus>(all_supertraits); 303 | quote! { : #supertraits_quoted } 304 | }; 305 | 306 | let code = (quote! { 307 | #[allow(non_camel_case_types)] 308 | #(#attrs)* 309 | #visibility 310 | #unsafety 311 | trait #ext_trait_name #impl_generics #supertraits_quoted #where_clause { 312 | #( 313 | #trait_consts 314 | )* 315 | 316 | #( 317 | #[allow( 318 | patterns_in_fns_without_body, 319 | clippy::inline_fn_without_body, 320 | unused_attributes 321 | )] 322 | #trait_methods 323 | )* 324 | } 325 | 326 | #(#attrs)* 327 | impl #impl_generics #ext_trait_name #ty_generics for #self_ty #where_clause { 328 | #(#items)* 329 | } 330 | }) 331 | .into(); 332 | 333 | Ok(code) 334 | } 335 | 336 | #[derive(Debug, Clone)] 337 | enum ExtType<'a> { 338 | Array(&'a TypeArray), 339 | Group(&'a TypeGroup), 340 | Never(&'a TypeNever), 341 | Paren(&'a TypeParen), 342 | Path(&'a TypePath), 343 | Ptr(&'a TypePtr), 344 | Reference(&'a TypeReference), 345 | Slice(&'a TypeSlice), 346 | Tuple(&'a TypeTuple), 347 | BareFn(&'a TypeBareFn), 348 | TraitObject(&'a TypeTraitObject), 349 | } 350 | 351 | #[allow(clippy::wildcard_in_or_patterns)] 352 | fn parse_self_ty(self_ty: &Type) -> Result { 353 | let ty = match self_ty { 354 | Type::Array(inner) => ExtType::Array(inner), 355 | Type::Group(inner) => ExtType::Group(inner), 356 | Type::Never(inner) => ExtType::Never(inner), 357 | Type::Paren(inner) => ExtType::Paren(inner), 358 | Type::Path(inner) => ExtType::Path(inner), 359 | Type::Ptr(inner) => ExtType::Ptr(inner), 360 | Type::Reference(inner) => ExtType::Reference(inner), 361 | Type::Slice(inner) => ExtType::Slice(inner), 362 | Type::Tuple(inner) => ExtType::Tuple(inner), 363 | Type::BareFn(inner) => ExtType::BareFn(inner), 364 | Type::TraitObject(inner) => ExtType::TraitObject(inner), 365 | 366 | Type::ImplTrait(_) | Type::Infer(_) | Type::Macro(_) | Type::Verbatim(_) | _ => { 367 | return Err(syn::Error::new( 368 | self_ty.span(), 369 | "#[ext] is not supported for this kind of type", 370 | )) 371 | } 372 | }; 373 | Ok(ty) 374 | } 375 | 376 | impl<'a> TryFrom<&'a Type> for ExtType<'a> { 377 | type Error = syn::Error; 378 | 379 | fn try_from(inner: &'a Type) -> Result> { 380 | parse_self_ty(inner) 381 | } 382 | } 383 | 384 | impl<'a> ToTokens for ExtType<'a> { 385 | fn to_tokens(&self, tokens: &mut TokenStream) { 386 | match self { 387 | ExtType::Array(inner) => inner.to_tokens(tokens), 388 | ExtType::Group(inner) => inner.to_tokens(tokens), 389 | ExtType::Never(inner) => inner.to_tokens(tokens), 390 | ExtType::Paren(inner) => inner.to_tokens(tokens), 391 | ExtType::Path(inner) => inner.to_tokens(tokens), 392 | ExtType::Ptr(inner) => inner.to_tokens(tokens), 393 | ExtType::Reference(inner) => inner.to_tokens(tokens), 394 | ExtType::Slice(inner) => inner.to_tokens(tokens), 395 | ExtType::Tuple(inner) => inner.to_tokens(tokens), 396 | ExtType::BareFn(inner) => inner.to_tokens(tokens), 397 | ExtType::TraitObject(inner) => inner.to_tokens(tokens), 398 | } 399 | } 400 | } 401 | 402 | fn ext_trait_name(self_ty: &ExtType) -> Result { 403 | fn inner_self_ty(self_ty: &ExtType) -> Result { 404 | match self_ty { 405 | ExtType::Path(inner) => find_and_combine_idents(inner), 406 | ExtType::Reference(inner) => { 407 | let name = inner_self_ty(&(&*inner.elem).try_into()?)?; 408 | if inner.mutability.is_some() { 409 | Ok(format_ident!("RefMut{}", name)) 410 | } else { 411 | Ok(format_ident!("Ref{}", name)) 412 | } 413 | } 414 | ExtType::Array(inner) => { 415 | let name = inner_self_ty(&(&*inner.elem).try_into()?)?; 416 | Ok(format_ident!("ListOf{}", name)) 417 | } 418 | ExtType::Group(inner) => { 419 | let name = inner_self_ty(&(&*inner.elem).try_into()?)?; 420 | Ok(format_ident!("Group{}", name)) 421 | } 422 | ExtType::Paren(inner) => { 423 | let name = inner_self_ty(&(&*inner.elem).try_into()?)?; 424 | Ok(format_ident!("Paren{}", name)) 425 | } 426 | ExtType::Ptr(inner) => { 427 | let name = inner_self_ty(&(&*inner.elem).try_into()?)?; 428 | Ok(format_ident!("PointerTo{}", name)) 429 | } 430 | ExtType::Slice(inner) => { 431 | let name = inner_self_ty(&(&*inner.elem).try_into()?)?; 432 | Ok(format_ident!("SliceOf{}", name)) 433 | } 434 | ExtType::Tuple(inner) => { 435 | let mut name = format_ident!("TupleOf"); 436 | for elem in &inner.elems { 437 | name = format_ident!("{}{}", name, inner_self_ty(&elem.try_into()?)?); 438 | } 439 | Ok(name) 440 | } 441 | ExtType::Never(_) => Ok(format_ident!("Never")), 442 | ExtType::BareFn(inner) => { 443 | let mut name = format_ident!("BareFn"); 444 | for input in inner.inputs.iter() { 445 | name = format_ident!("{}{}", name, inner_self_ty(&(&input.ty).try_into()?)?); 446 | } 447 | match &inner.output { 448 | syn::ReturnType::Default => { 449 | name = format_ident!("{}Unit", name); 450 | } 451 | syn::ReturnType::Type(_, ty) => { 452 | name = format_ident!("{}{}", name, inner_self_ty(&(&**ty).try_into()?)?); 453 | } 454 | } 455 | Ok(name) 456 | } 457 | ExtType::TraitObject(inner) => { 458 | let mut name = format_ident!("TraitObject"); 459 | for bound in inner.bounds.iter() { 460 | match bound { 461 | TypeParamBound::Trait(bound) => { 462 | for segment in bound.path.segments.iter() { 463 | name = format_ident!("{}{}", name, segment.ident); 464 | } 465 | } 466 | TypeParamBound::Lifetime(lifetime) => { 467 | name = format_ident!("{}{}", name, lifetime.ident); 468 | } 469 | other => { 470 | return Err(syn::Error::new(other.span(), "unsupported bound")); 471 | } 472 | } 473 | } 474 | Ok(name) 475 | } 476 | } 477 | } 478 | 479 | Ok(format_ident!("{}Ext", inner_self_ty(self_ty)?)) 480 | } 481 | 482 | fn find_and_combine_idents(type_path: &TypePath) -> Result { 483 | use syn::visit::{self, Visit}; 484 | 485 | struct IdentVisitor<'a>(Vec<&'a Ident>); 486 | 487 | impl<'a> Visit<'a> for IdentVisitor<'a> { 488 | fn visit_ident(&mut self, i: &'a Ident) { 489 | self.0.push(i); 490 | } 491 | } 492 | 493 | let mut visitor = IdentVisitor(Vec::new()); 494 | visit::visit_type_path(&mut visitor, type_path); 495 | let idents = visitor.0; 496 | 497 | if idents.is_empty() { 498 | Err(syn::Error::new(type_path.span(), "Empty type path")) 499 | } else { 500 | let start = &idents[0].span(); 501 | let combined_span = idents 502 | .iter() 503 | .map(|i| i.span()) 504 | .fold(*start, |a, b| a.join(b).unwrap_or(a)); 505 | 506 | let combined_name = idents.iter().map(|i| i.to_string()).collect::(); 507 | 508 | Ok(Ident::new(&combined_name, combined_span)) 509 | } 510 | } 511 | 512 | #[derive(Debug, Default)] 513 | struct MethodsAndConsts { 514 | trait_methods: Vec, 515 | trait_consts: Vec, 516 | } 517 | 518 | #[allow(clippy::wildcard_in_or_patterns)] 519 | fn extract_allowed_items(items: &[ImplItem]) -> Result { 520 | let mut acc = MethodsAndConsts::default(); 521 | for item in items { 522 | match item { 523 | ImplItem::Fn(method) => acc.trait_methods.push(TraitItemFn { 524 | attrs: method.attrs.clone(), 525 | sig: { 526 | let mut sig = method.sig.clone(); 527 | sig.inputs = sig 528 | .inputs 529 | .into_iter() 530 | .map(|fn_arg| match fn_arg { 531 | syn::FnArg::Receiver(recv) => syn::FnArg::Receiver(recv), 532 | syn::FnArg::Typed(mut pat_type) => { 533 | pat_type.pat = Box::new(match *pat_type.pat { 534 | syn::Pat::Ident(pat_ident) => syn::Pat::Ident(pat_ident), 535 | _ => { 536 | parse_quote!(_) 537 | } 538 | }); 539 | syn::FnArg::Typed(pat_type) 540 | } 541 | }) 542 | .collect(); 543 | sig 544 | }, 545 | default: None, 546 | semi_token: Some(Semi::default()), 547 | }), 548 | ImplItem::Const(const_) => acc.trait_consts.push(TraitItemConst { 549 | attrs: const_.attrs.clone(), 550 | generics: const_.generics.clone(), 551 | const_token: Default::default(), 552 | ident: const_.ident.clone(), 553 | colon_token: Default::default(), 554 | ty: const_.ty.clone(), 555 | default: None, 556 | semi_token: Default::default(), 557 | }), 558 | ImplItem::Type(_) => { 559 | return Err(syn::Error::new( 560 | item.span(), 561 | "Associated types are not allowed in #[ext] impls", 562 | )) 563 | } 564 | ImplItem::Macro(_) => { 565 | return Err(syn::Error::new( 566 | item.span(), 567 | "Macros are not allowed in #[ext] impls", 568 | )) 569 | } 570 | ImplItem::Verbatim(_) | _ => { 571 | return Err(syn::Error::new(item.span(), "Not allowed in #[ext] impls")) 572 | } 573 | } 574 | } 575 | Ok(acc) 576 | } 577 | 578 | #[derive(Debug)] 579 | struct Config { 580 | ext_trait_name: Option, 581 | visibility: Visibility, 582 | supertraits: Option>, 583 | } 584 | 585 | impl Parse for Config { 586 | fn parse(input: ParseStream) -> parse::Result { 587 | let mut config = Config::default(); 588 | 589 | if let Ok(visibility) = input.parse::() { 590 | config.visibility = visibility; 591 | } 592 | 593 | input.parse::().ok(); 594 | 595 | while !input.is_empty() { 596 | let ident = input.parse::()?; 597 | input.parse::()?; 598 | 599 | match &*ident.to_string() { 600 | "name" => { 601 | config.ext_trait_name = Some(input.parse()?); 602 | } 603 | "supertraits" => { 604 | config.supertraits = 605 | Some(Punctuated::::parse_terminated(input)?); 606 | } 607 | _ => return Err(syn::Error::new(ident.span(), "Unknown configuration name")), 608 | } 609 | 610 | input.parse::().ok(); 611 | } 612 | 613 | Ok(config) 614 | } 615 | } 616 | 617 | impl Default for Config { 618 | fn default() -> Self { 619 | Self { 620 | ext_trait_name: None, 621 | visibility: Visibility::Inherited, 622 | supertraits: None, 623 | } 624 | } 625 | } 626 | 627 | fn punctuated_from_iter(i: I) -> Punctuated 628 | where 629 | P: Default, 630 | I: IntoIterator, 631 | { 632 | let mut iter = i.into_iter().peekable(); 633 | let mut acc = Punctuated::default(); 634 | 635 | while let Some(item) = iter.next() { 636 | acc.push_value(item); 637 | 638 | if iter.peek().is_some() { 639 | acc.push_punct(P::default()); 640 | } 641 | } 642 | 643 | acc 644 | } 645 | 646 | #[cfg(test)] 647 | mod test { 648 | #[allow(unused_imports)] 649 | use super::*; 650 | 651 | #[test] 652 | fn test_ui() { 653 | let t = trybuild::TestCases::new(); 654 | t.pass("tests/compile_pass/*.rs"); 655 | t.compile_fail("tests/compile_fail/*.rs"); 656 | } 657 | } 658 | -------------------------------------------------------------------------------- /tests/compile_fail/double_vis.rs: -------------------------------------------------------------------------------- 1 | mod a { 2 | use extend::ext; 3 | 4 | #[ext(pub(super))] 5 | pub impl i32 { 6 | fn foo() -> Foo { 7 | Foo 8 | } 9 | } 10 | 11 | pub struct Foo; 12 | } 13 | 14 | fn main() {} 15 | -------------------------------------------------------------------------------- /tests/compile_fail/double_vis.stderr: -------------------------------------------------------------------------------- 1 | error: Cannot set visibility on `#[ext]` and `impl` block 2 | --> $DIR/double_vis.rs:4:11 3 | | 4 | 4 | #[ext(pub(super))] 5 | | ^^^ 6 | -------------------------------------------------------------------------------- /tests/compile_fail/supertraits_are_actually_included.rs: -------------------------------------------------------------------------------- 1 | use extend::ext; 2 | 3 | trait MyTrait {} 4 | 5 | #[ext(supertraits = MyTrait)] 6 | impl String { 7 | fn my_len(&self) -> usize { 8 | self.len() 9 | } 10 | } 11 | 12 | fn main() { 13 | assert_eq!(String::new().my_len(), 0); 14 | } 15 | -------------------------------------------------------------------------------- /tests/compile_fail/supertraits_are_actually_included.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `String: MyTrait` is not satisfied 2 | --> tests/compile_fail/supertraits_are_actually_included.rs:6:6 3 | | 4 | 6 | impl String { 5 | | ^^^^^^ the trait `MyTrait` is not implemented for `String` 6 | | 7 | note: required by a bound in `StringExt` 8 | --> tests/compile_fail/supertraits_are_actually_included.rs:5:21 9 | | 10 | 5 | #[ext(supertraits = MyTrait)] 11 | | ^^^^^^^ required by this bound in `StringExt` 12 | 6 | impl String { 13 | | ------ required by a bound in this 14 | -------------------------------------------------------------------------------- /tests/compile_pass/associated_constants.rs: -------------------------------------------------------------------------------- 1 | use extend::ext; 2 | 3 | #[ext] 4 | impl Option { 5 | const FOO: usize = 1; 6 | } 7 | 8 | fn main() { 9 | assert_eq!(Option::::FOO, 1); 10 | } 11 | -------------------------------------------------------------------------------- /tests/compile_pass/async_trait.rs: -------------------------------------------------------------------------------- 1 | use extend::ext; 2 | use async_trait::async_trait; 3 | 4 | #[ext] 5 | #[async_trait] 6 | impl String { 7 | async fn foo() -> usize { 8 | 1 9 | } 10 | } 11 | 12 | #[ext] 13 | #[async_trait] 14 | pub impl i32 { 15 | async fn bar() -> usize { 16 | 1 17 | } 18 | } 19 | 20 | async fn foo() { 21 | let _: usize = String::foo().await; 22 | let _: usize = i32::bar().await; 23 | } 24 | 25 | fn main() {} 26 | -------------------------------------------------------------------------------- /tests/compile_pass/changing_extension_trait_name.rs: -------------------------------------------------------------------------------- 1 | use extend::ext; 2 | 3 | #[ext(name = Foo)] 4 | impl i32 { 5 | fn foo() {} 6 | } 7 | 8 | fn main() { 9 | ::foo(); 10 | } 11 | -------------------------------------------------------------------------------- /tests/compile_pass/complex_trait_name.rs: -------------------------------------------------------------------------------- 1 | mod foo { 2 | use extend::ext; 3 | 4 | #[ext(pub)] 5 | impl (T1, T2, T3) { 6 | fn size(&self) -> usize { 7 | 3 8 | } 9 | } 10 | } 11 | 12 | fn main() { 13 | use foo::TupleOfT1T2T3Ext; 14 | 15 | assert_eq!(3, (0, 0, 0).size()); 16 | } 17 | -------------------------------------------------------------------------------- /tests/compile_pass/destructure.rs: -------------------------------------------------------------------------------- 1 | #![allow(warnings)] 2 | 3 | use extend::ext; 4 | 5 | #[ext] 6 | impl i32 { 7 | fn foo(self, (a, b): (i32, i32)) {} 8 | 9 | fn bar(self, [a, b]: [i32; 2]) {} 10 | } 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /tests/compile_pass/double_ext_on_same_type.rs: -------------------------------------------------------------------------------- 1 | use extend::ext; 2 | 3 | #[ext] 4 | impl Option { 5 | fn foo() -> usize { 6 | 1 7 | } 8 | } 9 | 10 | #[ext] 11 | impl Option { 12 | fn bar() -> i32 { 13 | 1 14 | } 15 | } 16 | 17 | fn main() {} 18 | -------------------------------------------------------------------------------- /tests/compile_pass/extension_on_complex_types.rs: -------------------------------------------------------------------------------- 1 | #![allow(warnings)] 2 | 3 | use extend::ext; 4 | 5 | #[ext] 6 | impl<'a> &'a str { 7 | fn foo(self) {} 8 | } 9 | 10 | #[ext] 11 | impl [T; 3] { 12 | fn foo(self) {} 13 | } 14 | 15 | #[ext] 16 | impl *const i32 { 17 | fn foo(self) {} 18 | } 19 | 20 | #[ext] 21 | impl [T] { 22 | fn foo(&self) {} 23 | } 24 | 25 | #[ext] 26 | impl<'a, T> &'a [T] { 27 | fn foo(self) {} 28 | } 29 | 30 | #[ext] 31 | impl (i32, i64) { 32 | fn foo(self) {} 33 | } 34 | 35 | #[ext] 36 | impl fn(i32) -> bool { 37 | fn foo(self) {} 38 | } 39 | 40 | fn bare_fn(_: i32) -> bool { 41 | false 42 | } 43 | 44 | #[ext] 45 | impl dyn Send + Sync + 'static {} 46 | 47 | fn main() { 48 | "".foo(); 49 | 50 | [1, 2, 3].foo(); 51 | 52 | let ptr: *const i32 = &123; 53 | ptr.foo(); 54 | 55 | &[1, 2, 3].foo(); 56 | 57 | (1i32, 1i64).foo(); 58 | 59 | (bare_fn as fn(i32) -> bool).foo(); 60 | } 61 | -------------------------------------------------------------------------------- /tests/compile_pass/generics.rs: -------------------------------------------------------------------------------- 1 | use extend::ext; 2 | 3 | #[ext] 4 | impl<'a, T: Clone> Vec<&'a T> 5 | where 6 | T: 'a + Copy, 7 | { 8 | fn size(&self) -> usize { 9 | self.len() 10 | } 11 | } 12 | 13 | fn main() { 14 | assert_eq!(3, vec![&1, &2, &3].size()); 15 | } 16 | -------------------------------------------------------------------------------- /tests/compile_pass/hello_world.rs: -------------------------------------------------------------------------------- 1 | use extend::ext; 2 | 3 | #[ext] 4 | impl i32 { 5 | fn add_one(&self) -> Self { 6 | self + 1 7 | } 8 | 9 | fn foo() -> MyType { 10 | MyType 11 | } 12 | } 13 | 14 | #[derive(Debug, Eq, PartialEq)] 15 | struct MyType; 16 | 17 | fn main() { 18 | assert_eq!(i32::foo(), MyType); 19 | assert_eq!(1.add_one(), 2); 20 | } 21 | -------------------------------------------------------------------------------- /tests/compile_pass/issue_2.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_variables)] 2 | 3 | use extend::ext; 4 | use std::iter::FromIterator; 5 | 6 | #[ext] 7 | impl C 8 | where 9 | C: IntoIterator, 10 | K: Eq, 11 | F: Fn(&T) -> K, 12 | { 13 | fn group_by(self, f: F) -> Out 14 | where 15 | Out: FromIterator<(K, Vec)>, 16 | { 17 | todo!() 18 | } 19 | 20 | fn group_by_and_map_values(self, f: F, g: G) -> Out 21 | where 22 | G: Fn(T) -> T2 + Copy, 23 | Out: FromIterator<(K, Vec)>, 24 | { 25 | todo!() 26 | } 27 | 28 | fn group_by_and_return_groups(self, f: F) -> Vec> { 29 | todo!() 30 | } 31 | } 32 | 33 | fn main() {} 34 | -------------------------------------------------------------------------------- /tests/compile_pass/more_than_one_extension.rs: -------------------------------------------------------------------------------- 1 | use extend::ext; 2 | 3 | #[ext] 4 | impl i32 { 5 | fn foo() {} 6 | } 7 | 8 | #[ext] 9 | impl i64 { 10 | fn bar() {} 11 | } 12 | 13 | fn main() {} 14 | -------------------------------------------------------------------------------- /tests/compile_pass/multiple_config.rs: -------------------------------------------------------------------------------- 1 | use extend::ext; 2 | 3 | #[ext(pub(crate), name = Foo)] 4 | impl i32 { 5 | fn foo() {} 6 | } 7 | 8 | #[ext(pub, name = Bar)] 9 | impl i64 { 10 | fn foo() {} 11 | } 12 | 13 | fn main() {} 14 | -------------------------------------------------------------------------------- /tests/compile_pass/multiple_generic_params.rs: -------------------------------------------------------------------------------- 1 | use extend::ext; 2 | use std::marker::PhantomData; 3 | 4 | struct Foo(PhantomData); 5 | 6 | #[ext] 7 | impl T { 8 | fn some_method(&self, _: Foo) {} 9 | } 10 | 11 | fn main() {} 12 | -------------------------------------------------------------------------------- /tests/compile_pass/pub_impl.rs: -------------------------------------------------------------------------------- 1 | mod a { 2 | use extend::ext; 3 | 4 | #[ext] 5 | pub impl i32 { 6 | fn foo() -> Foo { Foo } 7 | } 8 | 9 | pub struct Foo; 10 | } 11 | 12 | fn main() { 13 | use a::i32Ext; 14 | i32::foo(); 15 | } 16 | -------------------------------------------------------------------------------- /tests/compile_pass/ref_and_ref_mut.rs: -------------------------------------------------------------------------------- 1 | use extend::ext; 2 | 3 | #[ext] 4 | impl &i32 { 5 | fn foo() {} 6 | } 7 | 8 | #[ext] 9 | impl &mut i32 { 10 | fn bar() {} 11 | } 12 | 13 | fn main() {} 14 | -------------------------------------------------------------------------------- /tests/compile_pass/sized.rs: -------------------------------------------------------------------------------- 1 | use extend::ext_sized; 2 | 3 | #[ext_sized(name = One)] 4 | impl i32 { 5 | fn requires_sized(self) -> Option { 6 | Some(self) 7 | } 8 | } 9 | 10 | #[ext_sized(name = Two, supertraits = Default)] 11 | impl i32 { 12 | fn with_another_supertrait(self) -> Option { 13 | Some(self) 14 | } 15 | } 16 | 17 | #[ext_sized(name = Three, supertraits = Default + Clone + Copy)] 18 | impl i32 { 19 | fn multiple_supertraits(self) -> Option { 20 | Some(self) 21 | } 22 | } 23 | 24 | #[ext_sized(name = Four, supertraits = Sized)] 25 | impl i32 { 26 | fn already_sized(self) -> Option { 27 | Some(self) 28 | } 29 | } 30 | 31 | fn main() { 32 | 1.requires_sized(); 33 | 1.with_another_supertrait(); 34 | 1.multiple_supertraits(); 35 | 1.already_sized(); 36 | } 37 | -------------------------------------------------------------------------------- /tests/compile_pass/super_trait.rs: -------------------------------------------------------------------------------- 1 | use extend::ext; 2 | 3 | trait MyTrait {} 4 | 5 | impl MyTrait for String {} 6 | 7 | #[ext(supertraits = Default + Clone + MyTrait)] 8 | impl String { 9 | fn my_len(&self) -> usize { 10 | self.len() 11 | } 12 | } 13 | 14 | fn main() { 15 | assert_eq!(String::new().my_len(), 0); 16 | } 17 | -------------------------------------------------------------------------------- /tests/compile_pass/visibility_config.rs: -------------------------------------------------------------------------------- 1 | mod a { 2 | use extend::ext; 3 | 4 | #[ext(pub)] 5 | impl i32 { 6 | fn foo() -> Foo { Foo } 7 | } 8 | 9 | pub struct Foo; 10 | } 11 | 12 | fn main() { 13 | use a::i32Ext; 14 | i32::foo(); 15 | } 16 | --------------------------------------------------------------------------------