Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 14 additions & 12 deletions Lib/test/test_type_aliases.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,31 +144,33 @@ def test_subscripting(self):

def test_repr(self):
type Simple = int
type VeryGeneric[T, *Ts, **P] = Callable[P, tuple[T, *Ts]]
self.assertEqual(repr(Simple), Simple.__qualname__)

self.assertEqual(repr(Simple), "Simple")
self.assertEqual(repr(VeryGeneric), "VeryGeneric")
type VeryGeneric[T, *Ts, **P] = Callable[P, tuple[T, *Ts]]
self.assertEqual(repr(VeryGeneric), VeryGeneric.__qualname__)
fullname = f"{VeryGeneric.__module__}.{VeryGeneric.__qualname__}"
self.assertEqual(repr(VeryGeneric[int, bytes, str, [float, object]]),
"VeryGeneric[int, bytes, str, [float, object]]")
f"{fullname}[int, bytes, str, [float, object]]")
self.assertEqual(repr(VeryGeneric[int, []]),
"VeryGeneric[int, []]")
f"{fullname}[int, []]")
self.assertEqual(repr(VeryGeneric[int, [VeryGeneric[int], list[str]]]),
"VeryGeneric[int, [VeryGeneric[int], list[str]]]")
f"{fullname}[int, [{fullname}[int], list[str]]]")

def test_recursive_repr(self):
type Recursive = Recursive
self.assertEqual(repr(Recursive), "Recursive")
self.assertEqual(repr(Recursive), Recursive.__qualname__)

type X = list[Y]
type Y = list[X]
self.assertEqual(repr(X), "X")
self.assertEqual(repr(Y), "Y")
self.assertEqual(repr(X), X.__qualname__)
self.assertEqual(repr(Y), Y.__qualname__)

type GenericRecursive[X] = list[X | GenericRecursive[X]]
self.assertEqual(repr(GenericRecursive), "GenericRecursive")
self.assertEqual(repr(GenericRecursive[int]), "GenericRecursive[int]")
self.assertEqual(repr(GenericRecursive), GenericRecursive.__qualname__)
fullname = f"{GenericRecursive.__module__}.{GenericRecursive.__qualname__}"
self.assertEqual(repr(GenericRecursive[int]), f"{fullname}[int]")
self.assertEqual(repr(GenericRecursive[GenericRecursive[int]]),
"GenericRecursive[GenericRecursive[int]]")
f"{fullname}[{fullname}[int]]")

def test_raising(self):
type MissingName = list[_My_X]
Expand Down
42 changes: 21 additions & 21 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1432,16 +1432,16 @@ def test_repr_is_correct(self):
class G1(Generic[*Ts]): pass
class G2(Generic[Unpack[Ts]]): pass

self.assertEqual(repr(Ts), 'Ts')
self.assertEqual(repr(Ts), '~Ts')

self.assertEqual(repr((*Ts,)[0]), 'typing.Unpack[Ts]')
self.assertEqual(repr(Unpack[Ts]), 'typing.Unpack[Ts]')
self.assertEqual(repr((*Ts,)[0]), 'typing.Unpack[~Ts]')
self.assertEqual(repr(Unpack[Ts]), 'typing.Unpack[~Ts]')

self.assertEqual(repr(tuple[*Ts]), 'tuple[typing.Unpack[Ts]]')
self.assertEqual(repr(Tuple[Unpack[Ts]]), 'typing.Tuple[typing.Unpack[Ts]]')
self.assertEqual(repr(tuple[*Ts]), 'tuple[typing.Unpack[~Ts]]')
self.assertEqual(repr(Tuple[Unpack[Ts]]), 'typing.Tuple[typing.Unpack[~Ts]]')

self.assertEqual(repr(*tuple[*Ts]), '*tuple[typing.Unpack[Ts]]')
self.assertEqual(repr(Unpack[Tuple[Unpack[Ts]]]), 'typing.Unpack[typing.Tuple[typing.Unpack[Ts]]]')
self.assertEqual(repr(*tuple[*Ts]), '*tuple[typing.Unpack[~Ts]]')
self.assertEqual(repr(Unpack[Tuple[Unpack[Ts]]]), 'typing.Unpack[typing.Tuple[typing.Unpack[~Ts]]]')

def test_variadic_class_repr_is_correct(self):
Ts = TypeVarTuple('Ts')
Expand Down Expand Up @@ -1480,61 +1480,61 @@ def test_variadic_class_alias_repr_is_correct(self):
class A(Generic[Unpack[Ts]]): pass

B = A[*Ts]
self.assertEndsWith(repr(B), 'A[typing.Unpack[Ts]]')
self.assertEndsWith(repr(B), 'A[typing.Unpack[~Ts]]')
self.assertEndsWith(repr(B[()]), 'A[()]')
self.assertEndsWith(repr(B[float]), 'A[float]')
self.assertEndsWith(repr(B[float, str]), 'A[float, str]')

C = A[Unpack[Ts]]
self.assertEndsWith(repr(C), 'A[typing.Unpack[Ts]]')
self.assertEndsWith(repr(C), 'A[typing.Unpack[~Ts]]')
self.assertEndsWith(repr(C[()]), 'A[()]')
self.assertEndsWith(repr(C[float]), 'A[float]')
self.assertEndsWith(repr(C[float, str]), 'A[float, str]')

D = A[*Ts, int]
self.assertEndsWith(repr(D), 'A[typing.Unpack[Ts], int]')
self.assertEndsWith(repr(D), 'A[typing.Unpack[~Ts], int]')
self.assertEndsWith(repr(D[()]), 'A[int]')
self.assertEndsWith(repr(D[float]), 'A[float, int]')
self.assertEndsWith(repr(D[float, str]), 'A[float, str, int]')

E = A[Unpack[Ts], int]
self.assertEndsWith(repr(E), 'A[typing.Unpack[Ts], int]')
self.assertEndsWith(repr(E), 'A[typing.Unpack[~Ts], int]')
self.assertEndsWith(repr(E[()]), 'A[int]')
self.assertEndsWith(repr(E[float]), 'A[float, int]')
self.assertEndsWith(repr(E[float, str]), 'A[float, str, int]')

F = A[int, *Ts]
self.assertEndsWith(repr(F), 'A[int, typing.Unpack[Ts]]')
self.assertEndsWith(repr(F), 'A[int, typing.Unpack[~Ts]]')
self.assertEndsWith(repr(F[()]), 'A[int]')
self.assertEndsWith(repr(F[float]), 'A[int, float]')
self.assertEndsWith(repr(F[float, str]), 'A[int, float, str]')

G = A[int, Unpack[Ts]]
self.assertEndsWith(repr(G), 'A[int, typing.Unpack[Ts]]')
self.assertEndsWith(repr(G), 'A[int, typing.Unpack[~Ts]]')
self.assertEndsWith(repr(G[()]), 'A[int]')
self.assertEndsWith(repr(G[float]), 'A[int, float]')
self.assertEndsWith(repr(G[float, str]), 'A[int, float, str]')

H = A[int, *Ts, str]
self.assertEndsWith(repr(H), 'A[int, typing.Unpack[Ts], str]')
self.assertEndsWith(repr(H), 'A[int, typing.Unpack[~Ts], str]')
self.assertEndsWith(repr(H[()]), 'A[int, str]')
self.assertEndsWith(repr(H[float]), 'A[int, float, str]')
self.assertEndsWith(repr(H[float, str]), 'A[int, float, str, str]')

I = A[int, Unpack[Ts], str]
self.assertEndsWith(repr(I), 'A[int, typing.Unpack[Ts], str]')
self.assertEndsWith(repr(I), 'A[int, typing.Unpack[~Ts], str]')
self.assertEndsWith(repr(I[()]), 'A[int, str]')
self.assertEndsWith(repr(I[float]), 'A[int, float, str]')
self.assertEndsWith(repr(I[float, str]), 'A[int, float, str, str]')

J = A[*Ts, *tuple[str, ...]]
self.assertEndsWith(repr(J), 'A[typing.Unpack[Ts], *tuple[str, ...]]')
self.assertEndsWith(repr(J), 'A[typing.Unpack[~Ts], *tuple[str, ...]]')
self.assertEndsWith(repr(J[()]), 'A[*tuple[str, ...]]')
self.assertEndsWith(repr(J[float]), 'A[float, *tuple[str, ...]]')
self.assertEndsWith(repr(J[float, str]), 'A[float, str, *tuple[str, ...]]')

K = A[Unpack[Ts], Unpack[Tuple[str, ...]]]
self.assertEndsWith(repr(K), 'A[typing.Unpack[Ts], typing.Unpack[typing.Tuple[str, ...]]]')
self.assertEndsWith(repr(K), 'A[typing.Unpack[~Ts], typing.Unpack[typing.Tuple[str, ...]]]')
self.assertEndsWith(repr(K[()]), 'A[typing.Unpack[typing.Tuple[str, ...]]]')
self.assertEndsWith(repr(K[float]), 'A[float, typing.Unpack[typing.Tuple[str, ...]]]')
self.assertEndsWith(repr(K[float, str]), 'A[float, str, typing.Unpack[typing.Tuple[str, ...]]]')
Expand All @@ -1555,9 +1555,9 @@ class G(type(Unpack[Ts])): pass
with self.assertRaisesRegex(TypeError,
r'Cannot subclass typing\.Unpack'):
class H(Unpack): pass
with self.assertRaisesRegex(TypeError, r'Cannot subclass typing.Unpack\[Ts\]'):
with self.assertRaisesRegex(TypeError, r'Cannot subclass typing.Unpack\[~Ts\]'):
class I(*Ts): pass
with self.assertRaisesRegex(TypeError, r'Cannot subclass typing.Unpack\[Ts\]'):
with self.assertRaisesRegex(TypeError, r'Cannot subclass typing.Unpack\[~Ts\]'):
class J(Unpack[Ts]): pass

def test_variadic_class_args_are_correct(self):
Expand Down Expand Up @@ -5506,13 +5506,13 @@ class TsP(Generic[*Ts, P]):
MyCallable[[int], bool]: "MyCallable[[int], bool]",
MyCallable[[int, str], bool]: "MyCallable[[int, str], bool]",
MyCallable[[int, list[int]], bool]: "MyCallable[[int, list[int]], bool]",
MyCallable[Concatenate[*Ts, P], T]: "MyCallable[typing.Concatenate[typing.Unpack[Ts], ~P], ~T]",
MyCallable[Concatenate[*Ts, P], T]: "MyCallable[typing.Concatenate[typing.Unpack[~Ts], ~P], ~T]",

DoubleSpec[P2, P, T]: "DoubleSpec[~P2, ~P, ~T]",
DoubleSpec[[int], [str], bool]: "DoubleSpec[[int], [str], bool]",
DoubleSpec[[int, int], [str, str], bool]: "DoubleSpec[[int, int], [str, str], bool]",

TsP[*Ts, P]: "TsP[typing.Unpack[Ts], ~P]",
TsP[*Ts, P]: "TsP[typing.Unpack[~Ts], ~P]",
TsP[int, str, list[int], []]: "TsP[int, str, list[int], []]",
TsP[int, [str, list[int]]]: "TsP[int, [str, list[int]]]",

Expand Down
54 changes: 42 additions & 12 deletions crates/vm/src/stdlib/_typing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@ pub(crate) mod decl {
use crate::{
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
atomic_func,
builtins::{PyGenericAlias, PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef, type_},
builtins::{
PyFunction, PyGenericAlias, PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef, type_,
},
common::wtf8::Wtf8Buf,
function::FuncArgs,
function::{FuncArgs, PySetterValue},
protocol::{PyMappingMethods, PyNumberMethods},
types::{AsMapping, AsNumber, Callable, Constructor, Iterable, Representable},
};
Expand Down Expand Up @@ -197,10 +199,12 @@ pub(crate) mod decl {
pub(crate) struct TypeAliasType {
#[pymember(name = "__name__")]
name: PyStrRef,
#[pymember(name = "__qualname__")]
qualname: PyStrRef,
type_params: PyTupleRef,
compute_value: PyObjectRef,
cached_value: crate::common::lock::PyMutex<Option<PyObjectRef>>,
module: Option<PyObjectRef>,
module: crate::common::lock::PyMutex<Option<PyObjectRef>>,
is_lazy: bool,
}
impl TypeAliasType {
Expand All @@ -210,29 +214,38 @@ pub(crate) mod decl {
type_params: PyTupleRef,
compute_value: PyObjectRef,
) -> Self {
let qualname = compute_value
.downcast_ref::<PyFunction>()
.expect("type alias evaluator must be a function")
.code
.qualname
.to_owned();
Self {
name,
qualname,
type_params,
compute_value,
cached_value: crate::common::lock::PyMutex::new(None),
module: None,
module: crate::common::lock::PyMutex::new(None),
is_lazy: true,
}
}

/// Create with an eagerly evaluated value (used by constructor)
fn new_eager(
name: PyStrRef,
qualname: PyStrRef,
type_params: PyTupleRef,
value: PyObjectRef,
module: Option<PyObjectRef>,
) -> Self {
Self {
name,
qualname,
type_params,
compute_value: value.clone(),
cached_value: crate::common::lock::PyMutex::new(Some(value)),
module,
module: crate::common::lock::PyMutex::new(module),
is_lazy: false,
}
}
Expand Down Expand Up @@ -317,8 +330,9 @@ pub(crate) mod decl {

#[pygetset]
fn __module__(&self, vm: &VirtualMachine) -> PyObjectRef {
if let Some(ref module) = self.module {
return module.clone();
let module = self.module.lock().clone();
if let Some(module) = module {
return module;
}
// Fall back to compute_value's __module__ (like PyFunction_GetModule)
if self.is_lazy
Expand All @@ -329,6 +343,12 @@ pub(crate) mod decl {
vm.ctx.none()
}

#[pygetset(setter)]
fn set___module__(&self, value: PySetterValue) {
let previous = core::mem::replace(&mut *self.module.lock(), value.into_option());
drop(previous);
}

#[pymethod]
fn __reduce__(zelf: &Self, _vm: &VirtualMachine) -> PyObjectRef {
zelf.name.clone().into()
Expand All @@ -352,12 +372,14 @@ pub(crate) mod decl {
type Args = FuncArgs;

fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> {
// typealias(name, value, *, type_params=())
// name and value are positional-or-keyword; type_params is keyword-only.
// name and value are positional-or-keyword; type_params and qualname are keyword-only.

// Reject unexpected keyword arguments.
for key in args.kwargs.keys() {
if !matches!(key.as_str(), Ok("name" | "value" | "type_params")) {
if !matches!(
key.as_str(),
Ok("name" | "value" | "type_params" | "qualname")
) {
return Err(
vm.new_unexpected_keyword_type_error(Some("typealias"), &key.to_string())
);
Expand Down Expand Up @@ -418,17 +440,25 @@ pub(crate) mod decl {
vm.ctx.empty_tuple.clone()
};

let qualname = match args.kwargs.get("qualname") {
Some(qualname) if !vm.is_none(qualname) => qualname
.clone()
.downcast::<crate::builtins::PyStr>()
.map_err(|_| vm.new_type_error("qualname must be a string"))?,
_ => name.clone(),
};

// Get caller's module name from frame globals, like typevar.rs caller()
let module =
crate::frame::current_globals().and_then(|g| g.get_item("__name__", vm).ok());

Ok(Self::new_eager(name, type_params, value, module))
Ok(Self::new_eager(name, qualname, type_params, value, module))
}
}

impl Representable for TypeAliasType {
fn repr_wtf8(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<Wtf8Buf> {
Ok(zelf.name.as_wtf8().to_owned())
Ok(zelf.qualname.as_wtf8().to_owned())
}
}

Expand Down
Loading
Loading