Skip to content

Commit

Permalink
Merge pull request #31 from audunhalland/next
Browse files Browse the repository at this point in the history
entrait 0.7
  • Loading branch information
audunhalland authored Mar 27, 2024
2 parents 7633542 + 58b0338 commit 05517c4
Show file tree
Hide file tree
Showing 33 changed files with 362 additions and 949 deletions.
18 changes: 4 additions & 14 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,12 @@ 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" --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

test-nightly:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@nightly
- uses: taiki-e/install-action@cargo-hack
- name: Test nightly features
run: cargo hack --feature-powerset --exclude-features "default boxed-futures use-boxed-futures" --exclude-no-default-features test
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ 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.
- `async` entrait functions will get rewritten to the `fn f(..) -> impl Future<Output = ?>` form when appearing in trait definitions, due to the [async_fn_in_trait](https://doc.rust-lang.org/beta/rustc/lints/listing/warn-by-default.html#async-fn-in-trait) warn-by-default lint.
### 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
Expand Down
18 changes: 7 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[package]
name = "entrait"
version = "0.6.0"
version = "0.7.0-dev"
authors = ["Audun Halland <[email protected]>"]
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/"
Expand All @@ -13,29 +13,25 @@ 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.5", optional = true }
unimock = { version = "0.6.2", optional = true }

[dev-dependencies]
tokio = { version = "1", features = ["macros", "rt"] }
feignhttp = "0.5"
mockall = "0.11"
mockall = "0.12"
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.
doctest = false

[package.metadata.docs.rs]
features = ["unimock", "use-boxed-futures"]
features = ["unimock"]

[workspace]
members = [
Expand Down
37 changes: 3 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -527,37 +527,10 @@ fn foo<D>(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<D>(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<D>(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.
Expand Down Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion entrait_macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "entrait_macros"
version = "0.6.0"
version = "0.7.0-dev"
authors = ["Audun Halland <[email protected]>"]
edition = "2021"
rust-version = "1.60"
Expand Down
56 changes: 10 additions & 46 deletions entrait_macros/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<Item = &'o TraitFn>,
) -> Option<impl ToTokens + 's> {
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)
);
}
}
11 changes: 3 additions & 8 deletions entrait_macros/src/entrait_fn/input_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -33,13 +33,8 @@ impl Parse for EntraitFnAttr {
match input.parse::<EntraitOpt>()? {
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),
Expand All @@ -56,8 +51,8 @@ impl Parse for EntraitFnAttr {
default_span,
no_deps,
debug,
async_strategy,
export,
future_send,
mock_api,
unimock,
mockall,
Expand Down
25 changes: 14 additions & 11 deletions entrait_macros/src/entrait_fn/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand All @@ -33,22 +34,21 @@ 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,
&trait_fns,
&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,
Expand All @@ -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,
Expand All @@ -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);

Expand All @@ -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<TokenStream> {
Expand All @@ -103,24 +108,22 @@ pub fn entrait_for_mod(attr: &EntraitFnAttr, input_mod: InputMod) -> syn::Result
.analyze(input_fn.input_sig(), &mut generics_analyzer)
})
.collect::<syn::Result<Vec<_>>>()?;
let sub_attributes = analyze_sub_attributes(&input_mod.attrs);

let trait_dependency_mode = detect_trait_dependency_mode(
&fn_input_mode,
&trait_fns,
&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 {
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,
Expand All @@ -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);

Expand Down
4 changes: 2 additions & 2 deletions entrait_macros/src/entrait_impl/input_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Loading

0 comments on commit 05517c4

Please sign in to comment.