Passed
Push — master ( ce73ec...38d9d4 )
by Max
01:00
created

make_constructors()   A

Complexity

Conditions 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nop 1
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
"""Base class for Sum subclasses and Product.
2
3
Blocks access to many built-in methods.
4
"""
5
6
import inspect
7
import typing
8
import weakref
9
10
from . import _annotations
11
12
_T = typing.TypeVar("_T")
13
14
15
def _should_include(name, static):
16
    if name in SHADOWED_ATTRIBUTES and static is None:
17
        return False
18
    if isinstance(static, SumMember):
19
        return False
20
    return True
21
22
23
class ADTConstructor:
24
    """Base class for ADT Constructor classes."""
25
26
    __slots__ = ()
27
28
    def __dir__(self):
29
        return [
30
            attribute
31
            for attribute in super().__dir__()
32
            if _should_include(attribute, inspect.getattr_static(self, attribute))
33
        ]
34
35
    __eq__ = object.__eq__
36
    __ne__ = object.__ne__
37
    __hash__ = object.__hash__
38
39
40
SHADOWED_ATTRIBUTES = {
41
    "__add__",
42
    "__contains__",
43
    "__getitem__",
44
    "__iter__",
45
    "__len__",
46
    "__mul__",
47
    "__rmul__",
48
    "count",
49
    "index",
50
    "__lt__",
51
    "__le__",
52
    "__gt__",
53
    "__ge__",
54
}
55
56
57
for _attribute in SHADOWED_ATTRIBUTES:
58
    setattr(ADTConstructor, _attribute, None)
59
60
61
class SumMember:
62
    """Accessor for Sum subclass constructor, only accessible from the base."""
63
    def __init__(self, subcls: type):
64
        self.subcls = subcls
65
66
    def __get__(self, obj, cls):
67
        if cls is ADT_BASES[self.subcls] and obj is None:
68
            return self.subcls
69
        raise AttributeError("Can only access adt members through base class.")
70
71
72
ADT_BASES: typing.MutableMapping[type, type] = weakref.WeakKeyDictionary()
73
74
75
def make_constructor(_cls, name: str, args: typing.Tuple, subclass_order):
76
    """Create a subclass of _cls with a constructor generated from args."""
77
    length = len(args)
78
79
    # pylint: disable=missing-docstring
80
    class Constructor(_cls, ADTConstructor, tuple):  # type: ignore
81
        __doc__ = f"""Auto-generated subclass {name} of ADT {_cls.__qualname__}.
82
83
        Takes {length} argument{'' if length == 1 else 's'}.
84
        """
85
86
        __slots__ = ()
87
88
        def __new__(cls, *args):
89
            if len(args) != length:
90
                raise ValueError
91
            return super().__new__(cls, args)
92
93
    ADT_BASES[Constructor] = _cls
94
95
    Constructor.__name__ = name
96
    Constructor.__qualname__ = "{qualname}.{name}".format(
97
        qualname=_cls.__qualname__, name=name
98
    )
99
100
    setattr(_cls, name, SumMember(Constructor))
101
    subclass_order.append(Constructor)
102
103
    annotations = {f"_{index}": arg for (index, arg) in enumerate(args)}
104
    parameters = [
105
        inspect.Parameter(name, inspect.Parameter.POSITIONAL_ONLY, annotation=arg)
106
        for (name, arg) in annotations.items()
107
    ]
108
    annotations["return"] = _cls.__qualname__
109
110
    Constructor.__new__.__signature__ = inspect.Signature(  # type: ignore
111
        [inspect.Parameter("cls", inspect.Parameter.POSITIONAL_ONLY)] + parameters,
112
        return_annotation=_cls.__qualname__,
113
    )
114
    Constructor.__new__.__annotations__ = annotations
115
116
117
def make_constructors(cls):
118
    subclass_order: typing.List[typing.Type[_T]] = []
119
120
    for name, args in _annotations._sum_args_from_annotations(cls).items():
121
        make_constructor(cls, name, args, subclass_order)
122
123
    return tuple(subclass_order)
124
125
126
__all__ = ["ADTConstructor", "make_constructor"]
127