mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Honor inline values, custom mro, and type lookup (#8729)
* Add inline values flags and special-method lookup lookup_maybe_method clears AttributeError from descr_get. slot_tp_richcompare treats a miss as NotImplemented. hackcheck rejects object.__setattr__ on types. Heap types with a managed dict and itemsize 0 get INLINE_VALUES; _testinternalcapi.has_inline_values reports the valid bit. super is a base type; subclass __get__ binds the instance. __classcell__/__qualname__ may share a name with a slot. Unset member access uses the type's fully qualified name. Assisted-by: Grok:grok-4.6 * Call custom mro and descr_get for type.__new__ type.mro() computes C3 from the current bases. A metaclass other than type supplies the MRO via mro(). super() compares obj.__class__ to the instance type. slot_tp_new looks up __new__ through descr_get. is_subtype walks the stored MRO without an identity shortcut. __slots__ names are sorted so __class__ assignment matches. Assisted-by: Grok:grok-4.6 * Expose _testinternalcapi recursion and rare events get_recursion_depth feeds test.support.infinite_recursion. SPECIALIZATION_* and SHARED_KEYS_MAX_SIZE match the adaptive counters. Rare-event hooks increment on __class__/__bases__/function/builtins writes. Assisted-by: Grok:grok-4.6 * Add remaining _testinternalcapi code and locale APIs Expose get_co_localskinds, get_code_var_counts, verify_stateless_code, code_returns_only_none, normalize_path, EncodeLocaleEx, DecodeLocaleEx, and run_in_subinterp_with_config. Mark unimplemented compiler IR and specialization monitoring tests as expectedFailure. Assisted-by: Grok:grok-4.6 * Fix lint for _testinternalcapi identifiers Add CPython identifier spellings to the cspell dictionary and apply the rustfmt signature that CI rewrote. Assisted-by: Grok:grok-4.6 * Expand _testinternalcapi with remaining test APIs Add constants, compiler_cleandoc, dict_getitem_knownhash, time helpers, interpreter/frame accessors, configs, static types, a persistent hamt map, and GC/size counters used by the test suite. Assisted-by: Grok:grok-4.6 * Expose compiler IR APIs in _testinternalcapi Add InstructionSequence and compiler_codegen, optimize_cfg, and assemble_code_object. Drop expectedFailure marks on tests that pass. Assisted-by: Grok:grok-4.6 * Match optimize_cfg consts and addop locations Write optimized consts back into the caller's list, mark SETUP_* pseudo-ops as having an argument, and store addop locations in instruction-sequence field order. Drop expectedFailure marks on tests that pass. Assisted-by: Grok:grok-4.6 * Fix hamt API, SETUP_* HAS_ARG, and FOR_ITER events Honor explicit pseudo flags when generating opcode metadata so SETUP_FINALLY/CLEANUP/WITH keep HAS_ARG after regeneration. Complete the _testinternalcapi hamt mapping: identity on set/delete, richcompare, weakrefs, subscript, contains, and length-bearing iterators. Fire RAISE/STOP_ITERATION for FOR_ITER StopIteration, and skip both END_FOR and POP_ITER for the exhausted-branch destination. Assisted-by: Grok:grok-4.6 * Skip FOR_ITER RAISE for sequence iterators List/range/tuple exhaustion matches FOR_ITER_LIST/RANGE/TUPLE and must not emit a monitoring RAISE. That event stopped pdb's monitoring backend on for-over-list with Internal StopIteration. Assisted-by: Grok:grok-4.6 * Fix generator FOR_ITER events and review issues Restore settrace StopIteration for generator for-loops so test_bdb next/return cases pass, while sequence iterators still skip RAISE. Honor review notes: addop oparg bounds, linetable locations, AST instance errors, MRO subclass lock, builtin-dict notify, direct-base layout flags, and leftover _testinternalcapi args. Assisted-by: Grok:grok-4.6
This commit is contained in:
1 parent
982cbd4d25
commit
300d8bdc02
36 files changed
+2970
-194
No files matched your search
@@ -40,6 +40,7 @@ cidx
|
||||
cjkcodecs
|
||||
CLASSDEREF
|
||||
classdict
|
||||
cleandoc
|
||||
cmpop
|
||||
CNOTAB
|
||||
codedepth
|
||||
@@ -61,6 +62,7 @@ dictbytype
|
||||
DICTFLAG
|
||||
dictoffset
|
||||
distpoint
|
||||
drvsize
|
||||
dynload
|
||||
elts
|
||||
eooh
|
||||
@@ -85,6 +87,7 @@ finalizers
|
||||
firsttraceable
|
||||
flowgraph
|
||||
formatfloat
|
||||
framesize
|
||||
freelist
|
||||
freevar
|
||||
freevars
|
||||
@@ -93,6 +96,7 @@ fsrc
|
||||
getdict
|
||||
getfunc
|
||||
getiter
|
||||
getlasti
|
||||
getsets
|
||||
getslice
|
||||
globalgetvar
|
||||
@@ -145,6 +149,7 @@ mbiencoder
|
||||
mbstreamreader
|
||||
mbstreamwriter
|
||||
metavars
|
||||
mimalloc
|
||||
miscompiles
|
||||
mult
|
||||
multibytecodec
|
||||
@@ -177,6 +182,9 @@ npools
|
||||
nseen
|
||||
NSIGNALS
|
||||
numer
|
||||
numdupes
|
||||
numkwonly
|
||||
numposorkw
|
||||
nvars
|
||||
obmalloc
|
||||
opname
|
||||
@@ -201,6 +209,7 @@ preds
|
||||
preinitialized
|
||||
pybuilddir
|
||||
pycore
|
||||
PYGC
|
||||
pyinner
|
||||
pydecimal
|
||||
pyerrors
|
||||
@@ -215,6 +224,7 @@ pystrcmp
|
||||
PYTHONTRACEMALLOC
|
||||
PYTHONUTF8
|
||||
pythonw
|
||||
pytime
|
||||
PYTHREAD_NAME
|
||||
recvd
|
||||
releasebuffer
|
||||
@@ -258,6 +268,8 @@ templatelib
|
||||
testconsole
|
||||
threadstate
|
||||
ticketer
|
||||
timespec
|
||||
timeval
|
||||
tmptype
|
||||
tok_oldval
|
||||
tstate
|
||||
|
||||
Vendored
+1
-11
@@ -574,7 +574,6 @@ class ClassTests(unittest.TestCase):
|
||||
else:
|
||||
self.fail("Failed to raise RecursionError")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def testForExceptionsRaisedInInstanceGetattr2(self):
|
||||
# Tests for exceptions raised in instance_getattr2().
|
||||
|
||||
@@ -858,7 +857,7 @@ class ClassTests(unittest.TestCase):
|
||||
pass
|
||||
|
||||
|
||||
# from _testinternalcapi import has_inline_values # XXX: RUSTPYTHON
|
||||
from _testinternalcapi import has_inline_values
|
||||
|
||||
Py_TPFLAGS_INLINE_VALUES = (1 << 2)
|
||||
Py_TPFLAGS_MANAGED_DICT = (1 << 4)
|
||||
@@ -886,14 +885,12 @@ class VarSizedSubclass(tuple):
|
||||
|
||||
class TestInlineValues(unittest.TestCase):
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; NameError: name 'has_inline_values' is not defined.
|
||||
def test_no_flags_for_slots_class(self):
|
||||
flags = NoManagedDict.__flags__
|
||||
self.assertEqual(flags & Py_TPFLAGS_MANAGED_DICT, 0)
|
||||
self.assertEqual(flags & Py_TPFLAGS_INLINE_VALUES, 0)
|
||||
self.assertFalse(has_inline_values(NoManagedDict()))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 0 != 4
|
||||
def test_both_flags_for_regular_class(self):
|
||||
for cls in (Plain, WithAttrs):
|
||||
with self.subTest(cls=cls.__name__):
|
||||
@@ -902,33 +899,28 @@ class TestInlineValues(unittest.TestCase):
|
||||
self.assertEqual(flags & Py_TPFLAGS_INLINE_VALUES, Py_TPFLAGS_INLINE_VALUES)
|
||||
self.assertTrue(has_inline_values(cls()))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 0 != 4
|
||||
def test_managed_dict_only_for_varsized_subclass(self):
|
||||
flags = VarSizedSubclass.__flags__
|
||||
self.assertEqual(flags & Py_TPFLAGS_MANAGED_DICT, Py_TPFLAGS_MANAGED_DICT)
|
||||
self.assertEqual(flags & Py_TPFLAGS_INLINE_VALUES, 0)
|
||||
self.assertFalse(has_inline_values(VarSizedSubclass()))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_has_inline_values(self):
|
||||
c = Plain()
|
||||
self.assertTrue(has_inline_values(c))
|
||||
del c.__dict__
|
||||
self.assertFalse(has_inline_values(c))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_instances(self):
|
||||
self.assertTrue(has_inline_values(Plain()))
|
||||
self.assertTrue(has_inline_values(WithAttrs()))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_inspect_dict(self):
|
||||
for cls in (Plain, WithAttrs):
|
||||
c = cls()
|
||||
c.__dict__
|
||||
self.assertTrue(has_inline_values(c))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_update_dict(self):
|
||||
d = { "e": 5, "f": 6 }
|
||||
for cls in (Plain, WithAttrs):
|
||||
@@ -945,7 +937,6 @@ class TestInlineValues(unittest.TestCase):
|
||||
for i in range(100):
|
||||
self.assertEqual(getattr(obj, f"a{i}"), i)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_many_attributes(self):
|
||||
class C: pass
|
||||
c = C()
|
||||
@@ -956,7 +947,6 @@ class TestInlineValues(unittest.TestCase):
|
||||
c = C()
|
||||
self.assertTrue(has_inline_values(c))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_many_attributes_with_dict(self):
|
||||
class C: pass
|
||||
c = C()
|
||||
|
||||
Vendored
-13
@@ -1112,7 +1112,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
|
||||
with self.assertRaises(TypeError):
|
||||
frozenset().__class__ = MyFrozenSet
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
@support.thread_unsafe
|
||||
def test_slots(self):
|
||||
# Testing __slots__...
|
||||
@@ -1368,7 +1367,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
|
||||
a.foo = 42
|
||||
self.assertEqual(a.__dict__, {"foo": 42})
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_slots_special2(self):
|
||||
# Testing __qualname__ and __classcell__ in __slots__
|
||||
class Meta(type):
|
||||
@@ -1940,7 +1938,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
|
||||
self.assertEqual(b.foo, 3)
|
||||
self.assertEqual(b.__class__, B)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_altmro(self):
|
||||
# Testing mro() and overriding it...
|
||||
class A(object):
|
||||
@@ -2119,7 +2116,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
|
||||
set_add.__get__(0)
|
||||
self.assertEqual(cm.exception.args[0], expected_errmsg)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_special_method_lookup(self):
|
||||
# The lookup of special methods bypasses __getattr__ and
|
||||
# __getattribute__, but they still can be descriptors.
|
||||
@@ -2680,7 +2676,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
|
||||
|
||||
dir(C()) # This used to segfault
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_supers(self):
|
||||
# Testing super...
|
||||
|
||||
@@ -3349,7 +3344,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
|
||||
self.assertEqual(NewClass.__doc__, 'object=None; type=NewClass')
|
||||
self.assertEqual(NewClass().__doc__, 'object=NewClass instance; type=NewClass')
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_set_class(self):
|
||||
# Testing __class__ assignment...
|
||||
class C(object): pass
|
||||
@@ -3824,7 +3818,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
|
||||
m.foo = 1
|
||||
self.assertEqual(m.__dict__, {"foo": 1})
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_funny_new(self):
|
||||
# Testing __new__ returning something unexpected...
|
||||
class C(object):
|
||||
@@ -4235,7 +4228,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
|
||||
with self.assertRaises(TypeError):
|
||||
X.__bases__ = type(None), O
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_mutable_bases_with_failing_mro(self):
|
||||
# Testing mutable bases with failing mro...
|
||||
class WorkOnce(type):
|
||||
@@ -4476,7 +4468,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
|
||||
self.assertIsInstance(a, C) # Baseline
|
||||
self.assertIsInstance(pa, C) # Test
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_proxy_super(self):
|
||||
# Testing super() for a proxy object...
|
||||
class Proxy(object):
|
||||
@@ -4500,7 +4491,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
|
||||
p = Proxy(obj)
|
||||
self.assertEqual(C.__dict__["f"](p), "B.f->C.f")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_carloverre(self):
|
||||
# Testing prohibition of Carlo Verre's hack...
|
||||
try:
|
||||
@@ -4533,7 +4523,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
|
||||
except TypeError:
|
||||
self.fail("setattr through direct base types should be legal")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_carloverre_multi_inherit_invalid(self):
|
||||
class A(type):
|
||||
def __setattr__(cls, key, value):
|
||||
@@ -5900,7 +5889,6 @@ class MroTest(unittest.TestCase):
|
||||
class C(B):
|
||||
pass
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_reent_set_bases_tp_base_cycle(self):
|
||||
"""
|
||||
type_set_bases must check for an inheritance cycle not only through
|
||||
@@ -6043,7 +6031,6 @@ class MroTest(unittest.TestCase):
|
||||
class A(metaclass=M):
|
||||
pass
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_disappearing_custom_mro(self):
|
||||
"""
|
||||
gh-92112: A custom mro() returning a result conflicting with
|
||||
|
||||
Vendored
+2
@@ -1205,6 +1205,7 @@ class GCTests(unittest.TestCase):
|
||||
assert_python_ok("-c", source)
|
||||
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; long_lived_total does not count untracked tuples
|
||||
@unittest.skipUnless(Py_GIL_DISABLED, "requires free-threaded GC")
|
||||
@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
|
||||
def test_tuple_untrack_counts(self):
|
||||
@@ -1222,6 +1223,7 @@ class GCTests(unittest.TestCase):
|
||||
# Use n // 2 just in case some other objects were collected.
|
||||
self.assertTrue(new_count - count > (n // 2))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; tracked heap size is not generational
|
||||
@requires_gil_enabled('need generational GC')
|
||||
@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
|
||||
def test_heap_size(self):
|
||||
|
||||
-1
@@ -4308,7 +4308,6 @@ class TestSignatureObject(unittest.TestCase):
|
||||
self.assertEqual(self.signature(C.__new__, follow_wrapped=False),
|
||||
varargs_signature)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_signature_on_class_with_wrapped_new(self):
|
||||
with self.subTest('FunctionType'):
|
||||
class C:
|
||||
|
||||
Vendored
+1
-2
@@ -1054,6 +1054,7 @@ class ExceptionMonitoringTest(CheckEvents):
|
||||
)
|
||||
self.assertEqual(events[0], ("throw", IndexError))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; CALL_ALLOC_AND_ENTER_INIT specialization
|
||||
@unittest.skipUnless(_testinternalcapi, "requires _testinternalcapi")
|
||||
@requires_specialization_ft
|
||||
def test_no_unwind_for_shim_frame(self):
|
||||
@@ -1558,7 +1559,6 @@ BRANCH_OFFSET_RECORDERS = BranchLeftOffsetRecorder, BranchRightOffsetRecorder
|
||||
class TestBranchAndJumpEvents(CheckEvents):
|
||||
maxDiff = None
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; - bytecode layout differs from CPython
|
||||
def test_loop(self):
|
||||
|
||||
def func():
|
||||
@@ -1710,7 +1710,6 @@ class TestBranchAndJumpEvents(CheckEvents):
|
||||
('branch left', 'func', 12, 12)])
|
||||
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; - bytecode layout differs from CPython
|
||||
def test_match(self):
|
||||
|
||||
def func(v=1):
|
||||
|
||||
Vendored
-1
@@ -445,7 +445,6 @@ class TestSuper(unittest.TestCase):
|
||||
|
||||
self.assertEqual(C().method(), super)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: type 'super' is not an acceptable base type
|
||||
def test_super_subclass___class__(self):
|
||||
class mysuper(super):
|
||||
pass
|
||||
|
||||
@@ -193,6 +193,8 @@ struct Compiler<'a> {
|
||||
/// Mirrors `c_disable_warning` while compiling FINALLY_END copies.
|
||||
disable_warning: u32,
|
||||
syntax_warning_handler: Option<&'a mut SyntaxWarningHandler<'a>>,
|
||||
/// If true, keep unoptimized nested instruction sequences on the parent.
|
||||
save_nested_seqs: bool,
|
||||
}
|
||||
|
||||
/// A Python `__future__` feature flag imported via `from __future__ import <feature>`.
|
||||
@@ -451,6 +453,96 @@ pub fn compile_top_with_syntax_warning_handler<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Unoptimized instruction sequence and the metadata needed by optimize_cfg.
|
||||
pub struct CodegenOutput {
|
||||
pub seq: ir::InstructionSequence,
|
||||
pub argcount: u32,
|
||||
pub posonlyargcount: u32,
|
||||
pub kwonlyargcount: u32,
|
||||
pub consts: Vec<ConstantData>,
|
||||
}
|
||||
|
||||
/// compile.c _PyCompile_CodeGen
|
||||
pub fn compile_codegen(
|
||||
ast: ruff_python_ast::Mod,
|
||||
source_file: SourceFile,
|
||||
mode: Mode,
|
||||
opts: CompileOpts,
|
||||
) -> CompileResult<CodegenOutput> {
|
||||
compile_codegen_with_syntax_warning_handler(ast, source_file, mode, opts, None)
|
||||
}
|
||||
|
||||
fn compile_codegen_with_syntax_warning_handler<'a>(
|
||||
mut ast: ruff_python_ast::Mod,
|
||||
source_file: SourceFile,
|
||||
mode: Mode,
|
||||
mut opts: CompileOpts,
|
||||
mut syntax_warning_handler: Option<&'a mut SyntaxWarningHandler<'a>>,
|
||||
) -> CompileResult<CodegenOutput> {
|
||||
opts.future_features |= checked_future_features(&ast, &source_file)?;
|
||||
let future_annotations = opts
|
||||
.future_features
|
||||
.contains(bytecode::CodeFlags::FUTURE_ANNOTATIONS);
|
||||
if let Some(handler) = syntax_warning_handler.as_deref_mut() {
|
||||
preprocess::warn_control_flow_in_finally(&ast, |range, message| {
|
||||
warn_ast_preprocess_syntax(&source_file, handler, range, message)
|
||||
})?;
|
||||
}
|
||||
if matches!(mode, Mode::Single)
|
||||
&& let ruff_python_ast::Mod::Module(module) = &mut ast
|
||||
{
|
||||
preprocess::preprocess_statements(
|
||||
&mut module.body,
|
||||
opts.optimize,
|
||||
future_annotations,
|
||||
false,
|
||||
);
|
||||
} else {
|
||||
preprocess::preprocess_mod(&mut ast, opts.optimize, future_annotations, false);
|
||||
}
|
||||
|
||||
let mut compiler = Compiler::new_with_syntax_warning_handler(
|
||||
opts,
|
||||
source_file.clone(),
|
||||
"<module>",
|
||||
syntax_warning_handler,
|
||||
);
|
||||
compiler.save_nested_seqs = true;
|
||||
match ast {
|
||||
ruff_python_ast::Mod::Module(module) => match mode {
|
||||
Mode::Exec | Mode::Eval => {
|
||||
let symbol_table = scan_module_symbols(&module, &source_file, &compiler.opts)?;
|
||||
compiler.compile_program(&module, symbol_table)?;
|
||||
}
|
||||
Mode::Single => {
|
||||
let symbol_table = scan_module_symbols(&module, &source_file, &compiler.opts)?;
|
||||
compiler.compile_program_single(&module.body, symbol_table)?;
|
||||
}
|
||||
Mode::BlockExpr => {
|
||||
let symbol_table = scan_module_symbols(&module, &source_file, &compiler.opts)?;
|
||||
compiler.compile_block_expr(&module.body, symbol_table)?;
|
||||
}
|
||||
},
|
||||
ruff_python_ast::Mod::Expression(expr) => {
|
||||
let symbol_table = scan_expr_symbols(&expr, &source_file, &compiler.opts)?;
|
||||
compiler.compile_eval(&expr, symbol_table)?;
|
||||
}
|
||||
}
|
||||
|
||||
let mut unit = compiler
|
||||
.code_stack
|
||||
.pop()
|
||||
.expect("codegen leaves the top-level unit on the stack");
|
||||
unit.instr_sequence.apply_label_map();
|
||||
Ok(CodegenOutput {
|
||||
seq: unit.instr_sequence,
|
||||
argcount: unit.metadata.argcount,
|
||||
posonlyargcount: unit.metadata.posonlyargcount,
|
||||
kwonlyargcount: unit.metadata.kwonlyargcount,
|
||||
consts: unit.metadata.consts.into_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Compile a standard Python program to bytecode
|
||||
pub fn compile_program(
|
||||
ast: &ast::ModModule,
|
||||
@@ -1122,6 +1214,7 @@ impl<'warnings> Compiler<'warnings> {
|
||||
do_not_emit_bytecode: 0,
|
||||
disable_warning: 0,
|
||||
syntax_warning_handler,
|
||||
save_nested_seqs: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2153,8 +2246,14 @@ impl<'warnings> Compiler<'warnings> {
|
||||
|
||||
let pop = self.code_stack.pop();
|
||||
let stack_top = compiler_unwrap_option(self, pop);
|
||||
// No parent scope stack to maintain
|
||||
unwrap_internal(self, stack_top.finalize_code(&self.opts))
|
||||
let nested = self
|
||||
.save_nested_seqs
|
||||
.then(|| stack_top.instr_sequence.clone());
|
||||
let code = unwrap_internal(self, stack_top.finalize_code(&self.opts));
|
||||
if let (Some(nested), Some(parent)) = (nested, self.code_stack.last_mut()) {
|
||||
parent.instr_sequence.add_nested(nested);
|
||||
}
|
||||
code
|
||||
}
|
||||
|
||||
fn expose_annotation_format_parameter(code: &mut CodeObject) {
|
||||
@@ -2175,7 +2274,13 @@ impl<'warnings> Compiler<'warnings> {
|
||||
self.ctx = saved_ctx;
|
||||
let pop = self.code_stack.pop();
|
||||
let stack_top = compiler_unwrap_option(self, pop);
|
||||
let nested = self
|
||||
.save_nested_seqs
|
||||
.then(|| stack_top.instr_sequence.clone());
|
||||
let mut code = unwrap_internal(self, stack_top.finalize_code(&self.opts));
|
||||
if let (Some(nested), Some(parent)) = (nested, self.code_stack.last_mut()) {
|
||||
parent.instr_sequence.add_nested(nested);
|
||||
}
|
||||
Self::expose_annotation_format_parameter(&mut code);
|
||||
code
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ pub enum InternalError {
|
||||
InvalidStackEffect,
|
||||
MalformedControlFlowGraph,
|
||||
MissingSymbol(String),
|
||||
ConstIndexOutOfRange { index: usize, len: usize },
|
||||
}
|
||||
|
||||
impl Display for InternalError {
|
||||
@@ -42,6 +43,12 @@ impl Display for InternalError {
|
||||
f,
|
||||
"The symbol '{s}' must be present in the symbol table, even when it is undefined in python."
|
||||
),
|
||||
Self::ConstIndexOutOfRange { index, len } => {
|
||||
write!(
|
||||
f,
|
||||
"LOAD_CONST index {index} is out of range for consts (len={len})"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+279
-3
@@ -204,6 +204,16 @@ impl ConstantPool {
|
||||
self.insert_full(constant).1
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn from_ordered(constants: Vec<ConstantData>) -> Self {
|
||||
Self { constants }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn into_vec(self) -> Vec<ConstantData> {
|
||||
self.constants
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_index(&self, idx: usize) -> Option<&ConstantData> {
|
||||
self.constants.get(idx)
|
||||
@@ -613,6 +623,7 @@ struct InstructionSequenceEntry {
|
||||
except_handler: InstructionSequenceExceptHandlerInfo,
|
||||
i_target: i32,
|
||||
i_offset: i32,
|
||||
python_loc: Option<[i32; 4]>,
|
||||
}
|
||||
|
||||
impl InstructionSequenceEntry {
|
||||
@@ -622,14 +633,15 @@ impl InstructionSequenceEntry {
|
||||
except_handler,
|
||||
i_target: 0,
|
||||
i_offset: 0,
|
||||
python_loc: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const INSTRUCTION_SEQUENCE_UNSET_LABEL: i32 = -111;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct InstructionSequence {
|
||||
#[derive(Clone, Default)]
|
||||
pub struct InstructionSequence {
|
||||
/// CPython `instr_sequence.s_instrs`, including allocated slots beyond `s_used`.
|
||||
instrs: Vec<InstructionSequenceEntry>,
|
||||
/// CPython `instr_sequence.s_allocated`, the allocated size of `s_instrs`.
|
||||
@@ -641,12 +653,116 @@ pub(crate) struct InstructionSequence {
|
||||
label_map: Option<Vec<i32>>,
|
||||
label_map_allocation: usize,
|
||||
annotations_code: Option<Box<Self>>,
|
||||
nested: Vec<Self>,
|
||||
}
|
||||
|
||||
/// One instruction as returned by InstructionSequence.get_instructions.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct PythonInstruction {
|
||||
pub opcode: i32,
|
||||
pub oparg: Option<i32>,
|
||||
pub lineno: i32,
|
||||
pub end_lineno: i32,
|
||||
pub col_offset: i32,
|
||||
pub end_col_offset: i32,
|
||||
}
|
||||
|
||||
impl InstructionSequence {
|
||||
pub(crate) fn new() -> Self {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
instruction_sequence_new()
|
||||
}
|
||||
|
||||
pub fn addop(
|
||||
&mut self,
|
||||
opcode: i32,
|
||||
oparg: i32,
|
||||
lineno: i32,
|
||||
col_offset: i32,
|
||||
end_lineno: i32,
|
||||
end_col_offset: i32,
|
||||
) -> crate::InternalResult<()> {
|
||||
let opcode = u16::try_from(opcode).map_err(|_| InternalError::MalformedControlFlowGraph)?;
|
||||
if opcode > MAX_OPCODE {
|
||||
return Err(InternalError::MalformedControlFlowGraph);
|
||||
}
|
||||
let opcode =
|
||||
AnyOpcode::try_from(opcode).map_err(|_| InternalError::MalformedControlFlowGraph)?;
|
||||
let instr: AnyInstruction = opcode.into();
|
||||
let oparg = u32::try_from(oparg).map_err(|_| InternalError::MalformedControlFlowGraph)?;
|
||||
if oparg >= (1 << 30) || !(opcode.has_arg() || instr.has_target() || oparg == 0) {
|
||||
return Err(InternalError::MalformedControlFlowGraph);
|
||||
}
|
||||
let loc = [lineno, col_offset, end_lineno, end_col_offset];
|
||||
let entry =
|
||||
instruction_sequence_addop(self, instruction_info_from_python(instr, oparg, loc))?;
|
||||
// addop arguments are (lineno, col_offset, end_lineno, end_col_offset);
|
||||
// get_instructions reports (lineno, end_lineno, col_offset, end_col_offset)
|
||||
// after storing those four values in struct order.
|
||||
entry.python_loc = Some([lineno, col_offset, end_lineno, end_col_offset]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new_label(&mut self) -> i32 {
|
||||
instruction_sequence_new_label(self).0
|
||||
}
|
||||
|
||||
pub fn use_label(&mut self, label: i32) -> crate::InternalResult<()> {
|
||||
instruction_sequence_use_label(self, InstructionSequenceLabel(label))
|
||||
}
|
||||
|
||||
pub fn apply_label_map(&mut self) {
|
||||
instruction_sequence_apply_label_map(self);
|
||||
}
|
||||
|
||||
pub fn add_nested(&mut self, nested: Self) {
|
||||
self.nested.push(nested);
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn nested(&self) -> &[Self] {
|
||||
&self.nested
|
||||
}
|
||||
|
||||
pub fn nested_mut(&mut self) -> &mut Vec<Self> {
|
||||
&mut self.nested
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn python_instructions(&self) -> Vec<PythonInstruction> {
|
||||
let mut out = Vec::with_capacity(self.instr_used);
|
||||
for entry in &self.instrs[..self.instr_used] {
|
||||
let opcode = any_opcode_as_i32(entry.info.instr.into());
|
||||
let has_arg = AnyOpcode::from(entry.info.instr).has_arg();
|
||||
let [lineno, end_lineno, col_offset, end_col_offset] = entry
|
||||
.python_loc
|
||||
.unwrap_or_else(|| python_location_of(&entry.info));
|
||||
out.push(PythonInstruction {
|
||||
opcode,
|
||||
oparg: has_arg.then_some(u32::from(entry.info.arg) as i32),
|
||||
lineno,
|
||||
end_lineno,
|
||||
col_offset,
|
||||
end_col_offset,
|
||||
});
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn check_load_const_indices(&self, nconsts: usize) -> crate::InternalResult<()> {
|
||||
for entry in &self.instrs[..self.instr_used] {
|
||||
if matches!(entry.info.instr.real(), Some(Instruction::LoadConst { .. })) {
|
||||
let index = u32::from(entry.info.arg) as usize;
|
||||
if index >= nconsts {
|
||||
return Err(InternalError::ConstIndexOutOfRange {
|
||||
index,
|
||||
len: nconsts,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// instruction_sequence.c _PyInstructionSequence_New / inst_seq_create
|
||||
@@ -659,6 +775,67 @@ fn instruction_sequence_new() -> InstructionSequence {
|
||||
label_map: None,
|
||||
label_map_allocation: 0,
|
||||
annotations_code: None,
|
||||
nested: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn any_opcode_as_i32(opcode: AnyOpcode) -> i32 {
|
||||
match opcode {
|
||||
AnyOpcode::Real(op) => i32::from(u8::from(op)),
|
||||
AnyOpcode::Pseudo(op) => i32::from(u16::from(op)),
|
||||
}
|
||||
}
|
||||
|
||||
fn python_location_of(info: &InstructionInfo) -> [i32; 4] {
|
||||
let loc = info.instruction_linetable_location();
|
||||
[loc.line, loc.end_line, loc.col, loc.end_col]
|
||||
}
|
||||
|
||||
fn instruction_info_from_python(
|
||||
instr: AnyInstruction,
|
||||
oparg: u32,
|
||||
loc: [i32; 4],
|
||||
) -> InstructionInfo {
|
||||
let [lineno, col_offset, end_lineno, end_col_offset] = loc;
|
||||
let lineno_override = match lineno.cmp(&0) {
|
||||
core::cmp::Ordering::Less => Some(NO_LOCATION_OVERRIDE),
|
||||
core::cmp::Ordering::Equal => Some(0),
|
||||
core::cmp::Ordering::Greater => None,
|
||||
};
|
||||
let line = if lineno > 0 {
|
||||
OneIndexed::new(lineno as usize).unwrap_or(OneIndexed::MIN)
|
||||
} else {
|
||||
OneIndexed::MIN
|
||||
};
|
||||
let end_line = if end_lineno > 0 {
|
||||
OneIndexed::new(end_lineno as usize).unwrap_or(line)
|
||||
} else {
|
||||
line
|
||||
};
|
||||
let col = if col_offset >= 0 {
|
||||
OneIndexed::from_zero_indexed(col_offset as usize)
|
||||
} else {
|
||||
OneIndexed::MIN
|
||||
};
|
||||
let end_col = if end_col_offset >= 0 {
|
||||
OneIndexed::from_zero_indexed(end_col_offset as usize)
|
||||
} else {
|
||||
OneIndexed::MIN
|
||||
};
|
||||
InstructionInfo {
|
||||
instr,
|
||||
arg: OpArg::new(oparg),
|
||||
target: BlockIdx::NULL,
|
||||
location: SourceLocation {
|
||||
line,
|
||||
character_offset: col,
|
||||
},
|
||||
end_location: SourceLocation {
|
||||
line: end_line,
|
||||
character_offset: end_col,
|
||||
},
|
||||
except_handler: None,
|
||||
lineno_override,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3954,6 +4131,105 @@ fn optimized_cfg_to_instruction_sequence(
|
||||
Ok((max_stackdepth, nlocalsplus, instr_sequence))
|
||||
}
|
||||
|
||||
/// flowgraph.c _PyCompile_OptimizeCfg
|
||||
pub fn optimize_cfg_for_tests(
|
||||
seq: InstructionSequence,
|
||||
consts: Vec<ConstantData>,
|
||||
nlocals: usize,
|
||||
) -> crate::InternalResult<(InstructionSequence, Vec<ConstantData>)> {
|
||||
seq.check_load_const_indices(consts.len())?;
|
||||
let mut metadata = CodeUnitMetadata {
|
||||
name: String::new(),
|
||||
qualname: None,
|
||||
consts: ConstantPool::from_ordered(consts),
|
||||
names: IndexSet::default(),
|
||||
varnames: IndexSet::default(),
|
||||
cellvars: IndexSet::default(),
|
||||
freevars: IndexSet::default(),
|
||||
fast_hidden: IndexMap::default(),
|
||||
fast_hidden_final: IndexSet::default(),
|
||||
argcount: 0,
|
||||
posonlyargcount: 0,
|
||||
kwonlyargcount: 0,
|
||||
firstlineno: OneIndexed::MIN,
|
||||
};
|
||||
let mut blocks = Blocks::from([Block::default()]);
|
||||
optimize_code_unit(&mut metadata, &mut blocks, seq, nlocals, 0)?;
|
||||
let _ = blocks.calculate_stackdepth()?;
|
||||
blocks.optimize_load_fast()?;
|
||||
let mut out = instruction_sequence_new();
|
||||
blocks.cfg_to_instruction_sequence(&mut out)?;
|
||||
Ok((out, metadata.consts.into_vec()))
|
||||
}
|
||||
|
||||
/// compile.c _PyCompile_Assemble
|
||||
pub fn assemble_for_tests(
|
||||
filename: String,
|
||||
seq: InstructionSequence,
|
||||
metadata: CodeUnitMetadata,
|
||||
debug_ranges: bool,
|
||||
) -> crate::InternalResult<CodeObject> {
|
||||
let mut blocks = cfg_from_instruction_sequence(seq)?;
|
||||
translate_jump_labels_to_targets(&mut blocks)?;
|
||||
blocks.mark_except_handlers();
|
||||
label_exception_targets(&mut blocks)?;
|
||||
let flags = CodeFlags::empty();
|
||||
let (max_stackdepth, nlocalsplus, mut instr_sequence) =
|
||||
optimized_cfg_to_instruction_sequence(&metadata, flags, &mut blocks)?;
|
||||
let localsplusinfo = compute_localsplus_info(&metadata, nlocalsplus, flags)?;
|
||||
let CodeUnitMetadata {
|
||||
name: obj_name,
|
||||
qualname,
|
||||
consts: constants,
|
||||
names: name_cache,
|
||||
varnames: varname_cache,
|
||||
cellvars: _,
|
||||
freevars: freevar_cache,
|
||||
fast_hidden: _,
|
||||
fast_hidden_final: _,
|
||||
argcount: arg_count,
|
||||
posonlyargcount: posonlyarg_count,
|
||||
kwonlyargcount: kwonlyarg_count,
|
||||
firstlineno: first_line_number,
|
||||
} = metadata;
|
||||
let code_arg_count = posonlyarg_count
|
||||
.checked_add(arg_count)
|
||||
.ok_or(InternalError::MalformedControlFlowGraph)?;
|
||||
resolve_unconditional_jumps(&mut instr_sequence);
|
||||
resolve_jump_offsets(&mut instr_sequence);
|
||||
let assembled = assemble_emit(
|
||||
&mut instr_sequence,
|
||||
first_line_number.get() as i32,
|
||||
debug_ranges,
|
||||
)?;
|
||||
let locations = rustpython_compiler_core::marshal::linetable_to_locations(
|
||||
&assembled.linetable,
|
||||
first_line_number.get() as i32,
|
||||
assembled.instructions.len(),
|
||||
);
|
||||
Ok(CodeObject {
|
||||
flags,
|
||||
posonlyarg_count,
|
||||
arg_count: code_arg_count,
|
||||
kwonlyarg_count,
|
||||
source_path: filename,
|
||||
first_line_number: Some(first_line_number),
|
||||
obj_name: obj_name.clone(),
|
||||
qualname: qualname.unwrap_or(obj_name),
|
||||
max_stackdepth: max_stackdepth.max(1),
|
||||
instructions: CodeUnits::from(assembled.instructions),
|
||||
locations,
|
||||
constants: constants.into_iter().collect(),
|
||||
names: name_cache.into_iter().collect(),
|
||||
varnames: varname_cache.into_iter().collect(),
|
||||
cellvars: localsplusinfo.cellvars,
|
||||
freevars: freevar_cache.into_iter().collect(),
|
||||
localspluskinds: localsplusinfo.kinds,
|
||||
linetable: assembled.linetable,
|
||||
exceptiontable: assembled.exceptiontable,
|
||||
})
|
||||
}
|
||||
|
||||
impl CodeInfo {
|
||||
pub fn finalize_code(
|
||||
mut self,
|
||||
|
||||
@@ -1416,6 +1416,9 @@ mod tests {
|
||||
assert!(PseudoOpcode::JumpIfTrue.has_arg());
|
||||
assert!(PseudoOpcode::JumpNoInterrupt.has_arg());
|
||||
assert!(PseudoOpcode::LoadClosure.has_arg());
|
||||
assert!(PseudoOpcode::SetupCleanup.has_arg());
|
||||
assert!(PseudoOpcode::SetupFinally.has_arg());
|
||||
assert!(PseudoOpcode::SetupWith.has_arg());
|
||||
assert!(PseudoOpcode::StoreFastMaybeNull.has_arg());
|
||||
assert!(!PseudoOpcode::AnnotationsPlaceholder.has_arg());
|
||||
assert!(!PseudoOpcode::PopBlock.has_arg());
|
||||
|
||||
@@ -929,6 +929,9 @@ impl super::PseudoOpcode {
|
||||
| Self::JumpIfTrue
|
||||
| Self::JumpNoInterrupt
|
||||
| Self::LoadClosure
|
||||
| Self::SetupCleanup
|
||||
| Self::SetupFinally
|
||||
| Self::SetupWith
|
||||
| Self::StoreFastMaybeNull
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1312,15 +1312,14 @@ impl PyCode {
|
||||
let (src, left, right) = match op {
|
||||
Instruction::ForIter { .. } => {
|
||||
// left = fall-through past CACHE entries (continue iteration)
|
||||
// right = past END_FOR (iterator exhausted, skip cleanup)
|
||||
// arg is relative forward from after instruction+caches
|
||||
// right = next_offset + oparg + 2 (skip END_FOR and POP_ITER)
|
||||
let after_cache = i + 1 + caches;
|
||||
let target = after_cache + oparg as usize;
|
||||
let right = if matches!(
|
||||
instructions.get(target).map(|u| u.op),
|
||||
Some(Instruction::EndFor | Instruction::InstrumentedEndFor)
|
||||
) {
|
||||
(target + 1) * 2
|
||||
(target + 2) * 2
|
||||
} else {
|
||||
target * 2
|
||||
};
|
||||
|
||||
@@ -349,7 +349,12 @@ fn get_slot_from_object(
|
||||
.get_slot(offset)
|
||||
.unwrap_or_else(|| vm.ctx.new_bool(false).into()),
|
||||
MemberKind::ObjectEx => obj.get_slot(offset).ok_or_else(|| {
|
||||
vm.new_no_attribute_error(obj.clone(), vm.ctx.new_str(member.name.clone()))
|
||||
// '%T' : module.qualname
|
||||
vm.new_attribute_error(format!(
|
||||
"'{}' object has no attribute '{}'",
|
||||
obj.class().fully_qualified_name(vm),
|
||||
member.name
|
||||
))
|
||||
})?,
|
||||
};
|
||||
Ok(slot)
|
||||
@@ -654,11 +659,13 @@ impl SlotFunc {
|
||||
}
|
||||
Self::SetAttro(func) => {
|
||||
let (name, value): (PyRef<PyStr>, PyObjectRef) = args.bind(vm)?;
|
||||
crate::types::hackcheck_setattro(&obj, *func, "__setattr__", vm)?;
|
||||
func(&obj, &name, PySetterValue::Assign(value), vm)?;
|
||||
Ok(vm.ctx.none())
|
||||
}
|
||||
Self::DelAttro(func) => {
|
||||
let (name,): (PyRef<PyStr>,) = args.bind(vm)?;
|
||||
crate::types::hackcheck_setattro(&obj, *func, "__delattr__", vm)?;
|
||||
func(&obj, &name, PySetterValue::Delete, vm)?;
|
||||
Ok(vm.ctx.none())
|
||||
}
|
||||
|
||||
@@ -649,7 +649,11 @@ impl AsMapping for PyDict {
|
||||
ass_subscript: atomic_func!(|mapping, needle, value, vm| {
|
||||
let zelf = PyDict::mapping_downcast(mapping);
|
||||
if let Some(value) = value {
|
||||
zelf.inner_setitem(needle, value, vm)
|
||||
zelf.inner_setitem(needle, value, vm)?;
|
||||
if zelf.as_object().is(vm.builtins.dict().as_object()) {
|
||||
crate::stdlib::_testinternalcapi::note_builtin_dict();
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
zelf.inner_delitem(needle, vm)
|
||||
}
|
||||
@@ -811,6 +815,15 @@ impl Py<PyDict> {
|
||||
Ok(attrs)
|
||||
}
|
||||
|
||||
pub(crate) fn get_item_known_hash(
|
||||
&self,
|
||||
key: &PyObject,
|
||||
hash: crate::common::hash::PyHash,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<Option<PyObjectRef>> {
|
||||
self.entries.get_known_hash(vm, key, hash)
|
||||
}
|
||||
|
||||
pub fn get_item_opt<K: DictKey + ?Sized>(
|
||||
&self,
|
||||
key: &K,
|
||||
@@ -927,7 +940,11 @@ impl Py<PyDict> {
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
if self.exact_dict(vm) {
|
||||
self.inner_setitem(key, value, vm)
|
||||
self.inner_setitem(key, value, vm)?;
|
||||
if self.as_object().is(vm.builtins.dict().as_object()) {
|
||||
crate::stdlib::_testinternalcapi::note_builtin_dict();
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
self.as_object().set_item(key, value, vm)
|
||||
}
|
||||
|
||||
@@ -951,6 +951,7 @@ impl PyFunction {
|
||||
*jit_guard = None;
|
||||
}
|
||||
self.func_version.store(0, Relaxed);
|
||||
crate::stdlib::_testinternalcapi::note_func_modification();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -965,6 +966,7 @@ impl PyFunction {
|
||||
PySetterValue::Delete => None,
|
||||
};
|
||||
self.func_version.store(0, Relaxed);
|
||||
crate::stdlib::_testinternalcapi::note_func_modification();
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
@@ -978,6 +980,7 @@ impl PyFunction {
|
||||
PySetterValue::Delete => None,
|
||||
};
|
||||
self.func_version.store(0, Relaxed);
|
||||
crate::stdlib::_testinternalcapi::note_func_modification();
|
||||
}
|
||||
|
||||
// {"__closure__", T_OBJECT, OFF(func_closure), READONLY},
|
||||
|
||||
@@ -441,6 +441,7 @@ impl PyBaseObject {
|
||||
if both_mutable || both_module {
|
||||
super::type_::compatible_for_assignment(current_cls, &cls, "__class__", vm)?;
|
||||
instance.set_class(cls, vm);
|
||||
crate::stdlib::_testinternalcapi::note_set_class();
|
||||
Ok(())
|
||||
} else {
|
||||
Err(vm.new_type_error(
|
||||
@@ -517,6 +518,9 @@ pub(crate) fn object_set_dict(
|
||||
PySetterValue::Assign(value) => Some(downcast_dict(value, vm)?),
|
||||
PySetterValue::Delete => None,
|
||||
};
|
||||
if let Some(instance_dict) = obj.instance_dict() {
|
||||
instance_dict.invalidate_inline_values();
|
||||
}
|
||||
obj.set_dict(dict)
|
||||
.map_err(|_| vm.new_attribute_error("This object has no __dict__"))
|
||||
}
|
||||
|
||||
@@ -148,7 +148,10 @@ impl Initializer for PySuper {
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(with(GetAttr, GetDescriptor, Constructor, Initializer, Representable))]
|
||||
#[pyclass(
|
||||
with(GetAttr, GetDescriptor, Constructor, Initializer, Representable),
|
||||
flags(BASETYPE)
|
||||
)]
|
||||
impl PySuper {
|
||||
#[pygetset]
|
||||
fn __thisclass__(&self) -> PyTypeRef {
|
||||
@@ -225,13 +228,7 @@ impl GetDescriptor for PySuper {
|
||||
.into_ref(&vm.ctx)
|
||||
.into())
|
||||
} else {
|
||||
let (obj, typ) = {
|
||||
let lock = zelf.inner.read();
|
||||
let obj = lock.obj.as_ref().map(|(o, _)| o.to_owned());
|
||||
let typ = lock.typ.clone();
|
||||
(obj, typ)
|
||||
};
|
||||
let obj = vm.unwrap_or_none(obj);
|
||||
let typ = zelf.inner.read().typ.clone();
|
||||
PyType::call(zelf.class(), (typ, obj).into_args(vm), vm)
|
||||
}
|
||||
}
|
||||
@@ -265,7 +262,7 @@ fn super_check(ty: PyTypeRef, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult
|
||||
|
||||
let class_attr = obj.get_attr("__class__", vm)?;
|
||||
if let Ok(cls) = class_attr.downcast::<PyType>()
|
||||
&& !cls.is(&ty)
|
||||
&& !cls.is(obj.class())
|
||||
&& cls.fast_issubclass(&ty)
|
||||
{
|
||||
return Ok(cls);
|
||||
|
||||
+270
-98
@@ -31,6 +31,7 @@ use crate::{
|
||||
use core::{
|
||||
any::Any,
|
||||
borrow::Borrow,
|
||||
cell::Cell,
|
||||
ops::Deref,
|
||||
pin::Pin,
|
||||
sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, AtomicU64, Ordering},
|
||||
@@ -616,15 +617,17 @@ fn downcast_qualname(value: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyRef<
|
||||
}
|
||||
|
||||
fn is_subtype_with_mro(a_mro: &[PyTypeRef], a: &Py<PyType>, b: &Py<PyType>) -> bool {
|
||||
if a.is(b) {
|
||||
return true;
|
||||
}
|
||||
for item in a_mro {
|
||||
if item.is(b) {
|
||||
return true;
|
||||
if a_mro.is_empty() {
|
||||
let mut current = Some(a);
|
||||
while let Some(typ) = current {
|
||||
if typ.is(b) {
|
||||
return true;
|
||||
}
|
||||
current = typ.base.deref();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
false
|
||||
a_mro.iter().any(|item| item.is(b))
|
||||
}
|
||||
|
||||
impl PyType {
|
||||
@@ -632,9 +635,24 @@ impl PyType {
|
||||
fn with_type_lock<R>(vm: &VirtualMachine, f: impl FnOnce() -> R) -> R {
|
||||
// Drops deferred via try_defer_drop inside the critical section run
|
||||
// after the guard is released, outside the lock.
|
||||
//
|
||||
// The lock is reentrant on the same thread so a custom mro() can
|
||||
// assign __bases__ (and re-enter here) without deadlocking.
|
||||
thread_local! {
|
||||
static HELD: Cell<bool> = const { Cell::new(false) };
|
||||
}
|
||||
rustpython_common::refcount::with_deferred_drops(|| {
|
||||
let _guard = vm.state.type_mutex.lock();
|
||||
f()
|
||||
HELD.with(|held| {
|
||||
if held.get() {
|
||||
f()
|
||||
} else {
|
||||
let _guard = vm.state.type_mutex.lock();
|
||||
held.set(true);
|
||||
let result = f();
|
||||
held.set(false);
|
||||
result
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -704,6 +722,11 @@ impl PyType {
|
||||
}
|
||||
}
|
||||
|
||||
/// Store a specific `tp_version_tag` without going through assignment.
|
||||
pub(crate) fn assign_specific_version(&self, version: u32) {
|
||||
self.tp_version_tag.store(version, Ordering::Release);
|
||||
}
|
||||
|
||||
pub fn modified(&self) {
|
||||
if self.tp_version_tag.load(Ordering::Acquire) == 0 {
|
||||
return;
|
||||
@@ -773,7 +796,16 @@ impl PyType {
|
||||
let bases = PyTuple::new_ref_typed(bases, ctx);
|
||||
let base = bases[0].clone();
|
||||
|
||||
Self::new_heap_inner(base, bases, attrs, slots, heaptype_ext, metaclass, ctx)
|
||||
Self::new_heap_inner(
|
||||
base,
|
||||
bases,
|
||||
attrs,
|
||||
slots,
|
||||
heaptype_ext,
|
||||
metaclass,
|
||||
ctx,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
/// Equivalent to CPython's PyType_Check macro
|
||||
@@ -945,24 +977,35 @@ impl PyType {
|
||||
heaptype_ext: HeapTypeExt,
|
||||
metaclass: PyRef<Self>,
|
||||
ctx: &Context,
|
||||
defer_mro: bool,
|
||||
) -> Result<PyRef<Self>, String> {
|
||||
let mro = Self::resolve_mro(&bases)?;
|
||||
let mro = if defer_mro {
|
||||
// Leave tp_mro unset so a custom metaclass mro() sees __mro__ is None.
|
||||
Vec::new()
|
||||
} else {
|
||||
Self::resolve_mro(&bases)?
|
||||
};
|
||||
|
||||
// Inherit HAS_DICT from any base in MRO that has it
|
||||
// (not just the first base, as any base with __dict__ means subclass needs it too)
|
||||
if mro
|
||||
// Layout flags come from each direct base's own flags. A custom
|
||||
// mro() can omit a physical base from the stored MRO.
|
||||
if bases
|
||||
.iter()
|
||||
.any(|b| b.slots.flags.has_feature(PyTypeFlags::HAS_DICT))
|
||||
{
|
||||
slots.flags |= PyTypeFlags::HAS_DICT
|
||||
slots.flags |= PyTypeFlags::HAS_DICT;
|
||||
}
|
||||
if bases
|
||||
.iter()
|
||||
.any(|b| b.slots.flags.has_feature(PyTypeFlags::MANAGED_DICT))
|
||||
{
|
||||
slots.flags |= PyTypeFlags::MANAGED_DICT;
|
||||
}
|
||||
|
||||
// Inherit HAS_WEAKREF/MANAGED_WEAKREF from any base in MRO that has it
|
||||
if mro
|
||||
if bases
|
||||
.iter()
|
||||
.any(|b| b.slots.flags.has_feature(PyTypeFlags::HAS_WEAKREF))
|
||||
{
|
||||
slots.flags |= PyTypeFlags::HAS_WEAKREF | PyTypeFlags::MANAGED_WEAKREF
|
||||
slots.flags |= PyTypeFlags::HAS_WEAKREF | PyTypeFlags::MANAGED_WEAKREF;
|
||||
}
|
||||
|
||||
// Inherit SEQUENCE and MAPPING flags from base classes
|
||||
@@ -1005,9 +1048,10 @@ impl PyType {
|
||||
metaclass,
|
||||
None,
|
||||
);
|
||||
new_type.mro.write().insert(0, new_type.clone());
|
||||
|
||||
new_type.init_slots(ctx);
|
||||
if !defer_mro {
|
||||
new_type.mro.write().insert(0, new_type.clone());
|
||||
new_type.init_slots(ctx);
|
||||
}
|
||||
|
||||
let weakref_type = super::PyWeak::static_type();
|
||||
for base in new_type.bases.read().iter() {
|
||||
@@ -1100,7 +1144,12 @@ impl PyType {
|
||||
|
||||
pub(crate) fn init_slots(&self, ctx: &Context) {
|
||||
// Inherit slots from MRO (mro[0] is self, so skip it)
|
||||
let mro: Vec<_> = self.mro.read()[1..].to_vec();
|
||||
let mro_guard = self.mro.read();
|
||||
if mro_guard.is_empty() {
|
||||
return;
|
||||
}
|
||||
let mro: Vec<_> = mro_guard[1..].to_vec();
|
||||
drop(mro_guard);
|
||||
for base in &mro {
|
||||
self.inherit_slots(base);
|
||||
}
|
||||
@@ -1425,7 +1474,8 @@ impl PyType {
|
||||
}
|
||||
|
||||
pub fn get_super_attr(&self, attr_name: &'static PyStrInterned) -> Option<PyObjectRef> {
|
||||
self.mro.read()[1..]
|
||||
let mro = self.mro.read();
|
||||
mro.get(1..)?
|
||||
.iter()
|
||||
.find_map(|class| class.attributes.get(attr_name))
|
||||
}
|
||||
@@ -1606,7 +1656,11 @@ impl Py<PyType> {
|
||||
/// so only use this if `cls` is known to have not overridden the base __subclasscheck__ magic
|
||||
/// method.
|
||||
pub fn fast_issubclass(&self, cls: &impl Borrow<PyObject>) -> bool {
|
||||
self.as_object().is(cls.borrow()) || self.mro.read()[1..].iter().any(|c| c.is(cls.borrow()))
|
||||
let mro = self.mro.read();
|
||||
if mro.is_empty() {
|
||||
return self.as_object().is(cls.borrow());
|
||||
}
|
||||
mro.iter().any(|c| c.is(cls.borrow()))
|
||||
}
|
||||
|
||||
pub fn mro_map_collect<F, R>(&self, f: F) -> Vec<R>
|
||||
@@ -1682,8 +1736,6 @@ impl PyType {
|
||||
}
|
||||
let bases = bases_tuple.try_into_typed::<Self>(vm)?;
|
||||
|
||||
// TODO: check for mro cycles
|
||||
|
||||
// Compute the new solid base before committing anything. This also
|
||||
// validates the new bases (BASETYPE flag, no instance layout
|
||||
// conflict), the same checks type creation performs.
|
||||
@@ -1728,6 +1780,14 @@ impl PyType {
|
||||
};
|
||||
|
||||
let result = Self::with_type_lock(vm, || {
|
||||
for base in bases.iter() {
|
||||
if is_subtype_with_mro(&base.mro.read(), base, zelf)
|
||||
|| (!base.mro.read().is_empty() && type_is_subtype_base_chain(base, zelf, vm))
|
||||
{
|
||||
return Err(vm.new_type_error("a __bases__ item causes an inheritance cycle"));
|
||||
}
|
||||
}
|
||||
|
||||
// Remove this class from the old bases' subclass lists, pruning
|
||||
// dead entries along the way. Upgraded refs are retired so the
|
||||
// last strong reference is never dropped under the lock.
|
||||
@@ -1759,22 +1819,27 @@ impl PyType {
|
||||
cls: &Py<PyType>,
|
||||
undo: &mut Vec<(PyTypeRef, Vec<PyTypeRef>)>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
let mut mro =
|
||||
PyType::resolve_mro(&cls.bases.read()).map_err(|msg| vm.new_type_error(msg))?;
|
||||
// Preserve self (mro[0]) when updating MRO
|
||||
mro.insert(0, cls.mro.read()[0].to_owned());
|
||||
let old_mro = core::mem::replace(&mut *cls.mro.write(), mro);
|
||||
undo.push((cls.to_owned(), old_mro));
|
||||
for subclass in cls.subclasses.read().iter() {
|
||||
// Dead entries are pruned elsewhere; skip them here.
|
||||
let Some(subclass) = subclass.upgrade() else {
|
||||
continue;
|
||||
};
|
||||
let subclass: &Py<PyType> = subclass.downcast_ref().unwrap();
|
||||
update_mro_recursively(subclass, undo, vm)?;
|
||||
) -> PyResult<i32> {
|
||||
let old_ptr = cls.mro.read().as_ptr();
|
||||
let new_mro = mro_invoke(cls, vm)?;
|
||||
if cls.mro.read().as_ptr() != old_ptr {
|
||||
// A nested custom mro() already replaced tp_mro.
|
||||
return Ok(0);
|
||||
}
|
||||
Ok(())
|
||||
let old_mro = core::mem::replace(&mut *cls.mro.write(), new_mro);
|
||||
undo.push((cls.to_owned(), old_mro));
|
||||
cls.modified_inner();
|
||||
let subclasses: Vec<PyTypeRef> = cls
|
||||
.subclasses
|
||||
.read()
|
||||
.iter()
|
||||
.filter_map(|subclass| subclass.upgrade())
|
||||
.filter_map(|subclass| subclass.downcast::<PyType>().ok())
|
||||
.collect();
|
||||
for subclass in subclasses {
|
||||
update_mro_recursively(&subclass, undo, vm)?;
|
||||
}
|
||||
Ok(1)
|
||||
}
|
||||
let mut undo = Vec::new();
|
||||
if let Err(err) = update_mro_recursively(zelf, &mut undo, vm) {
|
||||
@@ -1811,6 +1876,7 @@ impl PyType {
|
||||
zelf.update_all_slots(&vm.ctx);
|
||||
|
||||
register_subclasses(&zelf.bases.read());
|
||||
crate::stdlib::_testinternalcapi::note_set_bases();
|
||||
Ok(())
|
||||
});
|
||||
drop(retired);
|
||||
@@ -2171,9 +2237,10 @@ impl PyType {
|
||||
|
||||
#[pygetset(setter)]
|
||||
fn set___dict__(&self, _value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
Err(vm.new_not_implemented_error(
|
||||
"Setting __dict__ attribute on a type isn't yet implemented",
|
||||
))
|
||||
Err(vm.new_attribute_error(format!(
|
||||
"attribute '__dict__' of '{}' objects is not writable",
|
||||
self.name()
|
||||
)))
|
||||
}
|
||||
|
||||
fn check_set_special_type_attr(
|
||||
@@ -2482,8 +2549,13 @@ impl Constructor for PyType {
|
||||
seen_weakref = true;
|
||||
}
|
||||
|
||||
// Check if slot name conflicts with class attributes
|
||||
if attributes.contains_key(vm.ctx.intern_str(slot.as_wtf8())) {
|
||||
// __qualname__ and __classcell__ are written by the compiler
|
||||
// and may share a name with a slot.
|
||||
if slot_name != b"__qualname__"
|
||||
&& slot_name != b"__classcell__"
|
||||
&& slot_name != b"__classdictcell__"
|
||||
&& attributes.contains_key(vm.ctx.intern_str(slot.as_wtf8()))
|
||||
{
|
||||
return Err(vm.new_value_error(format!(
|
||||
"'{}' in __slots__ conflicts with class variable",
|
||||
slot.as_wtf8()
|
||||
@@ -2508,17 +2580,15 @@ impl Constructor for PyType {
|
||||
let add_weakref = seen_weakref;
|
||||
|
||||
// Filter out __dict__ and __weakref__ from slots
|
||||
// (they become descriptors, not member slots)
|
||||
let filtered_slots = if has_dict || add_weakref {
|
||||
let filtered: Vec<PyStrRef> = slots
|
||||
.iter()
|
||||
.filter(|s| s.as_wtf8() != dict_name && s.as_wtf8() != weakref_name)
|
||||
.cloned()
|
||||
.collect();
|
||||
PyTuple::new_ref_typed(filtered, &vm.ctx)
|
||||
} else {
|
||||
slots
|
||||
};
|
||||
// (they become descriptors, not member slots), then sort so
|
||||
// __class__ assignment can compare layouts by slot name.
|
||||
let mut filtered: Vec<PyStrRef> = slots
|
||||
.iter()
|
||||
.filter(|s| s.as_wtf8() != dict_name && s.as_wtf8() != weakref_name)
|
||||
.cloned()
|
||||
.collect();
|
||||
filtered.sort_by(|a, b| a.as_bytes().cmp(b.as_bytes()));
|
||||
let filtered_slots = PyTuple::new_ref_typed(filtered, &vm.ctx);
|
||||
|
||||
(Some(filtered_slots), has_dict, add_weakref)
|
||||
} else {
|
||||
@@ -2546,6 +2616,11 @@ impl Constructor for PyType {
|
||||
// 2. __dict__ is in __slots__
|
||||
if (heaptype_slots.is_none() && may_add_dict) || add_dict {
|
||||
flags |= PyTypeFlags::HAS_DICT | PyTypeFlags::MANAGED_DICT;
|
||||
// type_ready_managed_dict: fixed-size managed-dict types
|
||||
// get an inline values array after the object.
|
||||
if base.slots.itemsize == 0 {
|
||||
flags |= PyTypeFlags::INLINE_VALUES;
|
||||
}
|
||||
}
|
||||
|
||||
// Add HAS_WEAKREF if:
|
||||
@@ -2574,6 +2649,7 @@ impl Constructor for PyType {
|
||||
(slots, heaptype_ext)
|
||||
};
|
||||
|
||||
let custom_mro = !metatype.is(vm.ctx.types.type_type);
|
||||
let typ = Self::new_heap_inner(
|
||||
base,
|
||||
bases,
|
||||
@@ -2582,9 +2658,42 @@ impl Constructor for PyType {
|
||||
heaptype_ext,
|
||||
metatype,
|
||||
&vm.ctx,
|
||||
custom_mro,
|
||||
)
|
||||
.map_err(|e| vm.new_type_error(e))?;
|
||||
|
||||
if custom_mro {
|
||||
mro_internal(&typ, vm)?;
|
||||
typ.init_slots(&vm.ctx);
|
||||
}
|
||||
|
||||
// Consume __classcell__ before slot members of the same name
|
||||
// overwrite the compiler-provided cell.
|
||||
if let Some(cell) = typ.attributes.get(identifier!(vm, __classcell__)) {
|
||||
let cell = PyCellRef::try_from_object(vm, cell.clone()).map_err(|_| {
|
||||
vm.new_type_error(format!(
|
||||
"__classcell__ must be a nonlocal cell, not {}",
|
||||
cell.class().name()
|
||||
))
|
||||
})?;
|
||||
cell.set(Some(typ.clone().into()));
|
||||
typ.attributes.remove(identifier!(vm, __classcell__));
|
||||
}
|
||||
if let Some(cell) = typ.attributes.get(identifier!(vm, __classdictcell__)) {
|
||||
let cell = PyCellRef::try_from_object(vm, cell.clone()).map_err(|_| {
|
||||
vm.new_type_error(format!(
|
||||
"__classdictcell__ must be a nonlocal cell, not {}",
|
||||
cell.class().name()
|
||||
))
|
||||
})?;
|
||||
let namespace = typ
|
||||
.attributes
|
||||
.as_dict()
|
||||
.expect("a type built by type.__new__ has a dict namespace");
|
||||
cell.set(Some(namespace.clone().into()));
|
||||
typ.attributes.remove(identifier!(vm, __classdictcell__));
|
||||
}
|
||||
|
||||
if let Some(ref slots) = heaptype_slots {
|
||||
let class_name = typ.name().to_string();
|
||||
for (offset, member) in (base_member_count..).zip(slots.as_slice().iter()) {
|
||||
@@ -2616,34 +2725,6 @@ impl Constructor for PyType {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(cell) = typ.attributes.get(identifier!(vm, __classcell__)) {
|
||||
let cell = PyCellRef::try_from_object(vm, cell.clone()).map_err(|_| {
|
||||
vm.new_type_error(format!(
|
||||
"__classcell__ must be a nonlocal cell, not {}",
|
||||
cell.class().name()
|
||||
))
|
||||
})?;
|
||||
cell.set(Some(typ.clone().into()));
|
||||
typ.attributes.remove(identifier!(vm, __classcell__));
|
||||
}
|
||||
if let Some(cell) = typ.attributes.get(identifier!(vm, __classdictcell__)) {
|
||||
let cell = PyCellRef::try_from_object(vm, cell.clone()).map_err(|_| {
|
||||
vm.new_type_error(format!(
|
||||
"__classdictcell__ must be a nonlocal cell, not {}",
|
||||
cell.class().name()
|
||||
))
|
||||
})?;
|
||||
// Annotation scopes look names up here long after the class body ran,
|
||||
// so the cell gets the type's own namespace rather than the dict passed
|
||||
// to __new__: later attribute changes have to be visible through it.
|
||||
let namespace = typ
|
||||
.attributes
|
||||
.as_dict()
|
||||
.expect("a type built by type.__new__ has a dict namespace");
|
||||
cell.set(Some(namespace.clone().into()));
|
||||
typ.attributes.remove(identifier!(vm, __classdictcell__));
|
||||
}
|
||||
|
||||
// All *classes* should have a dict. Exceptions are *instances* of
|
||||
// classes that define __slots__ and instances of built-in classes
|
||||
// (with exceptions, e.g function)
|
||||
@@ -2727,12 +2808,16 @@ impl Constructor for PyType {
|
||||
})?;
|
||||
}
|
||||
|
||||
if let Some(init_subclass) = typ.get_super_attr(identifier!(vm, __init_subclass__)) {
|
||||
let init_subclass = vm
|
||||
.call_get_descriptor_specific(&init_subclass, None, Some(typ.clone().into()))
|
||||
.unwrap_or(Ok(init_subclass))?;
|
||||
init_subclass.call(kwargs, vm)?;
|
||||
};
|
||||
// type_new_init_subclass: super(type, type).__init_subclass__(**kwds)
|
||||
let super_obj = vm
|
||||
.ctx
|
||||
.types
|
||||
.super_type
|
||||
.as_object()
|
||||
.call((typ.clone(), typ.clone()), vm)?;
|
||||
super_obj
|
||||
.get_attr(identifier!(vm, __init_subclass__), vm)?
|
||||
.call(kwargs, vm)?;
|
||||
|
||||
Ok(typ.into())
|
||||
}
|
||||
@@ -2855,9 +2940,14 @@ impl GetAttr for PyType {
|
||||
#[pyclass]
|
||||
impl Py<PyType> {
|
||||
#[pygetset]
|
||||
fn __mro__(&self) -> PyTuple {
|
||||
let elements: Vec<PyObjectRef> = self.mro_map_collect(|x| x.as_object().to_owned());
|
||||
PyTuple::new_unchecked(elements.into_boxed_slice())
|
||||
fn __mro__(&self, vm: &VirtualMachine) -> PyObjectRef {
|
||||
let mro = self.mro.read();
|
||||
if mro.is_empty() {
|
||||
return vm.ctx.none();
|
||||
}
|
||||
let elements: Vec<PyObjectRef> = mro.iter().map(|x| x.as_object().to_owned()).collect();
|
||||
drop(mro);
|
||||
vm.ctx.new_tuple(elements).into()
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
@@ -2937,8 +3027,11 @@ impl Py<PyType> {
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn mro(&self) -> Vec<PyObjectRef> {
|
||||
self.mro_map_collect(|cls| cls.to_owned().into())
|
||||
fn mro(&self, vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> {
|
||||
Ok(mro_implementation(self, vm)?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3392,6 +3485,86 @@ fn calculate_meta_class(
|
||||
Ok(winner)
|
||||
}
|
||||
|
||||
fn type_is_subtype_base_chain(a: &Py<PyType>, b: &Py<PyType>, vm: &VirtualMachine) -> bool {
|
||||
let mut current = Some(a);
|
||||
while let Some(typ) = current {
|
||||
if typ.is(b) {
|
||||
return true;
|
||||
}
|
||||
current = typ.base.deref();
|
||||
}
|
||||
b.is(vm.ctx.types.object_type)
|
||||
}
|
||||
|
||||
fn mro_implementation(typ: &Py<PyType>, vm: &VirtualMachine) -> PyResult<Vec<PyTypeRef>> {
|
||||
for base in typ.bases.read().iter() {
|
||||
if base.mro.read().is_empty() {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"Cannot extend an incomplete type '{}'",
|
||||
base.name()
|
||||
)));
|
||||
}
|
||||
}
|
||||
let mut mro = PyType::resolve_mro(&typ.bases.read()).map_err(|msg| vm.new_type_error(msg))?;
|
||||
mro.insert(0, typ.to_owned());
|
||||
Ok(mro)
|
||||
}
|
||||
|
||||
fn mro_check(typ: &Py<PyType>, mro: &[PyObjectRef], vm: &VirtualMachine) -> PyResult<()> {
|
||||
let solid = solid_base(typ, vm).to_owned();
|
||||
for obj in mro {
|
||||
let Some(base) = obj.downcast_ref::<PyType>() else {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"mro() returned a non-class ('{}')",
|
||||
obj.class().name()
|
||||
)));
|
||||
};
|
||||
if !is_subtype_with_mro(&solid.mro.read(), &solid, solid_base(base, vm)) {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"mro() returned base with unsuitable layout ('{}')",
|
||||
base.slot_name()
|
||||
)));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mro_invoke(typ: &Py<PyType>, vm: &VirtualMachine) -> PyResult<Vec<PyTypeRef>> {
|
||||
let custom = !typ.class().is(vm.ctx.types.type_type);
|
||||
if !custom {
|
||||
return mro_implementation(typ, vm);
|
||||
}
|
||||
|
||||
let mro_result = vm.call_special_method(typ.as_object(), identifier!(vm, mro), ())?;
|
||||
let items: Vec<PyObjectRef> = mro_result.try_to_value(vm)?;
|
||||
if items.is_empty() {
|
||||
return Err(vm.new_type_error("type MRO must not be empty"));
|
||||
}
|
||||
mro_check(typ, &items, vm)?;
|
||||
items
|
||||
.into_iter()
|
||||
.map(|obj| {
|
||||
obj.downcast::<PyType>().map_err(|obj| {
|
||||
vm.new_type_error(format!(
|
||||
"mro() returned a non-class ('{}')",
|
||||
obj.class().name()
|
||||
))
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn mro_internal(typ: &Py<PyType>, vm: &VirtualMachine) -> PyResult<i32> {
|
||||
let old_ptr = typ.mro.read().as_ptr();
|
||||
let new_mro = mro_invoke(typ, vm)?;
|
||||
if typ.mro.read().as_ptr() != old_ptr {
|
||||
return Ok(0);
|
||||
}
|
||||
*typ.mro.write() = new_mro;
|
||||
typ.modified();
|
||||
Ok(1)
|
||||
}
|
||||
|
||||
/// Returns true if the two types have different instance layouts.
|
||||
fn shape_differs(t1: &Py<PyType>, t2: &Py<PyType>) -> bool {
|
||||
t1.__basicsize__() != t2.__basicsize__() || t1.slots.itemsize != t2.slots.itemsize
|
||||
@@ -3492,8 +3665,7 @@ fn same_slots_added(a: &Py<PyType>, b: &Py<PyType>) -> bool {
|
||||
.zip(y.iter())
|
||||
.all(|(p, q)| p.as_wtf8() == q.as_wtf8())
|
||||
}
|
||||
(None, None) => true,
|
||||
_ => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,11 @@ static KEYS_VERSION: AtomicU32 = AtomicU32::new(0);
|
||||
/// Allocate a new keys-version stamp. Returns 0 once the stamp space is
|
||||
/// exhausted; stamps are only allocated on specialization, so exhaustion is
|
||||
/// unrealistic in practice.
|
||||
/// Next keys-version stamp that [`next_keys_version`] would assign.
|
||||
pub(crate) fn peek_next_keys_version() -> u32 {
|
||||
KEYS_VERSION.load(Relaxed).saturating_add(1)
|
||||
}
|
||||
|
||||
fn next_keys_version() -> u32 {
|
||||
KEYS_VERSION
|
||||
.try_update(Relaxed, Relaxed, |v| v.checked_add(1))
|
||||
@@ -573,6 +578,16 @@ impl<T: Clone> Dict<T> {
|
||||
self._get_inner(vm, key, hash)
|
||||
}
|
||||
|
||||
/// `_PyDict_GetItem_KnownHash`: lookup using a caller-supplied hash.
|
||||
pub(crate) fn get_known_hash<K: DictKey + ?Sized>(
|
||||
&self,
|
||||
vm: &VirtualMachine,
|
||||
key: &K,
|
||||
hash: HashValue,
|
||||
) -> PyResult<Option<T>> {
|
||||
self._get_inner(vm, key, hash)
|
||||
}
|
||||
|
||||
/// Return a stable entry hint for `key` if present.
|
||||
///
|
||||
/// The hint is the internal entry index and can be used with
|
||||
|
||||
+46
-7
@@ -7206,6 +7206,13 @@ impl ExecutingFrame<'_> {
|
||||
self.push_value(value);
|
||||
}
|
||||
Ok(PyIterReturn::StopIteration(value)) => {
|
||||
// FOR_ITER_GEN returns through END_FOR, which
|
||||
// fires STOP_ITERATION rather than RAISE.
|
||||
if self.monitoring_mask & monitoring::EVENT_STOP_ITERATION != 0 {
|
||||
let offset = (self.lasti() - 1) * 2;
|
||||
let val = vm.unwrap_or_none(value.clone());
|
||||
monitoring::fire_stop_iteration(vm, self.code, offset, &val)?;
|
||||
}
|
||||
if vm.use_tracing.get() && self.trace_is_set(vm) {
|
||||
let stop_exc = vm.new_stop_iteration(value);
|
||||
self.fire_exception_trace(&stop_exc, vm)?;
|
||||
@@ -7453,7 +7460,9 @@ impl ExecutingFrame<'_> {
|
||||
monitoring::fire_branch_left(vm, self.code, src_offset, dest_offset)?;
|
||||
}
|
||||
} else if self.monitoring_mask & monitoring::EVENT_BRANCH_RIGHT != 0 {
|
||||
let dest_offset = self.lasti() * 2;
|
||||
// INSTRUMENTED_POP_ITER: dest is the instruction after
|
||||
// POP_ITER (FOR_ITER jumps over END_FOR onto POP_ITER).
|
||||
let dest_offset = (self.lasti() + 1) * 2;
|
||||
monitoring::fire_branch_right(vm, self.code, src_offset, dest_offset)?;
|
||||
}
|
||||
Ok(None)
|
||||
@@ -8497,6 +8506,39 @@ impl ExecutingFrame<'_> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// `_PyEval_MonitorRaise` for a StopIteration produced by FOR_ITER.
|
||||
/// Sequence iterators match FOR_ITER_LIST/RANGE/TUPLE and do not raise.
|
||||
/// Generators still report RAISE and the settrace exception event
|
||||
/// (Internal StopIteration).
|
||||
fn monitor_for_iter_stop(
|
||||
&self,
|
||||
value: Option<PyObjectRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
let iter = self.top_value();
|
||||
if iter.downcast_ref_if_exact::<PyListIterator>(vm).is_some()
|
||||
|| iter.downcast_ref_if_exact::<PyRangeIterator>(vm).is_some()
|
||||
|| iter.downcast_ref_if_exact::<PyTupleIterator>(vm).is_some()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
let need_raise = self.monitoring_mask & monitoring::EVENT_RAISE != 0;
|
||||
let need_trace = vm.use_tracing.get() && self.trace_is_set(vm);
|
||||
if !need_raise && !need_trace {
|
||||
return Ok(());
|
||||
}
|
||||
let stop_exc = vm.new_stop_iteration(value);
|
||||
if need_raise {
|
||||
let offset = (self.lasti() - 1) * 2;
|
||||
let exc_obj: PyObjectRef = stop_exc.clone().into();
|
||||
monitoring::fire_raise(vm, self.code, offset, &exc_obj)?;
|
||||
}
|
||||
if need_trace {
|
||||
self.fire_exception_trace(&stop_exc, vm)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Advance the iterator on top of stack.
|
||||
/// Returns `true` if iteration continued (item pushed), `false` if exhausted (jumped).
|
||||
fn execute_for_iter(
|
||||
@@ -8529,12 +8571,9 @@ impl ExecutingFrame<'_> {
|
||||
Ok(true)
|
||||
}
|
||||
Ok(PyIterReturn::StopIteration(value)) => {
|
||||
// Fire 'exception' trace event for StopIteration, matching
|
||||
// FOR_ITER's inline call to _PyEval_MonitorRaise.
|
||||
if vm.use_tracing.get() && self.trace_is_set(vm) {
|
||||
let stop_exc = vm.new_stop_iteration(value);
|
||||
self.fire_exception_trace(&stop_exc, vm)?;
|
||||
}
|
||||
// _FOR_ITER / INSTRUMENTED_FOR_ITER: _PyEval_MonitorRaise
|
||||
// then clear the StopIteration and jump over END_FOR.
|
||||
self.monitor_for_iter_stop(value, vm)?;
|
||||
self.jump(self.for_iter_jump_target(target));
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
@@ -372,10 +372,15 @@ pub(super) struct ObjExt {
|
||||
}
|
||||
|
||||
impl ObjExt {
|
||||
fn new(dict: Option<PyDictRef>, member_count: usize, has_dict: bool) -> Self {
|
||||
fn new(
|
||||
dict: Option<PyDictRef>,
|
||||
member_count: usize,
|
||||
has_dict: bool,
|
||||
inline_values: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
dict: if has_dict {
|
||||
Some(InstanceDict::from_opt(dict))
|
||||
Some(InstanceDict::from_opt(dict, inline_values))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
@@ -1079,9 +1084,14 @@ impl Py<PyWeak> {
|
||||
}
|
||||
}
|
||||
|
||||
/// SHARED_KEYS_MAX_SIZE: beyond this many keys, inline values are
|
||||
/// converted to a regular heap dict.
|
||||
pub(crate) const SHARED_KEYS_MAX_SIZE: usize = 30;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct InstanceDict {
|
||||
pub(crate) d: PyRwLock<Option<PyDictRef>>,
|
||||
inline_values_valid: PyAtomic<bool>,
|
||||
}
|
||||
|
||||
impl From<PyDictRef> for InstanceDict {
|
||||
@@ -1093,16 +1103,35 @@ impl From<PyDictRef> for InstanceDict {
|
||||
|
||||
impl InstanceDict {
|
||||
#[inline]
|
||||
pub(crate) const fn new(d: PyDictRef) -> Self {
|
||||
pub(crate) fn new(d: PyDictRef) -> Self {
|
||||
Self::from_opt(Some(d), false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn from_opt(d: Option<PyDictRef>, inline_values: bool) -> Self {
|
||||
Self {
|
||||
d: PyRwLock::new(Some(d)),
|
||||
d: PyRwLock::new(d),
|
||||
inline_values_valid: Radium::new(inline_values),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) const fn from_opt(d: Option<PyDictRef>) -> Self {
|
||||
Self {
|
||||
d: PyRwLock::new(d),
|
||||
pub(crate) fn inline_values_valid(&self) -> bool {
|
||||
self.inline_values_valid.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn invalidate_inline_values(&self) {
|
||||
self.inline_values_valid.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_materialize_inline_values(&self) {
|
||||
if !self.inline_values_valid() {
|
||||
return;
|
||||
}
|
||||
let overflow = self.with(|d| d.is_some_and(|d| d.__len__() > SHARED_KEYS_MAX_SIZE));
|
||||
if overflow {
|
||||
self.invalidate_inline_values();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1283,11 +1312,10 @@ impl<T: PyPayload + core::fmt::Debug> PyInner<T> {
|
||||
unsafe {
|
||||
if let Some(offset) = ext_start {
|
||||
let ext_ptr = alloc_ptr.add(offset) as *mut ObjExt;
|
||||
let has_dict = typ
|
||||
.slots
|
||||
.flags
|
||||
.has_feature(crate::types::PyTypeFlags::HAS_DICT);
|
||||
ext_ptr.write(ObjExt::new(dict, member_count, has_dict));
|
||||
let flags = typ.slots.flags;
|
||||
let has_dict = flags.has_feature(crate::types::PyTypeFlags::HAS_DICT);
|
||||
let inline_values = flags.has_feature(crate::types::PyTypeFlags::INLINE_VALUES);
|
||||
ext_ptr.write(ObjExt::new(dict, member_count, has_dict, inline_values));
|
||||
}
|
||||
|
||||
if let Some(offset) = weakref_start {
|
||||
@@ -1700,6 +1728,18 @@ impl PyObject {
|
||||
self.0.ext_ref().and_then(|ext| ext.dict.as_ref())
|
||||
}
|
||||
|
||||
/// `_PyObject_InlineValues(obj)->valid` when the type has INLINE_VALUES.
|
||||
#[inline]
|
||||
pub fn has_inline_values(&self) -> bool {
|
||||
self.class()
|
||||
.slots
|
||||
.flags
|
||||
.has_feature(crate::types::PyTypeFlags::INLINE_VALUES)
|
||||
&& self
|
||||
.instance_dict()
|
||||
.is_some_and(InstanceDict::inline_values_valid)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn dict(&self) -> Option<PyDictRef> {
|
||||
self.instance_dict().and_then(|d| d.get())
|
||||
@@ -2789,7 +2829,7 @@ pub(crate) fn init_type_hierarchy() -> BootstrapTypeHierarchy {
|
||||
alloc_ptr.expose_provenance();
|
||||
|
||||
unsafe {
|
||||
(alloc_ptr as *mut ObjExt).write(ObjExt::new(None, 0, true));
|
||||
(alloc_ptr as *mut ObjExt).write(ObjExt::new(None, 0, true, false));
|
||||
(alloc_ptr.add(weakref_offset) as *mut WeakRefList).write(WeakRefList::new());
|
||||
alloc_ptr.add(inner_offset).cast()
|
||||
}
|
||||
|
||||
@@ -227,6 +227,7 @@ impl PyObject {
|
||||
instance_dict
|
||||
.get_or_insert(vm)
|
||||
.set_item(attr_name, value, vm)?;
|
||||
instance_dict.maybe_materialize_inline_values();
|
||||
} else if let Some(dict) = instance_dict.get() {
|
||||
dict.del_item(attr_name, vm).map_err(|e| {
|
||||
if e.fast_isinstance(vm.ctx.exceptions.key_error) {
|
||||
@@ -603,8 +604,8 @@ impl PyObject {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Check for __subclasscheck__ method using lookup_special (matches CPython)
|
||||
if let Some(checker) = cls.lookup_special(identifier!(vm, __subclasscheck__), vm) {
|
||||
// Check for __subclasscheck__ method using lookup_special
|
||||
if let Some(checker) = cls.lookup_special(identifier!(vm, __subclasscheck__), vm)? {
|
||||
let res = vm.with_recursion("in __subclasscheck__", || {
|
||||
checker.call((derived.to_owned(),), vm)
|
||||
})?;
|
||||
@@ -695,8 +696,8 @@ impl PyObject {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Check for __instancecheck__ method using lookup_special (matches CPython)
|
||||
if let Some(checker) = cls.lookup_special(identifier!(vm, __instancecheck__), vm) {
|
||||
// Check for __instancecheck__ method using lookup_special
|
||||
if let Some(checker) = cls.lookup_special(identifier!(vm, __instancecheck__), vm)? {
|
||||
let res = vm.with_recursion("in __instancecheck__", || {
|
||||
checker.call((self.to_owned(),), vm)
|
||||
})?;
|
||||
@@ -833,23 +834,26 @@ impl PyObject {
|
||||
Err(vm.new_type_error(msg))
|
||||
}
|
||||
|
||||
/// Equivalent to CPython's _PyObject_LookupSpecial
|
||||
/// Looks up a special method in the type's MRO without checking instance dict.
|
||||
/// Returns None if not found (masking AttributeError like CPython).
|
||||
pub fn lookup_special(&self, attr: &Py<PyStr>, vm: &VirtualMachine) -> Option<PyObjectRef> {
|
||||
/// _PyObject_LookupSpecial: look up a special method in the type MRO
|
||||
/// without checking the instance dict. A miss is silent; a descr_get
|
||||
/// error is propagated.
|
||||
pub fn lookup_special(
|
||||
&self,
|
||||
attr: &Py<PyStr>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<Option<PyObjectRef>> {
|
||||
let obj_cls = self.class();
|
||||
|
||||
// Use PyType::lookup_ref (equivalent to CPython's _PyType_LookupRef)
|
||||
let res = obj_cls.lookup_ref(attr, vm)?;
|
||||
let Some(res) = obj_cls.lookup_ref(attr, vm) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// If it's a descriptor, call its __get__ method
|
||||
let descr_get = res.class().slots.descr_get.load();
|
||||
if let Some(descr_get) = descr_get {
|
||||
let obj_cls = obj_cls.to_owned().into();
|
||||
// CPython ignores exceptions in _PyObject_LookupSpecial and returns NULL
|
||||
descr_get(res, Some(self.to_owned()), Some(obj_cls), vm).ok()
|
||||
descr_get(res, Some(self.to_owned()), Some(obj_cls), vm).map(Some)
|
||||
} else {
|
||||
Some(res)
|
||||
Ok(Some(res))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2439,6 +2439,45 @@ pub(crate) fn preprocess_ast_object(
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[cfg(feature = "codegen")]
|
||||
pub(crate) struct RustModFromObject {
|
||||
pub ast: ast::Mod,
|
||||
pub source_file: SourceFile,
|
||||
}
|
||||
|
||||
#[cfg(feature = "codegen")]
|
||||
pub(crate) fn rust_mod_from_object(
|
||||
vm: &VirtualMachine,
|
||||
object: PyObjectRef,
|
||||
filename: &str,
|
||||
) -> PyResult<RustModFromObject> {
|
||||
let is_mod = object
|
||||
.class()
|
||||
.fast_issubclass(pyast::NodeMod::static_type())
|
||||
|| object.is_instance(pyast::NodeMod::static_type().as_object(), vm)?;
|
||||
if !is_mod && !is_ast_instance(vm, &object)? {
|
||||
return Err(vm.new_type_error("expected an AST"));
|
||||
}
|
||||
let text = synthetic_source_from_ast_object(vm, &object)?;
|
||||
let source_file = SourceFileBuilder::new(filename.to_owned(), text).finish();
|
||||
let ast = Node::ast_from_object(vm, &source_file, object)?;
|
||||
validate::validate_mod(vm, &ast)?;
|
||||
let ast = match ast {
|
||||
Mod::Module(m) => ast::Mod::Module(m.module),
|
||||
Mod::Interactive(ModInteractive { range, body, .. }) => ast::Mod::Module(ast::ModModule {
|
||||
node_index: Default::default(),
|
||||
range,
|
||||
body,
|
||||
runtime_body: None,
|
||||
}),
|
||||
Mod::Expression(e) => ast::Mod::Expression(e),
|
||||
Mod::FunctionType(_) => {
|
||||
return Err(vm.new_runtime_error("this compiler does not handle FunctionTypes"));
|
||||
}
|
||||
};
|
||||
Ok(RustModFromObject { ast, source_file })
|
||||
}
|
||||
|
||||
#[cfg(feature = "codegen")]
|
||||
pub(crate) fn compile(
|
||||
vm: &VirtualMachine,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//!
|
||||
//! Mirrors CPython `Modules/_interpretersmodule.c`.
|
||||
|
||||
pub(crate) use _interpreters::{init_xi_types, module_def};
|
||||
pub(crate) use _interpreters::{config_from_pyobject, init_xi_types, module_def};
|
||||
#[cfg_attr(not(feature = "threading"), allow(unused_imports))]
|
||||
pub(crate) use _interpreters::{
|
||||
interpreter_error, interpreter_not_found, not_shareable_error, xibufferview_from_buffer,
|
||||
@@ -342,6 +342,14 @@ pub(crate) mod _interpreters {
|
||||
.ok_or_else(|| vm.new_value_error(format!("unsupported config name '{name}'")))
|
||||
}
|
||||
|
||||
/// `config_from_object`.
|
||||
pub(crate) fn config_from_pyobject(
|
||||
obj: &PyObject,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<InterpreterConfig> {
|
||||
parse_config(Some(obj), vm)
|
||||
}
|
||||
|
||||
/// `config_from_object`.
|
||||
fn parse_config(obj: Option<&PyObject>, vm: &VirtualMachine) -> PyResult<InterpreterConfig> {
|
||||
let Some(obj) = obj.filter(|o| !vm.is_none(o)) else {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -14,6 +14,7 @@ mod _string;
|
||||
mod _symtable;
|
||||
mod _sysconfig;
|
||||
mod _sysconfigdata;
|
||||
pub(crate) mod _testinternalcapi;
|
||||
mod _types;
|
||||
pub mod _typing;
|
||||
pub mod _warnings;
|
||||
@@ -133,6 +134,7 @@ pub fn builtin_module_defs(ctx: &Context) -> Vec<&'static PyModuleDef> {
|
||||
_sre::module_def(ctx),
|
||||
_stat::module_def(ctx),
|
||||
_string::module_def(ctx),
|
||||
_testinternalcapi::module_def(ctx),
|
||||
#[cfg(feature = "compiler")]
|
||||
_symtable::module_def(ctx),
|
||||
_sysconfigdata::module_def(ctx),
|
||||
|
||||
@@ -401,6 +401,7 @@ pub mod sys {
|
||||
"_suggestions",
|
||||
"_symtable",
|
||||
"_sysconfig",
|
||||
"_testinternalcapi",
|
||||
"_thread",
|
||||
"_threading_local",
|
||||
"_tkinter",
|
||||
|
||||
@@ -227,6 +227,7 @@ bitflags! {
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub struct PyTypeFlags: u64 {
|
||||
const INLINE_VALUES = 1 << 2;
|
||||
const MANAGED_WEAKREF = 1 << 3;
|
||||
const MANAGED_DICT = 1 << 4;
|
||||
const SEQUENCE = 1 << 5;
|
||||
@@ -549,6 +550,62 @@ fn getattro_wrapper(zelf: &PyObject, name: &Py<PyStr>, vm: &VirtualMachine) -> P
|
||||
}
|
||||
}
|
||||
|
||||
/// hackcheck: reject object.__setattr__/__delattr__ applied to a type
|
||||
/// whose C-level tp_setattro is not the wrapped function.
|
||||
pub(crate) fn hackcheck_setattro(
|
||||
obj: &PyObject,
|
||||
func: SetattroFunc,
|
||||
what: &str,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
let Some(_typ) = obj.downcast_ref::<PyType>() else {
|
||||
return Ok(());
|
||||
};
|
||||
let obj_cls = obj.class();
|
||||
let obj_setattro = obj_cls.slots.setattro.load();
|
||||
|
||||
let mut defining = obj_cls.to_owned();
|
||||
{
|
||||
let mro = obj_cls.mro.read();
|
||||
for base in mro.iter().rev() {
|
||||
let base_setattro = base.slots.setattro.load();
|
||||
if is_slot_tp_setattro(base_setattro) {
|
||||
continue;
|
||||
} else if setattro_eq(base_setattro, obj_setattro) {
|
||||
defining = base.clone();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut base = Some(defining);
|
||||
while let Some(b) = base {
|
||||
let b_setattro = b.slots.setattro.load();
|
||||
if setattro_eq(b_setattro, Some(func)) {
|
||||
return Ok(());
|
||||
} else if !is_slot_tp_setattro(b_setattro) {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"can't apply this {what} to {} object",
|
||||
obj_cls.slot_name()
|
||||
)));
|
||||
}
|
||||
base = b.base.deref().map(|cls| cls.to_owned());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_slot_tp_setattro(f: Option<SetattroFunc>) -> bool {
|
||||
f.is_some_and(|f| fn_addr(f) == fn_addr(setattro_wrapper as SetattroFunc))
|
||||
}
|
||||
|
||||
fn setattro_eq(a: Option<SetattroFunc>, b: Option<SetattroFunc>) -> bool {
|
||||
match (a, b) {
|
||||
(Some(a), Some(b)) => fn_addr(a) == fn_addr(b),
|
||||
(None, None) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn setattro_wrapper(
|
||||
zelf: &PyObject,
|
||||
name: &Py<PyStr>,
|
||||
@@ -573,8 +630,13 @@ pub(crate) fn richcompare_wrapper(
|
||||
op: PyComparisonOp,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<Either<PyObjectRef, PyComparisonValue>> {
|
||||
vm.call_special_method(zelf, op.method_name(&vm.ctx), (other.to_owned(),))
|
||||
.map(Either::A)
|
||||
// slot_tp_richcompare / _PyObject_MaybeCallSpecialOneArg: a missing
|
||||
// method, or a data descriptor whose __get__ raises AttributeError,
|
||||
// is NotImplemented rather than an error.
|
||||
match vm.get_special_method(zelf, op.method_name(&vm.ctx))? {
|
||||
Some(meth) => meth.invoke((other.to_owned(),), vm).map(Either::A),
|
||||
None => Ok(Either::B(PyComparisonValue::NotImplemented)),
|
||||
}
|
||||
}
|
||||
|
||||
fn iter_wrapper(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
|
||||
@@ -654,7 +716,7 @@ fn init_wrapper(obj: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResu
|
||||
}
|
||||
|
||||
pub(crate) fn new_wrapper(cls: PyTypeRef, mut args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
let new = cls.get_attr(identifier!(vm, __new__)).unwrap();
|
||||
let new = cls.as_object().get_attr(identifier!(vm, __new__), vm)?;
|
||||
args.prepend_arg(cls.into());
|
||||
new.call(args, vm)
|
||||
}
|
||||
|
||||
@@ -264,6 +264,7 @@ declare_const_name! {
|
||||
items,
|
||||
keys,
|
||||
modules,
|
||||
mro,
|
||||
n_fields,
|
||||
n_sequence_fields,
|
||||
n_unnamed_fields,
|
||||
|
||||
@@ -566,7 +566,7 @@ fn verify_stateless(
|
||||
}
|
||||
|
||||
/// The `co_names` entries `identify_unbound_names` reaches through `LOAD_GLOBAL`.
|
||||
fn global_names(code: &PyCode) -> impl Iterator<Item = &'static PyStrInterned> + '_ {
|
||||
pub(crate) fn global_names(code: &PyCode) -> impl Iterator<Item = &'static PyStrInterned> + '_ {
|
||||
walk_instructions(code).filter_map(|(_, instr, arg)| match instr {
|
||||
Instruction::LoadGlobal { namei } => Some(code.names[(namei.get(arg) >> 1) as usize]),
|
||||
_ => None,
|
||||
@@ -575,7 +575,9 @@ fn global_names(code: &PyCode) -> impl Iterator<Item = &'static PyStrInterned> +
|
||||
|
||||
/// The instruction stream with its inline caches skipped and its specialized
|
||||
/// and instrumented opcodes mapped back, yielding `(offset, instruction, arg)`.
|
||||
fn walk_instructions(code: &PyCode) -> impl Iterator<Item = (usize, Instruction, OpArg)> + '_ {
|
||||
pub(crate) fn walk_instructions(
|
||||
code: &PyCode,
|
||||
) -> impl Iterator<Item = (usize, Instruction, OpArg)> + '_ {
|
||||
let units = &code.instructions;
|
||||
let mut arg_state = OpArgState::default();
|
||||
let mut offset = 0;
|
||||
@@ -645,7 +647,7 @@ fn is_pure_function(code: &PyCode) -> bool {
|
||||
/// `_PyCode_ReturnsOnlyNone`. Here "value" means a non-None value, since a bare
|
||||
/// return is identical to returning None explicitly, as is a missing return
|
||||
/// statement at the end of the function.
|
||||
fn code_returns_only_none(code: &PyCode) -> bool {
|
||||
pub(crate) fn code_returns_only_none(code: &PyCode) -> bool {
|
||||
if !is_pure_function(code) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -91,6 +91,21 @@ impl PyMethod {
|
||||
obj: &PyObject,
|
||||
name: &'static PyStrInterned,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<Option<Self>> {
|
||||
Self::get_special_ex::<DIRECT>(obj, name, vm, false)
|
||||
}
|
||||
|
||||
/// lookup_method_ex.
|
||||
///
|
||||
/// `raise_attribute_error` is CPython's same-named argument: when false
|
||||
/// (`lookup_maybe_method`), AttributeError from `tp_descr_get` is cleared
|
||||
/// and the lookup reports a miss. When true (`lookup_method`), that error
|
||||
/// is kept.
|
||||
pub(crate) fn get_special_ex<const DIRECT: bool>(
|
||||
obj: &PyObject,
|
||||
name: &'static PyStrInterned,
|
||||
vm: &VirtualMachine,
|
||||
raise_attribute_error: bool,
|
||||
) -> PyResult<Option<Self>> {
|
||||
let obj_cls = obj.class();
|
||||
let attr = if DIRECT {
|
||||
@@ -116,9 +131,18 @@ impl PyMethod {
|
||||
}
|
||||
} else {
|
||||
let obj_cls = obj_cls.to_owned().into();
|
||||
let attr = vm
|
||||
.call_get_descriptor_specific(&func, Some(obj.to_owned()), Some(obj_cls))
|
||||
.unwrap_or(Ok(func))?;
|
||||
let attr =
|
||||
match vm.call_get_descriptor_specific(&func, Some(obj.to_owned()), Some(obj_cls)) {
|
||||
Some(Ok(attr)) => attr,
|
||||
Some(Err(e))
|
||||
if !raise_attribute_error
|
||||
&& e.fast_isinstance(vm.ctx.exceptions.attribute_error) =>
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
Some(Err(e)) => return Err(e),
|
||||
None => func,
|
||||
};
|
||||
Self::Attribute(attr)
|
||||
};
|
||||
Ok(Some(meth))
|
||||
|
||||
@@ -2104,7 +2104,7 @@ impl VirtualMachine {
|
||||
#[cfg_attr(any(miri, target_env = "musl"), allow(dead_code))]
|
||||
// 2× CPython's _PY_STACK_MARGIN_BYTES to account for both heavy and
|
||||
// light frame native stack usage per recursion step.
|
||||
const STACK_MARGIN_BYTES: usize =
|
||||
pub(crate) const STACK_MARGIN_BYTES: usize =
|
||||
(if cfg!(debug_assertions) { 16384 } else { 4096 }) * core::mem::size_of::<usize>();
|
||||
|
||||
/// How deep native recursion may go where the stack cannot be measured
|
||||
|
||||
@@ -176,7 +176,8 @@ impl VirtualMachine {
|
||||
method: &'static PyStrInterned,
|
||||
args: impl IntoFuncArgs,
|
||||
) -> PyResult {
|
||||
self.get_special_method(obj, method)?
|
||||
// lookup_method: AttributeError from a data descriptor is kept.
|
||||
PyMethod::get_special_ex::<false>(obj, method, self, true)?
|
||||
.ok_or_else(|| self.new_attribute_error(method.as_str().to_owned()))?
|
||||
.invoke(args, self)
|
||||
}
|
||||
|
||||
@@ -46,7 +46,22 @@ class OpcodeGen:
|
||||
|
||||
@property
|
||||
def fn_has_arg(self) -> str:
|
||||
return self.gen_fn_has_attr("has_arg", "oparg", "HAS_ARG_FLAG")
|
||||
arms = "|".join(
|
||||
f"Self::{opcode.rust_name}"
|
||||
for opcode in self
|
||||
if opcode.properties.oparg or opcode.have_argument
|
||||
)
|
||||
if arms:
|
||||
inner = f"matches!(self, {arms})"
|
||||
else:
|
||||
inner = "false"
|
||||
return f"""
|
||||
/// Does this opcode have 'HAS_ARG_FLAG' set.
|
||||
#[must_use]
|
||||
pub const fn has_arg(self) -> bool {{
|
||||
{inner}
|
||||
}}
|
||||
"""
|
||||
|
||||
@property
|
||||
def fn_has_const(self) -> str:
|
||||
|
||||
@@ -82,7 +82,7 @@ def iter_opcodes(text: str, override_confs: OverrideConfs) -> Iterable[Opcode]:
|
||||
|
||||
kwargs = {}
|
||||
if instr := analysis.instructions.get(cpython_name):
|
||||
kwargs["properties"] = instr.properties
|
||||
kwargs["properties"] = properties_with_pseudo_flags(instr)
|
||||
kwargs["family"] = getattr(instr, "family", None)
|
||||
kwargs["cache_entry"] = getattr(instr, "size", -1)
|
||||
|
||||
@@ -100,6 +100,33 @@ def iter_opcodes(text: str, override_confs: OverrideConfs) -> Iterable[Opcode]:
|
||||
yield dataclasses.replace(opcode, override=override, **kwargs)
|
||||
|
||||
|
||||
# opcode_metadata_generator.py generate_metadata_table: cflags(pseudo.properties)
|
||||
# plus each explicit flag from the pseudo() definition.
|
||||
_PSEUDO_FLAG_ATTRS = {
|
||||
"HAS_ARG": "oparg",
|
||||
"HAS_CONST": "uses_co_consts",
|
||||
"HAS_NAME": "uses_co_names",
|
||||
"HAS_JUMP": "jumps",
|
||||
"HAS_FREE": "has_free",
|
||||
"HAS_LOCAL": "uses_locals",
|
||||
"HAS_EVAL_BREAK": "eval_breaker",
|
||||
"HAS_DEOPT": "deopts",
|
||||
"HAS_EXIT": "side_exit",
|
||||
"HAS_PURE": "pure",
|
||||
}
|
||||
|
||||
|
||||
def properties_with_pseudo_flags(instr):
|
||||
props = instr.properties
|
||||
flags = getattr(instr, "flags", None)
|
||||
if not flags:
|
||||
return props
|
||||
updates = {attr: True for flag in flags if (attr := _PSEUDO_FLAG_ATTRS.get(flag))}
|
||||
if not updates:
|
||||
return props
|
||||
return dataclasses.replace(props, **updates)
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True, slots=True)
|
||||
class Opcode:
|
||||
rust_name: str
|
||||
|
||||
Reference in new issue
Block a user