Skip to content

Commit bef26a6

Browse files
committed
Ensure that TypeFunction instances are stateless.
Currently, if we serialize a stateless type-function we might still accidentally get two copies of it. But at least when we serialize it we won't accidentally serialize everything.
1 parent 2d91e70 commit bef26a6

File tree

2 files changed

+67
-48
lines changed

2 files changed

+67
-48
lines changed

typed_python/type_function.py

Lines changed: 53 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def isTypeFunctionType(type):
2929
If so, return a tuple of (func, args, kwargs). otherwise none
3030
"""
3131
if type in _type_to_typefunction:
32-
(func, (args, kwargs)) = _type_to_typefunction[type]
32+
(func, args, kwargs) = _type_to_typefunction[type]
3333
return (func, args, kwargs)
3434
return None
3535

@@ -42,23 +42,10 @@ def reconstructTypeFunctionType(typeFunction, args, kwargs):
4242
return typeFunction(*args, **dict(kwargs))
4343

4444

45-
def makeTypeFunction(f):
46-
"""Decorate 'f' to be a 'TypeFunction'.
45+
_typeFunctionMemo = {}
4746

48-
The resulting function is expected to take a set of hashable arguments and
49-
produce a type object. The function is memoized, so code in the
50-
decorated function is executed once only for each distinct set of
51-
arguments. The order of keyword arguments is not considered in the memo.
52-
The function should not have sideeffects.
53-
54-
TypeFunctions may call each other recursively and in self-referential
55-
cycles. If the function calls back into itself, a Forward will
56-
be returned instead of a concrete type, which lets you express recursive
57-
types in a natural way.
5847

59-
Don't stash the type you return, since the actual type returned by the
60-
function may not be the one you returned.
61-
"""
48+
def _buildTypeFunction(TypeFunction_, f, args, kwargs):
6249
def nameFor(args, kwargs):
6350
def toStr(x):
6451
if isinstance(x, type):
@@ -93,53 +80,71 @@ def mapArg(arg):
9380

9481
raise TypeError("Instance of type '%s' is not a valid argument to a type function" % type(arg))
9582

96-
_memoForKey = {}
83+
args = tuple(mapArg(a) for a in args)
84+
kwargs = tuple(sorted([(k, mapArg(v)) for k, v in kwargs.items()]))
9785

98-
def buildType(*args, **kwargs):
99-
args = tuple(mapArg(a) for a in args)
100-
kwargs = tuple(sorted([(k, mapArg(v)) for k, v in kwargs.items()]))
86+
key = (TypeFunction_, args, kwargs)
10187

102-
key = (args, kwargs)
88+
if key in _typeFunctionMemo:
89+
res = _typeFunctionMemo[key]
90+
if isinstance(res, Exception):
91+
raise res
10392

104-
if key in _memoForKey:
105-
res = _memoForKey[key]
93+
if getattr(res, '__typed_python_category__', None) != 'Forward':
94+
# only return fully resolved TypeFunction values without
95+
# locking.
96+
return res
97+
98+
with runtimeLock:
99+
if key in _typeFunctionMemo:
100+
res = _typeFunctionMemo[key]
106101
if isinstance(res, Exception):
107102
raise res
103+
return res
108104

109-
if getattr(res, '__typed_python_category__', None) != 'Forward':
110-
# only return fully resolved TypeFunction values without
111-
# locking.
112-
return res
105+
forward = Forward(nameFor(args, kwargs))
113106

114-
with runtimeLock:
115-
if key in _memoForKey:
116-
res = _memoForKey[key]
117-
if isinstance(res, Exception):
118-
raise res
119-
return res
107+
_typeFunctionMemo[key] = forward
108+
_type_to_typefunction[forward] = key
120109

121-
forward = Forward(nameFor(args, kwargs))
110+
try:
111+
resultType = f(*args, **dict(kwargs))
122112

123-
_memoForKey[key] = forward
124-
_type_to_typefunction[forward] = (TypeFunction_, key)
113+
forward.define(resultType)
125114

126-
try:
127-
resultType = f(*args, **dict(kwargs))
115+
_type_to_typefunction.pop(forward)
128116

129-
forward.define(resultType)
117+
if resultType not in _type_to_typefunction:
118+
_type_to_typefunction[resultType] = key
130119

131-
_type_to_typefunction.pop(forward)
120+
_typeFunctionMemo[key] = resultType
132121

133-
if resultType not in _type_to_typefunction:
134-
_type_to_typefunction[resultType] = (TypeFunction_, key)
122+
return resultType
123+
except Exception as e:
124+
_typeFunctionMemo[key] = e
125+
logging.exception("TypeFunction errored")
126+
raise
135127

136-
_memoForKey[key] = resultType
137128

138-
return resultType
139-
except Exception as e:
140-
_memoForKey[key] = e
141-
logging.exception("TypeFunction errored")
142-
raise
129+
def makeTypeFunction(f):
130+
"""Decorate 'f' to be a 'TypeFunction'.
131+
132+
The resulting function is expected to take a set of hashable arguments and
133+
produce a type object. The function is memoized, so code in the
134+
decorated function is executed once only for each distinct set of
135+
arguments. The order of keyword arguments is not considered in the memo.
136+
The function should not have sideeffects.
137+
138+
TypeFunctions may call each other recursively and in self-referential
139+
cycles. If the function calls back into itself, a Forward will
140+
be returned instead of a concrete type, which lets you express recursive
141+
types in a natural way.
142+
143+
Don't stash the type you return, since the actual type returned by the
144+
function may not be the one you returned.
145+
"""
146+
def buildType(*args, **kwargs):
147+
return _buildTypeFunction(TypeFunction_, f, args, kwargs)
143148

144149
class TypeFunction_(TypeFunction):
145150
__module__ = f.__module__

typed_python/type_function_test.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,3 +414,17 @@ def test_serialize_regular_class_output(self):
414414
C = RegularPythonClass(int)
415415

416416
assert b'methodName' not in SerializationContext().withoutCompression().serialize(C)
417+
418+
def test_anonymous_typefunction_is_stateless(self):
419+
@TypeFunction
420+
def F(T):
421+
class C:
422+
t = T
423+
424+
return C
425+
426+
bytes1 = SerializationContext().withoutCompression().serialize(F)
427+
F(int)
428+
bytes2 = SerializationContext().withoutCompression().serialize(F)
429+
430+
assert bytes1 == bytes2

0 commit comments

Comments
 (0)