Skip to content

Upgrade typing to 3.13.5 #5850

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 30, 2025
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
10 changes: 0 additions & 10 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -3351,8 +3351,6 @@ def x(self): ...
self.assertNotIsSubclass(C, Protocol)
self.assertNotIsInstance(C(), Protocol)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_protocols_issubclass_non_callable(self):
class C:
x = 1
Expand Down Expand Up @@ -3412,8 +3410,6 @@ def __init__(self) -> None:
):
issubclass(Eggs, Spam)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_no_weird_caching_with_issubclass_after_isinstance_2(self):
@runtime_checkable
class Spam(Protocol):
Expand All @@ -3434,8 +3430,6 @@ class Eggs: ...
):
issubclass(Eggs, Spam)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_no_weird_caching_with_issubclass_after_isinstance_3(self):
@runtime_checkable
class Spam(Protocol):
Expand Down Expand Up @@ -4091,8 +4085,6 @@ class MyChain(typing.ChainMap[str, T]): ...
self.assertIs(MyChain[int]().__class__, MyChain)
self.assertEqual(MyChain[int]().__orig_class__, MyChain[int])

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_all_repr_eq_any(self):
objs = (getattr(typing, el) for el in typing.__all__)
for obj in objs:
Expand Down Expand Up @@ -9591,8 +9583,6 @@ def test_all(self):
self.assertIn('SupportsBytes', a)
self.assertIn('SupportsComplex', a)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_all_exported_names(self):
# ensure all dynamically created objects are actualised
for name in typing.__all__:
Expand Down
131 changes: 87 additions & 44 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,8 @@ def _should_unflatten_callable_args(typ, args):
>>> P = ParamSpec('P')
>>> collections.abc.Callable[[int, int], str].__args__ == (int, int, str)
True
>>> collections.abc.Callable[P, str].__args__ == (P, str)
True

As a result, if we need to reconstruct the Callable from its __args__,
we need to unflatten it.
Expand Down Expand Up @@ -263,6 +265,8 @@ def _collect_type_parameters(args, *, enforce_default_ordering: bool = True):

>>> P = ParamSpec('P')
>>> T = TypeVar('T')
>>> _collect_type_parameters((T, Callable[P, T]))
(~T, ~P)
"""
# required type parameter cannot appear after parameter with default
default_encountered = False
Expand Down Expand Up @@ -1983,7 +1987,8 @@ def _allow_reckless_class_checks(depth=2):
The abc and functools modules indiscriminately call isinstance() and
issubclass() on the whole MRO of a user class, which may contain protocols.
"""
return _caller(depth) in {'abc', 'functools', None}
# XXX: RUSTPYTHON; https://github.com/python/cpython/pull/136115
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return _caller(depth) in {'abc', '_py_abc', 'functools', None}


_PROTO_ALLOWLIST = {
Expand Down Expand Up @@ -2090,11 +2095,11 @@ def __subclasscheck__(cls, other):
and cls.__dict__.get("__subclasshook__") is _proto_hook
):
_type_check_issubclass_arg_1(other)
# non_method_attrs = sorted(cls.__non_callable_proto_members__)
# raise TypeError(
# "Protocols with non-method members don't support issubclass()."
# f" Non-method members: {str(non_method_attrs)[1:-1]}."
# )
non_method_attrs = sorted(cls.__non_callable_proto_members__)
raise TypeError(
"Protocols with non-method members don't support issubclass()."
f" Non-method members: {str(non_method_attrs)[1:-1]}."
)
return _abc_subclasscheck(cls, other)

def __instancecheck__(cls, instance):
Expand Down Expand Up @@ -2526,6 +2531,18 @@ def get_origin(tp):

This supports generic types, Callable, Tuple, Union, Literal, Final, ClassVar,
Annotated, and others. Return None for unsupported types.

Examples::

>>> P = ParamSpec('P')
>>> assert get_origin(Literal[42]) is Literal
>>> assert get_origin(int) is None
>>> assert get_origin(ClassVar[int]) is ClassVar
>>> assert get_origin(Generic) is Generic
>>> assert get_origin(Generic[T]) is Generic
>>> assert get_origin(Union[T, int]) is Union
>>> assert get_origin(List[Tuple[T, T]][int]) is list
>>> assert get_origin(P.args) is P
"""
if isinstance(tp, _AnnotatedAlias):
return Annotated
Expand All @@ -2548,6 +2565,10 @@ def get_args(tp):

>>> T = TypeVar('T')
>>> assert get_args(Dict[str, int]) == (str, int)
>>> assert get_args(int) == ()
>>> assert get_args(Union[int, Union[T, int], str][int]) == (int, str)
>>> assert get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int])
>>> assert get_args(Callable[[], T][int]) == ([], int)
"""
if isinstance(tp, _AnnotatedAlias):
return (tp.__origin__,) + tp.__metadata__
Expand Down Expand Up @@ -3225,6 +3246,18 @@ def TypedDict(typename, fields=_sentinel, /, *, total=True):
associated with a value of a consistent type. This expectation
is not checked at runtime.

Usage::

>>> class Point2D(TypedDict):
... x: int
... y: int
... label: str
...
>>> a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK
>>> b: Point2D = {'z': 3, 'label': 'bad'} # Fails type check
>>> Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first')
True

The type info can be accessed via the Point2D.__annotations__ dict, and
the Point2D.__required_keys__ and Point2D.__optional_keys__ frozensets.
TypedDict supports an additional equivalent form::
Expand Down Expand Up @@ -3680,44 +3713,43 @@ def decorator(cls_or_fn):
return cls_or_fn
return decorator

# TODO: RUSTPYTHON

# type _Func = Callable[..., Any]


# def override[F: _Func](method: F, /) -> F:
# """Indicate that a method is intended to override a method in a base class.
#
# Usage::
#
# class Base:
# def method(self) -> None:
# pass
#
# class Child(Base):
# @override
# def method(self) -> None:
# super().method()
#
# When this decorator is applied to a method, the type checker will
# validate that it overrides a method or attribute with the same name on a
# base class. This helps prevent bugs that may occur when a base class is
# changed without an equivalent change to a child class.
#
# There is no runtime checking of this property. The decorator attempts to
# set the ``__override__`` attribute to ``True`` on the decorated object to
# allow runtime introspection.
#
# See PEP 698 for details.
# """
# try:
# method.__override__ = True
# except (AttributeError, TypeError):
# # Skip the attribute silently if it is not writable.
# # AttributeError happens if the object has __slots__ or a
# # read-only property, TypeError if it's a builtin class.
# pass
# return method

type _Func = Callable[..., Any]


def override[F: _Func](method: F, /) -> F:
"""Indicate that a method is intended to override a method in a base class.

Usage::

class Base:
def method(self) -> None:
pass

class Child(Base):
@override
def method(self) -> None:
super().method()

When this decorator is applied to a method, the type checker will
validate that it overrides a method or attribute with the same name on a
base class. This helps prevent bugs that may occur when a base class is
changed without an equivalent change to a child class.

There is no runtime checking of this property. The decorator attempts to
set the ``__override__`` attribute to ``True`` on the decorated object to
allow runtime introspection.

See PEP 698 for details.
"""
try:
method.__override__ = True
except (AttributeError, TypeError):
# Skip the attribute silently if it is not writable.
# AttributeError happens if the object has __slots__ or a
# read-only property, TypeError if it's a builtin class.
pass
return method


def is_protocol(tp: type, /) -> bool:
Expand All @@ -3740,8 +3772,19 @@ def is_protocol(tp: type, /) -> bool:
and tp != Protocol
)


def get_protocol_members(tp: type, /) -> frozenset[str]:
"""Return the set of members defined in a Protocol.

Example::

>>> from typing import Protocol, get_protocol_members
>>> class P(Protocol):
... def a(self) -> str: ...
... b: int
>>> get_protocol_members(P) == frozenset({'a', 'b'})
True

Raise a TypeError for arguments that are not Protocols.
"""
if not is_protocol(tp):
Expand Down
40 changes: 24 additions & 16 deletions vm/src/protocol/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,9 +535,14 @@ impl PyObject {
derived.recursive_issubclass(cls, vm)
}

// _PyObject_RealIsInstance
pub(crate) fn real_is_instance(&self, cls: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
self.object_isinstance(cls, vm)
}

/// Real isinstance check without going through __instancecheck__
/// This is equivalent to CPython's _PyObject_RealIsInstance/object_isinstance
pub fn real_is_instance(&self, cls: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
fn object_isinstance(&self, cls: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
if let Ok(cls) = cls.try_to_ref::<PyType>(vm) {
// PyType_Check(cls) - cls is a type object
let mut retval = self.class().is_subtype(cls);
Expand Down Expand Up @@ -576,8 +581,12 @@ impl PyObject {

/// Determines if `self` is an instance of `cls`, either directly, indirectly or virtually via
/// the __instancecheck__ magic method.
// This is object_recursive_isinstance from CPython's Objects/abstract.c
pub fn is_instance(&self, cls: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
self.object_recursive_isinstance(cls, vm)
}

// This is object_recursive_isinstance from CPython's Objects/abstract.c
fn object_recursive_isinstance(&self, cls: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
// PyObject_TypeCheck(inst, (PyTypeObject *)cls)
// This is an exact check of the type
if self.class().is(cls) {
Expand All @@ -586,29 +595,28 @@ impl PyObject {

// PyType_CheckExact(cls) optimization
if cls.class().is(vm.ctx.types.type_type) {
// When cls is exactly a type (not a subclass), use real_is_instance
// When cls is exactly a type (not a subclass), use object_isinstance
// to avoid going through __instancecheck__ (matches CPython behavior)
return self.real_is_instance(cls, vm);
return self.object_isinstance(cls, vm);
}

// Check for Union type (e.g., int | str) - CPython checks this before tuple
if cls.class().is(vm.ctx.types.union_type) {
let cls = if cls.class().is(vm.ctx.types.union_type) {
// Match CPython's _Py_union_args which directly accesses the args field
let union = cls
.try_to_ref::<crate::builtins::PyUnion>(vm)
.expect("checked by is");
let tuple = union.args();
for typ in tuple.iter() {
if vm.with_recursion("in __instancecheck__", || self.is_instance(typ, vm))? {
return Ok(true);
}
}
}
union.args().as_object()
} else {
cls
};

// Check if cls is a tuple
if let Ok(tuple) = cls.try_to_ref::<PyTuple>(vm) {
for typ in tuple {
if vm.with_recursion("in __instancecheck__", || self.is_instance(typ, vm))? {
if let Some(tuple) = cls.downcast_ref::<PyTuple>() {
for item in tuple {
if vm.with_recursion("in __instancecheck__", || {
self.object_recursive_isinstance(item, vm)
})? {
return Ok(true);
}
}
Expand All @@ -624,7 +632,7 @@ impl PyObject {
}

// Fall back to object_isinstance (without going through __instancecheck__ again)
self.real_is_instance(cls, vm)
self.object_isinstance(cls, vm)
}

pub fn hash(&self, vm: &VirtualMachine) -> PyResult<PyHash> {
Expand Down
5 changes: 4 additions & 1 deletion vm/src/stdlib/typing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ pub(crate) mod decl {
#[pyfunction(name = "override")]
pub(crate) fn r#override(func: PyObjectRef, vm: &VirtualMachine) -> PyResult {
// Set __override__ attribute to True
func.set_attr("__override__", vm.ctx.true_value.clone(), vm)?;
// Skip the attribute silently if it is not writable.
// AttributeError happens if the object has __slots__ or a
// read-only property, TypeError if it's a builtin class.
let _ = func.set_attr("__override__", vm.ctx.true_value.clone(), vm);
Ok(func)
}

Expand Down
Loading