@@ -167,7 +167,7 @@ pub(super) fn builtin_array_push(args: Vec<Value>) -> VmResult<Vec<Value>> {
167167/// Create an empty map.
168168#[ pd_host_function( name = "map_new" ) ]
169169pub ( super ) fn builtin_map_new_impl ( ) -> VmMap {
170- Vec :: new ( )
170+ VmMap :: new ( )
171171}
172172
173173/// Read a string entry.
@@ -208,12 +208,43 @@ pub(super) fn builtin_get_array_impl(items: VmArray, index: i64) -> VmResult<Unk
208208/// Read a map value by key.
209209#[ pd_host_function( name = "get" ) ]
210210pub ( super ) fn builtin_get_map_impl ( entries : VmMap , key : AnyValue ) -> VmResult < UnknownValue > {
211- for ( existing_key, value) in entries {
212- if existing_key == key {
213- return Ok ( value) ;
214- }
211+ entries
212+ . get ( & key)
213+ . cloned ( )
214+ . ok_or_else ( || VmError :: HostError ( "map key not found" . to_string ( ) ) )
215+ }
216+
217+ /// Check whether an array contains a valid index.
218+ #[ pd_host_function( name = "has" ) ]
219+ pub ( super ) fn builtin_has_array_impl ( items : VmArray , index : i64 ) -> bool {
220+ if index < 0 {
221+ return false ;
222+ }
223+ usize:: try_from ( index)
224+ . ok ( )
225+ . is_some_and ( |index| index < items. len ( ) )
226+ }
227+
228+ /// Check whether a map contains a key.
229+ #[ pd_host_function( name = "has" ) ]
230+ pub ( super ) fn builtin_has_map_impl ( entries : VmMap , key : AnyValue ) -> bool {
231+ entries. get ( & key) . is_some ( )
232+ }
233+
234+ pub ( super ) fn builtin_has ( args : & [ Value ] ) -> VmResult < Vec < Value > > {
235+ let container = arg :: < & Value > ( args, 0 , "has container" ) ?;
236+ let key = arg :: < & Value > ( args, 1 , "has key" ) ?;
237+ match container {
238+ Value :: Array ( values) => Ok ( return_values ( builtin_has_array_impl (
239+ unwrap_or_clone_shared ( values. clone ( ) ) ,
240+ key. as_int ( ) ?,
241+ ) ) ) ,
242+ Value :: Map ( entries) => Ok ( return_values ( builtin_has_map_impl (
243+ unwrap_or_clone_shared ( entries. clone ( ) ) ,
244+ key. clone ( ) ,
245+ ) ) ) ,
246+ _ => Err ( VmError :: TypeMismatch ( "array/map" ) ) ,
215247 }
216- Err ( VmError :: HostError ( "map key not found" . to_string ( ) ) )
217248}
218249
219250pub ( super ) fn builtin_get ( args : Vec < Value > ) -> VmResult < Vec < Value > > {
@@ -436,13 +467,10 @@ pub(super) fn builtin_set_array_impl(
436467/// Update a map entry and return the updated map.
437468#[ pd_host_function( name = "set" ) ]
438469pub ( super ) fn builtin_set_map_impl ( mut entries : VmMap , key : AnyValue , value : AnyValue ) -> VmMap {
439- if let Some ( ( _, existing_value) ) = entries
440- . iter_mut ( )
441- . find ( |( existing_key, _) | * existing_key == key)
442- {
443- * existing_value = value;
470+ if matches ! ( value, Value :: Null ) {
471+ entries. remove ( & key) ;
444472 } else {
445- entries. push ( ( key, value) ) ;
473+ entries. insert ( key, value) ;
446474 }
447475 entries
448476}
@@ -599,14 +627,51 @@ mod tests {
599627 panic ! ( "expected map alias" ) ;
600628 } ;
601629
630+ assert_eq ! ( alias_entries. len( ) , 1 ) ;
631+ assert_eq ! ( alias_entries. get( & Value :: string( "k" ) ) , Some ( & Value :: Int ( 1 ) ) ) ;
632+ assert_eq ! ( result. len( ) , 1 ) ;
633+ assert_eq ! ( result. get( & Value :: string( "k" ) ) , Some ( & Value :: Int ( 9 ) ) ) ;
634+ assert ! (
635+ !Arc :: ptr_eq( alias_entries, result) ,
636+ "mutating a shared map should detach backing storage"
637+ ) ;
638+ }
639+
640+ #[ test]
641+ fn set_map_null_removes_entry ( ) {
642+ let shared = Value :: map ( vec ! [ ( Value :: string( "drop" ) , Value :: Int ( 1 ) ) ] ) ;
643+ let alias = shared. clone ( ) ;
644+
645+ let out = builtin_set ( vec ! [ shared, Value :: string( "drop" ) , Value :: Null ] )
646+ . expect ( "map null set should work" ) ;
647+ let [ Value :: Map ( result) ] = out. as_slice ( ) else {
648+ panic ! ( "expected map result" ) ;
649+ } ;
650+ let Value :: Map ( alias_entries) = & alias else {
651+ panic ! ( "expected map alias" ) ;
652+ } ;
653+
654+ assert_eq ! ( alias_entries. len( ) , 1 ) ;
602655 assert_eq ! (
603- alias_entries. as_ref ( ) ,
604- & vec! [ ( Value :: string ( "k" ) , Value :: Int ( 1 ) ) ]
656+ alias_entries. get ( & Value :: string ( "drop" ) ) ,
657+ Some ( & Value :: Int ( 1 ) )
605658 ) ;
606- assert_eq ! ( result. as_ref( ) , & vec![ ( Value :: string( "k" ) , Value :: Int ( 9 ) ) ] ) ;
659+ assert_eq ! ( result. len( ) , 0 ) ;
660+ assert_eq ! ( result. get( & Value :: string( "drop" ) ) , None ) ;
607661 assert ! (
608662 !Arc :: ptr_eq( alias_entries, result) ,
609663 "mutating a shared map should detach backing storage"
610664 ) ;
611665 }
666+
667+ #[ test]
668+ fn has_map_uses_identity_for_heap_keys ( ) {
669+ let key = Value :: array ( vec ! [ Value :: Int ( 1 ) , Value :: Int ( 2 ) ] ) ;
670+ let alias = key. clone ( ) ;
671+ let structural_peer = Value :: array ( vec ! [ Value :: Int ( 1 ) , Value :: Int ( 2 ) ] ) ;
672+ let map = VmMap :: from ( vec ! [ ( key, Value :: Bool ( true ) ) ] ) ;
673+
674+ assert ! ( builtin_has_map_impl( map. clone( ) , alias) ) ;
675+ assert ! ( !builtin_has_map_impl( map, structural_peer) ) ;
676+ }
612677}
0 commit comments