"""Internal implementation of the Sum base class."""
from __future__ import annotations
import typing
from .. import _cant_modify
from . import constructor
from . import ordering
from . import prewritten_methods
from . import product_type
_T = typing.TypeVar("_T")
def _conditional_call(call: bool, func: typing.Callable, *args: typing.Any) -> None:
if call:
func(*args)
def _set_new_functions(cls: type, *functions: typing.Callable) -> typing.Optional[str]:
"""Attempt to set the attributes corresponding to the functions on cls.
If any attributes are already defined, fail *before* setting any, and
return the already-defined name.
"""
cant_set = product_type.cant_set_new_functions(cls, *functions)
if cant_set:
return cant_set
for function in functions:
setattr(
cls,
product_type.name_(cls, typing.cast(product_type.MethodLike, function)),
function,
)
return None
def _sum_new(_cls: typing.Type[_T], subclasses: typing.FrozenSet[type]) -> None:
def base(cls: typing.Type[_T], args: tuple) -> _T:
# By the way, this is for https://github.com/python/mypy/issues/7580
# When that's fixed, this can be made a one-liner again.
superclass = super(_cls, cls)
return superclass.__new__(cls, args) # type: ignore
new = vars(_cls).get("__new__", staticmethod(base))
def __new__(cls: typing.Type[_T], args: tuple) -> _T:
if cls not in subclasses:
raise TypeError
return new.__get__(None, cls)(cls, args)
_cls.__new__ = staticmethod(__new__) # type: ignore
[docs]class Sum(constructor.SumBase):
"""Base class of classes with disjoint constructors.
Examines PEP 526 __annotations__ to determine subclasses.
If repr is true, a __repr__() method is added to the class.
If order is true, rich comparison dunder methods are added.
The Sum class examines the class to find Ctor annotations.
A Ctor annotation is the adt.Ctor class itself, or the result of indexing
the class, either with a single type hint, or a tuple of type hints.
All other annotations are ignored.
The subclass is not subclassable, but has subclasses at each of the
names that had Ctor annotations. Each subclass takes a fixed number of
arguments, corresponding to the type hints given to its annotation, if any.
"""
__slots__ = ()
def __new__(cls, /, *args: typing.Any, **kwargs: typing.Any) -> Sum: # noqa: E225
if not issubclass(cls, constructor.ADTConstructor):
raise TypeError
return super().__new__(cls, *args, **kwargs)
# Both of these are for consistency with modules defined in the stdlib.
# BOOM!
def __init_subclass__(
cls: type,
*,
repr: bool = True, # pylint: disable=redefined-builtin
eq: bool = True, # pylint: disable=invalid-name
order: bool = False,
**kwargs: typing.Any,
) -> None:
super().__init_subclass__(**kwargs) # type: ignore
if issubclass(cls, constructor.ADTConstructor):
return
ordering.ordering_options_are_valid(eq=eq, order=order)
prewritten_methods.SUBCLASS_ORDER[cls] = constructor.make_constructors(cls)
source = prewritten_methods.PrewrittenSumMethods
cls.__init_subclass__ = source.__init_subclass__ # type: ignore
_sum_new(cls, frozenset(prewritten_methods.SUBCLASS_ORDER[cls]))
_conditional_call(repr, _set_new_functions, cls, source.__repr__)
equality_methods_were_set = eq and not _set_new_functions(
cls, source.__eq__, source.__ne__
)
if equality_methods_were_set:
cls.__hash__ = source.__hash__ # type: ignore
ordering.raise_for_collision(
(
order
and ordering.can_set_ordering(can_set=equality_methods_were_set)
and _set_new_functions(
cls, source.__lt__, source.__le__, source.__gt__, source.__ge__
)
),
cls.__name__,
)
def __bool__(self) -> bool:
return True
def __setattr__(self, name: str, value: typing.Any) -> None:
_cant_modify.guard(self, name)
super().__setattr__(name, value)
def __delattr__(self, name: str) -> None:
_cant_modify.guard(self, name)
super().__delattr__(name)