Skip to content

Commit eaa9a72

Browse files
committed
x
1 parent a037d89 commit eaa9a72

File tree

3 files changed

+56
-10
lines changed

3 files changed

+56
-10
lines changed

crates/codegen/src/compile.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4843,6 +4843,10 @@ impl Compiler {
48434843

48444844
if is_async {
48454845
emit!(self, Instruction::EndAsyncFor);
4846+
} else {
4847+
// CPython 3.14: FOR_ITER no longer pops iterator on exhaustion
4848+
// Pop the iterator after loop ends
4849+
emit!(self, Instruction::PopTop);
48464850
}
48474851
self.compile_statements(orelse)?;
48484852

@@ -7222,6 +7226,9 @@ impl Compiler {
72227226
if is_async {
72237227
emit!(self, Instruction::EndAsyncFor);
72247228
emit!(self, Instruction::PopTop);
7229+
} else {
7230+
// CPython 3.14: FOR_ITER no longer pops iterator on exhaustion
7231+
emit!(self, Instruction::PopTop);
72257232
}
72267233
}
72277234

crates/compiler-core/src/bytecode/instruction.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -582,11 +582,10 @@ impl InstructionMetadata for Instruction {
582582
Self::FormatSimple => 0,
583583
Self::FormatWithSpec => -1,
584584
Self::ForIter { .. } => {
585-
if jump {
586-
-1
587-
} else {
588-
1
589-
}
585+
// CPython 3.14: FOR_ITER no longer pops iterator on exhaustion
586+
// jump=False: push next value (+1)
587+
// jump=True: iterator stays on stack, no change (0)
588+
if jump { 0 } else { 1 }
590589
}
591590
Self::IsOp(_) => -1,
592591
Self::ContainsOp(_) => -1,

crates/vm/src/frame.rs

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -931,6 +931,48 @@ impl ExecutingFrame<'_> {
931931
dict.merge_object(source, vm)?;
932932
Ok(None)
933933
}
934+
Instruction::DictMerge { index } => {
935+
let source = self.pop_value();
936+
let idx = index.get(arg);
937+
938+
// Get the dict to merge into (same logic as DICT_UPDATE)
939+
let dict_ref = if idx <= 1 {
940+
self.top_value()
941+
} else {
942+
self.nth_value(idx - 1)
943+
};
944+
945+
let dict: &Py<PyDict> = unsafe { dict_ref.downcast_unchecked_ref() };
946+
947+
// Check if source is a mapping
948+
if vm
949+
.get_method(source.clone(), vm.ctx.intern_str("keys"))
950+
.is_none()
951+
{
952+
return Err(vm.new_type_error(format!(
953+
"'{}' object is not a mapping",
954+
source.class().name()
955+
)));
956+
}
957+
958+
// Check for duplicate keys
959+
let keys_iter = vm.call_method(&source, "keys", ())?;
960+
for key in keys_iter.try_to_value::<Vec<PyObjectRef>>(vm)? {
961+
if key.downcast_ref::<PyStr>().is_none() {
962+
return Err(vm.new_type_error("keywords must be strings".to_owned()));
963+
}
964+
if dict.contains_key(&*key, vm) {
965+
let key_repr = key.repr(vm)?;
966+
return Err(vm.new_type_error(format!(
967+
"got multiple values for keyword argument {}",
968+
key_repr.as_str()
969+
)));
970+
}
971+
let value = vm.call_method(&source, "__getitem__", (key.clone(),))?;
972+
dict.set_item(&*key, value, vm)?;
973+
}
974+
Ok(None)
975+
}
934976
Instruction::EndAsyncFor => {
935977
// END_ASYNC_FOR pops (awaitable, exc) from stack
936978
// Stack: [awaitable, exc] -> []
@@ -2379,15 +2421,13 @@ impl ExecutingFrame<'_> {
23792421
Ok(None)
23802422
}
23812423
Ok(PyIterReturn::StopIteration(_)) => {
2382-
// Pop iterator from stack:
2383-
self.pop_value();
2384-
2385-
// End of for loop
2424+
// CPython 3.14: Do NOT pop iterator here
2425+
// POP_ITER instruction will handle cleanup after the loop
23862426
self.jump(target);
23872427
Ok(None)
23882428
}
23892429
Err(next_error) => {
2390-
// Pop iterator from stack:
2430+
// On error, pop iterator and propagate
23912431
self.pop_value();
23922432
Err(next_error)
23932433
}

0 commit comments

Comments
 (0)