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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 0 additions & 4 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -4099,8 +4099,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 @@ -9601,8 +9599,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
128 changes: 85 additions & 43 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 @@ -2090,11 +2094,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 +2530,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 +2564,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 +3245,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 +3712,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 +3771,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
26 changes: 22 additions & 4 deletions compiler/codegen/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1042,14 +1042,32 @@ impl Compiler<'_> {
)));
};
let name_string = name.id.to_string();
if type_params.is_some() {
self.push_symbol_table();
}
self.compile_expression(value)?;

// For PEP 695 syntax, we need to compile type_params first
// so that they're available when compiling the value expression
if let Some(type_params) = type_params {
self.push_symbol_table();

// Compile type params first to define T1, T2, etc.
self.compile_type_params(type_params)?;
// Stack now has type_params tuple at top

// Compile value expression (can now see T1, T2)
self.compile_expression(value)?;
// Stack: [type_params_tuple, value]

// We need [value, type_params_tuple] for TypeAlias instruction
emit!(self, Instruction::Rotate2);

self.pop_symbol_table();
} else {
// No type params - push value first, then None (not empty tuple)
self.compile_expression(value)?;
// Push None for type_params (matching CPython)
self.emit_load_const(ConstantData::None);
}

// Push name last
self.emit_load_const(ConstantData::Str {
value: name_string.clone().into(),
});
Expand Down
16 changes: 12 additions & 4 deletions vm/src/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1266,10 +1266,18 @@ impl ExecutingFrame<'_> {
}
bytecode::Instruction::TypeAlias => {
let name = self.pop_value();
let type_params: PyTupleRef = self
.pop_value()
.downcast()
.map_err(|_| vm.new_type_error("Type params must be a tuple."))?;
let type_params_obj = self.pop_value();

// CPython allows None or tuple for type_params
let type_params: PyTupleRef = if vm.is_none(&type_params_obj) {
// If None, use empty tuple (matching CPython's behavior)
vm.ctx.empty_tuple.clone()
} else {
type_params_obj
.downcast()
.map_err(|_| vm.new_type_error("Type params must be a tuple."))?
};

let value = self.pop_value();
let type_alias = typing::TypeAliasType::new(name, type_params, value);
self.push_value(type_alias.into_ref(&vm.ctx).into());
Expand Down
10 changes: 9 additions & 1 deletion vm/src/stdlib/typing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ pub(crate) mod decl {
infer_variance: bool,
}

#[pyclass(flags(HAS_DICT), with(AsNumber, Constructor))]
#[pyclass(flags(HAS_DICT), with(AsNumber, Constructor, Representable))]
impl ParamSpec {
#[pymethod]
fn __mro_entries__(&self, _bases: PyObjectRef, vm: &VirtualMachine) -> PyResult {
Expand Down Expand Up @@ -555,6 +555,14 @@ pub(crate) mod decl {
}
}

impl Representable for ParamSpec {
#[inline(always)]
fn repr_str(zelf: &crate::Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
let name = zelf.__name__().str(vm)?;
Ok(format!("~{}", name))
}
}

pub(crate) fn make_paramspec(name: PyObjectRef) -> ParamSpec {
ParamSpec {
name,
Expand Down
Loading