Skip to content

Commit

Permalink
feat: add enum_either attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
SOF3 committed Jan 18, 2025
1 parent 281cf2f commit 92d10e6
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 13 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ log-filler = ["portrait-codegen/log-filler"]
portrait-codegen = {version = "0.3.0", path = "./codegen"}

[dev-dependencies]
either = "1.13.0"
log = "0.4.17"
static_assertions = "1.1.0"
95 changes: 84 additions & 11 deletions codegen/src/derive_fillers/derive_delegate.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use portrait_framework::{DeriveContext, GenerateDerive, NoArgs};
use proc_macro2::Span;
use syn::spanned::Spanned;
use syn::{punctuated::Punctuated, spanned::Spanned};

Check warning on line 3 in codegen/src/derive_fillers/derive_delegate.rs

View workflow job for this annotation

GitHub Actions / debug--release

unused import: `punctuated::Punctuated`

warning: unused import: `punctuated::Punctuated` --> codegen/src/derive_fillers/derive_delegate.rs:3:11 | 3 | use syn::{punctuated::Punctuated, spanned::Spanned}; | ^^^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default

Check warning on line 3 in codegen/src/derive_fillers/derive_delegate.rs

View workflow job for this annotation

GitHub Actions / debug--release

unused import: `punctuated::Punctuated`

warning: unused import: `punctuated::Punctuated` --> codegen/src/derive_fillers/derive_delegate.rs:3:11 | 3 | use syn::{punctuated::Punctuated, spanned::Spanned}; | ^^^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default

Check warning on line 3 in codegen/src/derive_fillers/derive_delegate.rs

View workflow job for this annotation

GitHub Actions / debug

unused import: `punctuated::Punctuated`

warning: unused import: `punctuated::Punctuated` --> codegen/src/derive_fillers/derive_delegate.rs:3:11 | 3 | use syn::{punctuated::Punctuated, spanned::Spanned}; | ^^^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default

Check warning on line 3 in codegen/src/derive_fillers/derive_delegate.rs

View workflow job for this annotation

GitHub Actions / debug

unused import: `punctuated::Punctuated`

warning: unused import: `punctuated::Punctuated` --> codegen/src/derive_fillers/derive_delegate.rs:3:11 | 3 | use syn::{punctuated::Punctuated, spanned::Spanned}; | ^^^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default

use crate::util;

Expand Down Expand Up @@ -233,7 +233,7 @@ fn transform_enum(
};

let mut arms = Vec::new();
for variant in &data.variants {
for (index, variant) in data.variants.iter().enumerate() {
let variant_ident = &variant.ident;
let arm_stmts = transform_return(
item,
Expand All @@ -245,6 +245,45 @@ fn transform_enum(
true,
)?;

let mut block = syn::Expr::Block(syn::ExprBlock {
attrs: Vec::new(),
label: None,
block: syn::Block {
brace_token: syn::token::Brace(variant.span()),
stmts: arm_stmts,
},
});

if let Some((_, either)) = &fn_args.enum_either.0 {
if index + 1 == data.variants.len() {
// if variants.len() == 4, 3 => Right(Right(Right))
for _ in 0..index {
block = syn::Expr::Call(syn::ExprCall {
attrs: Vec::new(),
func: Box::new(either_right(either.as_ref())),
args: [block].into_iter().collect(),
paren_token: either_paren(either.as_ref()),
});
}
} else {
// 0 => Left, 1 => Right(Left), 2 => Right(Right(Left)), ...
block = syn::Expr::Call(syn::ExprCall {
attrs: Vec::new(),
func: Box::new(either_left(either.as_ref())),
args: [block].into_iter().collect(),
paren_token: either_paren(either.as_ref()),
});
for _ in 0..index {
block = syn::Expr::Call(syn::ExprCall {
attrs: Vec::new(),
func: Box::new(either_right(either.as_ref())),
args: [block].into_iter().collect(),
paren_token: either_paren(either.as_ref()),
});
}
}
}

let fields = variant
.fields
.iter()
Expand Down Expand Up @@ -282,14 +321,7 @@ fn transform_enum(
}),
guard: None,
fat_arrow_token: syn::Token![=>](variant.span()),
body: Box::new(syn::Expr::Block(syn::ExprBlock {
attrs: Vec::new(),
label: None,
block: syn::Block {
brace_token: syn::token::Brace(variant.span()),
stmts: arm_stmts,
},
})),
body: Box::new(block),
comma: Some(syn::Token![,](variant.span())),
})
}
Expand Down Expand Up @@ -494,13 +526,15 @@ fn is_self_ty(ty: &syn::Type) -> bool {
mod kw {
syn::custom_keyword!(reduce);
syn::custom_keyword!(reduce_base);
syn::custom_keyword!(enum_either);
}

#[derive(Default)]
struct FnArgs {
reduce: util::Once<syn::Expr>,
reduce_base: util::Once<syn::Expr>,
with_try: util::Once<Option<syn::Expr>>,
enum_either: util::Once<Option<EnumEither>>,
}

impl util::ParseArgs for FnArgs {
Expand All @@ -526,13 +560,52 @@ impl util::ParseArgs for FnArgs {
};

self.with_try.set(ok_expr, key.span())?;
} else {
} else if lh.peek(kw::enum_either) {
let key: kw::enum_either = input.parse()?;
let value = input.peek(syn::Token![=]).then( ||{
let _: syn::Token![=] = input.parse()?;

let inner;
let paren = syn::parenthesized!(inner in input);

Ok(EnumEither { paren, left: inner.parse()?, _comma: inner.parse()?, right: inner.parse()? })
}).transpose()?;
self.enum_either.set(value, key.span())?;
}else {
return Err(lh.error());
}
Ok(())
}
}

struct EnumEither {
paren: syn::token::Paren,
left: syn::Expr,
_comma: syn::Token![,],
right: syn::Expr,
}

fn either_left(option: Option<&EnumEither>) -> syn::Expr {
match option {
Some(either) => either.left.clone(),
None => syn::parse_quote! { Either::Left },
}
}

fn either_right(option: Option<&EnumEither>) -> syn::Expr {
match option {
Some(either) => either.right.clone(),
None => syn::parse_quote! { Either::Right },
}
}

fn either_paren(option: Option<&EnumEither>) -> syn::token::Paren {
match option {
Some(either) => either.paren,
None => syn::token::Paren::default(),
}
}

fn cfg_attrs<'t>(attrs: impl IntoIterator<Item = &'t syn::Attribute>) -> Vec<syn::Attribute> {
attrs.into_iter().filter(|attr| attr.path().is_ident("cfg")).cloned().collect()
}
30 changes: 28 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,34 @@ pub use portrait_codegen::derive;
/// # */
/// ```
///
/// ## `either`
/// If the `either` option is applied, when deriving from enums,
/// each match arm is wrapped with a nested tree of `Either::Left(..)`/`Either::Right(..)`s
/// such that each arm bijects to a unique variant of some `Either<Either<Either<..>>>`.
/// This is useful for delegating to separate enum implementations
/// when the return type is opaque (e.g. return-impl-trait).
///
/// ```
/// # /*
/// #[portrait(derive_delegate(enum_either))]
/// fn iter(&self) -> impl Iterator<Item = Xxx>;
/// # */
/// ```
///
/// Note that portrait only knows how to implement the trait with this associated function,
/// but does not derive anything for the trait bound `Trait` in `-> impl Trait`.
/// In particular, this means that someone has to have implemented `Trait` for `Either<A, B>`.
///
/// For traits not implemented by `Either` by default, an alternative wrapper could be used
/// by specifying the "left" and "right" operands in the attribute:
///
/// ```
/// # /*
/// #[portrait(derive_delegate(enum_either = left_wrapper, right_wrapper))]
/// fn as_trait(&self) -> impl Trait;
/// # */
/// ```
///
/// # Example
/// ```
/// #[portrait::make]
Expand Down Expand Up @@ -381,7 +409,6 @@ pub use portrait_codegen::derive;
///
/// - All associated functions must have a receiver.
/// - Non-receiver parameters must not take a `Self`-based type.
///
/// ```
/// #[portrait::make]
/// trait Foo {
Expand All @@ -403,7 +430,6 @@ pub use portrait_codegen::derive;
///
/// Traits are implemented for generic types as long as the implementation is feasible,
/// unlike the standard macros that implement on the generic variables directly.
///
/// ```
/// #[portrait::make]
/// trait Create {
Expand Down
33 changes: 33 additions & 0 deletions tests/derive_delegate_either.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use either::Either;

#[portrait::make]
trait Foo {
#[portrait(derive_delegate(enum_either))]
fn foo(&self, non_copy: String) -> impl Iterator<Item = i32> + DoubleEndedIterator;
}

impl Foo for i32 {
fn foo(&self, _non_copy: String) -> impl Iterator<Item = i32> + DoubleEndedIterator {
[1, 2].into_iter()
}
}

impl Foo for String {
fn foo(&self, _non_copy: String) -> impl Iterator<Item = i32> + DoubleEndedIterator {
std::iter::repeat(5).take(5)
}
}

#[portrait::derive(Foo with portrait::derive_delegate)]
enum Impls {
A(i32),
B(i32),
C(String),
D(String),
}

fn main() {
fn assert(_: impl Foo) {}

assert(Impls::A(1));
}

0 comments on commit 92d10e6

Please sign in to comment.