_PyEval_UnpackIterableStackRef
(&mut self, size: u32, vm: &VirtualMachine)
| 7122 | |
| 7123 | /// _PyEval_UnpackIterableStackRef |
| 7124 | fn unpack_sequence(&mut self, size: u32, vm: &VirtualMachine) -> FrameResult { |
| 7125 | let value = self.pop_value(); |
| 7126 | let size = size as usize; |
| 7127 | |
| 7128 | // Fast path for exact tuple/list types (not subclasses) — push |
| 7129 | // elements directly from the slice without intermediate Vec allocation, |
| 7130 | // matching UNPACK_SEQUENCE_TUPLE / UNPACK_SEQUENCE_LIST specializations. |
| 7131 | let cls = value.class(); |
| 7132 | if cls.is(vm.ctx.types.tuple_type) { |
| 7133 | let tuple = value.downcast_ref::<PyTuple>().unwrap(); |
| 7134 | return self.unpack_fast(tuple.as_slice(), size, vm); |
| 7135 | } |
| 7136 | if cls.is(vm.ctx.types.list_type) { |
| 7137 | let list = value.downcast_ref::<PyList>().unwrap(); |
| 7138 | let borrowed = list.borrow_vec(); |
| 7139 | return self.unpack_fast(&borrowed, size, vm); |
| 7140 | } |
| 7141 | |
| 7142 | // General path — iterate up to `size + 1` elements to avoid |
| 7143 | // consuming the entire iterator (fixes hang on infinite sequences). |
| 7144 | let not_iterable = value.class().slots.iter.load().is_none() |
| 7145 | && value |
| 7146 | .get_class_attr(vm.ctx.intern_str("__getitem__")) |
| 7147 | .is_none(); |
| 7148 | let iter = PyIter::try_from_object(vm, value.clone()).map_err(|e| { |
| 7149 | if not_iterable && e.class().is(vm.ctx.exceptions.type_error) { |
| 7150 | vm.new_type_error(format!( |
| 7151 | "cannot unpack non-iterable {} object", |
| 7152 | value.class().name() |
| 7153 | )) |
| 7154 | } else { |
| 7155 | e |
| 7156 | } |
| 7157 | })?; |
| 7158 | |
| 7159 | let mut elements = Vec::with_capacity(size); |
| 7160 | for _ in 0..size { |
| 7161 | match iter.next(vm)? { |
| 7162 | PyIterReturn::Return(item) => elements.push(item), |
| 7163 | PyIterReturn::StopIteration(_) => { |
| 7164 | return Err(vm.new_value_error(format!( |
| 7165 | "not enough values to unpack (expected {size}, got {})", |
| 7166 | elements.len() |
| 7167 | ))); |
| 7168 | } |
| 7169 | } |
| 7170 | } |
| 7171 | |
| 7172 | // Check that the iterator is exhausted. |
| 7173 | match iter.next(vm)? { |
| 7174 | PyIterReturn::Return(_) => { |
| 7175 | // For exact dict types, show "got N" using the container's |
| 7176 | // size (PyDict_Size). Exact tuple/list are handled by the |
| 7177 | // fast path above and never reach here. |
| 7178 | let msg = if value.class().is(vm.ctx.types.dict_type) { |
| 7179 | if let Ok(got) = value.length(vm) { |
| 7180 | if got > size { |
| 7181 | format!("too many values to unpack (expected {size}, got {got})") |
no test coverage detected