Skip to content

Commit

Permalink
context: add more attrs
Browse files Browse the repository at this point in the history
  • Loading branch information
Nouzan committed Oct 4, 2023
1 parent 776f04c commit b2ea359
Show file tree
Hide file tree
Showing 4 changed files with 303 additions and 20 deletions.
1 change: 1 addition & 0 deletions crates/indicator/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use self::{

pub use self::{
anymap::Context,
extractor::{Data, Env, In, Prev},
layer::{
cache::Cache,
data::AddData,
Expand Down
54 changes: 40 additions & 14 deletions crates/indicator_derive/src/operator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::quote;
use syn::{
punctuated::Punctuated, FnArg, GenericParam, Ident, ItemFn, Lifetime, LifetimeParam, PatType,
ReturnType, Token, Type, Visibility,
punctuated::Punctuated, FnArg, GenericParam, Ident, ItemFn, Lifetime, LifetimeParam, Meta,
PatType, ReturnType, Token, Type, Visibility,
};

use self::args::{GenerateOut, OperatorArgs};
Expand All @@ -23,8 +23,8 @@ pub(super) fn generate_operator(
let args = syn::parse::<OperatorArgs>(args)?;
let mut next = syn::parse::<ItemFn>(input)?;

let next_fn = generate_next_fn(&next)?;
signature::expand(&mut next.sig, &args)?;
let unattributed = signature::expand(&mut next, &args)?;
let next_fn = generate_next_fn(&unattributed)?;

// Documentations.
let docs = next
Expand Down Expand Up @@ -73,28 +73,34 @@ pub(super) fn generate_operator(
let indicator = indicator();
let vis = &next.vis;
let input_type = &args.input_type;
let unattributed_type_generics = if unattributed.sig.generics.params.is_empty() {
quote!()
} else {
let (_, type_generics, _) = unattributed.sig.generics.split_for_impl();
quote!(::#type_generics)
};
let return_stmt = match args.generate_out {
Some(GenerateOut::Out) => {
quote! {
__next(#extractors).into()
__next #unattributed_type_generics (#extractors).into()
}
}
Some(GenerateOut::Data) => {
quote! {
__next(#extractors).map(Into::into)
__next #unattributed_type_generics (#extractors).map(Into::into)
}
}
Some(GenerateOut::WithData) => {
quote! {
{
let (__out, __data) = __next(#extractors);
let (__out, __data) = __next #unattributed_type_generics (#extractors);
(__out.into(), __data.map(Into::into))
}
}
}
None => {
quote! {
__next(#extractors)
__next #unattributed_type_generics (#extractors)
}
}
};
Expand Down Expand Up @@ -130,13 +136,33 @@ fn generate_next_fn(next: &ItemFn) -> syn::Result<TokenStream2> {

fn parse_extractor(arg: &PatType) -> syn::Result<TokenStream2> {
let indicator = indicator();
let PatType { ty, .. } = arg;
Ok(quote! {
{
let __a: #ty = #indicator::context::extractor::FromValueRef::from_value_ref(&__input);
__a
let PatType { ty, attrs, pat, .. } = arg;
let expanded = if let Some(attr) = attrs.first() {
let Meta::List(attr) = &attr.meta else {
unreachable!("must be meta list");
};
let kind = attr.path.get_ident().unwrap();
let name: Ident = syn::parse2(attr.tokens.clone()).unwrap();
let rt = match kind.to_string().as_str() {
"borrow" => quote!(core::borrow::Borrow::borrow(#name)),
"as_ref" => quote!(core::convert::AsRef::as_ref(#name)),
_ => unreachable!(),
};
quote! {
{
let #pat: #ty = #indicator::context::extractor::FromValueRef::from_value_ref(&__input);
#rt
}
}
})
} else {
quote! {
{
let __a: #ty = #indicator::context::extractor::FromValueRef::from_value_ref(&__input);
__a
}
}
};
Ok(expanded)
}

fn generate_struct_def(
Expand Down
256 changes: 253 additions & 3 deletions crates/indicator_derive/src/operator/signature.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
use convert_case::{Case, Casing};
use proc_macro2::{Ident, Span};
use quote::quote;
use syn::{Result, ReturnType, Signature, Type, TypeTuple};
use syn::{
FnArg, ItemFn, Meta, Pat, Result, ReturnType, Signature, Type, TypeReference, TypeTuple,
};

use super::args::{GenerateOut, OperatorArgs};

pub(super) fn expand(sig: &mut Signature, args: &OperatorArgs) -> Result<()> {
expand_generics(sig, args)?;
pub(super) fn expand(input: &mut ItemFn, args: &OperatorArgs) -> Result<ItemFn> {
let mut unattributed = input.clone();
remove_input_attributes(&mut unattributed)?;
expand_generics(&mut input.sig, args)?;
expand_inputs(&mut input.sig, args)?;
Ok(unattributed)
}

fn remove_input_attributes(item_fn: &mut ItemFn) -> Result<()> {
for arg in item_fn.sig.inputs.iter_mut() {
let FnArg::Typed(arg) = arg else {
return Err(syn::Error::new_spanned(arg, "expected typed argument"));
};
arg.attrs.clear();
}
Ok(())
}

Expand Down Expand Up @@ -54,6 +71,239 @@ fn expand_generics(sig: &mut Signature, args: &OperatorArgs) -> Result<()> {
Ok(())
}

fn expand_inputs(sig: &mut Signature, args: &OperatorArgs) -> Result<()> {
let mut generics = vec![];
let mut ctx = Ctx::default();
for arg in sig.inputs.iter_mut() {
if let Some(generic) = expand_input(&mut ctx, arg, &args.input_type)? {
generics.push(generic);
}
}
if !generics.is_empty() {
sig.generics.params.extend(generics);
}
Ok(())
}

#[derive(Default)]
struct Ctx {
input: usize,
env: usize,
data: usize,
}

enum Way {
Borrow,
AsRef,
}

impl Way {
fn is_borrow(&self) -> bool {
matches!(self, Way::Borrow)
}
}

enum ArgKind {
Input(Way),
Env(Way),
Data(Way),
Prev(Way),
}

impl<'a> TryFrom<&'a Meta> for ArgKind {
type Error = syn::Error;

fn try_from(value: &'a Meta) -> std::result::Result<Self, Self::Error> {
match value {
Meta::Path(path) => {
let ident = path.get_ident().ok_or_else(|| {
syn::Error::new_spanned(
path,
"unsupported attribute, expected `input`, `env` or `data`",
)
})?;
match ident.to_string().as_str() {
"input" => Ok(ArgKind::Input(Way::Borrow)),
"env" => Ok(ArgKind::Env(Way::Borrow)),
"data" => Ok(ArgKind::Data(Way::Borrow)),
"prev" => Ok(ArgKind::Prev(Way::Borrow)),
_ => Err(syn::Error::new_spanned(
path,
"unsupported attribute, expected `input`, `env` or `data`",
)),
}
}
Meta::List(list) => {
let way: Ident = syn::parse2(list.tokens.clone())?;
let way = match way.to_string().as_str() {
"borrow" => Way::Borrow,
"as_ref" => Way::AsRef,
_ => {
return Err(syn::Error::new_spanned(
way,
"unsupported value, expected `borrow` or `as_ref`",
))
}
};
let ident = list.path.get_ident().ok_or_else(|| {
syn::Error::new_spanned(
list,
"unsupported attribute, expected `input`, `env` or `data`",
)
})?;
match ident.to_string().as_str() {
"input" => Ok(ArgKind::Input(way)),
"env" => Ok(ArgKind::Env(way)),
"data" => Ok(ArgKind::Data(way)),
"prev" => Ok(ArgKind::Prev(way)),
_ => Err(syn::Error::new_spanned(
ident,
"unsupported attribute, expected `input`, `env` or `data`",
)),
}
}
_ => Err(syn::Error::new_spanned(
value,
"unsupported attribute, expected `input`, `env` or `data`",
)),
}
}
}

fn expand_input(
ctx: &mut Ctx,
fn_arg: &mut FnArg,
input_ty: &Type,
) -> Result<Option<syn::GenericParam>> {
let indicator = super::indicator();

let FnArg::Typed(arg) = fn_arg else {
return Err(syn::Error::new_spanned(fn_arg, "expected typed argument"));
};
let attr = arg.attrs.pop();
if !arg.attrs.is_empty() {
return Err(syn::Error::new_spanned(
arg,
"expected at most one attribute",
));
}
let Some(attr) = attr else {
return Ok(None);
};
let Type::Reference(TypeReference {
elem: target_ty,
lifetime: None,
mutability: None,
..
}) = &*arg.ty
else {
return Err(syn::Error::new_spanned(
arg,
"expected reference type without lifetime and mutability, e.g. `&T`",
));
};
let kind: ArgKind = ArgKind::try_from(&attr.meta)?;
let name = get_variable_name(&arg.pat).map(|n| n.to_string());
let generic = match kind {
ArgKind::Input(way) => {
let name = Ident::new(
&name.unwrap_or_else(|| format!("input{}", ctx.input)),
Span::call_site(),
);
ctx.input += 1;
let pat = quote!(#indicator::context::In(#name));
let ty = input_ty.clone();
let (generic, attr) = if way.is_borrow() {
(
syn::parse2(quote!(#ty: core::borrow::Borrow<#target_ty>))?,
quote!(#[borrow(#name)]),
)
} else {
(
syn::parse2(quote!(#ty: AsRef<#target_ty>))?,
quote!(#[as_ref(#name)]),
)
};
*fn_arg = syn::parse2(quote!(#attr #pat: #indicator::context::In<&#ty>))?;
generic
}
ArgKind::Env(way) => {
let name_string = name.unwrap_or_else(|| format!("env{}", ctx.env));
let name = Ident::new(&name_string, Span::call_site());
ctx.env += 1;
let pat = quote!(#indicator::context::Env(#name));
let ty = Ident::new(&name_string.to_case(Case::Pascal), Span::call_site());
let (generic, attr) = if way.is_borrow() {
(
syn::parse2(
quote!(#ty: core::borrow::Borrow<#target_ty> + Send + Sync + 'static),
)?,
quote!(#[borrow(#name)]),
)
} else {
(
syn::parse2(quote!(#ty: AsRef<#target_ty> + Send + Sync + 'static))?,
quote!(#[as_ref(#name)]),
)
};
*fn_arg = syn::parse2(quote!(#attr #pat: #indicator::context::Env<&#ty>))?;
generic
}
ArgKind::Data(way) => {
let name_string = name.unwrap_or_else(|| format!("data{}", ctx.env));
let name = Ident::new(&name_string, Span::call_site());
ctx.data += 1;
let pat = quote!(#indicator::context::Data(#name));
let ty = Ident::new(&name_string.to_case(Case::Pascal), Span::call_site());
let (generic, attr) = if way.is_borrow() {
(
syn::parse2(
quote!(#ty: core::borrow::Borrow<#target_ty> + Send + Sync + 'static),
)?,
quote!(#[borrow(#name)]),
)
} else {
(
syn::parse2(quote!(#ty: AsRef<#target_ty> + Send + Sync + 'static))?,
quote!(#[as_ref(#name)]),
)
};
*fn_arg = syn::parse2(quote!(#attr #pat: #indicator::context::Data<&#ty>))?;
generic
}
ArgKind::Prev(way) => {
let name_string = name.unwrap_or_else(|| format!("prev{}", ctx.env));
let name = Ident::new(&name_string, Span::call_site());
ctx.data += 1;
let pat = quote!(#indicator::context::Prev(#name));
let ty = Ident::new(&name_string.to_case(Case::Pascal), Span::call_site());
let (generic, attr) = if way.is_borrow() {
(
syn::parse2(
quote!(#ty: core::borrow::Borrow<#target_ty> + Send + Sync + 'static),
)?,
quote!(#[borrow(#name)]),
)
} else {
(
syn::parse2(quote!(#ty: AsRef<#target_ty> + Send + Sync + 'static))?,
quote!(#[as_ref(#name)]),
)
};
*fn_arg = syn::parse2(quote!(#attr #pat: #indicator::context::Prev<&#ty>))?;
generic
}
};
Ok(Some(generic))
}

fn get_variable_name(pat: &Pat) -> Option<&Ident> {
match pat {
Pat::Ident(pat) => Some(&pat.ident),
_ => None,
}
}

fn get_return_type(sig: &Signature) -> Type {
match &sig.output {
ReturnType::Default => Type::Tuple(TypeTuple {
Expand Down
Loading

0 comments on commit b2ea359

Please sign in to comment.