fix(macros): respect local feature in #[prompt] macro — omit + Send bound (#803)
* refactor(prompt): update return type handling * fix(prompt): add omit send and test
This commit is contained in:
parent
c99903a67a
commit
6603c1ff15
1 changed files with 50 additions and 3 deletions
|
|
@ -20,6 +20,9 @@ pub struct PromptAttribute {
|
||||||
pub icons: Option<Expr>,
|
pub icons: Option<Expr>,
|
||||||
/// Optional metadata for the prompt
|
/// Optional metadata for the prompt
|
||||||
pub meta: Option<Expr>,
|
pub meta: Option<Expr>,
|
||||||
|
/// When true, the generated future will not require `Send`. Useful for `!Send` handlers
|
||||||
|
/// (e.g. single-threaded database connections). Also enabled globally by the `local` crate feature.
|
||||||
|
pub local: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ResolvedPromptAttribute {
|
pub struct ResolvedPromptAttribute {
|
||||||
|
|
@ -78,6 +81,7 @@ pub fn prompt(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream>
|
||||||
};
|
};
|
||||||
let mut fn_item = syn::parse2::<ImplItemFn>(input.clone())?;
|
let mut fn_item = syn::parse2::<ImplItemFn>(input.clone())?;
|
||||||
let fn_ident = &fn_item.sig.ident;
|
let fn_ident = &fn_item.sig.ident;
|
||||||
|
let omit_send = cfg!(feature = "local") || attribute.local;
|
||||||
|
|
||||||
let prompt_attr_fn_ident = format_ident!("{}_prompt_attr", fn_ident);
|
let prompt_attr_fn_ident = format_ident!("{}_prompt_attr", fn_ident);
|
||||||
|
|
||||||
|
|
@ -123,7 +127,8 @@ pub fn prompt(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream>
|
||||||
// Modify the input function for async support (same as tool macro)
|
// Modify the input function for async support (same as tool macro)
|
||||||
if fn_item.sig.asyncness.is_some() {
|
if fn_item.sig.asyncness.is_some() {
|
||||||
// 1. remove asyncness from sig
|
// 1. remove asyncness from sig
|
||||||
// 2. make return type: `futures::future::BoxFuture<'_, #ReturnType>`
|
// 2. make return type: `std::pin::Pin<Box<dyn std::future::Future<Output = #ReturnType> + Send + '_>>`
|
||||||
|
// (omit `+ Send` when the `local` crate feature is active or `#[prompt(local)]` is used)
|
||||||
// 3. make body: { Box::pin(async move { #body }) }
|
// 3. make body: { Box::pin(async move { #body }) }
|
||||||
let new_output = syn::parse2::<ReturnType>({
|
let new_output = syn::parse2::<ReturnType>({
|
||||||
let mut lt = quote! { 'static };
|
let mut lt = quote! { 'static };
|
||||||
|
|
@ -138,10 +143,18 @@ pub fn prompt(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream>
|
||||||
}
|
}
|
||||||
match &fn_item.sig.output {
|
match &fn_item.sig.output {
|
||||||
syn::ReturnType::Default => {
|
syn::ReturnType::Default => {
|
||||||
quote! { -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = ()> + Send + #lt>> }
|
if omit_send {
|
||||||
|
quote! { -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = ()> + #lt>> }
|
||||||
|
} else {
|
||||||
|
quote! { -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = ()> + Send + #lt>> }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
syn::ReturnType::Type(_, ty) => {
|
syn::ReturnType::Type(_, ty) => {
|
||||||
quote! { -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = #ty> + Send + #lt>> }
|
if omit_send {
|
||||||
|
quote! { -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = #ty> + #lt>> }
|
||||||
|
} else {
|
||||||
|
quote! { -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = #ty> + Send + #lt>> }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
@ -226,4 +239,38 @@ mod test {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_async_prompt_default_send_behavior() -> syn::Result<()> {
|
||||||
|
let attr = quote! {};
|
||||||
|
let input = quote! {
|
||||||
|
async fn test_prompt_default_send(&self) -> String {
|
||||||
|
"ok".to_string()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let result = prompt(attr, input)?;
|
||||||
|
|
||||||
|
let result_str = result.to_string();
|
||||||
|
if cfg!(feature = "local") {
|
||||||
|
assert!(!result_str.contains("+ Send +"));
|
||||||
|
} else {
|
||||||
|
assert!(result_str.contains("+ Send +"));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_async_prompt_local_omits_send() -> syn::Result<()> {
|
||||||
|
let attr = quote! { local };
|
||||||
|
let input = quote! {
|
||||||
|
async fn test_prompt_local_no_send(&self) -> String {
|
||||||
|
"ok".to_string()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let result = prompt(attr, input)?;
|
||||||
|
|
||||||
|
let result_str = result.to_string();
|
||||||
|
assert!(!result_str.contains("+ Send +"));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue