@@ -653,3 +653,228 @@ pub(super) fn vm_map_key_from_native_value(value: NativeValue) -> VmMapKey {
653653 other => VmMapKey :: String ( Rc :: new ( format ! ( "{other:?}" ) ) ) ,
654654 }
655655}
656+
657+ #[ cfg( test) ]
658+ mod tests {
659+ //! VM <-> native value marshalling: every variant in both directions, the
660+ //! `Option` bridge, managed-unwrap, the closure rejection, and the map-key
661+ //! fallback. This is the host boundary, so a missed arm here is a silently
662+ //! wrong value at a native call.
663+ use super :: * ;
664+ use crate :: vm_value:: { VmClosure , VmNative } ;
665+ use std:: collections:: { BTreeMap , VecDeque } ;
666+
667+ fn to_native ( value : VmValue ) -> NativeValue {
668+ native_value_from_vm_value ( value) . expect ( "conversion should succeed" )
669+ }
670+
671+ #[ test]
672+ fn vm_to_native_scalars ( ) {
673+ assert_eq ! ( to_native( VmValue :: Unit ) , NativeValue :: Unit ) ;
674+ assert_eq ! ( to_native( VmValue :: Int ( 7 ) ) , NativeValue :: Int ( 7 ) ) ;
675+ assert_eq ! ( to_native( VmValue :: Float ( 1.5 ) ) , NativeValue :: Float ( 1.5 ) ) ;
676+ assert_eq ! ( to_native( VmValue :: Bool ( true ) ) , NativeValue :: Bool ( true ) ) ;
677+ assert_eq ! ( to_native( VmValue :: Char ( 'x' ) ) , NativeValue :: Char ( 'x' ) ) ;
678+ assert_eq ! (
679+ to_native( VmValue :: string( "hi" ) ) ,
680+ NativeValue :: String ( "hi" . to_string( ) )
681+ ) ;
682+ assert_eq ! (
683+ to_native( VmValue :: Bytes ( Rc :: new( vec![ 1 , 2 , 3 ] ) ) ) ,
684+ NativeValue :: Bytes ( vec![ 1 , 2 , 3 ] )
685+ ) ;
686+ assert_eq ! (
687+ to_native( VmValue :: Json ( Rc :: new( serde_json:: json!( { "a" : 1 } ) ) ) ) ,
688+ NativeValue :: Json ( serde_json:: json!( { "a" : 1 } ) )
689+ ) ;
690+ }
691+
692+ #[ test]
693+ fn vm_to_native_collections ( ) {
694+ let list = VmValue :: List ( Rc :: new ( RefCell :: new ( vec ! [
695+ VmValue :: Int ( 1 ) ,
696+ VmValue :: Int ( 2 ) ,
697+ ] ) ) ) ;
698+ assert_eq ! (
699+ to_native( list) ,
700+ NativeValue :: List ( vec![ NativeValue :: Int ( 1 ) , NativeValue :: Int ( 2 ) ] )
701+ ) ;
702+
703+ // `Deque` also marshals to a native list.
704+ let deque = VmValue :: Deque ( Rc :: new ( RefCell :: new ( VecDeque :: from ( vec ! [ VmValue :: Int ( 9 ) ] ) ) ) ) ;
705+ assert_eq ! (
706+ to_native( deque) ,
707+ NativeValue :: List ( vec![ NativeValue :: Int ( 9 ) ] )
708+ ) ;
709+
710+ let mut map: ValueMap = ValueMap :: default ( ) ;
711+ map. insert ( VmMapKey :: Int ( 1 ) , VmValue :: string ( "one" ) ) ;
712+ assert_eq ! (
713+ to_native( VmValue :: Map ( Rc :: new( RefCell :: new( map) ) ) ) ,
714+ NativeValue :: Map ( vec![ (
715+ NativeValue :: Int ( 1 ) ,
716+ NativeValue :: String ( "one" . to_string( ) )
717+ ) ] )
718+ ) ;
719+ }
720+
721+ #[ test]
722+ fn vm_to_native_struct_variant_native ( ) {
723+ let s = VmValue :: Struct ( Rc :: new ( VmStruct :: from_named (
724+ "Point" ,
725+ [ ( "x" , VmValue :: Int ( 1 ) ) , ( "y" , VmValue :: Int ( 2 ) ) ] ,
726+ ) ) ) ;
727+ match to_native ( s) {
728+ NativeValue :: Struct { name, fields } => {
729+ assert_eq ! ( name, "Point" ) ;
730+ assert_eq ! ( fields[ "x" ] , NativeValue :: Int ( 1 ) ) ;
731+ assert_eq ! ( fields[ "y" ] , NativeValue :: Int ( 2 ) ) ;
732+ }
733+ other => panic ! ( "expected struct, got {other:?}" ) ,
734+ }
735+
736+ let v = VmValue :: Variant ( Rc :: new ( VmStruct :: from_named (
737+ "Tag" ,
738+ [ ( "v" , VmValue :: Bool ( true ) ) ] ,
739+ ) ) ) ;
740+ match to_native ( v) {
741+ NativeValue :: Variant { name, fields } => {
742+ assert_eq ! ( name, "Tag" ) ;
743+ assert_eq ! ( fields[ "v" ] , NativeValue :: Bool ( true ) ) ;
744+ }
745+ other => panic ! ( "expected variant, got {other:?}" ) ,
746+ }
747+
748+ let native = VmValue :: Native ( Rc :: new ( VmNative {
749+ type_name : Rc :: from ( "Handle" ) ,
750+ id : 42 ,
751+ } ) ) ;
752+ assert_eq ! (
753+ to_native( native) ,
754+ NativeValue :: Native {
755+ type_name: "Handle" . to_string( ) ,
756+ id: 42
757+ }
758+ ) ;
759+ }
760+
761+ #[ test]
762+ fn vm_to_native_bridges_option ( ) {
763+ match to_native ( VmValue :: OptionSome ( Box :: new ( VmValue :: Int ( 5 ) ) ) ) {
764+ NativeValue :: Variant { name, fields } => {
765+ assert_eq ! ( name, "Some" ) ;
766+ assert_eq ! ( fields[ "value" ] , NativeValue :: Int ( 5 ) ) ;
767+ }
768+ other => panic ! ( "expected Some variant, got {other:?}" ) ,
769+ }
770+ match to_native ( VmValue :: OptionNone ) {
771+ NativeValue :: Variant { name, fields } => {
772+ assert_eq ! ( name, "None" ) ;
773+ assert ! ( fields. is_empty( ) ) ;
774+ }
775+ other => panic ! ( "expected None variant, got {other:?}" ) ,
776+ }
777+ }
778+
779+ #[ test]
780+ fn vm_to_native_unwraps_managed ( ) {
781+ let managed = VmValue :: Managed ( Rc :: new ( RefCell :: new ( VmValue :: Int ( 11 ) ) ) ) ;
782+ assert_eq ! ( to_native( managed) , NativeValue :: Int ( 11 ) ) ;
783+ }
784+
785+ #[ test]
786+ fn vm_to_native_rejects_closure ( ) {
787+ let closure = VmValue :: Closure ( Rc :: new ( VmClosure {
788+ function : 0 ,
789+ captures : Vec :: new ( ) ,
790+ } ) ) ;
791+ match native_value_from_vm_value ( closure) {
792+ Err ( EvalError :: Runtime ( message) ) => assert ! ( message. contains( "Closure" ) , "{message}" ) ,
793+ other => panic ! ( "expected closure rejection, got {other:?}" ) ,
794+ }
795+ }
796+
797+ #[ test]
798+ fn native_to_vm_round_trips_every_variant ( ) {
799+ // A native list carrying one of each variant; round-tripping the whole
800+ // thing exercises every arm of `vm_value_from_native_value` and the
801+ // matching arm of `native_value_from_vm_value`. Single-entry map keeps
802+ // ordering stable across the HashMap hop.
803+ let mut struct_fields = BTreeMap :: new ( ) ;
804+ struct_fields. insert ( "a" . to_string ( ) , NativeValue :: Int ( 1 ) ) ;
805+ let mut variant_fields = BTreeMap :: new ( ) ;
806+ variant_fields. insert ( "b" . to_string ( ) , NativeValue :: Bool ( true ) ) ;
807+
808+ let native = NativeValue :: List ( vec ! [
809+ NativeValue :: Unit ,
810+ NativeValue :: Int ( 1 ) ,
811+ NativeValue :: Float ( 2.5 ) ,
812+ NativeValue :: Bool ( false ) ,
813+ NativeValue :: Char ( 'q' ) ,
814+ NativeValue :: String ( "s" . to_string( ) ) ,
815+ NativeValue :: Bytes ( vec![ 7 , 8 ] ) ,
816+ NativeValue :: Json ( serde_json:: json!( [ 1 , 2 ] ) ) ,
817+ NativeValue :: Map ( vec![ (
818+ NativeValue :: Int ( 3 ) ,
819+ NativeValue :: String ( "v" . to_string( ) ) ,
820+ ) ] ) ,
821+ NativeValue :: Struct {
822+ name: "S" . to_string( ) ,
823+ fields: struct_fields,
824+ } ,
825+ NativeValue :: Variant {
826+ name: "Custom" . to_string( ) ,
827+ fields: variant_fields,
828+ } ,
829+ NativeValue :: Native {
830+ type_name: "H" . to_string( ) ,
831+ id: 5 ,
832+ } ,
833+ ] ) ;
834+
835+ let back = native_value_from_vm_value ( vm_value_from_native_value ( native. clone ( ) ) )
836+ . expect ( "round trip should succeed" ) ;
837+ assert_eq ! ( native, back) ;
838+ }
839+
840+ #[ test]
841+ fn native_to_vm_bridges_option_variants ( ) {
842+ let mut some_fields = BTreeMap :: new ( ) ;
843+ some_fields. insert ( "value" . to_string ( ) , NativeValue :: Int ( 8 ) ) ;
844+ assert ! ( matches!(
845+ vm_value_from_native_value( NativeValue :: Variant {
846+ name: "Some" . to_string( ) ,
847+ fields: some_fields,
848+ } ) ,
849+ VmValue :: OptionSome ( _)
850+ ) ) ;
851+ assert ! ( matches!(
852+ vm_value_from_native_value( NativeValue :: Variant {
853+ name: "None" . to_string( ) ,
854+ fields: BTreeMap :: new( ) ,
855+ } ) ,
856+ VmValue :: OptionNone
857+ ) ) ;
858+ }
859+
860+ #[ test]
861+ fn map_key_from_native_falls_back_to_string ( ) {
862+ assert_eq ! (
863+ vm_map_key_from_native_value( NativeValue :: Bool ( true ) ) ,
864+ VmMapKey :: Bool ( true )
865+ ) ;
866+ assert_eq ! (
867+ vm_map_key_from_native_value( NativeValue :: Int ( 2 ) ) ,
868+ VmMapKey :: Int ( 2 )
869+ ) ;
870+ assert_eq ! (
871+ vm_map_key_from_native_value( NativeValue :: String ( "k" . to_string( ) ) ) ,
872+ VmMapKey :: String ( Rc :: new( "k" . to_string( ) ) )
873+ ) ;
874+ // A non-scalar key has no VM key form, so it falls back to its debug string.
875+ match vm_map_key_from_native_value ( NativeValue :: Float ( 1.0 ) ) {
876+ VmMapKey :: String ( s) => assert ! ( s. contains( "Float" ) , "{s}" ) ,
877+ other => panic ! ( "expected string fallback, got {other:?}" ) ,
878+ }
879+ }
880+ }
0 commit comments