Side-effecting / impure ops: assert, del, global/nonlocal, import, type alias, raise, await. */
(&mut self, op: OpCode, operand: u16, chunk: &SSAChunk, slots: &mut [Val])
| 175 | |
| 176 | /* Side-effecting / impure ops: assert, del, global/nonlocal, import, type alias, raise, await. */ |
| 177 | pub(crate) fn handle_side(&mut self, op: OpCode, operand: u16, chunk: &SSAChunk, slots: &mut [Val]) -> Result<(), VmErr> { |
| 178 | match op { |
| 179 | OpCode::Assert => { |
| 180 | let v = self.pop()?; |
| 181 | if !self.truthy_op(v, chunk, slots)? { |
| 182 | // Bare `assert` raises a catchable AssertionError with empty args. |
| 183 | let inst = self.heap.alloc(HeapObj::ExcInstance("AssertionError".into(), Vec::new()))?; |
| 184 | self.pending.exc_val = Some(inst); |
| 185 | return Err(VmErr::Raised("AssertionError".into())); |
| 186 | } |
| 187 | } |
| 188 | OpCode::Del => { |
| 189 | let slot = operand as usize; |
| 190 | // Deleting an already-unbound name raises NameError, matching CPython. |
| 191 | match slots.get_mut(slot) { |
| 192 | Some(s) if !s.is_undef() => *s = Val::undef(), |
| 193 | _ => { |
| 194 | let name = chunk.names.get(slot).map(|n| ssa_strip(n)).unwrap_or_default(); |
| 195 | return Err(VmErr::Name(name.into())); |
| 196 | } |
| 197 | } |
| 198 | // At module scope, drop it from module_state too so later reads see the deletion. |
| 199 | if core::ptr::eq(chunk, self.chunk) && let Some(n) = chunk.names.get(slot) { |
| 200 | self.module_state.remove(ssa_strip(n)); |
| 201 | } |
| 202 | // Unbind the shared closure cell too, so closures over this name see the deletion. |
| 203 | let cell = self.call_stack.last() |
| 204 | .and_then(|f| f.cells.iter().find(|(s, _)| *s == slot).map(|&(_, c)| c)); |
| 205 | if let Some(cell) = cell && cell.is_heap() && let HeapObj::List(rc) = self.heap.get(cell) { |
| 206 | rc.borrow_mut()[0] = Val::undef(); // cells are 1-element boxes |
| 207 | } |
| 208 | } |
| 209 | OpCode::Global | OpCode::Nonlocal => self.mark_impure(), |
| 210 | OpCode::Raise | OpCode::RaiseFrom => { |
| 211 | self.mark_impure(); |
| 212 | // Bare `raise` (operand 1): re-raise the exception currently being handled. |
| 213 | if op == OpCode::Raise && operand == 1 { |
| 214 | let Some(exc) = self.handling_exc else { |
| 215 | return Err(VmErr::Runtime("No active exception to re-raise")); |
| 216 | }; |
| 217 | let name = self.exc_type_name(exc); |
| 218 | self.pending.exc_val = Some(exc); |
| 219 | return Err(VmErr::Raised(name)); |
| 220 | } |
| 221 | // RaiseFrom emits both `expr` then `from expr`, the topmost value is the cause, but the exception to raise is the LHS. |
| 222 | if op == OpCode::RaiseFrom { let _cause = self.pop()?; } |
| 223 | let exc = self.pop()?; |
| 224 | // Stash the Val for `except as e` binding; non-Exc values use `display()`. |
| 225 | self.pending.exc_val = None; |
| 226 | // Extract owned (class name, first arg) so display() can run after the heap borrow ends. |
| 227 | let info: Option<(alloc::string::String, Option<Val>)> = if exc.is_heap() { |
| 228 | match self.heap.get(exc) { |
| 229 | HeapObj::ExcInstance(n, args) => { |
| 230 | self.pending.exc_val = Some(exc); |
| 231 | Some((n.clone(), args.first().copied())) |
| 232 | } |
| 233 | HeapObj::Type(n) => { |
| 234 | // Bare `raise X`: build empty ExcInstance so `e.args` is `()`. |
no test coverage detected