From d2f0f94dee9060117366d86183cd3765b9760ecb Mon Sep 17 00:00:00 2001 From: jfh Date: Sat, 16 Oct 2021 14:52:28 +0300 Subject: [PATCH] Allow non-types as metaclass argument. --- Lib/test/test_types.py | 2 -- vm/src/stdlib/builtins.rs | 54 ++++++++++++++++++++++----------------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 68b00e040..be218da34 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -987,8 +987,6 @@ class ClassCreationTests(unittest.TestCase): self.assertIs(ns, expected_ns) self.assertEqual(len(kwds), 0) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_bad___prepare__(self): # __prepare__() must return a mapping. class BadMeta(type): diff --git a/vm/src/stdlib/builtins.rs b/vm/src/stdlib/builtins.rs index 120bff1d3..0932193d8 100644 --- a/vm/src/stdlib/builtins.rs +++ b/vm/src/stdlib/builtins.rs @@ -18,7 +18,7 @@ mod builtins { iter::PyCallableIterator, list::{PyList, SortOptions}, PyByteArray, PyBytes, PyBytesRef, PyCode, PyDictRef, PyStr, PyStrRef, PyTuple, - PyTupleRef, PyTypeRef, + PyTupleRef, PyType, }, common::{hash::PyHash, str::to_ascii}, function::{ @@ -810,16 +810,9 @@ mod builtins { let name = qualified_name.as_str().split('.').next_back().unwrap(); let name_obj = vm.ctx.new_str(name); - let mut metaclass = if let Some(metaclass) = kwargs.pop_kwarg("metaclass") { - PyTypeRef::try_from_object(vm, metaclass)? - } else { - vm.ctx.types.type_type.clone() - }; - + // Update bases. let mut new_bases: Option> = None; - let bases = PyTuple::new_ref(bases.into_vec(), &vm.ctx); - for (i, base) in bases.as_slice().iter().enumerate() { if base.isinstance(&vm.ctx.types.type_type) { if let Some(bases) = &mut new_bases { @@ -850,23 +843,38 @@ mod builtins { None => (None, bases), }; - for base in bases.as_slice().iter() { - let base_class = base.class(); - if base_class.issubclass(&metaclass) { - metaclass = base.clone_class(); - } else if !metaclass.issubclass(&base_class) { - return Err(vm.new_type_error( - "metaclass conflict: the metaclass of a derived class must be a (non-strict) \ - subclass of the metaclasses of all its bases" - .to_owned(), - )); + // Use downcast_exact to keep ref to old object on error. + let metaclass = kwargs + .pop_kwarg("metaclass") + .map(|metaclass| metaclass.downcast_exact::(vm)) + .unwrap_or_else(|| Ok(vm.ctx.types.type_type.clone())); + + let (metaclass, meta_name) = match metaclass { + Ok(mut metaclass) => { + for base in bases.as_slice().iter() { + let base_class = base.class(); + if base_class.issubclass(&metaclass) { + metaclass = base.clone_class(); + } else if !metaclass.issubclass(&base_class) { + return Err(vm.new_type_error( + "metaclass conflict: the metaclass of a derived class must be a (non-strict) \ + subclass of the metaclasses of all its bases" + .to_owned(), + )); + } + } + let meta_name = metaclass.slot_name(); + (metaclass.into(), meta_name) } - } + Err(obj) => (obj, "".to_owned()), + }; let bases: PyObjectRef = bases.into(); // Prepare uses full __getattribute__ resolution chain. - let prepare = vm.get_attribute(metaclass.clone().into(), "__prepare__")?; + // TODO RUSTPYTHON: Doesn't do what we want all the time: + // e.g class M(metaclass=2): pass should fail with 'int not callable'. + let prepare = vm.get_attribute(metaclass.clone(), "__prepare__")?; let namespace = vm.invoke( &prepare, FuncArgs::new(vec![name_obj.clone().into(), bases.clone()], kwargs.clone()), @@ -876,7 +884,7 @@ mod builtins { let namespace = PyMapping::try_from_object(vm, namespace.clone()).map_err(|_| { vm.new_type_error(format!( "{}.__prepare__() must return a mapping, not {}", - metaclass.name(), + meta_name, namespace.class().name() )) })?; @@ -891,7 +899,7 @@ mod builtins { } let class = vm.invoke( - metaclass.as_object(), + &metaclass, FuncArgs::new(vec![name_obj.into(), bases, namespace.into()], kwargs), )?;