From 416644097b841f1ad8fd5f3b1a594941f064cba3 Mon Sep 17 00:00:00 2001 From: Audun Halland Date: Wed, 27 Mar 2024 03:43:28 +0100 Subject: [PATCH] tmp: entrait 0.7 --- .github/workflows/rust.yml | 8 +- CHANGELOG.md | 9 ++ Cargo.toml | 12 +- README.md | 37 +---- entrait_macros/Cargo.toml | 2 +- entrait_macros/src/attributes.rs | 56 ++----- entrait_macros/src/entrait_fn/input_attr.rs | 11 +- entrait_macros/src/entrait_fn/mod.rs | 25 +-- entrait_macros/src/entrait_impl/input_attr.rs | 4 +- entrait_macros/src/entrait_impl/mod.rs | 24 ++- .../src/entrait_trait/input_attr.rs | 11 +- entrait_macros/src/entrait_trait/mod.rs | 108 ++++--------- entrait_macros/src/fn_delegation_codegen.rs | 53 ++----- entrait_macros/src/generics.rs | 38 +---- entrait_macros/src/lib.rs | 86 +---------- entrait_macros/src/opt.rs | 93 ++++++------ entrait_macros/src/signature/future.rs | 102 ------------- entrait_macros/src/signature/lifetimes.rs | 32 +--- entrait_macros/src/signature/mod.rs | 66 -------- entrait_macros/src/static_async_trait/mod.rs | 142 ------------------ entrait_macros/src/sub_attributes.rs | 46 ++++++ entrait_macros/src/trait_codegen.rs | 68 +++++++-- examples/async-graphql/Cargo.toml | 4 +- examples/async-graphql/src/main.rs | 2 +- examples/axum/Cargo.toml | 4 +- examples/axum/src/main.rs | 4 +- src/lib.rs | 106 +++---------- test_all_stable.sh | 6 +- tests/it/delegation_modes.rs | 5 +- tests/it/dependency_inversion.rs | 6 +- tests/it/main.rs | 2 +- tests/it/simple.rs | 27 +++- tests/it/unimock.rs | 44 ++---- 33 files changed, 328 insertions(+), 915 deletions(-) delete mode 100644 entrait_macros/src/static_async_trait/mod.rs create mode 100644 entrait_macros/src/sub_attributes.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 1994651..c135b0e 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -15,13 +15,13 @@ jobs: - uses: actions/checkout@v3 - uses: taiki-e/install-action@cargo-hack - name: Test feature powerset - run: cargo hack --feature-powerset --exclude-features "default use-associated-futures nightly-tests" --exclude-no-default-features test + run: cargo hack --feature-powerset --exclude-features "default nightly-tests" --exclude-no-default-features test - name: Test workspace - run: cargo test --workspace --features "boxed-futures" + run: cargo test --workspace --features "unimock" - name: Doctest - run: cargo test --doc --features "unimock use-boxed-futures" + run: cargo test --doc --features "unimock" - name: Clippy - run: cargo clippy --features "unimock use-boxed-futures" -- -D warnings + run: cargo clippy --features "unimock" -- -D warnings - name: Build examples run: cargo build --all diff --git a/CHANGELOG.md b/CHANGELOG.md index b56a466..a4a0316 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased +### Changed +- Unimock bumped to 0.6. +- Reworked async support. Rust now has native support for async functions in traits, which means that entrait doesn't need to interact with this in a hacky way anymore. +- Minimum Supported Rust Version bumped to 1.75. +### Added +- Improved interoperability with the `async_trait` macro, for scenarios where dynamic dispatch-delegation is used in combination with `async`. +- `?Send` argument to the entrait macro, for allowing opt-out of `Send` bounds for the `Future`s generated from `async` trait methods. +### Removed +- features `boxed-futures`, `use-boxed-futures` and `use-associated-futures`. ## [0.6.0] - 2023-10-15 ### Changed diff --git a/Cargo.toml b/Cargo.toml index 622f0b6..063b2bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "entrait" -version = "0.6.0" +version = "0.7.0-dev" authors = ["Audun Halland "] edition = "2021" -rust-version = "1.60" +rust-version = "1.75" license = "MIT" description = "Loosely coupled Rust application design made easy" repository = "https://github.com/audunhalland/entrait/" @@ -13,22 +13,20 @@ categories = ["rust-patterns", "development-tools::testing"] [features] default = [] unimock = ["dep:unimock"] -use-boxed-futures = ["boxed-futures"] -use-associated-futures = [] -boxed-futures = ["dep:async-trait"] nightly-tests = [] [dependencies] -entrait_macros = { path = "entrait_macros", version = "0.6.0" } +entrait_macros = { path = "entrait_macros", version = "0.7.0-dev" } implementation = "0.1" async-trait = { version = "0.1", optional = true } -unimock = { version = "0.6", optional = true } +unimock = { version = "0.6.2", optional = true } [dev-dependencies] tokio = { version = "1", features = ["macros", "rt"] } feignhttp = "0.5" mockall = "0.11" tracing = "0.1" +async-trait = "0.1" [lib] # do not run doctest by default with `cargo hack`. They are tested with a separate `cargo test --doc` run. diff --git a/README.md b/README.md index ab27a87..71ca137 100644 --- a/README.md +++ b/README.md @@ -527,37 +527,10 @@ fn foo(deps: &D) { // <-- private function ``` ##### `async` support -Since Rust at the time of writing does not natively support async methods in traits, you may opt in to having `#[async_trait]` generated for your trait. -Enable the `boxed-futures` cargo feature and pass the `box_future` option like this: +Zero-cost async works out of the box. -```rust -#[entrait(Foo, box_future)] -async fn foo(deps: &D) { -} -``` -This is designed to be forwards compatible with [static async fn in traits](https://rust-lang.github.io/rfcs/3185-static-async-fn-in-trait.html). -When that day comes, you should be able to just remove that option and get a proper zero-cost future. - -There is a cargo feature to automatically apply `#[async_trait]` to every generated async trait: `use-boxed-futures`. - -##### Zero-cost async inversion of control - preview mode -Entrait has experimental support for zero-cost futures. A nightly Rust compiler is needed for this feature. - -The entrait option is called `associated_future`, and uses GATs and `feature(type_alias_impl_trait)`. -This feature generates an associated future inside the trait, and the implementations use `impl Trait` syntax to infer -the resulting type of the future: - -```rust -#![feature(type_alias_impl_trait)] - -use entrait::*; - -#[entrait(Foo, associated_future)] -async fn foo(deps: &D) { -} -``` - -There is a feature for turning this on everywhere: `use-associated-futures`. +When dynamic dispatch is needed, for example in combination with `delegate_by=ref`, entrait understands the `#[async_trait]` attribute when applied after the entrait macro. +Entrait will re-apply that macro to the various impl blocks that get generated. ##### Integrating with other `fn`-targeting macros, and `no_deps` Some macros are used to transform the body of a function, or generate a body from scratch. @@ -598,10 +571,6 @@ It is also possible to reduce noise by doing `use entrait::entrait_export as ent | Feature | Implies | Description | | ------------------- | --------------- | ------------------- | | `unimock` | | Adds the [unimock] dependency, and turns on Unimock implementations for all traits. | -| `use-boxed-futures` | `boxed-futures` | Automatically applies the [async_trait] macro to async trait methods. | -| `use-associated-futures` | | Automatically transforms the return type of async trait methods into an associated future by using type-alias-impl-trait syntax. Requires a nightly compiler. | -| `boxed-futures` | | Pulls in the [async_trait] optional dependency, enabling the `box_future` entrait option (macro parameter). | - ## "Philosophy" diff --git a/entrait_macros/Cargo.toml b/entrait_macros/Cargo.toml index 17b8b90..86d1df6 100644 --- a/entrait_macros/Cargo.toml +++ b/entrait_macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "entrait_macros" -version = "0.6.0" +version = "0.7.0-dev" authors = ["Audun Halland "] edition = "2021" rust-version = "1.60" diff --git a/entrait_macros/src/attributes.rs b/entrait_macros/src/attributes.rs index 3a9f40e..0949073 100644 --- a/entrait_macros/src/attributes.rs +++ b/entrait_macros/src/attributes.rs @@ -2,7 +2,7 @@ use crate::analyze_generics::TraitFn; use crate::generics::{self, TraitIndirection}; use crate::idents::CrateIdents; use crate::input::FnInputMode; -use crate::opt::{AsyncStrategy, MockApiIdent, Opts, SpanOpt}; +use crate::opt::{MockApiIdent, Opts}; use crate::token_util::{comma_sep, push_tokens}; use proc_macro2::{Span, TokenStream}; @@ -229,58 +229,22 @@ impl ToTokens for MockallAutomockParams { } } -pub fn opt_async_trait_attr<'s, 'o>( - opts: &'s Opts, - crate_idents: &'s CrateIdents, - trait_fns: impl Iterator, -) -> Option { - match ( - opts.async_strategy(), - generics::has_any_async(trait_fns.map(|trait_fn| trait_fn.sig())), - ) { - (SpanOpt(AsyncStrategy::BoxFuture, span), true) => Some(Attr(AsyncTraitParams { - crate_idents, - use_static: false, - span, - })), - (SpanOpt(AsyncStrategy::AssociatedFuture, span), true) => Some(Attr(AsyncTraitParams { - crate_idents, - use_static: true, - span, - })), - _ => None, - } -} - pub struct AsyncTraitParams<'a> { pub crate_idents: &'a CrateIdents, - pub use_static: bool, pub span: Span, } impl<'a> ToTokens for AsyncTraitParams<'a> { fn to_tokens(&self, stream: &mut TokenStream) { let span = self.span; - if self.use_static { - push_tokens!( - stream, - syn::token::PathSep(span), - self.crate_idents.entrait, - syn::token::PathSep(span), - syn::Ident::new("static_async", span), - syn::token::PathSep(span), - syn::Ident::new("async_trait", span) - ); - } else { - push_tokens!( - stream, - syn::token::PathSep(span), - self.crate_idents.entrait, - syn::token::PathSep(span), - syn::Ident::new("__async_trait", span), - syn::token::PathSep(span), - syn::Ident::new("async_trait", span) - ); - } + push_tokens!( + stream, + syn::token::PathSep(span), + self.crate_idents.entrait, + syn::token::PathSep(span), + syn::Ident::new("__async_trait", span), + syn::token::PathSep(span), + syn::Ident::new("async_trait", span) + ); } } diff --git a/entrait_macros/src/entrait_fn/input_attr.rs b/entrait_macros/src/entrait_fn/input_attr.rs index bfc502f..b504623 100644 --- a/entrait_macros/src/entrait_fn/input_attr.rs +++ b/entrait_macros/src/entrait_fn/input_attr.rs @@ -21,8 +21,8 @@ impl Parse for EntraitFnAttr { let mut no_deps = None; let mut debug = None; - let mut async_strategy = None; let mut export = None; + let mut future_send = None; let mut mock_api = None; let mut unimock = None; let mut mockall = None; @@ -33,13 +33,8 @@ impl Parse for EntraitFnAttr { match input.parse::()? { EntraitOpt::NoDeps(opt) => no_deps = Some(opt), EntraitOpt::Debug(opt) => debug = Some(opt), - EntraitOpt::BoxFuture(opt) => { - async_strategy = Some(SpanOpt(AsyncStrategy::BoxFuture, opt.1)) - } - EntraitOpt::AssociatedFuture(opt) => { - async_strategy = Some(SpanOpt(AsyncStrategy::AssociatedFuture, opt.1)) - } EntraitOpt::Export(opt) => export = Some(opt), + EntraitOpt::MaybeSend(send) => future_send = Some(send), EntraitOpt::MockApi(ident) => mock_api = Some(ident), EntraitOpt::Unimock(opt) => unimock = Some(opt), EntraitOpt::Mockall(opt) => mockall = Some(opt), @@ -56,8 +51,8 @@ impl Parse for EntraitFnAttr { default_span, no_deps, debug, - async_strategy, export, + future_send, mock_api, unimock, mockall, diff --git a/entrait_macros/src/entrait_fn/mod.rs b/entrait_macros/src/entrait_fn/mod.rs index cb4d0f8..e98ac92 100644 --- a/entrait_macros/src/entrait_fn/mod.rs +++ b/entrait_macros/src/entrait_fn/mod.rs @@ -13,6 +13,7 @@ use crate::generics; use crate::input::FnInputMode; use crate::input::{InputFn, InputMod, ModItem}; use crate::signature; +use crate::sub_attributes::analyze_sub_attributes; use crate::trait_codegen::Supertraits; use crate::trait_codegen::TraitCodegen; use input_attr::*; @@ -33,6 +34,7 @@ pub fn entrait_for_single_fn(attr: &EntraitFnAttr, input_fn: InputFn) -> syn::Re opts: &attr.opts, } .analyze(input_fn.input_sig(), &mut generics_analyzer)?]; + let sub_attributes = analyze_sub_attributes(&input_fn.fn_attrs); let trait_dependency_mode = detect_trait_dependency_mode( &fn_input_mode, @@ -40,15 +42,13 @@ pub fn entrait_for_single_fn(attr: &EntraitFnAttr, input_fn: InputFn) -> syn::Re &attr.crate_idents, attr.trait_ident.span(), )?; - let use_associated_future = - generics::detect_use_associated_future(&attr.opts, [&input_fn].into_iter()); - let trait_generics = generics_analyzer.into_trait_generics(); let trait_def = TraitCodegen { opts: &attr.opts, crate_idents: &attr.crate_idents, trait_indirection: generics::TraitIndirection::Plain, trait_dependency_mode: &trait_dependency_mode, + sub_attributes: &sub_attributes, } .gen_trait_def( &attr.trait_visibility, @@ -58,6 +58,7 @@ pub fn entrait_for_single_fn(attr: &EntraitFnAttr, input_fn: InputFn) -> syn::Re &trait_fns, &fn_input_mode, )?; + let impl_block = fn_delegation_codegen::FnDelegationCodegen { opts: &attr.opts, crate_idents: &attr.crate_idents, @@ -67,7 +68,7 @@ pub fn entrait_for_single_fn(attr: &EntraitFnAttr, input_fn: InputFn) -> syn::Re trait_generics: &trait_generics, fn_input_mode: &fn_input_mode, trait_dependency_mode: &trait_dependency_mode, - use_associated_future, + sub_attributes: &sub_attributes, } .gen_impl_block(&trait_fns); @@ -79,11 +80,15 @@ pub fn entrait_for_single_fn(attr: &EntraitFnAttr, input_fn: InputFn) -> syn::Re .. } = input_fn; - Ok(quote! { + let out = quote! { #(#fn_attrs)* #fn_vis #fn_sig #fn_body #trait_def #impl_block - }) + }; + + // println!("\n\nfn output: {out}"); + + Ok(out) } pub fn entrait_for_mod(attr: &EntraitFnAttr, input_mod: InputMod) -> syn::Result { @@ -103,6 +108,7 @@ pub fn entrait_for_mod(attr: &EntraitFnAttr, input_mod: InputMod) -> syn::Result .analyze(input_fn.input_sig(), &mut generics_analyzer) }) .collect::>>()?; + let sub_attributes = analyze_sub_attributes(&input_mod.attrs); let trait_dependency_mode = detect_trait_dependency_mode( &fn_input_mode, @@ -110,10 +116,6 @@ pub fn entrait_for_mod(attr: &EntraitFnAttr, input_mod: InputMod) -> syn::Result &attr.crate_idents, attr.trait_ident.span(), )?; - let use_associated_future = generics::detect_use_associated_future( - &attr.opts, - input_mod.items.iter().filter_map(ModItem::filter_pub_fn), - ); let trait_generics = generics_analyzer.into_trait_generics(); let trait_def = TraitCodegen { @@ -121,6 +123,7 @@ pub fn entrait_for_mod(attr: &EntraitFnAttr, input_mod: InputMod) -> syn::Result crate_idents: &attr.crate_idents, trait_indirection: generics::TraitIndirection::Plain, trait_dependency_mode: &trait_dependency_mode, + sub_attributes: &sub_attributes, } .gen_trait_def( &attr.trait_visibility, @@ -139,7 +142,7 @@ pub fn entrait_for_mod(attr: &EntraitFnAttr, input_mod: InputMod) -> syn::Result trait_generics: &trait_generics, fn_input_mode: &fn_input_mode, trait_dependency_mode: &trait_dependency_mode, - use_associated_future, + sub_attributes: &sub_attributes, } .gen_impl_block(&trait_fns); diff --git a/entrait_macros/src/entrait_impl/input_attr.rs b/entrait_macros/src/entrait_impl/input_attr.rs index dffc833..0d248a8 100644 --- a/entrait_macros/src/entrait_impl/input_attr.rs +++ b/entrait_macros/src/entrait_impl/input_attr.rs @@ -52,8 +52,8 @@ impl Parse for EntraitSimpleImplAttr { default_span: span, no_deps: None, debug, - async_strategy: None, export: None, + future_send: None, mock_api: None, unimock: None, mockall: None, @@ -96,8 +96,8 @@ impl Parse for EntraitImplAttr { default_span: span, no_deps: None, debug, - async_strategy: None, export: None, + future_send: None, mock_api: None, unimock: None, mockall: None, diff --git a/entrait_macros/src/entrait_impl/mod.rs b/entrait_macros/src/entrait_impl/mod.rs index c20ddb5..faf17a4 100644 --- a/entrait_macros/src/entrait_impl/mod.rs +++ b/entrait_macros/src/entrait_impl/mod.rs @@ -7,9 +7,9 @@ use crate::fn_delegation_codegen; use crate::generics; use crate::input::ImplItem; use crate::input::InputImpl; -use crate::opt::AsyncStrategy; -use crate::opt::SpanOpt; use crate::signature; +use crate::sub_attributes::analyze_sub_attributes; +use crate::sub_attributes::SubAttribute; use quote::quote; use syn::spanned::Spanned; @@ -18,7 +18,7 @@ use self::input_attr::EntraitSimpleImplAttr; use self::input_attr::ImplKind; pub fn output_tokens_for_impl( - mut attr: EntraitSimpleImplAttr, + attr: EntraitSimpleImplAttr, InputImpl { attrs, unsafety, @@ -30,11 +30,6 @@ pub fn output_tokens_for_impl( items, }: InputImpl, ) -> syn::Result { - // Using a dyn implementation implies boxed futures. - if matches!(attr.impl_kind, ImplKind::DynRef) { - attr.opts.async_strategy = Some(SpanOpt(AsyncStrategy::BoxFuture, self_ty.span())); - } - let trait_span = trait_path .segments .last() @@ -58,16 +53,13 @@ pub fn output_tokens_for_impl( .analyze(input_fn.input_sig(), &mut generics_analyzer) }) .collect::>>()?; + let sub_attributes = analyze_sub_attributes(&attrs); let trait_generics = generics_analyzer.into_trait_generics(); let fn_input_mode = crate::input::FnInputMode::ImplBlock(&self_ty); let trait_dependency_mode = detect_trait_dependency_mode(&fn_input_mode, &trait_fns, &attr.crate_idents, trait_span)?; - let use_associated_future = generics::detect_use_associated_future( - &attr.opts, - items.iter().filter_map(ImplItem::filter_fn), - ); let impl_indirection = match attr.impl_kind { ImplKind::Static => generics::ImplIndirection::Static { ty: &self_ty }, @@ -83,12 +75,16 @@ pub fn output_tokens_for_impl( trait_generics: &trait_generics, fn_input_mode: &fn_input_mode, trait_dependency_mode: &trait_dependency_mode, - use_associated_future, + sub_attributes: &sub_attributes, } .gen_impl_block(&trait_fns); + let inherent_sub_attrs = sub_attributes + .iter() + .filter(|sub_attr| !matches!(sub_attr, SubAttribute::AsyncTrait(_))); + Ok(quote! { - #(#attrs)* + #(#inherent_sub_attrs)* #unsafety #impl_token #self_ty { #(#items)* } diff --git a/entrait_macros/src/entrait_trait/input_attr.rs b/entrait_macros/src/entrait_trait/input_attr.rs index fe7b702..a8fc0f2 100644 --- a/entrait_macros/src/entrait_trait/input_attr.rs +++ b/entrait_macros/src/entrait_trait/input_attr.rs @@ -30,8 +30,8 @@ impl Parse for EntraitTraitAttr { } let mut debug = None; - let mut async_strategy = None; let mut mock_api = None; + let mut future_send = None; let mut unimock = None; let mut mockall = None; let mut delegation_kind = None; @@ -40,13 +40,8 @@ impl Parse for EntraitTraitAttr { loop { match input.parse::()? { EntraitOpt::Debug(opt) => debug = Some(opt), - EntraitOpt::BoxFuture(opt) => { - async_strategy = Some(SpanOpt(AsyncStrategy::BoxFuture, opt.1)) - } - EntraitOpt::AssociatedFuture(opt) => { - async_strategy = Some(SpanOpt(AsyncStrategy::AssociatedFuture, opt.1)) - } EntraitOpt::MockApi(ident) => mock_api = Some(ident), + EntraitOpt::MaybeSend(send) => future_send = Some(send), EntraitOpt::Unimock(opt) => unimock = Some(opt), EntraitOpt::Mockall(opt) => mockall = Some(opt), EntraitOpt::DelegateBy(kind) => delegation_kind = Some(kind), @@ -69,8 +64,8 @@ impl Parse for EntraitTraitAttr { default_span: proc_macro2::Span::call_site(), no_deps: None, debug, - async_strategy, export: None, + future_send, mock_api, unimock, mockall, diff --git a/entrait_macros/src/entrait_trait/mod.rs b/entrait_macros/src/entrait_trait/mod.rs index d8beecf..77dd715 100644 --- a/entrait_macros/src/entrait_trait/mod.rs +++ b/entrait_macros/src/entrait_trait/mod.rs @@ -7,7 +7,6 @@ use input_attr::EntraitTraitAttr; use proc_macro2::Span; use crate::analyze_generics::TraitFn; -use crate::attributes; use crate::entrait_trait::input_attr::ImplTrait; use crate::generics; use crate::generics::TraitDependencyMode; @@ -15,6 +14,8 @@ use crate::idents::GenericIdents; use crate::input::FnInputMode; use crate::input::LiteralAttrs; use crate::opt::*; +use crate::sub_attributes::analyze_sub_attributes; +use crate::sub_attributes::SubAttribute; use crate::token_util::*; use crate::trait_codegen::Supertraits; use crate::trait_codegen::TraitCodegen; @@ -46,19 +47,15 @@ pub fn output_tokens( syn::TraitItem::Fn(method) => method.sig.asyncness.is_some(), _ => false, })); - let impl_attrs = item_trait - .attrs - .iter() - .filter(|attr| { - matches!( - attr.path().segments.last(), - Some(last_segment) if last_segment.ident == "async_trait" - ) - }) - .cloned() - .collect::>(); let out_trait = out_trait::analyze_trait(item_trait)?; + let sub_attributes = analyze_sub_attributes(&out_trait.attrs); + let impl_sub_attributes: Vec<_> = sub_attributes + .iter() + .copied() + .filter(|sub_attr| matches!(sub_attr, SubAttribute::AsyncTrait(_))) + .collect(); + let trait_dependency_mode = TraitDependencyMode::Generic(GenericIdents::new( &attr.crate_idents, out_trait.ident.span(), @@ -68,20 +65,20 @@ pub fn output_tokens( _ => panic!(), }; - let mut impl_async_trait_attr = - attributes::opt_async_trait_attr(&attr.opts, &attr.crate_idents, out_trait.fns.iter()); - if !impl_attrs.is_empty() { - impl_async_trait_attr = None; - } - - let delegation_trait_def = - gen_impl_delegation_trait_defs(&out_trait, &trait_dependency_mode, generic_idents, &attr)?; + let delegation_trait_def = gen_impl_delegation_trait_defs( + &out_trait, + &trait_dependency_mode, + generic_idents, + &impl_sub_attributes, + &attr, + )?; let trait_def = TraitCodegen { crate_idents: &attr.crate_idents, opts: &attr.opts, trait_indirection: generics::TraitIndirection::Trait, trait_dependency_mode: &trait_dependency_mode, + sub_attributes: &sub_attributes, } .gen_trait_def( &out_trait.vis, @@ -95,7 +92,6 @@ pub fn output_tokens( let trait_ident = &out_trait.ident; let params = out_trait.generics.impl_params_from_idents( generic_idents, - generics::UseAssociatedFuture(false), generics::TakesSelfByValue(false), // BUG? ); let args = out_trait @@ -111,35 +107,30 @@ pub fn output_tokens( span: trait_ident_span, }; - let impl_assoc_types = out_trait.fns.iter().filter_map(|trait_fn| { - trait_fn - .entrait_sig - .associated_fut_impl(generics::TraitIndirection::Plain, &attr.crate_idents) - }); - let method_items = out_trait .fns .iter() .map(|trait_fn| gen_delegation_method(trait_fn, generic_idents, &attr, contains_async)); - Ok(quote! { + let out = quote! { #trait_def #delegation_trait_def - #(#impl_attrs)* - #impl_async_trait_attr + #(#impl_sub_attributes)* impl #params #trait_ident #args for #self_ty #where_clause { - #(#impl_assoc_types)* #(#method_items)* } - }) + }; + + Ok(out) } fn gen_impl_delegation_trait_defs( out_trait: &OutTrait, trait_dependency_mode: &TraitDependencyMode, generic_idents: &GenericIdents, + impl_sub_attributes: &[SubAttribute], attr: &EntraitTraitAttr, ) -> syn::Result> { let entrait = &generic_idents.crate_idents.entrait; @@ -192,6 +183,7 @@ fn gen_impl_delegation_trait_defs( opts: &no_mock_opts, trait_indirection: generics::TraitIndirection::StaticImpl, trait_dependency_mode, + sub_attributes: impl_sub_attributes, } .gen_trait_def( &trait_copy.vis, @@ -206,6 +198,7 @@ fn gen_impl_delegation_trait_defs( )?; Ok(Some(quote! { + #(#impl_sub_attributes)* #trait_def pub trait #delegation_ident { @@ -245,6 +238,7 @@ fn gen_impl_delegation_trait_defs( opts: &no_mock_opts, trait_indirection: generics::TraitIndirection::DynamicImpl, trait_dependency_mode, + sub_attributes: impl_sub_attributes, } .gen_trait_def( &trait_copy.vis, @@ -258,7 +252,10 @@ fn gen_impl_delegation_trait_defs( &FnInputMode::RawTrait(LiteralAttrs(&[])), )?; - Ok(Some(trait_def)) + Ok(Some(quote! { + #(#impl_sub_attributes)* + #trait_def + })) } _ => Err(syn::Error::new( proc_macro2::Span::call_site(), @@ -289,9 +286,7 @@ fn gen_delegation_method<'s>( match (&attr.impl_trait, &attr.delegation_kind) { (Some(ImplTrait(_, impl_trait_ident)), Some(SpanOpt(Delegate::ByTrait(_), _))) => { DelegatingMethod { - attr, trait_fn, - needs_async_move: true, call: quote! { // TODO: pass additional generic arguments(?) <#impl_t::Target as #impl_trait_ident<#impl_t>>::#fn_ident(self, #(#arguments),*) @@ -322,33 +317,22 @@ fn gen_delegation_method<'s>( } }; - DelegatingMethod { - attr, - trait_fn, - needs_async_move: false, - call, - } + DelegatingMethod { trait_fn, call } } (None, Some(SpanOpt(Delegate::ByRef(RefDelegate::AsRef), _))) => DelegatingMethod { - attr, trait_fn, - needs_async_move: false, call: quote! { self.as_ref().as_ref().#fn_ident(#(#arguments),*) }, }, (None, Some(SpanOpt(Delegate::ByRef(RefDelegate::Borrow), _))) => DelegatingMethod { - attr, trait_fn, - needs_async_move: false, call: quote! { self.as_ref().borrow().#fn_ident(#(#arguments),*) }, }, _ => DelegatingMethod { - attr, trait_fn, - needs_async_move: false, call: quote! { self.as_ref().#fn_ident(#(#arguments),*) }, @@ -357,34 +341,10 @@ fn gen_delegation_method<'s>( } struct DelegatingMethod<'s> { - attr: &'s EntraitTraitAttr, trait_fn: &'s TraitFn, - needs_async_move: bool, call: TokenStream, } -impl<'s> DelegatingMethod<'s> { - fn should_inline(&self) -> bool { - if matches!( - &self.attr.delegation_kind, - Some(SpanOpt(Delegate::ByRef(_), _)) - ) { - return false; - } - - if self.trait_fn.originally_async - && matches!( - self.attr.opts.async_strategy(), - SpanOpt(AsyncStrategy::BoxFuture, _) - ) - { - return false; - } - - true - } -} - impl<'s> ToTokens for DelegatingMethod<'s> { fn to_tokens(&self, stream: &mut TokenStream) { // Just "mirroring" all the attributes from @@ -395,12 +355,10 @@ impl<'s> ToTokens for DelegatingMethod<'s> { push_tokens!(stream, attr); } - if self.should_inline() { - quote! { #[inline] }.to_tokens(stream); - } self.trait_fn.sig().to_tokens(stream); syn::token::Brace::default().surround(stream, |stream| { - if self.needs_async_move && self.trait_fn.entrait_sig.associated_fut.is_some() { + // if self.needs_async_move && self.trait_fn.entrait_sig.associated_fut.is_some() { + if false { push_tokens!( stream, syn::token::Async::default(), diff --git a/entrait_macros/src/fn_delegation_codegen.rs b/entrait_macros/src/fn_delegation_codegen.rs index 491e995..5f9fba3 100644 --- a/entrait_macros/src/fn_delegation_codegen.rs +++ b/entrait_macros/src/fn_delegation_codegen.rs @@ -5,16 +5,14 @@ use quote::{quote, quote_spanned}; use syn::spanned::Spanned; use crate::analyze_generics::TraitFn; -use crate::attributes; use crate::generics; use crate::generics::ImplIndirection; use crate::generics::TraitDependencyMode; use crate::idents::CrateIdents; use crate::input::FnInputMode; -use crate::opt::AsyncStrategy; use crate::opt::Mockable; use crate::opt::Opts; -use crate::opt::SpanOpt; +use crate::sub_attributes::SubAttribute; use crate::token_util::push_tokens; use crate::token_util::TokenPair; @@ -28,7 +26,7 @@ pub struct FnDelegationCodegen<'s, TR> { pub trait_generics: &'s generics::TraitGenerics, pub fn_input_mode: &'s FnInputMode<'s>, pub trait_dependency_mode: &'s TraitDependencyMode<'s, 's>, - pub use_associated_future: generics::UseAssociatedFuture, + pub sub_attributes: &'s [SubAttribute<'s>], } impl<'s, TR: ToTokens> FnDelegationCodegen<'s, TR> { @@ -44,11 +42,8 @@ impl<'s, TR: ToTokens> FnDelegationCodegen<'s, TR> { /// ``` /// pub fn gen_impl_block(&self, trait_fns: &[TraitFn]) -> TokenStream { - let async_trait_attribute = - attributes::opt_async_trait_attr(self.opts, self.crate_idents, trait_fns.iter()); let params = self.trait_generics.impl_params( self.trait_dependency_mode, - self.use_associated_future, generics::has_any_self_by_value(trait_fns.iter().map(|trait_fn| trait_fn.sig())), ); let args = self.trait_generics.arguments(&self.impl_indirection); @@ -65,13 +60,6 @@ impl<'s, TR: ToTokens> FnDelegationCodegen<'s, TR> { self.trait_span, ); - let opt_inline_attr = if !matches!(&self.impl_indirection, ImplIndirection::Dynamic { .. }) - { - Some(quote! { #[inline] }) - } else { - None - }; - let opt_self_scoping = if let FnInputMode::ImplBlock(ty) = self.fn_input_mode { Some(TokenPair( syn::token::SelfType(ty.span()), @@ -82,29 +70,24 @@ impl<'s, TR: ToTokens> FnDelegationCodegen<'s, TR> { }; let items = trait_fns.iter().map(|trait_fn| { - let associated_fut_impl = &trait_fn.entrait_sig.associated_fut_impl( - self.impl_indirection.to_trait_indirection(), - self.crate_idents, - ); - - let fn_item = self.gen_delegating_fn_item( - trait_fn, - self.trait_span, - opt_inline_attr.as_ref(), - &opt_self_scoping, - ); + let fn_item = self.gen_delegating_fn_item(trait_fn, self.trait_span, &opt_self_scoping); quote! { - #associated_fut_impl #fn_item } }); + let trait_impl_sub_attributes = self + .sub_attributes + .iter() + .copied() + .filter(|sub_attr| matches!(sub_attr, SubAttribute::AsyncTrait(_))); + let trait_span = self.trait_span; let trait_ref = &self.trait_ref; quote_spanned! { trait_span=> - #async_trait_attribute + #(#trait_impl_sub_attributes)* impl #params #trait_ref #args for #self_ty #where_clause { #(#items)* } @@ -116,7 +99,6 @@ impl<'s, TR: ToTokens> FnDelegationCodegen<'s, TR> { &self, trait_fn: &TraitFn, span: Span, - mut opt_inline_attr: Option<&TokenStream>, opt_self_scoping: &impl ToTokens, ) -> TokenStream { let entrait_sig = &trait_fn.entrait_sig; @@ -146,22 +128,9 @@ impl<'s, TR: ToTokens> FnDelegationCodegen<'s, TR> { }, }); - let mut opt_dot_await = trait_fn.opt_dot_await(span); - if entrait_sig.associated_fut.is_some() { - opt_dot_await = None; - } - - if trait_fn.originally_async - && matches!( - self.opts.async_strategy(), - SpanOpt(AsyncStrategy::BoxFuture, _) - ) - { - opt_inline_attr = None; - } + let opt_dot_await = trait_fn.opt_dot_await(span); quote_spanned! { span=> - #opt_inline_attr #trait_fn_sig { #opt_self_scoping #fn_ident(#opt_self_comma #(#arguments),*) #opt_dot_await } diff --git a/entrait_macros/src/generics.rs b/entrait_macros/src/generics.rs index bd0ce41..63033ce 100644 --- a/entrait_macros/src/generics.rs +++ b/entrait_macros/src/generics.rs @@ -3,8 +3,6 @@ use proc_macro2::TokenStream; use crate::{ analyze_generics::TraitFn, idents::GenericIdents, - input::InputFn, - opt::{AsyncStrategy, Opts, SpanOpt}, token_util::{push_tokens, EmptyToken, Punctuator, TokenPair}, }; @@ -15,16 +13,6 @@ pub enum ImplIndirection<'s> { Dynamic { ty: &'s syn::Type }, } -impl<'s> ImplIndirection<'s> { - pub fn to_trait_indirection(&'s self) -> TraitIndirection { - match self { - Self::None => TraitIndirection::Plain, - Self::Static { .. } => TraitIndirection::StaticImpl, - Self::Dynamic { .. } => TraitIndirection::DynamicImpl, - } - } -} - #[derive(Clone, Copy)] pub enum TraitIndirection { /// Normal entrait/fn, entrait/mod etc @@ -40,23 +28,6 @@ pub enum TraitIndirection { #[derive(Clone, Copy)] pub struct UseAssociatedFuture(pub bool); -pub fn detect_use_associated_future<'i>( - opts: &Opts, - input_fns: impl Iterator, -) -> UseAssociatedFuture { - UseAssociatedFuture(matches!( - ( - opts.async_strategy(), - has_any_async(input_fns.map(|input_fn| &input_fn.fn_sig)) - ), - (SpanOpt(AsyncStrategy::AssociatedFuture, _), true) - )) -} - -pub fn has_any_async<'s>(mut signatures: impl Iterator) -> bool { - signatures.any(|sig| sig.asyncness.is_some()) -} - #[derive(Clone, Copy)] pub struct TakesSelfByValue(pub bool); @@ -95,7 +66,6 @@ impl TraitGenerics { ParamsGenerator { params: &self.params, impl_t: None, - use_associated_future: UseAssociatedFuture(false), takes_self_by_value: TakesSelfByValue(false), } } @@ -109,7 +79,6 @@ impl TraitGenerics { pub fn impl_params<'i>( &'i self, trait_dependency_mode: &'i TraitDependencyMode<'i, '_>, - use_associated_future: UseAssociatedFuture, takes_self_by_value: TakesSelfByValue, ) -> ParamsGenerator<'_> { ParamsGenerator { @@ -118,7 +87,6 @@ impl TraitGenerics { TraitDependencyMode::Generic(idents) => Some(&idents.impl_t), TraitDependencyMode::Concrete(_) => None, }, - use_associated_future, takes_self_by_value, } } @@ -126,13 +94,11 @@ impl TraitGenerics { pub fn impl_params_from_idents<'i>( &'i self, idents: &'i GenericIdents, - use_associated_future: UseAssociatedFuture, takes_self_by_value: TakesSelfByValue, ) -> ParamsGenerator<'_> { ParamsGenerator { params: &self.params, impl_t: Some(&idents.impl_t), - use_associated_future, takes_self_by_value, } } @@ -195,7 +161,6 @@ impl<'g, 'c> quote::ToTokens for ImplPath<'g, 'c> { pub struct ParamsGenerator<'g> { params: &'g syn::punctuated::Punctuated, impl_t: Option<&'g syn::Ident>, - use_associated_future: UseAssociatedFuture, takes_self_by_value: TakesSelfByValue, } @@ -226,7 +191,8 @@ impl<'g> quote::ToTokens for ParamsGenerator<'g> { ); } - if self.use_associated_future.0 { + // if self.use_associated_future.0 { + if true { push_tokens!( stream, syn::token::Plus::default(), diff --git a/entrait_macros/src/lib.rs b/entrait_macros/src/lib.rs index 8ad81ad..f7833a7 100644 --- a/entrait_macros/src/lib.rs +++ b/entrait_macros/src/lib.rs @@ -19,12 +19,11 @@ mod idents; mod input; mod opt; mod signature; -mod static_async_trait; +mod sub_attributes; mod token_util; mod trait_codegen; use input::Input; -use opt::AsyncStrategy; use opt::Opts; #[proc_macro_attribute] @@ -39,39 +38,6 @@ pub fn entrait_export(attr: TokenStream, input: TokenStream) -> TokenStream { }) } -#[proc_macro_attribute] -pub fn entrait_use_box_futures(attr: TokenStream, input: TokenStream) -> TokenStream { - invoke(attr, input, |opts| { - opts.set_fallback_async_strategy(AsyncStrategy::BoxFuture); - }) -} - -#[proc_macro_attribute] -pub fn entrait_export_use_box_futures( - attr: TokenStream, - input: TokenStream, -) -> proc_macro::TokenStream { - invoke(attr, input, |opts| { - set_fallbacks([&mut opts.export]); - opts.set_fallback_async_strategy(AsyncStrategy::BoxFuture); - }) -} - -#[proc_macro_attribute] -pub fn entrait_use_associated_futures(attr: TokenStream, input: TokenStream) -> TokenStream { - invoke(attr, input, |opts| { - opts.set_fallback_async_strategy(AsyncStrategy::AssociatedFuture); - }) -} - -#[proc_macro_attribute] -pub fn entrait_export_use_associated_futures(attr: TokenStream, input: TokenStream) -> TokenStream { - invoke(attr, input, |opts| { - set_fallbacks([&mut opts.export]); - opts.set_fallback_async_strategy(AsyncStrategy::AssociatedFuture); - }) -} - #[proc_macro_attribute] pub fn entrait_unimock(attr: TokenStream, input: TokenStream) -> TokenStream { invoke(attr, input, |opts| { @@ -86,56 +52,6 @@ pub fn entrait_export_unimock(attr: TokenStream, input: TokenStream) -> TokenStr }) } -#[proc_macro_attribute] -pub fn entrait_unimock_use_box_futures(attr: TokenStream, input: TokenStream) -> TokenStream { - invoke(attr, input, |opts| { - set_fallbacks([&mut opts.unimock]); - opts.set_fallback_async_strategy(AsyncStrategy::BoxFuture); - }) -} - -#[proc_macro_attribute] -pub fn entrait_export_unimock_use_box_futures( - attr: TokenStream, - input: TokenStream, -) -> TokenStream { - invoke(attr, input, |opts| { - set_fallbacks([&mut opts.export, &mut opts.unimock]); - opts.set_fallback_async_strategy(AsyncStrategy::BoxFuture); - }) -} - -#[proc_macro_attribute] -pub fn entrait_unimock_use_associated_futures( - attr: TokenStream, - input: TokenStream, -) -> TokenStream { - invoke(attr, input, |opts| { - set_fallbacks([&mut opts.unimock]); - opts.set_fallback_async_strategy(AsyncStrategy::AssociatedFuture); - }) -} - -#[proc_macro_attribute] -pub fn entrait_export_unimock_use_associated_futures( - attr: TokenStream, - input: TokenStream, -) -> TokenStream { - invoke(attr, input, |opts| { - set_fallbacks([&mut opts.export, &mut opts.unimock]); - opts.set_fallback_async_strategy(AsyncStrategy::AssociatedFuture); - }) -} - -#[proc_macro_attribute] -pub fn static_async_trait(_: TokenStream, input: TokenStream) -> TokenStream { - let item = syn::parse_macro_input!(input as syn::Item); - match static_async_trait::output_tokens(item) { - Ok(stream) => stream.into(), - Err(error) => error.into_compile_error().into(), - } -} - fn set_fallbacks(opts: [&mut Option>; N]) { for opt in opts.into_iter() { opt.get_or_insert(opt::SpanOpt::of(true)); diff --git a/entrait_macros/src/opt.rs b/entrait_macros/src/opt.rs index f70018c..0a0e5f2 100644 --- a/entrait_macros/src/opt.rs +++ b/entrait_macros/src/opt.rs @@ -6,11 +6,12 @@ pub struct Opts { pub no_deps: Option>, pub debug: Option>, - pub async_strategy: Option>, /// Whether to export mocks (i.e. not gated with cfg(test)) pub export: Option>, + pub future_send: Option>, + pub mock_api: Option, /// Mocking with unimock @@ -21,10 +22,6 @@ pub struct Opts { } impl Opts { - pub fn set_fallback_async_strategy(&mut self, strategy: AsyncStrategy) { - self.async_strategy.get_or_insert(SpanOpt::of(strategy)); - } - pub fn no_deps_value(&self) -> bool { self.default_option(self.no_deps, false).0 } @@ -33,14 +30,14 @@ impl Opts { self.default_option(self.debug, false).0 } - pub fn async_strategy(&self) -> SpanOpt { - self.default_option(self.async_strategy, AsyncStrategy::NoHack) - } - pub fn export_value(&self) -> bool { self.default_option(self.export, false).0 } + pub fn future_send(&self) -> FutureSend { + self.default_option(self.future_send, FutureSend(true)).0 + } + pub fn mockable(&self) -> Mockable { if (self.unimock.is_some() && self.mock_api.is_some()) || self.mockall.is_some() { Mockable::Yes @@ -69,13 +66,6 @@ impl Mockable { } } -#[derive(Clone, Copy)] -pub enum AsyncStrategy { - NoHack, - BoxFuture, - AssociatedFuture, -} - #[derive(Clone)] #[allow(clippy::enum_variant_names)] pub enum Delegate { @@ -90,6 +80,9 @@ pub enum RefDelegate { Borrow, } +#[derive(Clone, Copy)] +pub struct FutureSend(pub bool); + #[derive(Copy, Clone)] pub struct SpanOpt(pub T, pub Span); @@ -109,11 +102,10 @@ impl SpanOpt { pub enum EntraitOpt { NoDeps(SpanOpt), Debug(SpanOpt), - BoxFuture(SpanOpt), - AssociatedFuture(SpanOpt), DelegateBy(SpanOpt), /// Whether to export mocks Export(SpanOpt), + MaybeSend(SpanOpt), /// How to name the mock API MockApi(MockApiIdent), /// Whether to generate unimock impl @@ -127,9 +119,8 @@ impl EntraitOpt { match self { Self::NoDeps(opt) => opt.1, Self::Debug(opt) => opt.1, - Self::BoxFuture(opt) => opt.1, - Self::AssociatedFuture(opt) => opt.1, Self::DelegateBy(opt) => opt.1, + Self::MaybeSend(opt) => opt.1, Self::Export(opt) => opt.1, Self::MockApi(ident) => ident.0.span(), Self::Unimock(opt) => opt.1, @@ -140,33 +131,47 @@ impl EntraitOpt { impl Parse for EntraitOpt { fn parse(input: ParseStream) -> syn::Result { - let ident: syn::Ident = input.parse()?; - let span = ident.span(); - let ident_string = ident.to_string(); - use EntraitOpt::*; - match ident_string.as_str() { - "no_deps" => Ok(NoDeps(parse_eq_bool(input, true, span)?)), - "debug" => Ok(Debug(parse_eq_bool(input, true, span)?)), - "box_future" => Ok(BoxFuture(parse_eq_bool(input, true, span)?)), - "associated_future" => Ok(AssociatedFuture(parse_eq_bool(input, true, span)?)), - "delegate_by" => Ok(DelegateBy(parse_eq_delegate_by( - input, - Delegate::BySelf, - span, - )?)), - "export" => Ok(Export(parse_eq_bool(input, true, span)?)), - "mock_api" => { - let _: syn::token::Eq = input.parse()?; - Ok(Self::MockApi(MockApiIdent(input.parse()?))) + if input.peek(syn::token::Question) { + let _q: syn::token::Question = input.parse().unwrap(); + + let ident: syn::Ident = input.parse()?; + let span = ident.span(); + let ident_string = ident.to_string(); + + match ident_string.as_str() { + "Send" => Ok(MaybeSend(SpanOpt(FutureSend(false), span))), + _ => Err(syn::Error::new( + span, + format!("Unkonwn entrait option \"{ident_string}\""), + )), + } + } else { + let ident: syn::Ident = input.parse()?; + let span = ident.span(); + let ident_string = ident.to_string(); + + match ident_string.as_str() { + "no_deps" => Ok(NoDeps(parse_eq_bool(input, true, span)?)), + "debug" => Ok(Debug(parse_eq_bool(input, true, span)?)), + "delegate_by" => Ok(DelegateBy(parse_eq_delegate_by( + input, + Delegate::BySelf, + span, + )?)), + "export" => Ok(Export(parse_eq_bool(input, true, span)?)), + "mock_api" => { + let _: syn::token::Eq = input.parse()?; + Ok(Self::MockApi(MockApiIdent(input.parse()?))) + } + "unimock" => Ok(Unimock(parse_eq_bool(input, true, span)?)), + "mockall" => Ok(Mockall(parse_eq_bool(input, true, span)?)), + _ => Err(syn::Error::new( + span, + format!("Unkonwn entrait option \"{ident_string}\""), + )), } - "unimock" => Ok(Unimock(parse_eq_bool(input, true, span)?)), - "mockall" => Ok(Mockall(parse_eq_bool(input, true, span)?)), - _ => Err(syn::Error::new( - span, - format!("Unkonwn entrait option \"{ident_string}\""), - )), } } } diff --git a/entrait_macros/src/signature/future.rs b/entrait_macros/src/signature/future.rs index 5426ad5..2d3cf3c 100644 --- a/entrait_macros/src/signature/future.rs +++ b/entrait_macros/src/signature/future.rs @@ -1,4 +1,3 @@ -use proc_macro2::Span; use proc_macro2::TokenStream; use quote::quote; use quote::ToTokens; @@ -8,90 +7,8 @@ use crate::idents::CrateIdents; use crate::token_util::EmptyToken; use crate::token_util::Punctuator; -use super::lifetimes; use super::AssociatedFut; use super::EntraitSignature; -use super::ReceiverGeneration; -use super::SigComponent; -use super::UsedInOutput; -use super::UserProvidedLifetime; - -impl EntraitSignature { - pub fn convert_to_associated_future( - &mut self, - receiver_generation: ReceiverGeneration, - trait_span: Span, - ) { - lifetimes::de_elide_lifetimes(self, receiver_generation); - - let base_lifetime = syn::Lifetime::new("'entrait_future", Span::call_site()); - self.et_lifetimes.push(super::EntraitLifetime { - lifetime: base_lifetime.clone(), - source: SigComponent::Base, - user_provided: UserProvidedLifetime(false), - used_in_output: UsedInOutput(false), - }); - - let output = clone_output_type(&self.sig.output); - - // make the function generic if it wasn't already - let sig = &mut self.sig; - sig.asyncness = None; - let generics = &mut sig.generics; - generics.lt_token.get_or_insert(syn::parse_quote! { < }); - generics.gt_token.get_or_insert(syn::parse_quote! { > }); - - // insert generated/non-user-provided lifetimes - for fut_lifetime in self.et_lifetimes.iter().filter(|lt| !lt.user_provided.0) { - generics - .params - .push(syn::GenericParam::Lifetime(syn::LifetimeParam { - attrs: vec![], - lifetime: fut_lifetime.lifetime.clone(), - colon_token: None, - bounds: syn::punctuated::Punctuated::new(), - })); - } - - let fut_ident = quote::format_ident!("Fut__{}", sig.ident); - - let fut_lifetimes = self - .et_lifetimes_in_assoc_future() - .map(|et| &et.lifetime) - .collect::>(); - - self.sig.output = syn::parse_quote_spanned! { trait_span => - -> Self::#fut_ident<#(#fut_lifetimes),*> - }; - - let sig_where_clause = self.sig.generics.make_where_clause(); - for lifetime in &self.et_lifetimes { - if !matches!(lifetime.source, SigComponent::Base) { - let lt = &lifetime.lifetime; - - sig_where_clause.predicates.push(syn::parse_quote! { - #lt: #base_lifetime - }); - } - } - sig_where_clause.predicates.push(syn::parse_quote! { - Self: #base_lifetime - }); - - self.associated_fut = Some(AssociatedFut { - ident: fut_ident, - output, - base_lifetime, - }); - } -} - -fn clone_output_type(return_type: &syn::ReturnType) -> syn::Type { - match return_type { - syn::ReturnType::Default => syn::parse_quote! { () }, - syn::ReturnType::Type(_, ty) => ty.as_ref().clone(), - } -} pub struct FutDecl<'s> { pub signature: &'s EntraitSignature, @@ -158,25 +75,6 @@ impl<'s> ToTokens for FutImpl<'s> { } } -struct FutParams<'s> { - signature: &'s EntraitSignature, -} - -impl<'s> ToTokens for FutParams<'s> { - fn to_tokens(&self, stream: &mut TokenStream) { - let mut punctuator = Punctuator::new( - stream, - syn::token::Lt::default(), - syn::token::Comma::default(), - syn::token::Gt::default(), - ); - - for lt in self.signature.et_lifetimes_in_assoc_future() { - punctuator.push(<.lifetime); - } - } -} - struct FutWhereClause<'s> { signature: &'s EntraitSignature, trait_indirection: TraitIndirection, diff --git a/entrait_macros/src/signature/lifetimes.rs b/entrait_macros/src/signature/lifetimes.rs index 6ea31bc..8152b99 100644 --- a/entrait_macros/src/signature/lifetimes.rs +++ b/entrait_macros/src/signature/lifetimes.rs @@ -1,40 +1,10 @@ use super::{ - EntraitLifetime, EntraitSignature, ReceiverGeneration, SigComponent, UsedInOutput, - UserProvidedLifetime, + EntraitLifetime, ReceiverGeneration, SigComponent, UsedInOutput, UserProvidedLifetime, }; use std::collections::HashSet; use syn::visit_mut::VisitMut; -pub fn de_elide_lifetimes( - entrait_sig: &mut EntraitSignature, - receiver_generation: ReceiverGeneration, -) { - let mut elision_detector = ElisionDetector::new(receiver_generation); - elision_detector.detect(&mut entrait_sig.sig); - - let mut visitor = LifetimeMutVisitor::new(elision_detector.elided_params); - - match receiver_generation { - ReceiverGeneration::None => { - for (index, arg) in entrait_sig.sig.inputs.iter_mut().enumerate() { - visitor.de_elide_param(index, arg); - } - } - ReceiverGeneration::Rewrite | ReceiverGeneration::Insert => { - visitor.de_elide_receiver(entrait_sig.sig.inputs.first_mut().unwrap()); - - for (index, arg) in entrait_sig.sig.inputs.iter_mut().skip(1).enumerate() { - visitor.de_elide_param(index, arg); - } - } - } - - visitor.de_elide_output(&mut entrait_sig.sig.output); - - entrait_sig.et_lifetimes.append(&mut visitor.et_lifetimes); -} - /// Looks at elided lifetimes and makes them explicit. /// Also collects all lifetimes into `et_lifetimes`. struct LifetimeMutVisitor { diff --git a/entrait_macros/src/signature/mod.rs b/entrait_macros/src/signature/mod.rs index 0e2e88f..a4a1036 100644 --- a/entrait_macros/src/signature/mod.rs +++ b/entrait_macros/src/signature/mod.rs @@ -1,14 +1,9 @@ pub mod converter; -pub mod future; -pub mod lifetimes; mod fn_params; use std::ops::Deref; -use crate::generics::TraitIndirection; -use crate::idents::CrateIdents; - #[derive(Clone, Copy)] pub struct InputSig<'s> { sig: &'s syn::Signature, @@ -41,7 +36,6 @@ pub enum ImplReceiverKind { #[derive(Clone)] pub struct EntraitSignature { pub sig: syn::Signature, - pub associated_fut: Option, pub et_lifetimes: Vec, } @@ -49,52 +43,9 @@ impl EntraitSignature { pub fn new(sig: syn::Signature) -> Self { Self { sig, - associated_fut: None, et_lifetimes: vec![], } } - - pub fn associated_fut_decl<'s>( - &'s self, - trait_indirection: TraitIndirection, - crate_idents: &'s CrateIdents, - ) -> Option> { - self.associated_fut - .as_ref() - .map(|associated_fut| future::FutDecl { - signature: self, - associated_fut, - trait_indirection, - crate_idents, - }) - } - - pub fn associated_fut_impl<'s>( - &'s self, - trait_indirection: TraitIndirection, - crate_idents: &'s CrateIdents, - ) -> Option> { - self.associated_fut - .as_ref() - .map(|associated_fut| future::FutImpl { - signature: self, - associated_fut, - trait_indirection, - crate_idents, - }) - } - - fn et_lifetimes_in_assoc_future(&self) -> impl Iterator { - self.et_lifetimes - .iter() - .filter(|et| et.used_in_output.0 || matches!(et.source, SigComponent::Base)) - } - - fn et_lifetimes_in_assoc_future_except_base( - &self, - ) -> impl Iterator { - self.et_lifetimes.iter().filter(|et| et.used_in_output.0) - } } #[derive(Clone)] @@ -108,25 +59,8 @@ pub struct AssociatedFut { #[derive(Clone)] pub struct EntraitLifetime { pub lifetime: syn::Lifetime, - pub source: SigComponent, - pub user_provided: UserProvidedLifetime, - pub used_in_output: UsedInOutput, -} - -#[derive(Copy, Clone, Eq, PartialEq)] -pub enum SigComponent { - Receiver, - Param(usize), - Output, - Base, } -#[derive(Clone, Copy)] -pub struct UserProvidedLifetime(bool); - -#[derive(Clone, Copy)] -pub struct UsedInOutput(bool); - #[derive(Clone, Copy)] pub enum ReceiverGeneration { Insert, diff --git a/entrait_macros/src/static_async_trait/mod.rs b/entrait_macros/src/static_async_trait/mod.rs deleted file mode 100644 index 7acfbc7..0000000 --- a/entrait_macros/src/static_async_trait/mod.rs +++ /dev/null @@ -1,142 +0,0 @@ -use proc_macro2::{Span, TokenStream}; -use quote::{quote, ToTokens}; -use syn::{spanned::Spanned, ImplItemFn, ItemTrait}; - -use crate::{ - generics::TraitIndirection, - idents::CrateIdents, - signature::{EntraitSignature, ReceiverGeneration}, -}; - -pub fn output_tokens(item: syn::Item) -> syn::Result { - match item { - syn::Item::Trait(item_trait) => process_trait(item_trait), - syn::Item::Impl(item_impl) => process_impl(item_impl), - other => Err(syn::Error::new( - other.span(), - "Cannot make this static-async", - )), - } -} - -fn process_trait(item_trait: syn::ItemTrait) -> syn::Result { - let crate_idents = CrateIdents::new(item_trait.ident.span()); - let trait_span = item_trait.ident.span(); - - let ItemTrait { - attrs, - vis, - unsafety, - auto_token, - restriction: _, - trait_token, - ident, - generics, - colon_token, - supertraits, - brace_token: _, - items, - } = item_trait; - let mut new_items = TokenStream::new(); - - for item in items.into_iter() { - match item { - syn::TraitItem::Fn(method) if method.sig.asyncness.is_some() => { - let (sig, trait_indirection) = convert_sig(method.sig, trait_span); - let fut = sig.associated_fut_decl(trait_indirection, &crate_idents); - let trait_fn_sig = &sig.sig; - - quote! { - #fut - #trait_fn_sig; - } - .to_tokens(&mut new_items); - } - item => { - item.to_tokens(&mut new_items); - } - } - } - - Ok(quote! { - #(#attrs)* - #vis #unsafety #auto_token #trait_token #ident #generics #colon_token #supertraits { - #new_items - } - }) -} -fn process_impl(item_impl: syn::ItemImpl) -> syn::Result { - let syn::ItemImpl { - attrs, - defaultness, - unsafety, - impl_token, - generics, - trait_, - self_ty, - brace_token: _, - items, - } = item_impl; - - let mut new_items = TokenStream::new(); - - if trait_.is_some() { - let impl_span = impl_token.span(); - let crate_idents = CrateIdents::new(impl_token.span()); - for item in items.into_iter() { - match item { - syn::ImplItem::Fn(method) if method.sig.asyncness.is_some() => { - let ImplItemFn { - attrs, - vis, - defaultness, - sig, - block: syn::Block { stmts, .. }, - } = method; - - let (sig, trait_indirection) = convert_sig(sig, impl_span); - let fut = sig.associated_fut_impl(trait_indirection, &crate_idents); - let trait_fn_sig = &sig.sig; - - quote! { - #fut - - #(#attrs)* - #vis #defaultness #trait_fn_sig { - async move { #(#stmts)* } - } - } - .to_tokens(&mut new_items); - } - item => { - item.to_tokens(&mut new_items); - } - } - } - } else { - new_items = quote! { #(#items)* }; - } - - let trait_ = trait_.map(|(bang, path, for_)| quote! { #bang #path #for_ }); - let where_clause = &generics.where_clause; - - Ok(quote! { - #(#attrs)* - #defaultness #unsafety #impl_token #generics #trait_ #self_ty #where_clause { - #new_items - } - }) -} - -fn convert_sig(sig: syn::Signature, span: Span) -> (EntraitSignature, TraitIndirection) { - let trait_indirection = if matches!(sig.inputs.first(), Some(syn::FnArg::Receiver(_))) { - TraitIndirection::Plain - } else { - TraitIndirection::StaticImpl - }; - - let mut entrait_sig = EntraitSignature::new(sig); - entrait_sig.convert_to_associated_future(ReceiverGeneration::Rewrite, span); - - (entrait_sig, trait_indirection) -} diff --git a/entrait_macros/src/sub_attributes.rs b/entrait_macros/src/sub_attributes.rs new file mode 100644 index 0000000..daa508a --- /dev/null +++ b/entrait_macros/src/sub_attributes.rs @@ -0,0 +1,46 @@ +//! The other attributes specified _under_ entrait + +use quote::ToTokens; + +#[derive(Clone, Copy)] +pub enum SubAttribute<'t> { + AsyncTrait(&'t syn::Attribute), + Other(&'t syn::Attribute), + Automock(&'t syn::Attribute), +} + +pub fn analyze_sub_attributes(attributes: &[syn::Attribute]) -> Vec> { + attributes + .iter() + .map(|attribute| { + let last_segment = attribute.path().segments.last(); + let ident = last_segment.map(|segment| segment.ident.to_string()); + + if let Some(ident) = ident { + match ident.as_str() { + "async_trait" => SubAttribute::AsyncTrait(attribute), + "automock" => SubAttribute::Automock(attribute), + _ => SubAttribute::Other(attribute), + } + } else { + SubAttribute::Other(attribute) + } + }) + .collect() +} + +pub fn contains_async_trait(sub_attributes: &[SubAttribute]) -> bool { + sub_attributes + .iter() + .any(|sub_attributes| matches!(sub_attributes, SubAttribute::AsyncTrait(_))) +} + +impl<'t> ToTokens for SubAttribute<'t> { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + match self { + Self::AsyncTrait(attr) => attr.to_tokens(tokens), + Self::Automock(attr) => attr.to_tokens(tokens), + Self::Other(attr) => attr.to_tokens(tokens), + } + } +} diff --git a/entrait_macros/src/trait_codegen.rs b/entrait_macros/src/trait_codegen.rs index f44dc1c..35052e1 100644 --- a/entrait_macros/src/trait_codegen.rs +++ b/entrait_macros/src/trait_codegen.rs @@ -1,5 +1,6 @@ use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned, ToTokens}; +use syn::spanned::Spanned; use crate::{ analyze_generics::TraitFn, @@ -8,6 +9,8 @@ use crate::{ idents::CrateIdents, input::FnInputMode, opt::{Opts, SpanOpt}, + signature::EntraitSignature, + sub_attributes::{contains_async_trait, SubAttribute}, token_util::push_tokens, }; @@ -16,6 +19,7 @@ pub struct TraitCodegen<'s> { pub crate_idents: &'s CrateIdents, pub trait_indirection: TraitIndirection, pub trait_dependency_mode: &'s TraitDependencyMode<'s, 's>, + pub sub_attributes: &'s [SubAttribute<'s>], } impl<'s> TraitCodegen<'s> { @@ -62,29 +66,17 @@ impl<'s> TraitCodegen<'s> { }), _ => None, }; - let opt_async_trait_attr = - attributes::opt_async_trait_attr(self.opts, self.crate_idents, trait_fns.iter()); - - let literal_attrs = if let FnInputMode::RawTrait(literal_attrs) = fn_input_mode { - Some(literal_attrs) - } else { - None - }; - let trait_visibility = TraitVisibility { visibility, fn_input_mode, }; let fn_defs = trait_fns.iter().map(|trait_fn| { - let opt_associated_fut_decl = &trait_fn - .entrait_sig - .associated_fut_decl(self.trait_indirection, self.crate_idents); let attrs = &trait_fn.attrs; - let trait_fn_sig = trait_fn.sig(); + let trait_fn_sig = + make_trait_fn_sig(&trait_fn.entrait_sig, self.sub_attributes, self.opts); quote! { - #opt_associated_fut_decl #(#attrs)* #trait_fn_sig; } @@ -93,12 +85,18 @@ impl<'s> TraitCodegen<'s> { let params = trait_generics.trait_params(); let where_clause = trait_generics.trait_where_clause(); + let trait_sub_attributes = self.sub_attributes.iter().filter(|attr| { + matches!( + attr, + SubAttribute::AsyncTrait(_) | SubAttribute::Automock(_) + ) + }); + Ok(quote_spanned! { span=> #opt_unimock_attr #opt_entrait_for_trait_attr #opt_mockall_automock_attr - #opt_async_trait_attr - #literal_attrs + #(#trait_sub_attributes)* #trait_visibility trait #trait_ident #params #supertraits #where_clause { #(#fn_defs)* } @@ -159,3 +157,41 @@ impl<'a> ToTokens for TraitVisibility<'a> { } } } + +fn make_trait_fn_sig( + entrait_sig: &EntraitSignature, + sub_attributes: &[SubAttribute], + opts: &Opts, +) -> syn::Signature { + let mut sig = entrait_sig.sig.clone(); + + if entrait_sig.sig.asyncness.is_some() && !contains_async_trait(sub_attributes) { + sig.asyncness = None; + + let mut return_type = syn::ReturnType::Default; + std::mem::swap(&mut return_type, &mut sig.output); + + let span = return_type.span(); + + let output_type: syn::Type = match return_type { + syn::ReturnType::Default => syn::parse_quote! { () }, + syn::ReturnType::Type(_, ty) => *ty, + }; + + let mut bounds: Vec = vec![quote! { + ::core::future::Future + }]; + + if opts.future_send().0 { + bounds.push(quote! { + ::core::marker::Send + }); + } + + sig.output = syn::parse_quote_spanned! {span=> + -> impl #(#bounds)+* + }; + } + + sig +} diff --git a/examples/async-graphql/Cargo.toml b/examples/async-graphql/Cargo.toml index 657302e..f34b385 100644 --- a/examples/async-graphql/Cargo.toml +++ b/examples/async-graphql/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" publish = false [dependencies] -entrait = { path = "../../", features = ["unimock", "boxed-futures"] } +entrait = { path = "../../", features = ["unimock"] } async-graphql = "4" tokio = { version = "1", features = ["full"] } async-trait = "0.1" @@ -16,4 +16,4 @@ tower = "0.4" tower-http = { version = "0.3", features = ["trace"] } hyper = { version = "0.14", features = ["full"] } serde_json = "1" -unimock = "0.6" +unimock = "0.6.2" diff --git a/examples/async-graphql/src/main.rs b/examples/async-graphql/src/main.rs index 78f4bdf..72473db 100644 --- a/examples/async-graphql/src/main.rs +++ b/examples/async-graphql/src/main.rs @@ -1,7 +1,7 @@ mod db { use entrait::*; - #[entrait(pub FetchSomeValue, no_deps, box_future, mock_api=FetchSomeValueMock)] + #[entrait(pub FetchSomeValue, no_deps, mock_api=FetchSomeValueMock)] async fn fetch_some_value() -> String { "real".to_string() } diff --git a/examples/axum/Cargo.toml b/examples/axum/Cargo.toml index 2f0438a..ef0b674 100644 --- a/examples/axum/Cargo.toml +++ b/examples/axum/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" publish = false [dependencies] -entrait = { path = "../../", features = ["unimock", "boxed-futures"] } +entrait = { path = "../../", features = ["unimock"] } axum = "0.6" tokio = { version = "1", features = ["full"] } serde = { version = "1", features = ["derive"] } @@ -17,4 +17,4 @@ tower = "0.4" tower-http = { version = "0.3", features = ["trace"] } hyper = { version = "0.14", features = ["full"] } serde_json = "1" -unimock = "0.6" +unimock = "0.6.2" diff --git a/examples/axum/src/main.rs b/examples/axum/src/main.rs index af0e25e..0465041 100644 --- a/examples/axum/src/main.rs +++ b/examples/axum/src/main.rs @@ -1,4 +1,4 @@ -#![allow(clippy::blacklisted_name)] +#![allow(clippy::disallowed_names)] use entrait::*; @@ -11,7 +11,7 @@ pub struct Foo { mod business { use super::*; - #[entrait(pub GetFoo, no_deps, box_future, mock_api=GetFooMock)] + #[entrait(pub GetFoo, no_deps, mock_api=GetFooMock)] async fn get_foo() -> Foo { Foo { value: "real".to_string(), diff --git a/src/lib.rs b/src/lib.rs index 2be8c45..821ad19 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -582,40 +582,25 @@ //! } //! ``` //! -//! #### `async` support -//! Since Rust at the time of writing does not natively support async methods in traits, you may opt in to having `#[async_trait]` generated for your trait. -//! Enable the `boxed-futures` cargo feature and pass the `box_future` option like this: +//! #### async support +//! Zero-cost, static-dispatch `async` works out of the box[^1]. //! -//! ```rust -//! # use entrait::entrait; -//! #[entrait(Foo, box_future)] -//! async fn foo(deps: &D) { -//! } -//! ``` -//! This is designed to be forwards compatible with [static async fn in traits](https://rust-lang.github.io/rfcs/3185-static-async-fn-in-trait.html). -//! When that day comes, you should be able to just remove that option and get a proper zero-cost future. -//! -//! There is a cargo feature to automatically apply `#[async_trait]` to every generated async trait: `use-boxed-futures`. -//! -//! #### Zero-cost async inversion of control - preview mode -//! Entrait has experimental support for zero-cost futures. A nightly Rust compiler is needed for this feature. -//! -//! The entrait option is called `associated_future`, and uses GATs and `feature(type_alias_impl_trait)`. -//! This feature generates an associated future inside the trait, and the implementations use `impl Trait` syntax to infer -//! the resulting type of the future: +//! When dynamic dispatch is needed, for example in combination with `delegate_by=ref`, entrait understands the `#[async_trait]` attribute when applied _after_ the entrait macro. +//! Entrait will re-apply that macro to the various generated impl blocks as needed. //! -//! ```ignore -//! #![feature(type_alias_impl_trait)] +//! ##### async `Send`-ness +//! Similar to `async_trait`, entrait generates a [Send]-bound on futures by default. +//! To opt out of the Send bound, pass `?Send` as a macro argument: //! -//! use entrait::*; -//! -//! #[entrait(Foo, associated_future)] -//! async fn foo(deps: &D) { +//! ```rust +//! # use std::{rc::Rc, any::Any}; +//! # use entrait::*; +//! #[entrait(ReturnRc, ?Send)] +//! async fn return_rc(_deps: impl Any) -> Rc { +//! Rc::new(42) //! } //! ``` //! -//! There is a feature for turning this on everywhere: `use-associated-futures`. -//! //! #### Integrating with other `fn`-targeting macros, and `no_deps` //! Some macros are used to transform the body of a function, or generate a body from scratch. //! For example, we can use [`feignhttp`](https://docs.rs/feignhttp/latest/feignhttp/) to generate an HTTP client. Entrait will try as best as it @@ -658,10 +643,6 @@ //! | Feature | Implies | Description | //! | ------------------- | --------------- | ------------------- | //! | `unimock` | | Adds the [unimock] dependency, and turns on Unimock implementations for all traits. | -//! | `use-boxed-futures` | `boxed-futures` | Automatically applies the [async_trait] macro to async trait methods. | -//! | `use-associated-futures` | | Automatically transforms the return type of async trait methods into an associated future by using type-alias-impl-trait syntax. Requires a nightly compiler. | -//! | `boxed-futures` | | Pulls in the [async_trait] optional dependency, enabling the `box_future` entrait option (macro parameter). | -//! //! //! //! # "Philosophy" @@ -704,53 +685,20 @@ //! One might say that a layered application architecture should never contain cycles. //! If you do need recursive algorithms, you could model this as utility functions outside of the entraited APIs of the application. //! +//! [^1]: Literally, out of the [Box]! In entrait version 0.7 and newer, asynchronous functions are zero-cost by default. #![forbid(unsafe_code)] #[cfg(feature = "unimock")] mod macros { - #[cfg(feature = "use-boxed-futures")] - mod entrait_auto_async { - pub use entrait_macros::entrait_export_unimock_use_box_futures as entrait_export; - pub use entrait_macros::entrait_unimock_use_box_futures as entrait; - } - - #[cfg(all(feature = "use-associated-futures", not(feature = "use-boxed-futures")))] - mod entrait_auto_async { - pub use entrait_macros::entrait_export_unimock_use_associated_futures as entrait_export; - pub use entrait_macros::entrait_unimock_use_associated_futures as entrait; - } - - #[cfg(not(any(feature = "use-boxed-futures", feature = "use-associated-futures")))] - mod entrait_auto_async { - pub use entrait_macros::entrait_export_unimock as entrait_export; - pub use entrait_macros::entrait_unimock as entrait; - } - - pub use entrait_auto_async::*; + pub use entrait_macros::entrait_export_unimock as entrait_export; + pub use entrait_macros::entrait_unimock as entrait; } #[cfg(not(feature = "unimock"))] mod macros { - #[cfg(feature = "use-boxed-futures")] - mod entrait_auto_async { - pub use entrait_macros::entrait_export_use_box_futures as entrait_export; - pub use entrait_macros::entrait_use_box_futures as entrait; - } - - #[cfg(all(feature = "use-associated-futures", not(feature = "use-boxed-futures")))] - mod entrait_auto_async { - pub use entrait_macros::entrait_export_use_associated_futures as entrait_export; - pub use entrait_macros::entrait_use_associated_futures as entrait; - } - - #[cfg(not(any(feature = "use-boxed-futures", feature = "use-associated-futures")))] - mod entrait_auto_async { - pub use entrait_macros::entrait; - pub use entrait_macros::entrait_export; - } - - pub use entrait_auto_async::*; + pub use entrait_macros::entrait; + pub use entrait_macros::entrait_export; } /// The entrait attribute macro, used to generate traits and _delegating implementations_ of them. @@ -928,15 +876,10 @@ mod macros { /// | `mock_api` | `ident` | `fn`+`mod`+`trait` | | The identifier to use for mock APIs (for libraries that support custom identifiers. The `unimock` library requires this to be explicitly specified. | /// | `unimock` | `bool` | `fn`+`mod`+`trait` | `false`[^1] | Used to turn _off_ unimock implementation when the `unimock` _feature_ is enabled. | /// | `mockall` | `bool` | `fn`+`mod`+`trait` | `false` | Enable mockall mocks. | -/// | `box_future` | `bool` | `fn`+`mod`+`trait` | `false`[^2] | In the case of an `async fn`, use the `async_trait` macro on the resulting trait. Requires the `boxed-futures` entrait feature. | -/// | `associated_future` | `bool` | `fn`+`mod`+`trait` | `false`[^3] | In the case of an `async fn`, use an associated future to avoid heap allocation. Currently requires a nighlty Rust compiler, with `feature(type_alias_impl_trait)`. | /// | `delegate_by` | `Self`/`ref`/custom ident | `trait` | `Self` | Controls the generated `Impl` delegation of this trait. `Self` generates a `T: Trait` bound. `ref` generates a [`T: AsRef`](::core::convert::AsRef) bound. `Borrow` is deprecated and uses the [core::borrow::Borrow] trait. Any other value generates a new trait with that name which controls the delegation. | +/// | `?Send` | `true` | `fn`+`mod`+`trait` | `false` | Opts out of `Send` bounds for Future outputs from `async` functions in generated traits.| /// /// [^1]: Enabled by default by turning on the `unimock` cargo feature. -/// -/// [^2]: Enabled by default by turning on the `use-boxed-futures` cargo feature. -/// -/// [^3]: Enabled by default by turning on the `use-associated-futures` cargo feature. pub use macros::entrait; /// Same as the [`entrait`](entrait) macro, only that the `export` option is set to true. @@ -953,14 +896,3 @@ pub use ::implementation::Impl; #[cfg(feature = "unimock")] #[doc(hidden)] pub use ::unimock as __unimock; - -#[cfg(feature = "boxed-futures")] -#[doc(hidden)] -pub mod __async_trait { - pub use ::async_trait::async_trait; -} - -#[doc(hidden)] -pub mod static_async { - pub use entrait_macros::static_async_trait as async_trait; -} diff --git a/test_all_stable.sh b/test_all_stable.sh index 36418bd..fc7c208 100755 --- a/test_all_stable.sh +++ b/test_all_stable.sh @@ -2,6 +2,6 @@ set -e set -x -cargo hack --feature-powerset --exclude-features "default use-associated-futures nightly-tests" --exclude-no-default-features test -cargo test --workspace --features "unimock use-boxed-futures" -cargo test --doc --features "unimock use-boxed-futures" +cargo hack --feature-powerset --exclude-features "default nightly-tests" --exclude-no-default-features test +cargo test --workspace --features "unimock" +cargo test --doc --features "unimock" diff --git a/tests/it/delegation_modes.rs b/tests/it/delegation_modes.rs index 6df43db..24abb0a 100644 --- a/tests/it/delegation_modes.rs +++ b/tests/it/delegation_modes.rs @@ -39,18 +39,17 @@ mod borrow_dyn_sync { } } -#[cfg(feature = "boxed-futures")] mod borrow_dyn_use_boxed_futures { use super::*; use async_trait::*; use entrait::*; - #[entrait(Foo, box_future)] + #[entrait(Foo)] async fn foo(deps: &impl Bar) { deps.bar().await; } - #[entrait(delegate_by=ref, box_future)] + #[entrait(delegate_by=ref)] #[async_trait] trait Bar: Sync + 'static { async fn bar(&self); diff --git a/tests/it/dependency_inversion.rs b/tests/it/dependency_inversion.rs index 278640f..03ec2a6 100644 --- a/tests/it/dependency_inversion.rs +++ b/tests/it/dependency_inversion.rs @@ -92,7 +92,6 @@ mod simple_dyn { } } -#[cfg(feature = "nightly-tests")] mod async_static { use entrait::*; @@ -128,11 +127,11 @@ mod async_static { } } -#[cfg(any(feature = "boxed-futures"))] mod async_dyn { use entrait::*; - #[entrait(FoobarImpl, delegate_by=ref, box_future)] + #[entrait(FoobarImpl, delegate_by=ref)] + #[async_trait::async_trait] pub trait Foobar { async fn foo(&self) -> i32; async fn bar(&self) -> u32; @@ -141,6 +140,7 @@ mod async_dyn { pub struct Implementor2; #[entrait(ref)] + #[async_trait::async_trait] impl FoobarImpl for Implementor2 { pub async fn bar(_: &D) -> u32 { 1337 diff --git a/tests/it/main.rs b/tests/it/main.rs index 90b34aa..59cbbea 100644 --- a/tests/it/main.rs +++ b/tests/it/main.rs @@ -1,6 +1,6 @@ #![allow(dead_code)] #![allow(unused)] -#![allow(clippy::blacklisted_name)] +#![allow(clippy::disallowed_names)] #![cfg_attr( any(feature = "nightly-tests", feature = "use-associated-futures"), feature(type_alias_impl_trait) diff --git a/tests/it/simple.rs b/tests/it/simple.rs index 5214e2e..3c0a186 100644 --- a/tests/it/simple.rs +++ b/tests/it/simple.rs @@ -82,11 +82,6 @@ mod bounds { } } -#[cfg(any( - feature = "use-boxed-futures", - feature = "use-associated-futures", - feature = "nightly-tests" -))] mod no_deps_and_feign { use entrait::entrait; @@ -192,7 +187,7 @@ mod module { not_included(); } - static S: &'static str = ""; + static S: &str = ""; fn not_included() {} } @@ -255,3 +250,23 @@ mod cfg_attributes { app.compiled(); } } + +mod future_send_opt_out { + use std::rc::Rc; + + use entrait::*; + + #[entrait(Spawning, ?Send)] + async fn spawning(deps: &(impl Bar + Clone + Send + Sync + 'static)) -> Rc { + let deps = deps.clone(); + + tokio::task::spawn_local(async move { deps.bar().await }) + .await + .unwrap() + } + + #[entrait(Bar, ?Send, mock_api = BarMock)] + async fn bar(_: T) -> Rc { + Rc::new(42) + } +} diff --git a/tests/it/unimock.rs b/tests/it/unimock.rs index 450dbf3..f7f97b7 100644 --- a/tests/it/unimock.rs +++ b/tests/it/unimock.rs @@ -1,6 +1,8 @@ #![allow(dead_code)] #![allow(unused_variables)] +use std::future::Future; + mod sync { use entrait::*; @@ -15,11 +17,10 @@ mod sync { } } -#[cfg(any( - feature = "use-boxed-futures", - feature = "use-associated-futures", - feature = "nightly-tests" -))] +trait Lol { + fn hei(&self) -> impl Future + Send; +} + mod auth { use entrait::*; use unimock::*; @@ -121,11 +122,6 @@ mod auth { } } -#[cfg(any( - feature = "use-boxed-futures", - feature = "use-associated-futures", - feature = "nightly-tests" -))] mod multi_mock { use entrait::*; use unimock::*; @@ -211,8 +207,9 @@ mod multi_mock { /// Note: This test does not run under "nightly-tests", /// because it uses tokio::spawn, and there is currently /// no way to Send-bound futures from plain async fns in traits. -#[cfg(any(feature = "use-boxed-futures", feature = "use-associated-futures"))] -mod tokio_spawn { +mod future_send_bound_for_tokio_spawn { + use std::future::Future; + use entrait::*; use unimock::*; @@ -260,11 +257,6 @@ mod tokio_spawn { } } -#[cfg(any( - feature = "use-boxed-futures", - feature = "use-associated-futures", - feature = "nightly-tests" -))] mod more_async { use entrait::*; use unimock::*; @@ -305,11 +297,6 @@ mod more_async { } } -#[cfg(any( - feature = "use-boxed-futures", - feature = "use-associated-futures", - feature = "nightly-tests" -))] mod async_no_deps_etc { use entrait::*; use unimock::*; @@ -419,7 +406,7 @@ mod generics { .some_call(matching!("hey")) .returns(42), ) - .generic_deps_generic_param(format!("hey")) + .generic_deps_generic_param("hey".to_string()) ); assert_eq!( @@ -540,11 +527,6 @@ mod module { } } -#[cfg(any( - feature = "use-boxed-futures", - feature = "use-associated-futures", - feature = "nightly-tests" -))] mod module_async { use entrait::*; @@ -556,9 +538,9 @@ mod module_async { pub async fn bar_async(_: &impl Any) {} } - fn takes_mixed(deps: &impl Mixed) { - let _ = deps.bar(); - let _ = deps.bar_async(); + async fn takes_mixed(deps: &impl Mixed) { + deps.bar(); + let _ = deps.bar_async().await; } #[entrait(pub MultiAsync)]