Skip to content

Commit

Permalink
Add rebind
Browse files Browse the repository at this point in the history
  • Loading branch information
cdonovick committed Jul 15, 2019
1 parent 3bbb99e commit 1e7d6e6
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 22 deletions.
22 changes: 0 additions & 22 deletions hwtypes/adt.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,28 +76,6 @@ def value_dict(self):
d[k] = getattr(self, k)
return MappingProxyType(d)

@classmethod
def from_fields(cls,
class_name: str,
fields: tp.Mapping[str, type],
module: tp.Optional[str] = None,
qualname: tp.Optional[str] = None):
if cls.is_bound:
raise TypeError('Type must not be bound')

ns = {}

if module is None:
module = cls.__module__

if qualname is None:
qualname = class_name

ns['__module__'] = module
ns['__qualname__'] = qualname

return cls._from_fields(fields, class_name, (cls,), ns)

class Sum(metaclass=SumMeta):
def __init__(self, value):
if not isinstance(value, tuple(type(self).fields)):
Expand Down
47 changes: 47 additions & 0 deletions hwtypes/adt_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,16 @@ def unbound_t(cls) -> 'BoundMeta':
def __repr__(cls):
return f"{cls.__name__}"

def rebind(cls, A : type, B : type):
new_fields = []
for T in cls.fields:
if T == A:
new_fields.append(B)
elif isinstance(T, BoundMeta):
new_fields.append(T.rebind(A, B))
else:
new_fields.append(T)
return cls.unbound_t[new_fields]

class TupleMeta(BoundMeta):
def __getitem__(cls, idx):
Expand Down Expand Up @@ -314,6 +324,38 @@ def __repr__(cls):
def field_dict(cls):
return MappingProxyType(cls._field_table_)

def from_fields(cls,
class_name: str,
fields: tp.Mapping[str, type],
module: tp.Optional[str] = None,
qualname: tp.Optional[str] = None):
if cls.is_bound:
raise TypeError('Type must not be bound')

ns = {}

if module is None:
module = cls.__module__

if qualname is None:
qualname = class_name

ns['__module__'] = module
ns['__qualname__'] = qualname

return cls._from_fields(fields, class_name, (cls,), ns)


def rebind(cls, A : type, B : type):
new_fields = OrderedDict()
for field, T in cls.field_dict.items():
if T == A:
new_fields[field] = B
elif isinstance(T, BoundMeta):
new_fields[field] = T.rebind(A, B)
else:
new_fields[field] = T
return cls.unbound_t.from_fields(cls.__name__, new_fields, cls.__module__, cls.__qualname__)

class SumMeta(BoundMeta):
def _fields_cb(cls, idx):
Expand Down Expand Up @@ -392,3 +434,8 @@ def field_dict(cls):

def enumerate(cls):
yield from cls.fields

def rebind(cls, A : type, B : type):
# Enums aren't bound to types
# could potentialy rebind values but that seems annoying
return cls
37 changes: 37 additions & 0 deletions tests/test_adt.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ class En2(Enum):
d = 1


class En3(Enum):
e = 3
f = 4


class Pr(Product):
x = En1
y = En2
Expand Down Expand Up @@ -224,3 +229,35 @@ def test_unbound_t(t, base):
class sub_t(t): pass
with pytest.raises(AttributeError):
sub_t.unbound_t

@pytest.mark.parametrize("T", [Tu, Su, Pr])
def test_rebind(T):
assert En1 in T.fields
assert En3 not in T.fields
T2 = T.rebind(En1, En3)
assert En1 not in T2.fields
assert En3 in T2.fields


class A: pass
class B: pass
class C: pass
class D: pass
class P1(Product):
A = A
B = B

S1 = Sum[C, P1]

class P2(Product):
S1 = S1
C = C

def test_rebind_recusrive():
P3 = P2.rebind(A, D)
assert P3.S1.field_dict['P1'].A == D
assert P3.S1.field_dict['P1'].B == B
assert C in P3.S1.fields
P4 = P3.rebind(C, D)
assert P4.C == D
assert D in P4.S1.fields

0 comments on commit 1e7d6e6

Please sign in to comment.