@@ -37,11 +37,6 @@ use crate::{
3737struct ReplExecutor {
3838 /// Number of slots needed in the global namespace.
3939 namespace_size : usize ,
40- /// Maps variable names to their indices in the namespace.
41- ///
42- /// Stable slot assignment is required across snippets so previously created
43- /// objects continue to resolve names correctly.
44- name_map : AHashMap < String , NamespaceId > ,
4540 /// Compiled bytecode for the snippet/module.
4641 module_code : Code ,
4742 /// Interned strings and compiled functions for this snippet/module.
@@ -55,7 +50,11 @@ impl ReplExecutor {
5550 ///
5651 /// This is equivalent to normal module compilation but scoped to REPL
5752 /// infrastructure so `run.rs` can remain unchanged.
58- fn new ( code : String , script_name : & str , input_names : Vec < String > ) -> Result < Self , MontyException > {
53+ fn new (
54+ code : String ,
55+ script_name : & str ,
56+ input_names : Vec < String > ,
57+ ) -> Result < ( Self , AHashMap < String , NamespaceId > ) , MontyException > {
5958 let parse_result = parse ( & code, script_name) . map_err ( |e| e. into_python_exc ( script_name, & code) ) ?;
6059 let prepared = prepare ( parse_result, input_names) . map_err ( |e| e. into_python_exc ( script_name, & code) ) ?;
6160
@@ -65,13 +64,15 @@ impl ReplExecutor {
6564 . map_err ( |e| e. into_python_exc ( script_name, & code) ) ?;
6665 interns. set_functions ( compile_result. functions ) ;
6766
68- Ok ( Self {
69- namespace_size : prepared. namespace_size ,
70- name_map : prepared. name_map ,
71- module_code : compile_result. code ,
72- interns,
73- code,
74- } )
67+ Ok ( (
68+ Self {
69+ namespace_size : prepared. namespace_size ,
70+ module_code : compile_result. code ,
71+ interns,
72+ code,
73+ } ,
74+ prepared. name_map ,
75+ ) )
7576 }
7677
7778 /// Compiles one incremental REPL snippet against existing session metadata.
@@ -80,11 +81,15 @@ impl ReplExecutor {
8081 /// no-replay execution:
8182 /// - Seeds parsing from `existing_interns` so old `StringId` values stay stable.
8283 /// - Seeds compilation with existing functions so old `FunctionId` values remain valid.
83- /// - Reuses `existing_name_map` and appends new global names only.
84+ /// - Reuses `existing_name_map` in place and appends new global names only.
85+ ///
86+ /// If snippet preparation fails, newly reserved global slots remain in the
87+ /// caller-owned name map. That is harmless because the corresponding runtime
88+ /// namespace entries stay `Undefined` until a later successful assignment.
8489 fn new_repl_snippet (
8590 code : String ,
8691 script_name : & str ,
87- existing_name_map : AHashMap < String , NamespaceId > ,
92+ existing_name_map : & mut AHashMap < String , NamespaceId > ,
8893 existing_interns : & Interns ,
8994 ) -> Result < Self , MontyException > {
9095 let seeded_interner = InternerBuilder :: from_interns ( existing_interns, & code) ;
@@ -103,7 +108,6 @@ impl ReplExecutor {
103108
104109 Ok ( Self {
105110 namespace_size : prepared. namespace_size ,
106- name_map : prepared. name_map ,
107111 module_code : compile_result. code ,
108112 interns,
109113 code,
@@ -282,7 +286,7 @@ impl<T: ResourceTracker> MontyRepl<T> {
282286 resource_tracker : T ,
283287 print : & mut PrintWriter < ' _ > ,
284288 ) -> Result < ( Self , MontyObject ) , MontyException > {
285- let executor = ReplExecutor :: new ( code, script_name, input_names) ?;
289+ let ( executor, global_name_map ) = ReplExecutor :: new ( code, script_name, input_names) ?;
286290
287291 let mut heap = Heap :: new ( executor. namespace_size , resource_tracker) ;
288292 let mut namespaces = executor. prepare_namespaces ( inputs, & mut heap) ?;
@@ -306,7 +310,7 @@ impl<T: ResourceTracker> MontyRepl<T> {
306310 let repl = Self {
307311 script_name : script_name. to_owned ( ) ,
308312 next_input_id : 0 ,
309- global_name_map : executor . name_map ,
313+ global_name_map,
310314 interns : executor. interns ,
311315 heap,
312316 namespaces,
@@ -345,7 +349,7 @@ impl<T: ResourceTracker> MontyRepl<T> {
345349 let executor = match ReplExecutor :: new_repl_snippet (
346350 code. to_owned ( ) ,
347351 & input_script_name,
348- this. global_name_map . clone ( ) ,
352+ & mut this. global_name_map ,
349353 & this. interns ,
350354 ) {
351355 Ok ( exec) => exec,
@@ -377,6 +381,9 @@ impl<T: ResourceTracker> MontyRepl<T> {
377381 /// partially mutating globals, those mutations remain visible in later feeds,
378382 /// matching Python REPL semantics.
379383 ///
384+ /// Compile-time failures may still reserve new global slots in the REPL map.
385+ /// Those slots remain `Undefined` until a later successful snippet writes them.
386+ ///
380387 /// # Errors
381388 /// Returns `MontyException` for syntax/compile/runtime failures.
382389 pub fn feed ( & mut self , code : & str , print : & mut PrintWriter < ' _ > ) -> Result < MontyObject , MontyException > {
@@ -388,13 +395,12 @@ impl<T: ResourceTracker> MontyRepl<T> {
388395 let executor = ReplExecutor :: new_repl_snippet (
389396 code. to_owned ( ) ,
390397 & input_script_name,
391- self . global_name_map . clone ( ) ,
398+ & mut self . global_name_map ,
392399 & self . interns ,
393400 ) ?;
394401
395402 let ReplExecutor {
396403 namespace_size,
397- name_map,
398404 module_code,
399405 interns,
400406 code,
@@ -417,10 +423,8 @@ impl<T: ResourceTracker> MontyRepl<T> {
417423
418424 vm. cleanup ( ) ;
419425
420- // Commit compiler metadata even on runtime errors.
421- // Snippets can mutate globals before raising, and those values may contain
422- // FunctionId/StringId values that must be interpreted with the updated tables.
423- self . global_name_map = name_map;
426+ // Commit intern/function metadata even on runtime errors. The REPL global
427+ // name map was already updated in place during preparation.
424428 self . interns = interns;
425429
426430 frame_exit_to_object ( frame_exit_result, & mut self . heap , & self . interns )
@@ -976,8 +980,7 @@ fn handle_repl_vm_result<T: ResourceTracker>(
976980 match result {
977981 Ok ( FrameExit :: Return ( value) ) => {
978982 let output = MontyObject :: new ( value, & mut repl. heap , & executor. interns ) ;
979- let ReplExecutor { name_map, interns, .. } = executor;
980- repl. global_name_map = name_map;
983+ let ReplExecutor { interns, .. } = executor;
981984 repl. interns = interns;
982985 Ok ( ReplProgress :: Complete { repl, value : output } )
983986 }
@@ -1055,11 +1058,9 @@ fn handle_repl_vm_result<T: ResourceTracker>(
10551058 }
10561059 Err ( err) => {
10571060 let error = err. into_python_exception ( & executor. interns , & executor. code ) ;
1058- // Commit compiler metadata even on runtime errors, matching feed() behavior.
1059- // Snippets can create new variables or functions before raising, and those
1060- // values may reference FunctionId/StringId values from the new tables.
1061- let ReplExecutor { name_map, interns, .. } = executor;
1062- repl. global_name_map = name_map;
1061+ // Commit intern/function metadata even on runtime errors, matching feed() behavior.
1062+ // The global name map was already updated in place during preparation.
1063+ let ReplExecutor { interns, .. } = executor;
10631064 repl. interns = interns;
10641065 Err ( Box :: new ( ReplStartError { repl, error } ) )
10651066 }
0 commit comments