Skip to content

Commit cd58d15

Browse files
authored
Type alias type (#6011)
* Constructor for TypeAliasType * Fix PyBaseObject::py_new * Representable for TypeAliasType
1 parent 3bce41b commit cd58d15

File tree

7 files changed

+116
-27
lines changed

7 files changed

+116
-27
lines changed

Lib/test/test_ast/test_ast.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,8 @@ class S(str):
706706
with assertNumDeprecated():
707707
self.assertNotIsInstance(Constant(S("42")), Num)
708708

709+
# TODO: RUSTPYTHON; will be removed in Python 3.14
710+
@unittest.expectedFailure
709711
def test_constant_subclasses_deprecated(self):
710712
with warnings.catch_warnings():
711713
warnings.filterwarnings("ignore", "", DeprecationWarning)

Lib/test/test_collections.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -952,8 +952,6 @@ def __aiter__(self):
952952
self.validate_abstract_methods(AsyncIterable, '__aiter__')
953953
self.validate_isinstance(AsyncIterable, '__aiter__')
954954

955-
# TODO: RUSTPYTHON
956-
@unittest.expectedFailure
957955
def test_AsyncIterator(self):
958956
class AI:
959957
def __aiter__(self):
@@ -1152,8 +1150,6 @@ class NonCol(ColImpl):
11521150
self.assertFalse(issubclass(NonCol, Collection))
11531151
self.assertFalse(isinstance(NonCol(), Collection))
11541152

1155-
# TODO: RUSTPYTHON
1156-
@unittest.expectedFailure
11571153
def test_Iterator(self):
11581154
non_samples = [None, 42, 3.14, 1j, b"", "", (), [], {}, set()]
11591155
for x in non_samples:
@@ -1850,8 +1846,6 @@ def test_Set_hash_matches_frozenset(self):
18501846
fs = frozenset(s)
18511847
self.assertEqual(hash(fs), Set._hash(fs), msg=s)
18521848

1853-
# TODO: RUSTPYTHON
1854-
@unittest.expectedFailure
18551849
def test_Mapping(self):
18561850
for sample in [dict]:
18571851
self.assertIsInstance(sample(), Mapping)
@@ -1868,8 +1862,6 @@ def __iter__(self):
18681862
self.validate_comparison(MyMapping())
18691863
self.assertRaises(TypeError, reversed, MyMapping())
18701864

1871-
# TODO: RUSTPYTHON
1872-
@unittest.expectedFailure
18731865
def test_MutableMapping(self):
18741866
for sample in [dict]:
18751867
self.assertIsInstance(sample(), MutableMapping)
@@ -1904,8 +1896,6 @@ def test_MutableMapping_subclass(self):
19041896
mymap['blue'] = 7 # Shouldn't affect 'z'
19051897
self.assertEqual(z, {('orange', 3), ('red', 5)})
19061898

1907-
# TODO: RUSTPYTHON
1908-
@unittest.expectedFailure
19091899
def test_Sequence(self):
19101900
for sample in [tuple, list, bytes, str]:
19111901
self.assertIsInstance(sample(), Sequence)
@@ -1988,8 +1978,6 @@ def test_Buffer(self):
19881978
self.assertFalse(issubclass(sample, Buffer))
19891979
self.validate_abstract_methods(Buffer, '__buffer__')
19901980

1991-
# TODO: RUSTPYTHON
1992-
@unittest.expectedFailure
19931981
def test_MutableSequence(self):
19941982
for sample in [tuple, str, bytes]:
19951983
self.assertNotIsInstance(sample(), MutableSequence)

Lib/test/test_exception_group.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,8 +315,6 @@ def setUp(self):
315315
self.eg = create_simple_eg()
316316
self.eg_template = [ValueError(1), TypeError(int), ValueError(2)]
317317

318-
# TODO: RUSTPYTHON
319-
@unittest.expectedFailure
320318
def test_basics_subgroup_split__bad_arg_type(self):
321319
class C:
322320
pass

Lib/test/test_typing.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2948,8 +2948,6 @@ class E(C, BP): pass
29482948
self.assertNotIsInstance(D(), E)
29492949
self.assertNotIsInstance(E(), D)
29502950

2951-
# TODO: RUSTPYTHON
2952-
@unittest.expectedFailure
29532951
def test_no_instantiation(self):
29542952
class P(Protocol): pass
29552953

@@ -4500,8 +4498,6 @@ def __init__(self, arg):
45004498
self.assertEqual(c.from_a, 'foo')
45014499
self.assertEqual(c.from_c, 'foo')
45024500

4503-
# TODO: RUSTPYTHON
4504-
@unittest.expectedFailure
45054501
def test_new_no_args(self):
45064502

45074503
class A(Generic[T]):

vm/src/builtins/object.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,42 @@ impl PyPayload for PyBaseObject {
3131
impl Constructor for PyBaseObject {
3232
type Args = FuncArgs;
3333

34-
fn py_new(cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult {
34+
// = object_new
35+
fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult {
36+
if !args.args.is_empty() || !args.kwargs.is_empty() {
37+
// Check if type's __new__ != object.__new__
38+
let tp_new = cls.get_attr(identifier!(vm, __new__));
39+
let object_new = vm.ctx.types.object_type.get_attr(identifier!(vm, __new__));
40+
41+
if let (Some(tp_new), Some(object_new)) = (tp_new, object_new) {
42+
if !tp_new.is(&object_new) {
43+
// Type has its own __new__, so object.__new__ is being called
44+
// with excess args. This is the first error case in CPython
45+
return Err(vm.new_type_error(
46+
"object.__new__() takes exactly one argument (the type to instantiate)"
47+
.to_owned(),
48+
));
49+
}
50+
51+
// If we reach here, tp_new == object_new
52+
// Now check if type's __init__ == object.__init__
53+
let tp_init = cls.get_attr(identifier!(vm, __init__));
54+
let object_init = vm.ctx.types.object_type.get_attr(identifier!(vm, __init__));
55+
56+
if let (Some(tp_init), Some(object_init)) = (tp_init, object_init) {
57+
if tp_init.is(&object_init) {
58+
// Both __new__ and __init__ are object's versions,
59+
// so the type accepts no arguments
60+
return Err(
61+
vm.new_type_error(format!("{}() takes no arguments", cls.name()))
62+
);
63+
}
64+
}
65+
// If tp_init != object_init, then the type has custom __init__
66+
// which might accept arguments, so we allow it
67+
}
68+
}
69+
3570
// more or less __new__ operator
3671
let dict = if cls.is(vm.ctx.types.object_type) {
3772
None

vm/src/stdlib/ast/python.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@ use super::{PY_CF_OPTIMIZED_AST, PY_CF_TYPE_COMMENTS, PY_COMPILE_FLAG_AST_ONLY};
44
pub(crate) mod _ast {
55
use crate::{
66
AsObject, Context, PyObjectRef, PyPayload, PyResult, VirtualMachine,
7-
builtins::{PyStrRef, PyTupleRef},
7+
builtins::{PyStrRef, PyTupleRef, PyTypeRef},
88
function::FuncArgs,
9+
types::Constructor,
910
};
1011
#[pyattr]
1112
#[pyclass(module = "_ast", name = "AST")]
1213
#[derive(Debug, PyPayload)]
1314
pub(crate) struct NodeAst;
1415

15-
#[pyclass(flags(BASETYPE, HAS_DICT))]
16+
#[pyclass(with(Constructor), flags(BASETYPE, HAS_DICT))]
1617
impl NodeAst {
1718
#[pyslot]
1819
#[pymethod]
@@ -52,6 +53,34 @@ pub(crate) mod _ast {
5253
}
5354
}
5455

56+
impl Constructor for NodeAst {
57+
type Args = FuncArgs;
58+
59+
fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
60+
// AST nodes accept extra arguments (unlike object.__new__)
61+
// This matches CPython's behavior where AST has its own tp_new
62+
let dict = if cls
63+
.slots
64+
.flags
65+
.contains(crate::types::PyTypeFlags::HAS_DICT)
66+
{
67+
Some(vm.ctx.new_dict())
68+
} else {
69+
None
70+
};
71+
let zelf = vm.ctx.new_base_object(cls, dict);
72+
73+
// Initialize the instance with the provided arguments
74+
NodeAst::__init__(zelf.clone(), args, vm)?;
75+
76+
Ok(zelf)
77+
}
78+
79+
fn py_new(_cls: PyTypeRef, _args: Self::Args, _vm: &VirtualMachine) -> PyResult {
80+
unreachable!("slow_new is implemented");
81+
}
82+
}
83+
5584
#[pyattr(name = "PyCF_ONLY_AST")]
5685
use super::PY_COMPILE_FLAG_AST_ONLY;
5786

vm/src/stdlib/typing.rs

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
3030
#[pymodule(name = "_typing")]
3131
pub(crate) mod decl {
3232
use crate::{
33-
PyObjectRef, PyPayload, PyResult, VirtualMachine,
33+
Py, PyObjectRef, PyPayload, PyResult, VirtualMachine,
3434
builtins::{PyTupleRef, PyTypeRef, pystr::AsPyStr},
3535
function::{FuncArgs, IntoFuncArgs},
3636
types::{Constructor, Representable},
@@ -88,7 +88,7 @@ pub(crate) mod decl {
8888

8989
impl Representable for NoDefault {
9090
#[inline(always)]
91-
fn repr_str(_zelf: &crate::Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
91+
fn repr_str(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
9292
Ok("typing.NoDefault".to_owned())
9393
}
9494
}
@@ -104,7 +104,7 @@ pub(crate) mod decl {
104104
// compute_value: PyObjectRef,
105105
// module: PyObjectRef,
106106
}
107-
#[pyclass(flags(BASETYPE))]
107+
#[pyclass(with(Constructor, Representable), flags(BASETYPE))]
108108
impl TypeAliasType {
109109
pub const fn new(name: PyObjectRef, type_params: PyTupleRef, value: PyObjectRef) -> Self {
110110
Self {
@@ -128,10 +128,51 @@ pub(crate) mod decl {
128128
fn __type_params__(&self) -> PyTupleRef {
129129
self.type_params.clone()
130130
}
131+
}
132+
133+
impl Constructor for TypeAliasType {
134+
type Args = FuncArgs;
135+
136+
fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult {
137+
// TypeAliasType(name, value, *, type_params=None)
138+
if args.args.len() < 2 {
139+
return Err(vm.new_type_error(format!(
140+
"TypeAliasType() missing {} required positional argument{}: {}",
141+
2 - args.args.len(),
142+
if 2 - args.args.len() == 1 { "" } else { "s" },
143+
if args.args.is_empty() {
144+
"'name' and 'value'"
145+
} else {
146+
"'value'"
147+
}
148+
)));
149+
}
150+
if args.args.len() > 2 {
151+
return Err(vm.new_type_error(format!(
152+
"TypeAliasType() takes 2 positional arguments but {} were given",
153+
args.args.len()
154+
)));
155+
}
156+
157+
let name = args.args[0].clone();
158+
let value = args.args[1].clone();
159+
160+
let type_params = if let Some(tp) = args.kwargs.get("type_params") {
161+
tp.clone()
162+
.downcast::<crate::builtins::PyTuple>()
163+
.map_err(|_| vm.new_type_error("type_params must be a tuple".to_owned()))?
164+
} else {
165+
vm.ctx.empty_tuple.clone()
166+
};
167+
168+
let ta = TypeAliasType::new(name, type_params, value);
169+
ta.into_ref_with_type(vm, cls).map(Into::into)
170+
}
171+
}
131172

132-
#[pymethod(name = "__repr__")]
133-
fn repr(&self, vm: &VirtualMachine) -> PyResult<String> {
134-
let name = self.name.str(vm)?;
173+
impl Representable for TypeAliasType {
174+
fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
175+
let name = zelf.name.str(vm)?;
135176
Ok(name.as_str().to_owned())
136177
}
137178
}

0 commit comments

Comments
 (0)