@@ -308,13 +308,13 @@ fn generate_vm_wrapper(
308308
309309 Ok ( quote ! {
310310 #[ allow( dead_code) ]
311- pub ( super ) fn #wrapper_name( #( #imm_wrapper_params) , * ) -> #wrapper_output {
311+ pub ( crate ) fn #wrapper_name( #( #imm_wrapper_params) , * ) -> #wrapper_output {
312312 #( #imm_extract_stmts) *
313313 #call_expr
314314 }
315315
316316 #[ allow( dead_code) ]
317- pub ( super ) fn #mutable_wrapper_name( #( #mut_wrapper_params) , * ) -> #wrapper_output {
317+ pub ( crate ) fn #mutable_wrapper_name( #( #mut_wrapper_params) , * ) -> #wrapper_output {
318318 #( #mut_extract_stmts) *
319319 #call_expr
320320 }
@@ -344,7 +344,7 @@ fn generate_async_vm_wrapper(
344344 let ty = & pat_type. ty ;
345345 if is_host_context_param ( input) {
346346 extract_stmts. push ( quote ! {
347- let #ident = <#ty as super :: CaptureAsyncHostContext >:: capture ( vm) ?;
347+ let #ident = <#ty as super :: CaptureAsyncHostContext >:: capture_with_args ( vm, args ) ?;
348348 } ) ;
349349 call_args. push ( quote ! ( #ident) ) ;
350350 continue ;
@@ -366,12 +366,14 @@ fn generate_async_vm_wrapper(
366366 } else {
367367 quote ! ( #impl_name( #( #call_args) , * ) . await )
368368 } ;
369- let body = quote ! {
370- # ( #extract_stmts ) *
371- vm . submit_host_future ( Box :: pin ( async move {
372- let value = #await_value ;
369+ let future_result = if return_is_host_future_output ( & item . sig . output ) {
370+ quote ! ( Ok ( value . map ( super :: return_one ) ) )
371+ } else {
372+ quote ! {
373373 match super :: IntoHostCallOutcome :: into_host_call_outcome( value) {
374- super :: CallOutcome :: Return ( values) => Ok ( values) ,
374+ super :: CallOutcome :: Return ( values) => {
375+ Ok ( super :: HostFutureOutput :: returning( values) )
376+ }
375377 super :: CallOutcome :: Pending ( op_id) => Err ( super :: VmError :: HostError (
376378 format!( "async host function returned nested pending operation {op_id}" ) ,
377379 ) ) ,
@@ -381,20 +383,27 @@ fn generate_async_vm_wrapper(
381383 ) ,
382384 ) ,
383385 }
386+ }
387+ } ;
388+ let body = quote ! {
389+ #( #extract_stmts) *
390+ vm. submit_host_future( Box :: pin( async move {
391+ let value = #await_value;
392+ #future_result
384393 } ) )
385394 } ;
386395
387396 Ok ( quote ! {
388397 #[ allow( dead_code) ]
389- pub ( super ) fn #wrapper_name(
398+ pub ( crate ) fn #wrapper_name(
390399 vm: & mut super :: super :: Vm ,
391400 args: & [ super :: super :: Value ] ,
392401 ) -> super :: super :: VmResult <super :: CallOutcome > {
393402 #body
394403 }
395404
396405 #[ allow( dead_code) ]
397- pub ( super ) fn #mutable_wrapper_name(
406+ pub ( crate ) fn #mutable_wrapper_name(
398407 vm: & mut super :: super :: Vm ,
399408 args: & mut [ super :: super :: Value ] ,
400409 ) -> super :: super :: VmResult <super :: CallOutcome > {
@@ -468,6 +477,20 @@ fn unwrap_vm_result_type(ty: &Type) -> Result<Option<Type>, Error> {
468477 }
469478}
470479
480+ fn return_is_host_future_output ( output : & ReturnType ) -> bool {
481+ vm_result_inner_type ( output)
482+ . expect ( "pd_host_function return type should already be validated" )
483+ . and_then ( |ty| match ty {
484+ Type :: Path ( path) => path
485+ . path
486+ . segments
487+ . last ( )
488+ . map ( |segment| segment. ident . clone ( ) ) ,
489+ _ => None ,
490+ } )
491+ . is_some_and ( |ident| ident == "HostFutureOutput" )
492+ }
493+
471494fn return_is_vm_result ( output : & ReturnType ) -> bool {
472495 vm_result_inner_type ( output)
473496 . expect ( "pd_host_function return type should already be validated" )
@@ -529,7 +552,8 @@ fn type_label(ty: &Type) -> Result<String, Error> {
529552 let inner_label = type_label ( inner) ?;
530553 Ok ( format ! ( "{inner_label} | null" ) )
531554 }
532- "VmResult" | "BuiltinResult" | "HostResult" | "HostCallResult" => {
555+ "VmResult" | "BuiltinResult" | "HostResult" | "HostCallResult"
556+ | "HostFutureOutput" => {
533557 let syn:: PathArguments :: AngleBracketed ( args) = & segment. arguments else {
534558 return Err ( Error :: new_spanned (
535559 & segment. arguments ,
@@ -690,9 +714,26 @@ mod tests {
690714 assert ! ( expanded. contains( "async move" ) ) ;
691715 assert ! ( expanded. contains( "borrow_arg" ) ) ;
692716 assert ! ( expanded. contains( "CaptureAsyncHostContext" ) ) ;
717+ assert ! ( expanded. contains( "capture_with_args" ) ) ;
693718 assert ! ( !expanded. contains( "pd_host_context" ) ) ;
694719 }
695720
721+ #[ test]
722+ fn async_host_future_output_maps_its_inner_value_to_call_return ( ) {
723+ let attr: Punctuated < Meta , Token ! [ , ] > = parse_quote ! ( name = "test::completion" ) ;
724+ let item: ItemFn = parse_quote ! {
725+ /// Completes after mutating VM-owned state.
726+ async fn completion( ) -> VmResult <HostFutureOutput <i64 >> {
727+ todo!( )
728+ }
729+ } ;
730+
731+ let expanded = expand_pd_host_function ( attr, item)
732+ . expect ( "host future output should be accepted" )
733+ . to_string ( ) ;
734+ assert ! ( expanded. contains( "value . map (super :: return_one)" ) ) ;
735+ }
736+
696737 #[ test]
697738 fn async_signature_rejects_borrowed_parameters ( ) {
698739 let attr: Punctuated < Meta , Token ! [ , ] > = parse_quote ! ( name = "test::borrowed" ) ;
0 commit comments