@@ -113,7 +113,7 @@ impl<T: ResourceTracker> MontyRepl<T> {
113113 let executor = match ReplExecutor :: new_repl_snippet (
114114 code. to_owned ( ) ,
115115 & input_script_name,
116- this. global_name_map . clone ( ) ,
116+ & mut this. global_name_map ,
117117 & this. interns ,
118118 input_names,
119119 ) {
@@ -154,6 +154,9 @@ impl<T: ResourceTracker> MontyRepl<T> {
154154 /// partially mutating globals, those mutations remain visible in later feeds,
155155 /// matching Python REPL semantics.
156156 ///
157+ /// Compile-time failures may still reserve new global slots in the REPL map.
158+ /// Those slots remain `Undefined` until a later successful snippet writes them.
159+ ///
157160 /// # Errors
158161 /// Returns `MontyException` for syntax/compile/runtime failures.
159162 pub fn feed_run (
@@ -172,7 +175,7 @@ impl<T: ResourceTracker> MontyRepl<T> {
172175 let executor = ReplExecutor :: new_repl_snippet (
173176 code. to_owned ( ) ,
174177 & input_script_name,
175- self . global_name_map . clone ( ) ,
178+ & mut self . global_name_map ,
176179 & self . interns ,
177180 input_names,
178181 ) ?;
@@ -205,16 +208,9 @@ impl<T: ResourceTracker> MontyRepl<T> {
205208 self . globals = vm. take_globals ( ) ;
206209 vm. cleanup ( ) ;
207210
208- // Commit compiler metadata even on runtime errors.
209- // Snippets can mutate globals before raising, and those values may contain
210- // FunctionId/StringId values that must be interpreted with the updated tables.
211- let ReplExecutor {
212- name_map,
213- interns,
214- code,
215- ..
216- } = executor;
217- self . global_name_map = name_map;
211+ // Commit intern/function metadata even on runtime errors. The REPL global
212+ // name map was already updated in place during preparation.
213+ let ReplExecutor { interns, code, .. } = executor;
218214 self . interns = interns;
219215
220216 result. map_err ( |e| e. into_python_exception ( & self . interns , & code) )
@@ -803,22 +799,18 @@ pub fn detect_repl_continuation_mode(source: &str) -> ReplContinuationMode {
803799struct ReplExecutor {
804800 /// Number of slots needed in the global namespace.
805801 namespace_size : usize ,
806- /// Maps variable names to their indices in the namespace.
807- ///
808- /// Stable slot assignment is required across snippets so previously created
809- /// objects continue to resolve names correctly.
810- name_map : AHashMap < String , NamespaceId > ,
811802 /// Compiled bytecode for the snippet.
812803 module_code : Code ,
813804 /// Interned strings and compiled functions for this snippet.
814805 interns : Interns ,
815806 /// Source code used for traceback/error rendering.
816807 code : String ,
817- /// Input variable names that were injected for this snippet.
808+ /// Global slots assigned to inputs for this snippet.
818809 ///
819- /// Stored so that `inject_inputs` can look up their namespace slots
820- /// after compilation assigns them.
821- input_names : Vec < String > ,
810+ /// Inputs are pre-registered in the REPL-global name map before preparation.
811+ /// We store only the assigned slots here so execution can inject values without
812+ /// needing a cloned copy of the full global name map.
813+ input_slots : Vec < usize > ,
822814}
823815
824816impl ReplExecutor {
@@ -828,23 +820,29 @@ impl ReplExecutor {
828820 /// no-replay execution:
829821 /// - Seeds parsing from `existing_interns` so old `StringId` values stay stable.
830822 /// - Seeds compilation with existing functions so old `FunctionId` values remain valid.
831- /// - Reuses `existing_name_map` and appends new global names only.
823+ /// - Reuses `existing_name_map` in place and appends new global names only.
832824 ///
833- /// `input_names` are pre-registered in the name map before preparation so they
825+ /// `input_names` are pre-registered in the REPL-global name map before preparation so they
834826 /// receive stable namespace slots that `inject_inputs` can use to store values.
827+ ///
828+ /// If snippet preparation fails, newly reserved global slots remain in the
829+ /// caller-owned name map. That is harmless because the corresponding runtime
830+ /// global entries stay `Undefined` until a later successful assignment.
835831 fn new_repl_snippet (
836832 code : String ,
837833 script_name : & str ,
838- mut existing_name_map : AHashMap < String , NamespaceId > ,
834+ existing_name_map : & mut AHashMap < String , NamespaceId > ,
839835 existing_interns : & Interns ,
840836 input_names : Vec < String > ,
841837 ) -> Result < Self , MontyException > {
842838 // Pre-register input names so they get stable slots before preparation.
839+ let mut input_slots = Vec :: with_capacity ( input_names. len ( ) ) ;
843840 for name in & input_names {
844841 let next_slot = existing_name_map. len ( ) ;
845- existing_name_map
842+ let slot = * existing_name_map
846843 . entry ( name. clone ( ) )
847844 . or_insert_with ( || NamespaceId :: new ( next_slot) ) ;
845+ input_slots. push ( slot. index ( ) ) ;
848846 }
849847
850848 let seeded_interner = InternerBuilder :: from_interns ( existing_interns, & code) ;
@@ -863,11 +861,10 @@ impl ReplExecutor {
863861
864862 Ok ( Self {
865863 namespace_size : prepared. namespace_size ,
866- name_map : prepared. name_map ,
867864 module_code : compile_result. code ,
868865 interns,
869866 code,
870- input_names ,
867+ input_slots ,
871868 } )
872869 }
873870}
@@ -965,12 +962,7 @@ fn inject_inputs_into_vm(
965962 input_values : Vec < MontyObject > ,
966963 vm : & mut VM < ' _ , ' _ , impl ResourceTracker > ,
967964) -> Result < ( ) , MontyException > {
968- for ( name, obj) in executor. input_names . iter ( ) . zip ( input_values) {
969- let slot = executor
970- . name_map
971- . get ( name)
972- . expect ( "input name should have a namespace slot" )
973- . index ( ) ;
965+ for ( slot, obj) in executor. input_slots . iter ( ) . copied ( ) . zip ( input_values) {
974966 let value = obj
975967 . to_value ( vm)
976968 . map_err ( |e| MontyException :: runtime_error ( format ! ( "invalid input type: {e}" ) ) ) ?;
@@ -1048,8 +1040,7 @@ fn build_repl_progress<T: ResourceTracker>(
10481040
10491041 match converted {
10501042 ConvertedExit :: Complete ( obj) => {
1051- let ReplExecutor { name_map, interns, .. } = executor;
1052- repl. global_name_map = name_map;
1043+ let ReplExecutor { interns, .. } = executor;
10531044 repl. interns = interns;
10541045 Ok ( ReplProgress :: Complete { repl, value : obj } )
10551046 }
@@ -1097,11 +1088,9 @@ fn build_repl_progress<T: ResourceTracker>(
10971088 } ) ) ,
10981089 ConvertedExit :: Error ( err) => {
10991090 let error = err. into_python_exception ( & executor. interns , & executor. code ) ;
1100- // Commit compiler metadata even on runtime errors, matching feed() behavior.
1101- // Snippets can create new variables or functions before raising, and those
1102- // values may reference FunctionId/StringId values from the new tables.
1103- let ReplExecutor { name_map, interns, .. } = executor;
1104- repl. global_name_map = name_map;
1091+ // Commit intern/function metadata even on runtime errors, matching feed() behavior.
1092+ // The global name map was already updated in place during preparation.
1093+ let ReplExecutor { interns, .. } = executor;
11051094 repl. interns = interns;
11061095 Err ( Box :: new ( ReplStartError { repl, error } ) )
11071096 }
0 commit comments