Skip to content

Commit b5111f4

Browse files
committed
Ensure that regular classes get into the type-function memo.
There's a tricky bug where SerializationContext memoizes types based on their type identity. Its very important that any class that gets serialized that's produced by a TypeFunction gets serialized by noting the type function and its arguments rather than by serializing the class itself. Otherwise, depending on whether you have called the TypeFunction or deserialized an instance of it, the memo inside the serialization library can have different versions of the type, which leads to all kinds of chaos. This bug exposes another possible bug, where if a type function defines two types internally, only one of them will be registered by the type function memo. This is something we need to think about more deeply to really fix. For now the rules for type functions are: TypeFunction can either return a type that is primitive, a type that is a compound of primitives (e.g. OneOf, NamedTuple, Tuple, ListOf, etc), a type that is named by looking at __module__ and __name__ and finding that type in that location, or by returning a single class type, inheriting from object or Class respectively.
1 parent 13bd099 commit b5111f4

File tree

3 files changed

+33
-2
lines changed

3 files changed

+33
-2
lines changed

typed_python/SerializationContext.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -488,10 +488,19 @@ def representationFor(self, inst):
488488
return (threading.RLock, ())
489489

490490
if isinstance(inst, type):
491-
# only serialize Class and Alternative objects from type functions.
491+
# only serialize Class, Alternative, named tuple subclass, and actual python class
492+
# objects from type functions.
493+
492494
# otherwise, we'll end up changing how we serialize things like 'int',
493495
# if they ever make their way into a type function.
494-
if getattr(inst, '__typed_python_category__', None) in ('Class', 'Alternative'):
496+
497+
# note that we need to ensure that type functions don't make two classes and
498+
# return only one of them.
499+
if (
500+
getattr(inst, '__typed_python_category__', None) in ('Class', 'Alternative')
501+
or not issubclass(inst, Type)
502+
or (issubclass(inst, NamedTuple) and inst.__bases__[0] != NamedTuple)
503+
):
495504
funcArgsAndKwargs = isTypeFunctionType(inst)
496505

497506
if funcArgsAndKwargs is not None:

typed_python/type_function_test.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,18 @@ def f(self, x):
3838
return A
3939

4040

41+
@TypeFunction
42+
def RegularPythonClass(T):
43+
class RegularPythonClass:
44+
def __init__(self):
45+
self.T = T
46+
47+
def methodName(self):
48+
pass
49+
50+
return RegularPythonClass
51+
52+
4153
class TypeFunctionTest(unittest.TestCase):
4254
def test_basic(self):
4355
@TypeFunction
@@ -372,3 +384,8 @@ def __init__(self, y):
372384

373385
assert issubclass(Temp(int), Temp)
374386
assert issubclass(Temp, TypeFunction)
387+
388+
def test_serialize_regular_class_output(self):
389+
C = RegularPythonClass(int)
390+
391+
assert b'methodName' not in SerializationContext().withoutCompression().serialize(C)

typed_python/types_serialization_test.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1888,6 +1888,11 @@ def test_serialize_methods_on_named_classes(self):
18881888

18891889
self.assertIs(m1, m2)
18901890

1891+
def test_serialize_frozenset(self):
1892+
sc = SerializationContext()
1893+
1894+
assert sc.nameForObject(frozenset) is not None
1895+
18911896
def test_serialize_entrypointed_modulelevel_functions(self):
18921897
sc = SerializationContext()
18931898

0 commit comments

Comments
 (0)