trait-variant-0.1.2/.cargo_vcs_info.json0000644000000001530000000000100136200ustar { "git": { "sha1": "a9bea3f025b2474c60b508408b09324642978e5a" }, "path_in_vcs": "trait-variant" }trait-variant-0.1.2/Cargo.lock0000644000000074770000000000100116130ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "backtrace" version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", ] [[package]] name = "cc" version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "libc", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "gimli" version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "libc" version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "memchr" version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "miniz_oxide" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", ] [[package]] name = "object" version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] [[package]] name = "pin-project-lite" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "proc-macro2" version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "syn" version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tokio" version = "1.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c" dependencies = [ "backtrace", "pin-project-lite", ] [[package]] name = "trait-variant" version = "0.1.2" dependencies = [ "proc-macro2", "quote", "syn", "tokio", ] [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" trait-variant-0.1.2/Cargo.toml0000644000000020660000000000100116230ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.75" name = "trait-variant" version = "0.1.2" description = "Utilities for working with impl traits in Rust" readme = "README.md" keywords = [ "async", "trait", "impl", ] categories = [ "asynchronous", "no-std", "rust-patterns", ] license = "MIT OR Apache-2.0" repository = "https://github.com/rust-lang/impl-trait-utils" [lib] proc-macro = true [dependencies.proc-macro2] version = "1.0" [dependencies.quote] version = "1.0" [dependencies.syn] version = "2.0" features = ["full"] [dev-dependencies.tokio] version = "1" features = ["rt"] trait-variant-0.1.2/Cargo.toml.orig000064400000000000000000000016261046102023000153050ustar 00000000000000# Copyright (c) 2023 Google LLC # # Licensed under the Apache License, Version 2.0 or the MIT license # , at your # option. This file may not be copied, modified, or distributed # except according to those terms. [package] name = "trait-variant" version = "0.1.2" description = "Utilities for working with impl traits in Rust" categories = ["asynchronous", "no-std", "rust-patterns"] keywords = ["async", "trait", "impl"] license.workspace = true repository.workspace = true edition = "2021" rust-version = "1.75" [lib] proc-macro = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] proc-macro2 = "1.0" quote = "1.0" syn = { version = "2.0", features = ["full"] } [dev-dependencies] tokio = { version = "1", features = ["rt"] } trait-variant-0.1.2/README.md000064400000000000000000000033551046102023000136760ustar 00000000000000[![Latest Version]][crates.io] [![Documentation]][docs.rs] [![GHA Status]][GitHub Actions] ![License] Utilities for working with `impl Trait`s in Rust. ## `trait_variant` `trait_variant` generates a specialized version of a base trait that uses `async fn` and/or `-> impl Trait`. For example, if you want a [`Send`][rust-std-send]able version of your trait, you'd write: ```rust #[trait_variant::make(IntFactory: Send)] trait LocalIntFactory { async fn make(&self) -> i32; fn stream(&self) -> impl Iterator; fn call(&self) -> u32; } ``` The `trait_variant::make` would generate an additional trait called `IntFactory`: ```rust use core::future::Future; trait IntFactory: Send { fn make(&self) -> impl Future + Send; fn stream(&self) -> impl Iterator + Send; fn call(&self) -> u32; } ``` Implementers can choose to implement either `LocalIntFactory` or `IntFactory` as appropriate. For more details, see the docs for [`trait_variant::make`]. [`trait_variant::make`]: https://docs.rs/trait-variant/latest/trait_variant/attr.make.html #### License and usage notes Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or [MIT license](LICENSE-MIT) at your option. [GitHub Actions]: https://github.com/rust-lang/impl-trait-utils/actions [GHA Status]: https://github.com/rust-lang/impl-trait-utils/actions/workflows/rust.yml/badge.svg [crates.io]: https://crates.io/crates/trait-variant [Latest Version]: https://img.shields.io/crates/v/trait-variant.svg [Documentation]: https://img.shields.io/docsrs/trait-variant [docs.rs]: https://docs.rs/trait-variant [License]: https://img.shields.io/crates/l/trait-variant.svg [rust-std-send]: https://doc.rust-lang.org/std/marker/trait.Send.html trait-variant-0.1.2/examples/variant.rs000064400000000000000000000031341046102023000162420ustar 00000000000000// Copyright (c) 2023 Google LLC // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use std::{fmt::Display, future::Future}; #[trait_variant::make(IntFactory: Send)] pub trait LocalIntFactory { const NAME: &'static str; type MyFut<'a>: Future where Self: 'a; async fn make(&self, x: u32, y: &str) -> i32; fn stream(&self) -> impl Iterator; fn call(&self) -> u32; fn another_async(&self, input: Result<(), &str>) -> Self::MyFut<'_>; } #[allow(dead_code)] fn spawn_task(factory: impl IntFactory + 'static) { tokio::spawn(async move { let _int = factory.make(1, "foo").await; }); } #[trait_variant::make(GenericTrait: Send)] pub trait LocalGenericTrait<'x, S: Sync, Y, const X: usize> where Y: Sync, { const CONST: usize = 3; type F; type A; type B: FromIterator; async fn take(&self, s: S); fn build(&self, items: impl Iterator) -> Self::B; } #[trait_variant::make(Send + Sync)] pub trait GenericTraitWithBounds<'x, S: Sync, Y, const X: usize> where Y: Sync, { const CONST: usize = 3; type F; type A; type B: FromIterator; async fn take(&self, s: S); fn build(&self, items: impl Iterator) -> Self::B; } fn main() {} trait-variant-0.1.2/src/lib.rs000064400000000000000000000036501046102023000143200ustar 00000000000000// Copyright (c) 2023 Google LLC // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. #![doc = include_str!("../README.md")] mod variant; /// Creates a specialized version of a base trait that adds bounds to `async /// fn` and/or `-> impl Trait` return types. /// /// ``` /// #[trait_variant::make(Send)] /// trait IntFactory { /// async fn make(&self) -> i32; /// fn stream(&self) -> impl Iterator; /// fn call(&self) -> u32; /// } /// ``` /// /// The above example causes the trait to be rewritten as: /// /// ``` /// # use core::future::Future; /// trait IntFactory: Send { /// fn make(&self) -> impl Future + Send; /// fn stream(&self) -> impl Iterator + Send; /// fn call(&self) -> u32; /// } /// ``` /// /// Note that ordinary methods such as `call` are not affected. /// /// If you want to preserve an original trait untouched, `make` can be used to create a new trait with bounds on `async /// fn` and/or `-> impl Trait` return types. /// /// ``` /// #[trait_variant::make(IntFactory: Send)] /// trait LocalIntFactory { /// async fn make(&self) -> i32; /// fn stream(&self) -> impl Iterator; /// fn call(&self) -> u32; /// } /// ``` /// /// The example causes a second trait called `IntFactory` to be created. /// Implementers of the trait can choose to implement the variant instead of the /// original trait. The macro creates a blanket impl which ensures that any type /// which implements the variant also implements the original trait. #[proc_macro_attribute] pub fn make( attr: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { variant::make(attr, item) } trait-variant-0.1.2/src/variant.rs000064400000000000000000000177511046102023000152250ustar 00000000000000// Copyright (c) 2023 Google LLC // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use std::iter; use proc_macro2::TokenStream; use quote::quote; use syn::{ parse::{Parse, ParseStream}, parse_macro_input, parse_quote, punctuated::Punctuated, token::Plus, Error, FnArg, GenericParam, Ident, ItemTrait, Pat, PatType, Result, ReturnType, Signature, Token, TraitBound, TraitItem, TraitItemConst, TraitItemFn, TraitItemType, Type, TypeGenerics, TypeImplTrait, TypeParam, TypeParamBound, }; struct Attrs { variant: MakeVariant, } impl Parse for Attrs { fn parse(input: ParseStream) -> Result { Ok(Self { variant: MakeVariant::parse(input)?, }) } } enum MakeVariant { // Creates a variant of a trait under a new name with additional bounds while preserving the original trait. Create { name: Ident, _colon: Token![:], bounds: Punctuated, }, // Rewrites the original trait into a new trait with additional bounds. Rewrite { bounds: Punctuated, }, } impl Parse for MakeVariant { fn parse(input: ParseStream) -> Result { let variant = if input.peek(Ident) && input.peek2(Token![:]) { MakeVariant::Create { name: input.parse()?, _colon: input.parse()?, bounds: input.parse_terminated(TraitBound::parse, Token![+])?, } } else { MakeVariant::Rewrite { bounds: input.parse_terminated(TraitBound::parse, Token![+])?, } }; Ok(variant) } } pub fn make( attr: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { let attrs = parse_macro_input!(attr as Attrs); let item = parse_macro_input!(item as ItemTrait); match attrs.variant { MakeVariant::Create { name, bounds, .. } => { let maybe_allow_async_lint = if bounds .iter() .any(|b| b.path.segments.last().unwrap().ident == "Send") { quote! { #[allow(async_fn_in_trait)] } } else { quote! {} }; let variant = mk_variant(&name, bounds, &item); let blanket_impl = mk_blanket_impl(&name, &item); quote! { #maybe_allow_async_lint #item #variant #blanket_impl } .into() } MakeVariant::Rewrite { bounds, .. } => { let variant = mk_variant(&item.ident, bounds, &item); quote! { #variant } .into() } } } fn mk_variant( variant: &Ident, with_bounds: Punctuated, tr: &ItemTrait, ) -> TokenStream { let bounds: Vec<_> = with_bounds .into_iter() .map(|b| TypeParamBound::Trait(b.clone())) .collect(); let variant = ItemTrait { ident: variant.clone(), supertraits: tr.supertraits.iter().chain(&bounds).cloned().collect(), items: tr .items .iter() .map(|item| transform_item(item, &bounds)) .collect(), ..tr.clone() }; quote! { #variant } } // Transforms one item declaration within the definition if it has `async fn` and/or `-> impl Trait` return types by adding new bounds. fn transform_item(item: &TraitItem, bounds: &Vec) -> TraitItem { let TraitItem::Fn(fn_item @ TraitItemFn { sig, .. }) = item else { return item.clone(); }; let (arrow, output) = if sig.asyncness.is_some() { let orig = match &sig.output { ReturnType::Default => quote! { () }, ReturnType::Type(_, ty) => quote! { #ty }, }; let future = syn::parse2(quote! { ::core::future::Future }).unwrap(); let ty = Type::ImplTrait(TypeImplTrait { impl_token: syn::parse2(quote! { impl }).unwrap(), bounds: iter::once(TypeParamBound::Trait(future)) .chain(bounds.iter().cloned()) .collect(), }); (syn::parse2(quote! { -> }).unwrap(), ty) } else { match &sig.output { ReturnType::Type(arrow, ty) => match &**ty { Type::ImplTrait(it) => { let ty = Type::ImplTrait(TypeImplTrait { impl_token: it.impl_token, bounds: it.bounds.iter().chain(bounds).cloned().collect(), }); (*arrow, ty) } _ => return item.clone(), }, ReturnType::Default => return item.clone(), } }; TraitItem::Fn(TraitItemFn { sig: Signature { asyncness: None, output: ReturnType::Type(arrow, Box::new(output)), ..sig.clone() }, ..fn_item.clone() }) } fn mk_blanket_impl(variant: &Ident, tr: &ItemTrait) -> TokenStream { let orig = &tr.ident; let (_impl, orig_ty_generics, _where) = &tr.generics.split_for_impl(); let items = tr .items .iter() .map(|item| blanket_impl_item(item, variant, orig_ty_generics)); let blanket_bound: TypeParam = parse_quote!(TraitVariantBlanketType: #variant #orig_ty_generics); let blanket = &blanket_bound.ident.clone(); let mut blanket_generics = tr.generics.clone(); blanket_generics .params .push(GenericParam::Type(blanket_bound)); let (blanket_impl_generics, _ty, blanket_where_clause) = &blanket_generics.split_for_impl(); quote! { impl #blanket_impl_generics #orig #orig_ty_generics for #blanket #blanket_where_clause { #(#items)* } } } fn blanket_impl_item( item: &TraitItem, variant: &Ident, trait_ty_generics: &TypeGenerics<'_>, ) -> TokenStream { // impl IntFactory for T where T: SendIntFactory { // const NAME: &'static str = ::NAME; // type MyFut<'a> = ::MyFut<'a> where Self: 'a; // async fn make(&self, x: u32, y: &str) -> i32 { // ::make(self, x, y).await // } // } match item { TraitItem::Const(TraitItemConst { ident, generics, ty, .. }) => { quote! { const #ident #generics: #ty = ::#ident; } } TraitItem::Fn(TraitItemFn { sig, .. }) => { let ident = &sig.ident; let args = sig.inputs.iter().map(|arg| match arg { FnArg::Receiver(_) => quote! { self }, FnArg::Typed(PatType { pat, .. }) => match &**pat { Pat::Ident(arg) => quote! { #arg }, _ => Error::new_spanned(pat, "patterns are not supported in arguments") .to_compile_error(), }, }); let maybe_await = if sig.asyncness.is_some() { quote! { .await } } else { quote! {} }; quote! { #sig { ::#ident(#(#args),*)#maybe_await } } } TraitItem::Type(TraitItemType { ident, generics, .. }) => { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); quote! { type #ident #impl_generics = ::#ident #ty_generics #where_clause; } } _ => Error::new_spanned(item, "unsupported item type").into_compile_error(), } }