Type alias type (#6011)

* Constructor for TypeAliasType

* Fix PyBaseObject::py_new

* Representable for TypeAliasType
This commit is contained in:
Jeong, YunWon
2025-07-21 14:42:18 +09:00
committed by GitHub
parent 3bce41baab
commit cd58d154cf
7 changed files with 116 additions and 27 deletions

View File

@@ -706,6 +706,8 @@ class AST_Tests(unittest.TestCase):
with assertNumDeprecated():
self.assertNotIsInstance(Constant(S("42")), Num)
# TODO: RUSTPYTHON; will be removed in Python 3.14
@unittest.expectedFailure
def test_constant_subclasses_deprecated(self):
with warnings.catch_warnings():
warnings.filterwarnings("ignore", "", DeprecationWarning)

View File

@@ -952,8 +952,6 @@ class TestOneTrickPonyABCs(ABCTestCase):
self.validate_abstract_methods(AsyncIterable, '__aiter__')
self.validate_isinstance(AsyncIterable, '__aiter__')
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_AsyncIterator(self):
class AI:
def __aiter__(self):
@@ -1152,8 +1150,6 @@ class TestOneTrickPonyABCs(ABCTestCase):
self.assertFalse(issubclass(NonCol, Collection))
self.assertFalse(isinstance(NonCol(), Collection))
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_Iterator(self):
non_samples = [None, 42, 3.14, 1j, b"", "", (), [], {}, set()]
for x in non_samples:
@@ -1850,8 +1846,6 @@ class TestCollectionABCs(ABCTestCase):
fs = frozenset(s)
self.assertEqual(hash(fs), Set._hash(fs), msg=s)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_Mapping(self):
for sample in [dict]:
self.assertIsInstance(sample(), Mapping)
@@ -1868,8 +1862,6 @@ class TestCollectionABCs(ABCTestCase):
self.validate_comparison(MyMapping())
self.assertRaises(TypeError, reversed, MyMapping())
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_MutableMapping(self):
for sample in [dict]:
self.assertIsInstance(sample(), MutableMapping)
@@ -1904,8 +1896,6 @@ class TestCollectionABCs(ABCTestCase):
mymap['blue'] = 7 # Shouldn't affect 'z'
self.assertEqual(z, {('orange', 3), ('red', 5)})
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_Sequence(self):
for sample in [tuple, list, bytes, str]:
self.assertIsInstance(sample(), Sequence)
@@ -1988,8 +1978,6 @@ class TestCollectionABCs(ABCTestCase):
self.assertFalse(issubclass(sample, Buffer))
self.validate_abstract_methods(Buffer, '__buffer__')
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_MutableSequence(self):
for sample in [tuple, str, bytes]:
self.assertNotIsInstance(sample(), MutableSequence)

View File

@@ -315,8 +315,6 @@ class ExceptionGroupSubgroupTests(ExceptionGroupTestBase):
self.eg = create_simple_eg()
self.eg_template = [ValueError(1), TypeError(int), ValueError(2)]
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_basics_subgroup_split__bad_arg_type(self):
class C:
pass

View File

@@ -2948,8 +2948,6 @@ class ProtocolTests(BaseTestCase):
self.assertNotIsInstance(D(), E)
self.assertNotIsInstance(E(), D)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_no_instantiation(self):
class P(Protocol): pass
@@ -4500,8 +4498,6 @@ class GenericTests(BaseTestCase):
self.assertEqual(c.from_a, 'foo')
self.assertEqual(c.from_c, 'foo')
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_new_no_args(self):
class A(Generic[T]):

View File

@@ -31,7 +31,42 @@ impl PyPayload for PyBaseObject {
impl Constructor for PyBaseObject {
type Args = FuncArgs;
fn py_new(cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult {
// = object_new
fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult {
if !args.args.is_empty() || !args.kwargs.is_empty() {
// Check if type's __new__ != object.__new__
let tp_new = cls.get_attr(identifier!(vm, __new__));
let object_new = vm.ctx.types.object_type.get_attr(identifier!(vm, __new__));
if let (Some(tp_new), Some(object_new)) = (tp_new, object_new) {
if !tp_new.is(&object_new) {
// Type has its own __new__, so object.__new__ is being called
// with excess args. This is the first error case in CPython
return Err(vm.new_type_error(
"object.__new__() takes exactly one argument (the type to instantiate)"
.to_owned(),
));
}
// If we reach here, tp_new == object_new
// Now check if type's __init__ == object.__init__
let tp_init = cls.get_attr(identifier!(vm, __init__));
let object_init = vm.ctx.types.object_type.get_attr(identifier!(vm, __init__));
if let (Some(tp_init), Some(object_init)) = (tp_init, object_init) {
if tp_init.is(&object_init) {
// Both __new__ and __init__ are object's versions,
// so the type accepts no arguments
return Err(
vm.new_type_error(format!("{}() takes no arguments", cls.name()))
);
}
}
// If tp_init != object_init, then the type has custom __init__
// which might accept arguments, so we allow it
}
}
// more or less __new__ operator
let dict = if cls.is(vm.ctx.types.object_type) {
None

View File

@@ -4,15 +4,16 @@ use super::{PY_CF_OPTIMIZED_AST, PY_CF_TYPE_COMMENTS, PY_COMPILE_FLAG_AST_ONLY};
pub(crate) mod _ast {
use crate::{
AsObject, Context, PyObjectRef, PyPayload, PyResult, VirtualMachine,
builtins::{PyStrRef, PyTupleRef},
builtins::{PyStrRef, PyTupleRef, PyTypeRef},
function::FuncArgs,
types::Constructor,
};
#[pyattr]
#[pyclass(module = "_ast", name = "AST")]
#[derive(Debug, PyPayload)]
pub(crate) struct NodeAst;
#[pyclass(flags(BASETYPE, HAS_DICT))]
#[pyclass(with(Constructor), flags(BASETYPE, HAS_DICT))]
impl NodeAst {
#[pyslot]
#[pymethod]
@@ -52,6 +53,34 @@ pub(crate) mod _ast {
}
}
impl Constructor for NodeAst {
type Args = FuncArgs;
fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
// AST nodes accept extra arguments (unlike object.__new__)
// This matches CPython's behavior where AST has its own tp_new
let dict = if cls
.slots
.flags
.contains(crate::types::PyTypeFlags::HAS_DICT)
{
Some(vm.ctx.new_dict())
} else {
None
};
let zelf = vm.ctx.new_base_object(cls, dict);
// Initialize the instance with the provided arguments
NodeAst::__init__(zelf.clone(), args, vm)?;
Ok(zelf)
}
fn py_new(_cls: PyTypeRef, _args: Self::Args, _vm: &VirtualMachine) -> PyResult {
unreachable!("slow_new is implemented");
}
}
#[pyattr(name = "PyCF_ONLY_AST")]
use super::PY_COMPILE_FLAG_AST_ONLY;

View File

@@ -30,7 +30,7 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
#[pymodule(name = "_typing")]
pub(crate) mod decl {
use crate::{
PyObjectRef, PyPayload, PyResult, VirtualMachine,
Py, PyObjectRef, PyPayload, PyResult, VirtualMachine,
builtins::{PyTupleRef, PyTypeRef, pystr::AsPyStr},
function::{FuncArgs, IntoFuncArgs},
types::{Constructor, Representable},
@@ -88,7 +88,7 @@ pub(crate) mod decl {
impl Representable for NoDefault {
#[inline(always)]
fn repr_str(_zelf: &crate::Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
fn repr_str(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
Ok("typing.NoDefault".to_owned())
}
}
@@ -104,7 +104,7 @@ pub(crate) mod decl {
// compute_value: PyObjectRef,
// module: PyObjectRef,
}
#[pyclass(flags(BASETYPE))]
#[pyclass(with(Constructor, Representable), flags(BASETYPE))]
impl TypeAliasType {
pub const fn new(name: PyObjectRef, type_params: PyTupleRef, value: PyObjectRef) -> Self {
Self {
@@ -128,10 +128,51 @@ pub(crate) mod decl {
fn __type_params__(&self) -> PyTupleRef {
self.type_params.clone()
}
}
#[pymethod(name = "__repr__")]
fn repr(&self, vm: &VirtualMachine) -> PyResult<String> {
let name = self.name.str(vm)?;
impl Constructor for TypeAliasType {
type Args = FuncArgs;
fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult {
// TypeAliasType(name, value, *, type_params=None)
if args.args.len() < 2 {
return Err(vm.new_type_error(format!(
"TypeAliasType() missing {} required positional argument{}: {}",
2 - args.args.len(),
if 2 - args.args.len() == 1 { "" } else { "s" },
if args.args.is_empty() {
"'name' and 'value'"
} else {
"'value'"
}
)));
}
if args.args.len() > 2 {
return Err(vm.new_type_error(format!(
"TypeAliasType() takes 2 positional arguments but {} were given",
args.args.len()
)));
}
let name = args.args[0].clone();
let value = args.args[1].clone();
let type_params = if let Some(tp) = args.kwargs.get("type_params") {
tp.clone()
.downcast::<crate::builtins::PyTuple>()
.map_err(|_| vm.new_type_error("type_params must be a tuple".to_owned()))?
} else {
vm.ctx.empty_tuple.clone()
};
let ta = TypeAliasType::new(name, type_params, value);
ta.into_ref_with_type(vm, cls).map(Into::into)
}
}
impl Representable for TypeAliasType {
fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
let name = zelf.name.str(vm)?;
Ok(name.as_str().to_owned())
}
}