Skip to content

Commit 37ef82e

Browse files
committed
feat: make Builders, Patterns and various Annotation classes hashable
1 parent 8961fbd commit 37ef82e

File tree

8 files changed

+478
-43
lines changed

8 files changed

+478
-43
lines changed

koerce/annots.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
_any,
2020
pattern,
2121
)
22-
from .utils import get_type_hints, get_type_origin
22+
from .utils import PseudoHashable, get_type_hints, get_type_origin
2323

2424
EMPTY = inspect.Parameter.empty
2525
_ensure_pattern = pattern
@@ -38,6 +38,9 @@ def __init__(self, pattern: Any = _any, default: Any = EMPTY):
3838
def __repr__(self):
3939
return f"<{self.__class__.__name__} pattern={self.pattern!r} default={self.default_!r}>"
4040

41+
def __hash__(self) -> int:
42+
return hash((self.__class__, self.pattern, PseudoHashable(self.default_)))
43+
4144
def __eq__(self, other: Any) -> bool:
4245
if not isinstance(other, Attribute):
4346
return NotImplemented
@@ -124,6 +127,17 @@ def __eq__(self, other: Any) -> bool:
124127
and self.typehint == right.typehint
125128
)
126129

130+
def __hash__(self) -> int:
131+
return hash(
132+
(
133+
self.__class__,
134+
self.kind,
135+
self.pattern,
136+
PseudoHashable(self.default_),
137+
self.typehint,
138+
)
139+
)
140+
127141

128142
@cython.final
129143
@cython.cclass
@@ -266,6 +280,16 @@ def __eq__(self, other: Any) -> bool:
266280
and self.return_typehint == right.return_typehint
267281
)
268282

283+
def __hash__(self) -> int:
284+
return hash(
285+
(
286+
self.__class__,
287+
PseudoHashable(self.parameters),
288+
self.return_pattern,
289+
self.return_typehint,
290+
)
291+
)
292+
269293
def __call__(self, /, *args, **kwargs):
270294
return self.bind(args, kwargs)
271295

koerce/builders.py

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
import functools
55
import inspect
66
import operator
7-
from typing import Any
7+
from typing import Any, Optional
88

99
import cython
1010

11+
from .utils import PseudoHashable
12+
1113
Context = dict[str, Any]
1214

1315

@@ -64,9 +66,6 @@ def __getitem__(self, name):
6466
def __call__(self, *args, **kwargs):
6567
return Deferred(Call(self, *args, **kwargs))
6668

67-
# def __contains__(self, item):
68-
# return Deferred(Binop(operator.contains, self, item))
69-
7069
def __invert__(self) -> Deferred:
7170
return Deferred(Unop(operator.invert, self))
7271

@@ -187,6 +186,12 @@ def build(self, ctx: Context): ...
187186
def __eq__(self, other: Any) -> bool:
188187
return type(self) is type(other) and self.equals(other)
189188

189+
def __hash__(self):
190+
return self._hash()
191+
192+
def __repr__(self):
193+
raise NotImplementedError(f"{self.__class__.__name__} is not reprable")
194+
190195

191196
def _deferred_repr(obj):
192197
try:
@@ -223,6 +228,9 @@ def __init__(self, func: Any):
223228
def __repr__(self):
224229
return _deferred_repr(self.func)
225230

231+
def _hash(self):
232+
return hash((self.__class__, self.func))
233+
226234
def equals(self, other: Func) -> bool:
227235
return self.func == other.func
228236

@@ -247,12 +255,17 @@ class Just(Builder):
247255
def __init__(self, value: Any):
248256
if isinstance(value, Just):
249257
self.value = cython.cast(Just, value).value
258+
elif isinstance(value, (Builder, Deferred)):
259+
raise TypeError(f"`{value}` cannot be used as a Just value")
250260
else:
251261
self.value = value
252262

253263
def __repr__(self):
254264
return _deferred_repr(self.value)
255265

266+
def _hash(self):
267+
return hash((self.__class__, PseudoHashable(self.value)))
268+
256269
def equals(self, other: Just) -> bool:
257270
return self.value == other.value
258271

@@ -279,6 +292,9 @@ def __init__(self, name: str):
279292
def __repr__(self):
280293
return f"${self.name}"
281294

295+
def _hash(self):
296+
return hash((self.__class__, self.name))
297+
282298
def equals(self, other: Var) -> bool:
283299
return self.name == other.name
284300

@@ -332,6 +348,9 @@ def __init__(self, func):
332348
def __repr__(self):
333349
return f"{self.func!r}()"
334350

351+
def _hash(self):
352+
return hash((self.__class__, self.func))
353+
335354
def equals(self, other: Call0) -> bool:
336355
return self.func == other.func
337356

@@ -364,6 +383,9 @@ def __init__(self, func, arg):
364383
def __repr__(self):
365384
return f"{self.func!r}({self.arg!r})"
366385

386+
def _hash(self):
387+
return hash((self.__class__, self.func, self.arg))
388+
367389
def equals(self, other: Call1) -> bool:
368390
return self.func == other.func and self.arg == other.arg
369391

@@ -401,6 +423,9 @@ def __init__(self, func, arg1, arg2):
401423
def __repr__(self):
402424
return f"{self.func!r}({self.arg1!r}, {self.arg2!r})"
403425

426+
def _hash(self):
427+
return hash((self.__class__, self.func, self.arg1, self.arg2))
428+
404429
def equals(self, other: Call2) -> bool:
405430
return (
406431
self.func == other.func
@@ -447,6 +472,9 @@ def __init__(self, func, arg1, arg2, arg3):
447472
def __repr__(self):
448473
return f"{self.func!r}({self.arg1!r}, {self.arg2!r}, {self.arg3!r})"
449474

475+
def _hash(self):
476+
return hash((self.__class__, self.func, self.arg1, self.arg2, self.arg3))
477+
450478
def equals(self, other: Call3) -> bool:
451479
return (
452480
self.func == other.func
@@ -482,12 +510,12 @@ class CallN(Builder):
482510
"""
483511

484512
func: Builder
485-
args: list[Builder]
513+
args: tuple[Builder, ...]
486514
kwargs: dict[str, Builder]
487515

488516
def __init__(self, func, *args, **kwargs):
489517
self.func = builder(func)
490-
self.args = [builder(arg) for arg in args]
518+
self.args = tuple(builder(arg) for arg in args)
491519
self.kwargs = {k: builder(v) for k, v in kwargs.items()}
492520

493521
def __repr__(self):
@@ -502,6 +530,9 @@ def __repr__(self):
502530
else:
503531
return f"{self.func!r}()"
504532

533+
def _hash(self):
534+
return hash((self.__class__, self.func, self.args, PseudoHashable(self.kwargs)))
535+
505536
def equals(self, other: CallN) -> bool:
506537
return (
507538
self.func == other.func
@@ -573,6 +604,9 @@ def __repr__(self):
573604
symbol = _operator_symbols[self.op]
574605
return f"{symbol}{self.arg!r}"
575606

607+
def _hash(self):
608+
return hash((self.__class__, self.op, self.arg))
609+
576610
def equals(self, other: Unop) -> bool:
577611
return self.op == other.op and self.arg == other.arg
578612

@@ -610,6 +644,9 @@ def __repr__(self):
610644
symbol = _operator_symbols[self.op]
611645
return f"({self.arg1!r} {symbol} {self.arg2!r})"
612646

647+
def _hash(self):
648+
return hash((self.__class__, self.op, self.arg1, self.arg2))
649+
613650
def equals(self, other: Binop) -> bool:
614651
return (
615652
self.op == other.op and self.arg1 == other.arg1 and self.arg2 == other.arg2
@@ -645,6 +682,9 @@ def __init__(self, obj, key):
645682
def __repr__(self):
646683
return f"{self.obj!r}[{self.key!r}]"
647684

685+
def _hash(self):
686+
return hash((self.__class__, self.obj, self.key))
687+
648688
def equals(self, other: Item) -> bool:
649689
return self.obj == other.obj and self.key == other.key
650690

@@ -678,6 +718,9 @@ def __init__(self, obj: Any, attr: str):
678718
def __repr__(self):
679719
return f"{self.obj!r}.{self.attr}"
680720

721+
def _hash(self):
722+
return hash((self.__class__, self.obj, self.attr))
723+
681724
def equals(self, other: Attr) -> bool:
682725
return self.obj == other.obj and self.attr == other.attr
683726

@@ -699,11 +742,11 @@ class Seq(Builder):
699742
"""
700743

701744
type_: Any
702-
items: list[Builder]
745+
items: tuple[Builder, ...]
703746

704747
def __init__(self, items):
705748
self.type_ = type(items)
706-
self.items = [builder(item) for item in items]
749+
self.items = tuple(builder(item) for item in items)
707750

708751
def __repr__(self):
709752
elems = ", ".join(map(repr, self.items))
@@ -714,6 +757,9 @@ def __repr__(self):
714757
else:
715758
return f"{self.type_.__name__}({elems})"
716759

760+
def _hash(self):
761+
return hash((self.__class__, self.type_, self.items))
762+
717763
def equals(self, other: Seq) -> bool:
718764
return self.type_ == other.type_ and self.items == other.items
719765

@@ -751,6 +797,9 @@ def __repr__(self):
751797
else:
752798
return f"{self.type_.__name__}({{{items}}})"
753799

800+
def _hash(self):
801+
return hash((self.__class__, self.type_, PseudoHashable(self.items)))
802+
754803
def equals(self, other: Map) -> bool:
755804
return self.type_ == other.type_ and self.items == other.items
756805

0 commit comments

Comments
 (0)