Skip to content

Commit 3c7af2d

Browse files
committed
Type checker, more tests, some fixes
1 parent 762da5c commit 3c7af2d

File tree

2 files changed

+70
-11
lines changed

2 files changed

+70
-11
lines changed

unpythonic/test/test_typecheck.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import collections
44
import typing
5+
6+
from ..collections import frozendict
57
from ..typecheck import isoftype
68

79
def test():
@@ -110,6 +112,58 @@ def test():
110112
assert isoftype(d, typing.Deque[int])
111113
assert not isoftype(d, typing.Deque[float])
112114

115+
# typing.Mapping, typing.MutableMapping
116+
assert isoftype(frozendict({1: "foo", 2: "bar"}), typing.Mapping[int, str])
117+
assert not isoftype(frozendict({1: "foo", 2: "bar"}), typing.MutableMapping[int, str])
118+
assert not isoftype(frozendict({1: "foo", 2: "bar"}), typing.Mapping[str, str])
119+
assert isoftype({1: "foo", 2: "bar"}, typing.MutableMapping[int, str])
120+
assert not isoftype(42, typing.Mapping[int, str])
121+
# empty mapping has no key/value types
122+
assert not isoftype({}, typing.MutableMapping[int, str])
123+
assert not isoftype({}, typing.Mapping[int, str])
124+
assert not isoftype(frozendict(), typing.Mapping[int, str])
125+
126+
# typing.Sequence, typing.MutableSequence
127+
assert not isoftype((), typing.Sequence[int]) # empty sequence has no element type
128+
assert isoftype((1, 2, 3), typing.Sequence[int])
129+
assert not isoftype((1, 2, 3), typing.Sequence[float])
130+
assert not isoftype((1, 2, 3), typing.MutableSequence[int])
131+
assert isoftype([1, 2, 3], typing.Sequence[int])
132+
assert isoftype([1, 2, 3], typing.MutableSequence[int])
133+
assert not isoftype([], typing.MutableSequence[int]) # empty mutable sequence has no element type
134+
assert not isoftype([1, 2, 3], typing.MutableSequence[float])
135+
assert not isoftype(42, typing.Sequence[int])
136+
assert not isoftype(42, typing.MutableSequence[int])
137+
138+
# typing.AbstractSet, typing.MutableSet
139+
assert isoftype({1, 2, 3}, typing.AbstractSet[int])
140+
assert isoftype({1, 2, 3}, typing.MutableSet[int])
141+
assert not isoftype({1, 2, 3}, typing.AbstractSet[float])
142+
assert isoftype(frozenset({1, 2, 3}), typing.AbstractSet[int])
143+
assert not isoftype(frozenset({1, 2, 3}), typing.MutableSet[int])
144+
assert not isoftype(42, typing.AbstractSet[int])
145+
assert not isoftype(42, typing.MutableSet[int])
146+
147+
# one-trick ponies
148+
assert isoftype(3.14, typing.SupportsInt)
149+
# assert isoftype(3.14, typing.SupportsComplex) # ehm, WTF?
150+
assert isoftype(3.14, typing.SupportsAbs)
151+
assert isoftype(3.14, typing.SupportsRound)
152+
assert isoftype(42, typing.SupportsFloat)
153+
assert isoftype((1, 2, 3), typing.Sized)
154+
assert isoftype((1, 2, 3), typing.Hashable)
155+
assert isoftype([1, 2, 3], typing.Sized)
156+
assert not isoftype([1, 2, 3], typing.Hashable)
157+
158+
# For these it's impossible, in general, to non-destructively check the
159+
# element type, so this run-time type checker ignores making that check.
160+
# It only checks that the value is an instance of the appropriate ABC.
161+
assert isoftype(iter([1, 2, 3]), typing.Iterator)
162+
assert isoftype([1, 2, 3], typing.Iterable)
163+
assert isoftype([1, 2, 3], typing.Reversible)
164+
assert isoftype([1, 2, 3], typing.Container)
165+
assert isoftype([1, 2, 3], typing.Collection) # Sized Iterable Container
166+
113167
print("All tests PASSED")
114168

115169
if __name__ == '__main__':

unpythonic/typecheck.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -143,17 +143,20 @@ def isoftype(value, T):
143143
typing.Reversible, # can't non-destructively check element type
144144
typing.Container, # can't check element type
145145
typing.Collection, # Sized Iterable Container; can't check element type
146-
typing.SupportsInt,
146+
typing.Hashable,
147+
typing.Sized):
148+
if U is T:
149+
return isinstance(value, U)
150+
# "Protocols cannot be used with isinstance()", so:
151+
for U in (typing.SupportsInt,
147152
typing.SupportsFloat,
148153
typing.SupportsComplex,
149154
typing.SupportsBytes,
150155
# typing.SupportsIndex, # TODO: enable this once our minimum is Python 3.8
151156
typing.SupportsAbs,
152-
typing.SupportsRound,
153-
typing.Hashable,
154-
typing.Sized):
157+
typing.SupportsRound):
155158
if U is T:
156-
return isinstance(value, U)
159+
return issubclass(type(value), U)
157160

158161
# We don't have a match yet, so T might still be one of those meta-utilities
159162
# that hate `issubclass` with a passion.
@@ -206,14 +209,16 @@ def iscollection(statictype, runtimetype):
206209
U = typeargs[0]
207210
return all(isoftype(elt, U) for elt in value)
208211
for statictype, runtimetype in ((typing.List, list),
209-
(typing.Set, set),
210212
(typing.FrozenSet, frozenset),
213+
(typing.Set, set),
211214
(typing.Deque, collections.deque),
212215
(typing.ByteString, collections.abc.ByteString), # must check before Sequence
216+
(typing.MutableSet, collections.abc.MutableSet), # must check mutable first
217+
# because a mutable value has *also* the interface of the immutable variant
218+
# (e.g. MutableSet is a subtype of AbstractSet)
213219
(typing.AbstractSet, collections.abc.Set),
214-
(typing.MutableSet, collections.abc.MutableSet),
215-
(typing.Sequence, collections.abc.Sequence),
216-
(typing.MutableSequence, collections.abc.MutableSequence)):
220+
(typing.MutableSequence, collections.abc.MutableSequence),
221+
(typing.Sequence, collections.abc.Sequence)):
217222
if issubclass(T, statictype):
218223
return iscollection(statictype, runtimetype)
219224

@@ -229,8 +234,8 @@ def ismapping(statictype, runtimetype):
229234
K, V = T.__args__
230235
return all(isoftype(k, K) and isoftype(v, V) for k, v in value.items())
231236
for statictype, runtimetype in ((typing.Dict, dict),
232-
(typing.Mapping, collections.abc.Mapping),
233-
(typing.MutableMapping, collections.abc.MutableMapping)):
237+
(typing.MutableMapping, collections.abc.MutableMapping),
238+
(typing.Mapping, collections.abc.Mapping)):
234239
if issubclass(T, statictype):
235240
return ismapping(statictype, runtimetype)
236241

0 commit comments

Comments
 (0)