co_consts · RustPython/RustPython@0793bd3 · GitHub
Skip to content

Commit 0793bd3

Browse files
committed
co_consts
1 parent f5b44f5 commit 0793bd3

8 files changed

Lines changed: 138 additions & 47 deletions

File tree

crates/codegen/src/compile.rs

Lines changed: 15 additions & 19 deletions

crates/compiler-core/src/bytecode.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,13 +290,16 @@ pub struct CodeObject<C: Constant = ConstantData> {
290290

291291
bitflags! {
292292
#[derive(Copy, Clone, Debug, PartialEq)]
293-
pub struct CodeFlags: u16 {
293+
pub struct CodeFlags: u32 {
294294
const OPTIMIZED = 0x0001;
295295
const NEWLOCALS = 0x0002;
296296
const VARARGS = 0x0004;
297297
const VARKEYWORDS = 0x0008;
298298
const GENERATOR = 0x0020;
299299
const COROUTINE = 0x0080;
300+
/// If a code object represents a function and has a docstring,
301+
/// this bit is set and the first item in co_consts is the docstring.
302+
const HAS_DOCSTRING = 0x4000000;
300303
}
301304
}
302305

crates/compiler-core/src/marshal.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ pub fn deserialize_code<R: Read, Bag: ConstantBag>(
202202
})
203203
.collect::<Result<Box<[(SourceLocation, SourceLocation)]>>>()?;
204204

205-
let flags = CodeFlags::from_bits_truncate(rdr.read_u16()?);
205+
let flags = CodeFlags::from_bits_truncate(rdr.read_u32()?);
206206

207207
let posonlyarg_count = rdr.read_u32()?;
208208
let arg_count = rdr.read_u32()?;
@@ -660,7 +660,7 @@ pub fn serialize_code<W: Write, C: Constant>(buf: &mut W, code: &CodeObject<C>)
660660
buf.write_u32(end.character_offset.to_zero_indexed() as _);
661661
}
662662

663-
buf.write_u16(code.flags.bits());
663+
buf.write_u32(code.flags.bits());
664664

665665
buf.write_u32(code.posonlyarg_count);
666666
buf.write_u32(code.arg_count);

crates/vm/src/builtins/code.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ pub struct ReplaceArgs {
152152
#[pyarg(named, optional)]
153153
co_names: OptionalArg<Vec<PyObjectRef>>,
154154
#[pyarg(named, optional)]
155-
co_flags: OptionalArg<u16>,
155+
co_flags: OptionalArg<u32>,
156156
#[pyarg(named, optional)]
157157
co_varnames: OptionalArg<Vec<PyObjectRef>>,
158158
#[pyarg(named, optional)]
@@ -411,7 +411,7 @@ pub struct PyCodeNewArgs {
411411
kwonlyargcount: u32,
412412
nlocals: u32,
413413
stacksize: u32,
414-
flags: u16,
414+
flags: u32,
415415
co_code: PyBytesRef,
416416
consts: PyTupleRef,
417417
names: PyTupleRef,
@@ -628,7 +628,7 @@ impl PyCode {
628628
}
629629

630630
#[pygetset]
631-
const fn co_flags(&self) -> u16 {
631+
const fn co_flags(&self) -> u32 {
632632
self.code.flags.bits()
633633
}
634634

crates/vm/src/builtins/function.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,17 @@ impl PyFunction {
7777
builtins
7878
};
7979

80+
// Get docstring from co_consts[0] if HAS_DOCSTRING flag is set
81+
let doc = if code.code.flags.contains(bytecode::CodeFlags::HAS_DOCSTRING) {
82+
code.code
83+
.constants
84+
.first()
85+
.map(|c| c.as_object().to_owned())
86+
.unwrap_or_else(|| vm.ctx.none())
87+
} else {
88+
vm.ctx.none()
89+
};
90+
8091
let qualname = vm.ctx.new_str(code.qualname.as_str());
8192
let func = Self {
8293
code: PyMutex::new(code.clone()),
@@ -89,7 +100,7 @@ impl PyFunction {
89100
type_params: PyMutex::new(vm.ctx.empty_tuple.clone()),
90101
annotations: PyMutex::new(vm.ctx.new_dict()),
91102
module: PyMutex::new(module),
92-
doc: PyMutex::new(vm.ctx.none()),
103+
doc: PyMutex::new(doc),
93104
#[cfg(feature = "jit")]
94105
jitted_code: OnceCell::new(),
95106
};

crates/vm/src/builtins/object.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,12 @@ fn object_getstate_default(obj: &PyObject, required: bool, vm: &VirtualMachine)
244244
let slots = vm.ctx.new_dict();
245245
for i in 0..slot_names_len {
246246
let borrowed_names = slot_names.borrow_vec();
247+
// Check if slotnames changed during iteration
248+
if borrowed_names.len() != slot_names_len {
249+
return Err(vm.new_runtime_error(
250+
"__slotnames__ changed size during iteration".to_owned(),
251+
));
252+
}
247253
let name = borrowed_names[i].downcast_ref::<PyStr>().unwrap();
248254
let Ok(value) = obj.get_attr(name, vm) else {
249255
continue;
@@ -702,11 +708,13 @@ fn reduce_newobj(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult {
702708

703709
(newobj, newargs.into())
704710
} else {
711+
// args == NULL with non-empty kwargs is BadInternalCall
712+
let Some(args) = args else {
713+
return Err(vm.new_system_error("bad internal call".to_owned()));
714+
};
705715
// Use copyreg.__newobj_ex__
706716
let newobj = copyreg.get_attr("__newobj_ex__", vm)?;
707-
let args_tuple: PyObjectRef = args
708-
.map(|a| a.into())
709-
.unwrap_or_else(|| vm.ctx.empty_tuple.clone().into());
717+
let args_tuple: PyObjectRef = args.into();
710718
let kwargs_dict: PyObjectRef = kwargs
711719
.map(|k| k.into())
712720
.unwrap_or_else(|| vm.ctx.new_dict().into());
Lines changed: 89 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,112 @@
1+
"""
2+
Test co_consts behavior for Python 3.14+
3+
4+
In Python 3.14+:
5+
- Functions with docstrings have the docstring as co_consts[0]
6+
- CO_HAS_DOCSTRING flag (0x4000000) indicates docstring presence
7+
- Functions without docstrings do NOT have None added as placeholder for docstring
8+
9+
Note: Other constants (small integers, code objects, etc.) may still appear in co_consts
10+
depending on optimization level. This test focuses on docstring behavior.
11+
"""
12+
13+
14+
# Test function with docstring - docstring should be co_consts[0]
15+
def with_doc():
16+
"""This is a docstring"""
17+
return 1
18+
19+
20+
assert with_doc.__code__.co_consts[0] == "This is a docstring", (
21+
with_doc.__code__.co_consts
22+
)
23+
assert with_doc.__doc__ == "This is a docstring"
24+
# Check CO_HAS_DOCSTRING flag (0x4000000)
25+
assert with_doc.__code__.co_flags & 0x4000000, hex(with_doc.__code__.co_flags)
26+
27+
28+
# Test function without docstring - should NOT have HAS_DOCSTRING flag
29+
def no_doc():
30+
return 1
31+
32+
33+
assert not (no_doc.__code__.co_flags & 0x4000000), hex(no_doc.__code__.co_flags)
34+
assert no_doc.__doc__ is None
35+
36+
37+
# Test async function with docstring
138
from asyncio import sleep
239

340

4-
def f():
5-
def g():
6-
return 1
41+
async def async_with_doc():
42+
"""Async docstring"""
43+
await sleep(1)
44+
return 1
745

8-
assert g.__code__.co_consts[0] == None
9-
return 2
1046

47+
assert async_with_doc.__code__.co_consts[0] == "Async docstring", (
48+
async_with_doc.__code__.co_consts
49+
)
50+
assert async_with_doc.__doc__ == "Async docstring"
51+
assert async_with_doc.__code__.co_flags & 0x4000000
1152

12-
assert f.__code__.co_consts[0] == None
1353

54+
# Test async function without docstring
55+
async def async_no_doc():
56+
await sleep(1)
57+
return 1
58+
59+
60+
assert not (async_no_doc.__code__.co_flags & 0x4000000)
61+
assert async_no_doc.__doc__ is None
1462

15-
def generator():
63+
64+
# Test generator with docstring
65+
def gen_with_doc():
66+
"""Generator docstring"""
1667
yield 1
1768
yield 2
1869

1970

20-
assert generator().gi_code.co_consts[0] == None
71+
assert gen_with_doc.__code__.co_consts[0] == "Generator docstring"
72+
assert gen_with_doc.__doc__ == "Generator docstring"
73+
assert gen_with_doc.__code__.co_flags & 0x4000000
2174

2275

23-
async def async_f():
24-
await sleep(1)
25-
return 1
76+
# Test generator without docstring
77+
def gen_no_doc():
78+
yield 1
79+
yield 2
80+
2681

82+
assert not (gen_no_doc.__code__.co_flags & 0x4000000)
83+
assert gen_no_doc.__doc__ is None
2784

28-
assert async_f.__code__.co_consts[0] == None
2985

86+
# Test lambda - cannot have docstring
3087
lambda_f = lambda: 0
31-
assert lambda_f.__code__.co_consts[0] == None
88+
assert not (lambda_f.__code__.co_flags & 0x4000000)
89+
assert lambda_f.__doc__ is None
90+
91+
92+
# Test class method with docstring
93+
class cls_with_doc:
94+
def method():
95+
"""Method docstring"""
96+
return 1
97+
3298

99+
assert cls_with_doc.method.__code__.co_consts[0] == "Method docstring"
100+
assert cls_with_doc.method.__doc__ == "Method docstring"
33101

34-
class cls:
35-
def f():
102+
103+
# Test class method without docstring
104+
class cls_no_doc:
105+
def method():
36106
return 1
37107

38108

39-
assert cls().f.__code__.co_consts[0] == None
109+
assert not (cls_no_doc.method.__code__.co_flags & 0x4000000)
110+
assert cls_no_doc.method.__doc__ is None
111+
112+
print("All co_consts tests passed!")

extra_tests/snippets/example_interactive.py

Lines changed: 2 additions & 2 deletions

0 commit comments

Comments
 (0)