Skip to content

Commit ba2b619

Browse files
Accept __index__-conforming objects for compile() flags / optimize (RustPython#7728)
CPython's compile() (Python/Python-ast.c) accepts any object with __index__ for the flags and optimize arguments. RustPython's CompileArgs typed both fields as OptionalArg<PyIntRef>, so a class with only __index__ raised 'TypeError: Expected type int but X found' during arg binding. bpo-36907's regression test (test_call.test_fastcall_clearing_dict) exercises exactly this: an IntWithDict.__index__ that mutates self.kwargs mid-call. CPython parses both args via __index__ and doesn't crash even when the kwargs dict is cleared between binding and use. Switch flags and optimize to OptionalArg<ArgPrimitiveIndex<i32>>, the same helper already used by range, slice, bytes.__mul__, hex, oct, etc. ArgPrimitiveIndex calls try_index (= __index__ protocol) and converts to the requested primitive in one step, so the three call sites in compile() simplify from .map_or(Ok(d), |v| v.try_to_primitive(vm))? to .map_or(d, |v| v.value). Unmasks test_call.FastCallTests.test_fastcall_clearing_dict.
1 parent c8ddbd2 commit ba2b619

2 files changed

Lines changed: 10 additions & 8 deletions

File tree

Lib/test/test_call.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,6 @@ def test_vectorcall(self):
585585
result = _testcapi.pyobject_vectorcall(func, args, kwnames)
586586
self.check_result(result, expected)
587587

588-
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: Expected type 'int' but 'IntWithDict' found.
589588
def test_fastcall_clearing_dict(self):
590589
# Test bpo-36907: the point of the test is just checking that this
591590
# does not crash.

crates/vm/src/stdlib/builtins.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ mod builtins {
2121
common::hash::PyHash,
2222
function::{
2323
ArgBytesLike, ArgCallable, ArgIndex, ArgIntoBool, ArgIterable, ArgMapping,
24-
ArgStrOrBytesLike, Either, FsPath, FuncArgs, KwArgs, OptionalArg, OptionalOption,
25-
PosArgs,
24+
ArgPrimitiveIndex, ArgStrOrBytesLike, Either, FsPath, FuncArgs, KwArgs, OptionalArg,
25+
OptionalOption, PosArgs,
2626
},
2727
protocol::{PyIter, PyIterReturn},
2828
py_io,
@@ -99,12 +99,15 @@ mod builtins {
9999
source: PyObjectRef,
100100
filename: FsPath,
101101
mode: PyUtf8StrRef,
102+
// CPython parity: flags / optimize accept any object with __index__,
103+
// not just exact int. Matches the behavior of `int(x)` arg conversion
104+
// used by Python/Python-ast.c::compile.
102105
#[pyarg(any, optional)]
103-
flags: OptionalArg<PyIntRef>,
106+
flags: OptionalArg<ArgPrimitiveIndex<i32>>,
104107
#[pyarg(any, optional)]
105108
dont_inherit: OptionalArg<bool>,
106109
#[pyarg(any, optional)]
107-
optimize: OptionalArg<PyIntRef>,
110+
optimize: OptionalArg<ArgPrimitiveIndex<i32>>,
108111
#[pyarg(any, optional)]
109112
_feature_version: OptionalArg<i32>,
110113
}
@@ -264,7 +267,7 @@ mod builtins {
264267

265268
let mode_str = args.mode.as_str();
266269

267-
let optimize: i32 = args.optimize.map_or(Ok(-1), |v| v.try_to_primitive(vm))?;
270+
let optimize: i32 = args.optimize.map_or(-1, |v| v.value);
268271
let optimize: u8 = if optimize == -1 {
269272
vm.state.config.settings.optimize
270273
} else {
@@ -277,7 +280,7 @@ mod builtins {
277280
.source
278281
.fast_isinstance(&_ast::NodeAst::make_static_type())
279282
{
280-
let flags: i32 = args.flags.map_or(Ok(0), |v| v.try_to_primitive(vm))?;
283+
let flags: i32 = args.flags.map_or(0, |v| v.value);
281284
let is_ast_only = !(flags & _ast::PY_CF_ONLY_AST).is_zero();
282285

283286
// func_type mode requires PyCF_ONLY_AST
@@ -342,7 +345,7 @@ mod builtins {
342345
let source = decode_source_bytes(&source, &args.filename.to_string_lossy(), vm)?;
343346
let source = source.as_str();
344347

345-
let flags = args.flags.map_or(Ok(0), |v| v.try_to_primitive(vm))?;
348+
let flags: i32 = args.flags.map_or(0, |v| v.value);
346349

347350
if !(flags & !_ast::PY_COMPILE_FLAGS_MASK).is_zero() {
348351
return Err(vm.new_value_error("compile() unrecognized flags"));

0 commit comments

Comments
 (0)