Skip to content

Commit 0ab0a0a

Browse files
authored
Merge pull request RustPython#1981 from skinny121/class-assign
Support assigning to __class__
2 parents 6b361bb + 6d383b6 commit 0ab0a0a

31 files changed

+338
-203
lines changed

tests/snippets/types_snippet.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from testutils import assert_raises
2+
13
try:
24
import gc
35
except ImportError:
@@ -99,3 +101,17 @@ class C(B, BB):
99101

100102
assert type(Exception.args).__name__ == 'getset_descriptor'
101103
assert type(None).__bool__(None) is False
104+
105+
class A:
106+
pass
107+
108+
class B:
109+
pass
110+
111+
a = A()
112+
a.__class__ = B
113+
assert isinstance(a, B)
114+
115+
b = 1
116+
with assert_raises(TypeError):
117+
b.__class__ = B

vm/src/builtins.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ mod decl {
4444
#[pyfunction]
4545
fn abs(x: PyObjectRef, vm: &VirtualMachine) -> PyResult {
4646
let method = vm.get_method_or_type_error(x.clone(), "__abs__", || {
47-
format!("bad operand type for abs(): '{}'", x.class().name)
47+
format!("bad operand type for abs(): '{}'", x.lease_class().name)
4848
})?;
4949
vm.invoke(&method, PyFuncArgs::new(vec![], vec![]))
5050
}
@@ -285,7 +285,7 @@ mod decl {
285285
.map_err(|obj| {
286286
vm.new_type_error(format!(
287287
"__format__ must return a str, not {}",
288-
obj.class().name
288+
obj.lease_class().name
289289
))
290290
})
291291
}
@@ -381,7 +381,7 @@ mod decl {
381381
|o| {
382382
format!(
383383
"isinstance() arg 2 must be a type or tuple of types, not {}",
384-
o.class()
384+
o.lease_class()
385385
)
386386
},
387387
vm,
@@ -396,7 +396,7 @@ mod decl {
396396
|o| {
397397
format!(
398398
"issubclass() arg 2 must be a class or tuple of classes, not {}",
399-
o.class()
399+
o.lease_class()
400400
)
401401
},
402402
vm,
@@ -603,8 +603,8 @@ mod decl {
603603
}
604604
OptionalArg::Present(m) => {
605605
// Check if the 3rd argument is defined and perform modulus on the result
606-
if !(objtype::isinstance(&x, &vm.ctx.int_type())
607-
&& objtype::isinstance(&y, &vm.ctx.int_type()))
606+
if !(objtype::isinstance(&x, &vm.ctx.types.int_type)
607+
&& objtype::isinstance(&y, &vm.ctx.types.int_type))
608608
{
609609
return Err(vm.new_type_error(
610610
"pow() 3rd argument not allowed unless all arguments are integers"
@@ -791,9 +791,10 @@ mod decl {
791791
};
792792

793793
for base in bases.clone() {
794-
if objtype::issubclass(&base.class(), &metaclass) {
794+
let base_class = base.lease_class();
795+
if objtype::issubclass(&base_class, &metaclass) {
795796
metaclass = base.class();
796-
} else if !objtype::issubclass(&metaclass, &base.class()) {
797+
} else if !objtype::issubclass(&metaclass, &base_class) {
797798
return Err(vm.new_type_error(
798799
"metaclass conflict: the metaclass of a derived class must be a (non-strict) \
799800
subclass of the metaclasses of all its bases"

vm/src/exceptions.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ impl PyBaseException {
141141
#[pymethod(name = "__repr__")]
142142
fn repr(zelf: PyRef<Self>, vm: &VirtualMachine) -> String {
143143
let repr_args = exception_args_as_string(vm, zelf.args(), false);
144-
let cls = zelf.class();
144+
let cls = zelf.lease_class();
145145
format!("{}({})", cls.name, repr_args.iter().format(", "))
146146
}
147147
}
@@ -226,7 +226,7 @@ pub fn write_exception_inner<W: Write>(
226226
let varargs = exc.args();
227227
let args_repr = exception_args_as_string(vm, varargs, true);
228228

229-
let exc_name = exc.class().name.clone();
229+
let exc_name = exc.lease_class().name.clone();
230230
match args_repr.len() {
231231
0 => writeln!(output, "{}", exc_name),
232232
1 => writeln!(output, "{}: {}", exc_name, args_repr[0]),
@@ -287,7 +287,7 @@ impl TryFromObject for ExceptionCtor {
287287
.map_err(|obj| {
288288
vm.new_type_error(format!(
289289
"exceptions must be classes or instances deriving from BaseException, not {}",
290-
obj.class().name
290+
obj.lease_class().name
291291
))
292292
})
293293
}
@@ -700,7 +700,7 @@ impl serde::Serialize for SerializeException<'_> {
700700
use serde::ser::*;
701701

702702
let mut struc = s.serialize_struct("PyBaseException", 7)?;
703-
struc.serialize_field("exc_type", &self.exc.class().name)?;
703+
struc.serialize_field("exc_type", &self.exc.lease_class().name)?;
704704
let tbs = {
705705
struct Tracebacks(PyTracebackRef);
706706
impl serde::Serialize for Tracebacks {

vm/src/frame.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ impl ExecutingFrame<'_> {
584584
vm.get_method_or_type_error(awaited_obj.clone(), "__await__", || {
585585
format!(
586586
"object {} can't be used in 'await' expression",
587-
awaited_obj.class().name,
587+
awaited_obj.lease_class().name,
588588
)
589589
})?;
590590
vm.invoke(&await_method, vec![])?
@@ -675,10 +675,10 @@ impl ExecutingFrame<'_> {
675675
bytecode::Instruction::UnpackSequence { size } => {
676676
let value = self.pop_value();
677677
let elements = vm.extract_elements(&value).map_err(|e| {
678-
if e.class().is(&vm.ctx.exceptions.type_error) {
678+
if e.lease_class().is(&vm.ctx.exceptions.type_error) {
679679
vm.new_type_error(format!(
680680
"cannot unpack non-iterable {} object",
681-
value.class().name
681+
value.lease_class().name
682682
))
683683
} else {
684684
e
@@ -978,7 +978,10 @@ impl ExecutingFrame<'_> {
978978
for obj in self.pop_multiple(size) {
979979
// Take all key-value pairs from the dict:
980980
let dict: PyDictRef = obj.downcast().map_err(|obj| {
981-
vm.new_type_error(format!("'{}' object is not a mapping", obj.class().name))
981+
vm.new_type_error(format!(
982+
"'{}' object is not a mapping",
983+
obj.lease_class().name
984+
))
982985
})?;
983986
for (key, value) in dict {
984987
if for_call {

vm/src/obj/objbool.rs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,13 @@ impl IntoPyObject for bool {
2020

2121
impl TryFromObject for bool {
2222
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<bool> {
23-
if objtype::isinstance(&obj, &vm.ctx.int_type()) {
23+
if objtype::isinstance(&obj, &vm.ctx.types.int_type) {
2424
Ok(get_value(&obj))
2525
} else {
26-
Err(vm.new_type_error(format!("Expected type bool, not {}", obj.class().name)))
26+
Err(vm.new_type_error(format!(
27+
"Expected type bool, not {}",
28+
obj.lease_class().name
29+
)))
2730
}
2831
}
2932
}
@@ -41,10 +44,10 @@ pub fn boolval(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<bool> {
4144
// If descriptor returns Error, propagate it further
4245
let method = method_or_err?;
4346
let bool_obj = vm.invoke(&method, PyFuncArgs::default())?;
44-
if !objtype::isinstance(&bool_obj, &vm.ctx.bool_type()) {
47+
if !objtype::isinstance(&bool_obj, &vm.ctx.types.bool_type) {
4548
return Err(vm.new_type_error(format!(
4649
"__bool__ should return bool, returned type {}",
47-
bool_obj.class().name
50+
bool_obj.lease_class().name
4851
)));
4952
}
5053

@@ -68,7 +71,7 @@ pub fn boolval(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<bool> {
6871
None => {
6972
return Err(vm.new_type_error(format!(
7073
"'{}' object cannot be interpreted as an integer",
71-
bool_obj.class().name
74+
bool_obj.lease_class().name
7275
)))
7376
}
7477
}
@@ -110,8 +113,8 @@ impl PyBool {
110113
#[pymethod(name = "__ror__")]
111114
#[pymethod(magic)]
112115
fn or(lhs: PyObjectRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult {
113-
if objtype::isinstance(&lhs, &vm.ctx.bool_type())
114-
&& objtype::isinstance(&rhs, &vm.ctx.bool_type())
116+
if objtype::isinstance(&lhs, &vm.ctx.types.bool_type)
117+
&& objtype::isinstance(&rhs, &vm.ctx.types.bool_type)
115118
{
116119
let lhs = get_value(&lhs);
117120
let rhs = get_value(&rhs);
@@ -124,8 +127,8 @@ impl PyBool {
124127
#[pymethod(name = "__rand__")]
125128
#[pymethod(magic)]
126129
fn and(lhs: PyObjectRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult {
127-
if objtype::isinstance(&lhs, &vm.ctx.bool_type())
128-
&& objtype::isinstance(&rhs, &vm.ctx.bool_type())
130+
if objtype::isinstance(&lhs, &vm.ctx.types.bool_type)
131+
&& objtype::isinstance(&rhs, &vm.ctx.types.bool_type)
129132
{
130133
let lhs = get_value(&lhs);
131134
let rhs = get_value(&rhs);
@@ -138,8 +141,8 @@ impl PyBool {
138141
#[pymethod(name = "__rxor__")]
139142
#[pymethod(magic)]
140143
fn xor(lhs: PyObjectRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult {
141-
if objtype::isinstance(&lhs, &vm.ctx.bool_type())
142-
&& objtype::isinstance(&rhs, &vm.ctx.bool_type())
144+
if objtype::isinstance(&lhs, &vm.ctx.types.bool_type)
145+
&& objtype::isinstance(&rhs, &vm.ctx.types.bool_type)
143146
{
144147
let lhs = get_value(&lhs);
145148
let rhs = get_value(&rhs);
@@ -151,7 +154,7 @@ impl PyBool {
151154

152155
#[pyslot]
153156
fn tp_new(zelf: PyObjectRef, x: OptionalArg<PyObjectRef>, vm: &VirtualMachine) -> PyResult {
154-
if !objtype::isinstance(&zelf, &vm.ctx.type_type()) {
157+
if !objtype::isinstance(&zelf, &vm.ctx.types.type_type) {
155158
let zelf_typ = zelf.class();
156159
let actual_type = vm.to_pystr(&zelf_typ)?;
157160
return Err(vm.new_type_error(format!(
@@ -172,7 +175,7 @@ pub(crate) fn init(context: &PyContext) {
172175
}
173176

174177
pub fn not(vm: &VirtualMachine, obj: &PyObjectRef) -> PyResult<bool> {
175-
if objtype::isinstance(obj, &vm.ctx.bool_type()) {
178+
if objtype::isinstance(obj, &vm.ctx.types.bool_type) {
176179
let value = get_value(obj);
177180
Ok(!value)
178181
} else {

vm/src/obj/objbytearray.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ impl PyByteArray {
604604
"'{}' decoder returned '{}' instead of 'str'; use codecs.encode() to \
605605
encode arbitrary types",
606606
encoding.as_ref().map_or("utf-8", |s| s.as_str()),
607-
obj.class().name,
607+
obj.lease_class().name,
608608
))
609609
})
610610
}

vm/src/obj/objbytes.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,7 @@ impl PyBytes {
504504
"'{}' decoder returned '{}' instead of 'str'; use codecs.encode() to \
505505
encode arbitrary types",
506506
encoding.as_ref().map_or("utf-8", |s| s.as_str()),
507-
obj.class().name,
507+
obj.lease_class().name,
508508
))
509509
})
510510
}

vm/src/obj/objdict.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::exceptions::PyBaseExceptionRef;
1010
use crate::function::{KwArgs, OptionalArg, PyFuncArgs};
1111
use crate::pyobject::{
1212
IdProtocol, IntoPyObject, ItemProtocol, PyAttributes, PyClassImpl, PyContext, PyIterable,
13-
PyObjectRef, PyRef, PyResult, PyValue, TryFromObject,
13+
PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol,
1414
};
1515
use crate::vm::{ReprGuard, VirtualMachine};
1616

@@ -442,7 +442,7 @@ impl PyDictRef {
442442
// and prevent the creation of the KeyError exception.
443443
// Also note, that we prevent the creation of a full PyString object
444444
// if we lookup local names (which happens all of the time).
445-
if self.typ().is(&vm.ctx.dict_type()) {
445+
if self.lease_class().is(&vm.ctx.dict_type()) {
446446
// We can take the short path here!
447447
match self.inner_getitem_option(key, vm) {
448448
Err(exc) => {
@@ -482,7 +482,7 @@ impl ItemProtocol for PyDictRef {
482482
value: PyObjectRef,
483483
vm: &VirtualMachine,
484484
) -> PyResult {
485-
if self.typ().is(&vm.ctx.dict_type()) {
485+
if self.lease_class().is(&vm.ctx.dict_type()) {
486486
self.inner_setitem_fast(key, value, vm)
487487
.map(|_| vm.ctx.none())
488488
} else {

vm/src/obj/objfloat.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -652,7 +652,7 @@ fn to_float(vm: &VirtualMachine, obj: &PyObjectRef) -> PyResult<f64> {
652652
let method = vm.get_method_or_type_error(obj.clone(), "__float__", || {
653653
format!(
654654
"float() argument must be a string or a number, not '{}'",
655-
obj.class().name
655+
obj.lease_class().name
656656
)
657657
})?;
658658
let result = vm.invoke(&method, vec![])?;

vm/src/obj/objfunction.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ impl PyBoundMethod {
323323

324324
#[pymethod(magic)]
325325
fn getattribute(zelf: PyRef<Self>, name: PyStringRef, vm: &VirtualMachine) -> PyResult {
326-
if let Some(obj) = zelf.class().get_attr(name.as_str()) {
326+
if let Some(obj) = zelf.get_class_attr(name.as_str()) {
327327
return vm.call_if_get_descriptor(obj, zelf.into_object());
328328
}
329329
vm.get_attribute(zelf.function.clone(), name)

vm/src/obj/objgetset.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ impl PyGetSet {
240240
Err(vm.new_attribute_error(format!(
241241
"attribute '{}' of '{}' objects is not writable",
242242
self.name,
243-
obj.class().name
243+
obj.lease_class().name
244244
)))
245245
}
246246
}

vm/src/obj/objint.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -443,13 +443,13 @@ impl PyInt {
443443
} else {
444444
return Err(vm.new_type_error(format!(
445445
"'{}' object cannot be interpreted as an integer",
446-
value.class().name
446+
value.lease_class().name
447447
)));
448448
}
449449
} else {
450450
return Err(vm.new_type_error(format!(
451451
"'{}' object cannot be interpreted as an integer",
452-
value.class().name
452+
value.lease_class().name
453453
)));
454454
}
455455
}
@@ -681,9 +681,9 @@ impl IntOptions {
681681
// FIXME: unnessessary bigint clone/creation
682682
let base = if let OptionalArg::Present(base) = self.base {
683683
if ![
684-
&vm.ctx.str_type(),
685-
&vm.ctx.bytes_type(),
686-
&vm.ctx.bytearray_type(),
684+
&vm.ctx.types.str_type,
685+
&vm.ctx.types.bytes_type,
686+
&vm.ctx.types.bytearray_type,
687687
]
688688
.iter()
689689
.any(|&typ| objtype::isinstance(&val, typ))
@@ -695,7 +695,7 @@ impl IntOptions {
695695
let base = vm.to_index(&base).unwrap_or_else(|| {
696696
Err(vm.new_type_error(format!(
697697
"'{}' object cannot be interpreted as an integer missing string argument",
698-
base.class().name
698+
base.lease_class().name
699699
)))
700700
})?;
701701
base.as_bigint().clone()

vm/src/obj/objiter.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ pub fn get_iter(vm: &VirtualMachine, iter_target: &PyObjectRef) -> PyResult {
2626
vm.invoke(&method, vec![])
2727
} else {
2828
vm.get_method_or_type_error(iter_target.clone(), "__getitem__", || {
29-
format!("'{}' object is not iterable", iter_target.class().name)
29+
format!(
30+
"'{}' object is not iterable",
31+
iter_target.lease_class().name
32+
)
3033
})?;
3134
Ok(PySequenceIterator::new_forward(iter_target.clone())
3235
.into_ref(vm)
@@ -125,7 +128,7 @@ pub fn length_hint(vm: &VirtualMachine, iter: PyObjectRef) -> PyResult<Option<us
125128
.ok_or_else(|| {
126129
vm.new_type_error(format!(
127130
"'{}' object cannot be interpreted as an integer",
128-
result.class().name
131+
result.lease_class().name
129132
))
130133
})?
131134
.as_bigint();

vm/src/obj/objlist.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ impl PyList {
137137
Err(vm.new_type_error(format!(
138138
"Cannot add {} and {}",
139139
Self::class(vm).name,
140-
other.class().name
140+
other.lease_class().name
141141
)))
142142
}
143143
}

vm/src/obj/objmemory.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ impl PyMemoryView {
2626
bytes_object: PyObjectRef,
2727
vm: &VirtualMachine,
2828
) -> PyResult<PyMemoryViewRef> {
29-
let object_type = bytes_object.class();
29+
let object_type = bytes_object.lease_class();
3030

3131
if issubclass(&object_type, &vm.ctx.types.memoryview_type)
3232
|| issubclass(&object_type, &vm.ctx.types.bytes_type)

0 commit comments

Comments
 (0)