Skip to content
Navigation Menu
{{ message }}
forked from kennknowles/python-rightarrow
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathannotations.py
More file actions
318 lines (238 loc) · 10.9 KB
/
Copy pathannotations.py
File metadata and controls
318 lines (238 loc) · 10.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
import copy
from collections import namedtuple, defaultdict
from decorator import decorator
# Kinds
class Kind(object): pass
# The kind of types
class Type(Kind): pass
# The kind of record field names
class Label(Kind): pass
class FunctionKind(Kind):
def __init__(self, dom, rng):
self.dom = dom
self.rng = rng
## Types proper
class Type(object): pass
class NamedType(Type):
def __init__(self, name):
self.name = name
def substitute(self, substitution):
return self
def __str__(self):
return self.name
def __eq__(self, other):
return isinstance(other, NamedType) and other.name == self.name
def enforce(self, val):
# TODO: Is it worth the boilerplate to have IntType, etc, and all primitives inherit and override here?
# and yes, this does look a lot like how these types already work, but I want to leave the possibility
# of doing something better with blame... or something
if self.name in ['int', 'long', 'float', 'complex', 'str', 'unicode']:
if type(val).__name__ == self.name:
return val
else:
raise TypeError('Type check failed: %s does not have type %s' % (val, self))
else:
return False # TODO: when we actually have nominal (abstract) types, do some check here
# TODO: Make into a higher-kinded type? Maybe that's just a headache?
class List(Type):
def __init__(self, elem_ty):
self.elem_ty = elem_ty
def substitute(self, substitution):
return List(self.elem_ty.substitute(substitution))
def __str__(self):
return '[%s]' % self.elem_ty
def __eq__(self, other):
return isinstance(other, List) and other.elem_ty == self.elem_ty
def enforce(self, val):
if type(val) != list:
raise TypeError('Type check failed: %s is not a list %s' % (val, self))
else:
return [self.elem_ty.enforce(x) for x in val] # This could be slooooow
class Dict(Type):
def __init__(self, key_ty, value_ty):
self.key_ty = key_ty
self.value_ty = value_ty
def substitute(self, substitution):
return Dict(key_ty=self.key_ty.substitute(substitution),
value_ty=self.value_ty.substitute(substitution))
def __str__(self):
return '{%s:%s}' % (self.key_ty, self.value_ty)
def __eq__(self, other):
return isinstance(other, Dict) and other.key_ty == self.key_ty and other.value_ty == self.value_ty
def enforce(self, val):
if type(val) != dict:
raise TypeError('Type check failed: %s is not a list %s' % (val, self))
else:
return dict([(self.key_ty.enforce(key), self.value_ty.enforce(value)) for key, value in val.items()])
class Variable(Type):
def __init__(self, name):
self.name = name
def substitute(self, substitution):
if self.name in substitution:
return substitution[self.name]
else:
return self
def __str__(self):
return '?%s' % self.name
def __eq__(self, other):
return isinstance(other, Variable) and other.name == self.name
def enforce(self, val):
raise BoundVariableException(BoundVariable(self.name, val))
class BoundVariable(Type):
def __init__(self, name, val):
self.name = name
self.val = val
def substitute(self, substitution):
return self
def __str__(self):
return "%s:%s" % (self.name, type(self.val))
def __eq__(self, other):
if isinstance(other, Variable):
return self.name == other.name
elif isinstance(other, BoundVariable):
return self.name == other.name
return False
def enforce(self, val):
if type(self.val) == type(val):
return val
else:
raise TypeError('Type check failed: %s is not of type %s' % (val, type(self.val)))
class BoundVariableException(BaseException):
def __init__(self, b):
self.bvar = b
class Function(Type):
def __init__(self, arg_types, return_type, vararg_type=None, kwonly_arg_types=None, kwarg_type=None):
self.arg_types = arg_types
self.return_type = return_type
self.vararg_type = vararg_type
self.kwarg_type = kwarg_type
self.kwonly_arg_types = kwonly_arg_types
def substitute(self, substitution):
return Function(arg_types = [ty.substitute(substitution) for ty in self.arg_types],
return_type = self.return_type.substitute(substitution),
vararg_type = None if self.vararg_type is None else self.vararg_type.substitute(substitution),
kwonly_arg_types = None if self.kwonly_arg_types is None else [ty.substitute(substitution) for ty in self.kwonly_arg_types],
kwarg_type = None if self.kwarg_type is None else self.kwarg_type.substitute(substitution))
def enforce(self, f):
def attempt_enforce(arg_types, args, vararg_type, varargs, kwarg_type, kwargs):
if len(args) < len(arg_types):
raise TypeError('Not enough arguments (%s, needed at least %s) to %s of type %s; only received %s' % (len(args), len(arg_types), f, self, args))
else:
wrapped_args = [ty.enforce(arg) for ty, arg in zip(arg_types, args)]
if len(varargs) == 0:
wrapped_varargs = list(varargs)
elif vararg_type == None:
raise TypeError('Function %s of type %s was passed varargs %s' % (f, self, varargs))
else:
wrapped_varargs = vararg_type,enforce(varargs)
if len(kwargs) == 0:
wrapped_kwargs = kwargs
elif kwarg_type == None:
raise TypeError('Function %s of type %s was passed kwargs %s' % (f, self, kwargs))
else:
wrapped_kwargs = kwarg_type.enforce(kwargs)
return wrapped_args, wrapped_varargs, wrapped_kwargs
def wrap_with_checks(f, *all_args, **kwargs):
args = all_args[:len(self.arg_types)]
varargs = all_args[len(self.arg_types):]
# TODO: Get named args right.
# Lining up the types and the args is probably the hardest part here.
# The decorator library has done similar
arg_types = self.arg_types
vararg_type = self.vararg_type
kwarg_type = self.kwarg_type
return_type = self.return_type
while True:
try:
wrapped_args, wrapped_varargs, wrapped_kwargs \
= attempt_enforce(arg_types, args, vararg_type, varargs, kwarg_type, kwargs)
break
except BoundVariableException as be:
b = be.bvar
arg_types = [ty.substitute({b.name: b}) for ty in arg_types]
return_type = return_type.substitute({b.name : b})
if vararg_type != None:
vararg_type.substitute({b.name : b})
if kwarg_type != None:
kwarg_type.substitute({b.name : b})
return return_type.enforce(f(*(wrapped_args + wrapped_varargs), **wrapped_kwargs))
return decorator(wrap_with_checks)(f)
def __str__(self):
comma_separated_bits = [unicode(v) for v in self.arg_types]
if self.vararg_type:
comma_separated_bits.append('*%s' % self.vararg_type)
if self.kwonly_arg_types:
comma_separated_bits += ['%s=%s' % (kwarg, ty) for (kwarg, ty) in self.kwonly_arg_types.items() ]
if self.kwarg_type:
comma_separated_bits.append('**%s' % self.kwarg_type)
if len(comma_separated_bits) == 1:
argument_list = comma_separated_bits[0]
else:
argument_list = '(%s)' % ','.join(comma_separated_bits)
return '%s -> %s' % (argument_list, self.return_type)
def __eq__(self, other):
return isinstance(other, Function) and other.vararg_type == self.vararg_type and other.kwarg_type == self.kwarg_type \
and other.arg_types == self.arg_types and other.kwonly_arg_types == self.kwonly_arg_types
class Application(Type):
def __init__(self, fn, args):
self.fn = fn
self.args = args
def substitute(self, substitution):
return Application(self.fn.substitute(substitution), [ty.substitute(substitution) for ty in self.args])
class Union(Type):
def __init__(self, types):
self.types = types
def __str__(self):
return '|'.join([str(ty) for ty in self.types])
def __eq__(self, other):
return isinstance(other, Union) and self.types == other.types
def enforce(self, val):
for ty in self.types:
try:
return ty.enforce(val)
except TypeError:
continue
raise TypeError('Type check failed: %s does not have type %s' % (val, self))
class Object(Type):
def __init__(self, self_ty_name, **field_tys):
self.self_ty_name = self_ty_name
self.field_tys = field_tys
def __str__(self):
return 'object(%s)' % ','.join([self.self_ty_name] + ['%s:%s' % (name, ty) for name, ty in self.field_tys.items()])
def __eq__(self, other):
return isinstance(other, Object) and self.self_ty_name == other.self_ty_name and self.field_tys == other.field_tys
def enforce(self, val):
# TODO: bind the self type
# We must have a copy with the same class, or it will break code relying on isinstance. Whether this is a "problem"
# is debatable, but given the usual encoding of coproducts as distinct subclasses, we'd better respect it.
newval = copy.copy(val)
# Technically lets other properties slip in, but due to every object having a bunch of __foo__ props that can wait
for field, ty in self.field_tys.items():
setattr(newval, field, ty.enforce(getattr(val, field)))
return newval
class Any(Type):
def __str__(self):
return '??'
def __eq__(self, other):
return isinstance(other, Any)
def substitute(self, substitution):
return self
def enforce(self, val):
return val # In Findler-Wadler this is wrapped with ?? -> ?? but I'm not sure that works for Python
# Fresh variable supply
used_vars = defaultdict(lambda: 0)
def fresh(prefix=None):
global used_vars
prefix = prefix or 'X'
used_vars[prefix] = used_vars[prefix] + 1
return Variable(prefix + str(used_vars[prefix]))
# TODO: Give these names a definition that they can be unfolded to.
bool_t = NamedType('bool')
int_t = NamedType('int')
long_t = NamedType('long')
float_t = NamedType('float')
complex_t = NamedType('complex')
str_t = NamedType('str')
unicode_t = NamedType('unicode')
numeric_t = Union([int_t, long_t, complex_t, float_t])
any_t = Any()
You can’t perform that action at this time.
