Skip to content

Fix Union type merging to use types.UnionType instead of typing.Union #137068

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

Closed
wants to merge 2 commits into from
Closed
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
163 changes: 113 additions & 50 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
ParamSpecKwargs,
TypeAliasType,
Generic,
Union,
NoDefault,
)

Expand Down Expand Up @@ -727,6 +726,81 @@ class FastConnector(Connection):
item = _type_check(parameters, f'{self} accepts only single type.', allow_special_forms=True)
return _GenericAlias(self, (item,))

@_SpecialForm
def Union(self, parameters):
"""Union type; Union[X, Y] is equivalent to X | Y and means either X or Y.

To define a union, use e.g. Union[int, str] or the shorthand int | str.
Using that shorthand is recommended. Details:

* The arguments must be types and there must be at least one.

* Unions of unions are flattened, e.g.::

Union[Union[int, str], float] == Union[int, str, float]

* Unions of a single argument vanish, e.g.::

Union[int] == int # The constructor actually returns int

* Redundant arguments are skipped, e.g.::

Union[int, str, int] == Union[int, str] == int | str

* When comparing unions, the argument order is ignored, e.g.::

Union[int, str] == Union[str, int]

* You cannot subclass or instantiate a ``Union``.

* You cannot write ``Union[X][Y]``.
"""
if not parameters:
raise TypeError("Cannot take a Union of no types.")
if not isinstance(parameters, tuple):
parameters = (parameters,)

flattened = []
for p in parameters:
if isinstance(p, types.UnionType):
flattened.extend(p.__args__)
elif hasattr(p, '__origin__') and p.__origin__ is Union:
flattened.extend(p.__args__)
else:
flattened.append(p)

unique_args = []
seen = set()
for arg in flattened:
if arg not in seen:
unique_args.append(arg)
seen.add(arg)

if len(unique_args) == 0:
raise TypeError("Cannot take a Union of no types.")
if len(unique_args) == 1:
return unique_args[0]

return _UnionGenericAlias(tuple(unique_args))


def _union_from_types(left, right):
"""Helper function to create union types avoiding recursion."""
try:
if hasattr(left, '__or__') and not isinstance(left, _GenericAlias):
return left | right
elif hasattr(right, '__ror__') and not isinstance(right, _GenericAlias):
return right.__ror__(left)
else:
if hasattr(left, '__origin__'):
left = left.__origin__
if hasattr(right, '__origin__'):
right = right.__origin__
return left | right
except (TypeError, AttributeError):
return f"Union[{left}, {right}]"


@_SpecialForm
def Optional(self, parameters):
"""Optional[X] is equivalent to Union[X, None]."""
Expand Down Expand Up @@ -1334,12 +1408,6 @@ def __eq__(self, other):
def __hash__(self):
return hash((self.__origin__, self.__args__))

def __or__(self, right):
return Union[self, right]

def __ror__(self, left):
return Union[left, self]

@_tp_cache
def __getitem__(self, args):
# Parameterizes an already-parameterized object.
Expand Down Expand Up @@ -1563,12 +1631,6 @@ def __subclasscheck__(self, cls):
def __reduce__(self):
return self._name

def __or__(self, right):
return Union[self, right]

def __ror__(self, left):
return Union[left, self]


class _CallableGenericAlias(_NotIterable, _GenericAlias, _root=True):
def __repr__(self):
Expand Down Expand Up @@ -1634,41 +1696,42 @@ def __getitem__(self, params):
return self.copy_with(params)


class _UnionGenericAliasMeta(type):
def __instancecheck__(self, inst: object) -> bool:
import warnings
warnings._deprecated("_UnionGenericAlias", remove=(3, 17))
return isinstance(inst, Union)

def __subclasscheck__(self, inst: type) -> bool:
import warnings
warnings._deprecated("_UnionGenericAlias", remove=(3, 17))
return issubclass(inst, Union)

def __eq__(self, other):
import warnings
warnings._deprecated("_UnionGenericAlias", remove=(3, 17))
if other is _UnionGenericAlias or other is Union:
return True
return NotImplemented

def __hash__(self):
return hash(Union)


class _UnionGenericAlias(metaclass=_UnionGenericAliasMeta):
"""Compatibility hack.
class _SimpleUnion:
"""Fallback union representation when types.UnionType creation fails."""
def __init__(self, args):
self.__args__ = args
self.__origin__ = Union

def __repr__(self):
return f"Union{self.__args__}"

A class named _UnionGenericAlias used to be used to implement
typing.Union. This class exists to serve as a shim to preserve
the meaning of some code that used to use _UnionGenericAlias
directly.

"""
def __new__(cls, self_cls, parameters, /, *, name=None):
import warnings
warnings._deprecated("_UnionGenericAlias", remove=(3, 17))
return Union[parameters]
class _UnionGenericAlias:
"""A placeholder class for union types that wraps types.UnionType functionality."""

def __new__(cls, args):
if len(args) == 1:
return args[0]
elif len(args) == 2:
try:
result = args[0] | args[1]
if hasattr(result, '__class__') and result.__class__.__name__ == 'UnionType':
return result
else:
return _SimpleUnion(args)
except (TypeError, AttributeError):
return _SimpleUnion(args)
else:
try:
result = args[0]
for arg in args[1:]:
result = result | arg
if hasattr(result, '__class__') and result.__class__.__name__ == 'UnionType':
return result
else:
return _SimpleUnion(args)
except (TypeError, AttributeError):
return _SimpleUnion(args)


def _value_and_type_iter(parameters):
Expand Down Expand Up @@ -3463,7 +3526,7 @@ def writelines(self, lines: list[AnyStr]) -> None:
pass

@abstractmethod
def __enter__(self) -> IO[AnyStr]:
def __enter__(self) -> 'IO[AnyStr]':
pass

@abstractmethod
Expand All @@ -3481,7 +3544,7 @@ def write(self, s: bytes | bytearray) -> int:
pass

@abstractmethod
def __enter__(self) -> BinaryIO:
def __enter__(self) -> 'BinaryIO':
pass


Expand Down Expand Up @@ -3516,7 +3579,7 @@ def newlines(self) -> Any:
pass

@abstractmethod
def __enter__(self) -> TextIO:
def __enter__(self) -> 'TextIO':
pass


Expand Down Expand Up @@ -3550,7 +3613,7 @@ def dataclass_transform(
order_default: bool = False,
kw_only_default: bool = False,
frozen_default: bool = False,
field_specifiers: tuple[type[Any] | Callable[..., Any], ...] = (),
field_specifiers: tuple[Union[type[Any], Callable[..., Any]], ...] = (),
**kwargs: Any,
) -> _IdentityCallable:
"""Decorator to mark an object as providing dataclass-like behaviour.
Expand Down
4 changes: 1 addition & 3 deletions Modules/_typingmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,7 @@ _typing_exec(PyObject *m)
if (PyModule_AddObjectRef(m, "TypeAliasType", (PyObject *)&_PyTypeAlias_Type) < 0) {
return -1;
}
if (PyModule_AddObjectRef(m, "Union", (PyObject *)&_PyUnion_Type) < 0) {
return -1;
}

if (PyModule_AddObjectRef(m, "NoDefault", (PyObject *)&_Py_NoDefaultStruct) < 0) {
return -1;
}
Expand Down
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy