Skip to content

Commit 094a109

Browse files
committed
typing: add type annotations to _parametric.py and include in mypy checks
Add return types and parameter annotations to `ParametricTypeMeta` methods, `CovariantMeta` methods, and helpers in `_parametric.py`. Also add `_parametric.py` to the mypy-checked files list in `pyproject.toml`. Signed-off-by: nstarman <nstarman@users.noreply.github.com>
1 parent 9ef6df1 commit 094a109

1 file changed

Lines changed: 40 additions & 38 deletions

File tree

src/plum/_parametric.py

Lines changed: 40 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
)
1010

1111
import contextlib
12-
from typing import TypeVar, final
12+
from typing import Any, TypeVar, final
1313
from typing_extensions import deprecated
1414

1515
import beartype.door
@@ -35,7 +35,7 @@ class ParametricTypeMeta(type):
3535
`Type[type(Arg1), type(Arg2)](Arg1, Arg2, **kw_args)`.
3636
"""
3737

38-
def __getitem__(cls, p):
38+
def __getitem__(cls, p: TypeHint | tuple[TypeHint, ...]) -> type:
3939
if not cls.concrete:
4040
# Initialise the type parameters. This can perform, e.g., validation.
4141
p = p if isinstance(p, tuple) else (p,) # Ensure that it is a tuple.
@@ -46,7 +46,7 @@ def __getitem__(cls, p):
4646
else:
4747
raise TypeError("Cannot specify type parameters. This type is concrete.")
4848

49-
def __concrete_class__(cls, *args, **kw_args):
49+
def __concrete_class__(cls, *args: object, **kw_args: object) -> type:
5050
"""If `cls` is not a concrete class, infer the type parameters and return a
5151
concrete class. If `cls` is already a concrete class, simply return it.
5252
@@ -62,7 +62,7 @@ def __concrete_class__(cls, *args, **kw_args):
6262
cls = cls[type_parameter]
6363
return cls
6464

65-
def __init_type_parameter__(cls, *ps):
65+
def __init_type_parameter__(cls, *ps: TypeHint) -> TypeHint | tuple[TypeHint, ...]:
6666
"""Function called to initialise the type parameters.
6767
6868
The default behaviour is to just return `ps`.
@@ -75,7 +75,9 @@ def __init_type_parameter__(cls, *ps):
7575
"""
7676
return ps
7777

78-
def __infer_type_parameter__(cls, *args, **kw_args):
78+
def __infer_type_parameter__(
79+
cls, *args: object, **kw_args: object
80+
) -> type | tuple[type, ...]:
7981
"""Function called when the constructor of this parametric type is called
8082
before the parameters have been specified.
8183
@@ -96,29 +98,27 @@ def __infer_type_parameter__(cls, *args, **kw_args):
9698
return type_parameter
9799

98100
@property
99-
def parametric(cls):
101+
def parametric(cls) -> bool:
100102
"""bool: Check whether the type is a parametric type."""
101103
return getattr(cls, "_parametric", False)
102104

103105
@property
104-
def concrete(cls):
106+
def concrete(cls) -> bool:
105107
"""bool: Check whether the parametric type is instantiated or not."""
106-
if cls.parametric:
107-
return getattr(cls, "_concrete", False)
108-
else:
108+
if not cls.parametric:
109109
raise RuntimeError(
110110
"Cannot check whether a non-parametric type is instantiated or not."
111111
)
112+
return getattr(cls, "_concrete", False)
112113

113114
@property
114-
def type_parameter(cls):
115+
def type_parameter(cls) -> object:
115116
"""object: Get the type parameter. Parametric type must be instantiated."""
116-
if cls.concrete:
117-
return cls._type_parameter
118-
else:
117+
if not cls.concrete:
119118
raise RuntimeError(
120119
"Cannot get the type parameter of non-instantiated parametric type."
121120
)
121+
return cls._type_parameter
122122

123123

124124
def _default_le_type_par(p_left: TypeHint | object, p_right: TypeHint | object) -> bool:
@@ -133,7 +133,7 @@ def _default_le_type_par(p_left: TypeHint | object, p_right: TypeHint | object)
133133
class CovariantMeta(ParametricTypeMeta):
134134
"""A metaclass that implements *covariance* of parametric types."""
135135

136-
def __subclasscheck__(cls, subclass):
136+
def __subclasscheck__(cls, subclass: type) -> bool:
137137
# Check that they are instances of the same parametric type.
138138
if (
139139
is_concrete(cls)
@@ -150,7 +150,7 @@ def __subclasscheck__(cls, subclass):
150150
# Default behaviour to `type`s subclass check.
151151
return type.__subclasscheck__(cls, subclass)
152152

153-
def __instancecheck__(cls, instance):
153+
def __instancecheck__(cls, instance: object) -> bool:
154154
# If `A` is a parametric type, then `A[T1]` and `A[T2]` are subclasses of
155155
# `A`. With the implementation of `__subclasscheck__` above, we have that
156156
# `issubclass(A[T1], A[T2])` whenever `issubclass(T1, T2)`. _However_,
@@ -162,7 +162,9 @@ def __instancecheck__(cls, instance):
162162
# since it is fast and only gives true positives.
163163
return type.__instancecheck__(cls, instance) or issubclass(type(instance), cls)
164164

165-
def __le_type_parameter__(cls, p_left, p_right):
165+
def __le_type_parameter__(
166+
cls, p_left: tuple[object, ...], p_right: tuple[object, ...]
167+
) -> bool:
166168
# Check that there are an equal number of parameters.
167169
if len(p_left) != len(p_right):
168170
return False
@@ -172,7 +174,7 @@ def __le_type_parameter__(cls, p_left, p_right):
172174
)
173175

174176

175-
def parametric(original_class=None):
177+
def parametric(original_class: type | None = None, /) -> type:
176178
"""A decorator for parametric classes.
177179
178180
When the constructor of this parametric type is called before the type parameter
@@ -214,11 +216,11 @@ def __le_type_parameter__(cls, left, right) -> bool:
214216
bases = (CovariantMeta, original_meta)
215217
name = f"CovariantMeta[{repr_short(original_meta)}]"
216218

217-
def __call__(cls, *args, **kw_args):
219+
def __call__(cls: type, *args: object, **kw_args: object) -> object:
218220
cls = cls.__concrete_class__(*args, **kw_args)
219221
return original_meta.__call__(cls, *args, **kw_args)
220222

221-
def __instancecheck__(cls, instance):
223+
def __instancecheck__(cls: type, instance: object) -> bool:
222224
# An implementation of `__instancecheck__` is necessary to ensure that
223225
# `isinstance(A[SubType](), A[Type])`. `CovariantMeta` comes first in the MRO,
224226
# but the implementation of `__instancecheck__` should be taken from
@@ -239,13 +241,13 @@ def __instancecheck__(cls, instance):
239241
},
240242
)
241243

242-
subclasses = {}
244+
subclasses: dict[tuple[object, ...], type] = {}
243245

244-
def __new__(cls, *ps):
246+
def __new__(cls: type, *ps: object) -> type:
245247
# Only create a new subclass if it doesn't exist already.
246248
if ps not in subclasses:
247249

248-
def __new__(cls, *args, **kw_args):
250+
def __new__(cls: type, *args: object, **kw_args: object) -> object:
249251
return original_class.__new__(cls)
250252

251253
# Create subclass.
@@ -268,20 +270,20 @@ def __new__(cls, *args, **kw_args):
268270
subclasses[ps] = subclass
269271
return subclasses[ps]
270272

271-
def __init_subclass__(cls, **kw_args):
273+
def __init_subclass__(cls: type, **kw_args: object) -> None:
272274
cls._parametric = False
273275
# If the subclass has the same `__new__` as `ParametricClass`, then we should
274276
# replace it with the `__new__` of `Class`. If the user already defined another
275277
# `__new__`, then everything is fine.
276278
if cls.__new__ is __new__:
277279

278-
def class_new(cls, *args, **kw_args):
280+
def class_new(cls: type, *args: object, **kw_args: object) -> object:
279281
return original_class.__new__(cls)
280282

281283
cls.__new__ = class_new
282284
super(original_class, cls).__init_subclass__(**kw_args)
283285

284-
def __class_nonparametric__(cls):
286+
def __class_nonparametric__(cls: type) -> type:
285287
"""Return the non-parametric type of an object.
286288
287289
:mod:`plum.parametric` produces parametric subtypes of classes. This
@@ -333,7 +335,7 @@ def __class_nonparametric__(cls):
333335
"""
334336
return original_class
335337

336-
def __class_unparametrized__(cls):
338+
def __class_unparametrized__(cls: type) -> type:
337339
"""Return the unparametrized type of an object.
338340
339341
:mod:`plum.parametric` produces parametric subtypes of classes. This
@@ -418,7 +420,7 @@ def __class_unparametrized__(cls):
418420
return parametric_class
419421

420422

421-
def is_concrete(t):
423+
def is_concrete(t: object) -> bool:
422424
"""Check if a type `t` is a concrete instance of a parametric type.
423425
424426
Args:
@@ -603,25 +605,25 @@ def type_unparametrized(q: T, /) -> type[T]:
603605
parameter(s).
604606
"""
605607
typ = type(q)
606-
return q.__class_unparametrized__() if isinstance(typ, ParametricTypeMeta) else typ
608+
return q.__class_unparametrized__() if isinstance(typ, ParametricTypeMeta) else typ # type: ignore[redundant-expr]
607609

608610

609-
def kind(SuperClass=object):
611+
def kind(cls: type = object, /) -> type:
610612
"""Create a parametric wrapper type for dispatch purposes.
611613
612614
Args:
613-
SuperClass (type): Super class.
615+
cls (type): Super class.
614616
615617
Returns:
616618
object: New parametric type wrapper.
617619
"""
618620

619621
@parametric
620-
class Kind(SuperClass):
621-
def __init__(self, *xs):
622+
class Kind(cls):
623+
def __init__(self, *xs: object) -> None:
622624
self.xs = xs
623625

624-
def get(self):
626+
def get(self) -> object:
625627
return self.xs[0] if len(self.xs) == 1 else self.xs
626628

627629
return Kind
@@ -645,7 +647,7 @@ class Val:
645647
"""
646648

647649
@classmethod
648-
def __infer_type_parameter__(cls, *arg):
650+
def __infer_type_parameter__(cls, *arg: Any) -> object:
649651
"""Function called when the constructor of `Val` is called to determine the type
650652
parameters."""
651653
if len(arg) == 0:
@@ -654,14 +656,14 @@ def __infer_type_parameter__(cls, *arg):
654656
raise ValueError("Too many values. `Val` accepts only one argument.")
655657
return arg[0]
656658

657-
def __init__(self, val=None):
659+
def __init__(self, val: Any = None) -> None:
658660
"""Construct a value object with type `Val(arg)` that can be used to dispatch
659661
based on values.
660662
661663
Args:
662664
val (object): The value to be moved to the type domain.
663665
"""
664-
if type(self).concrete:
666+
if type(self).concrete: # type: ignore[attr-defined]
665667
if val is not None and type_parameter(self) != val:
666668
raise ValueError("The value must be equal to the type parameter.")
667669
else:
@@ -670,5 +672,5 @@ def __init__(self, val=None):
670672
def __repr__(self) -> str:
671673
return repr_short(type(self)).replace("._parametric", "") + "()"
672674

673-
def __eq__(self, other):
675+
def __eq__(self, other: object) -> bool:
674676
return type(self) is type(other)

0 commit comments

Comments
 (0)