diff --git a/mypy/nodes.py b/mypy/nodes.py index f25d3abab7bc..5a27783e97e1 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -2907,6 +2907,8 @@ def serialize(self) -> JsonDict: None if self.typeddict_type is None else self.typeddict_type.serialize(), 'flags': get_flags(self, TypeInfo.FLAGS), 'metadata': self.metadata, + 'slots': list(sorted(self.slots)) if self.slots is not None else None, + 'deletable_attributes': self.deletable_attributes, } return data @@ -2944,6 +2946,8 @@ def deserialize(cls, data: JsonDict) -> 'TypeInfo': ti.typeddict_type = (None if data['typeddict_type'] is None else mypy.types.TypedDictType.deserialize(data['typeddict_type'])) ti.metadata = data['metadata'] + ti.slots = set(data['slots']) if data['slots'] is not None else None + ti.deletable_attributes = data['deletable_attributes'] set_flags(ti, data['flags']) return ti diff --git a/mypyc/ir/class_ir.py b/mypyc/ir/class_ir.py index ade04f39edcb..d6407610e2bc 100644 --- a/mypyc/ir/class_ir.py +++ b/mypyc/ir/class_ir.py @@ -325,6 +325,7 @@ def serialize(self) -> JsonDict: 'children': [ cir.fullname for cir in self.children ] if self.children is not None else None, + 'deletable': self.deletable, } @classmethod @@ -373,6 +374,7 @@ def deserialize(cls, data: JsonDict, ctx: 'DeserMaps') -> 'ClassIR': ir.mro = [ctx.classes[s] for s in data['mro']] ir.base_mro = [ctx.classes[s] for s in data['base_mro']] ir.children = data['children'] and [ctx.classes[s] for s in data['children']] + ir.deletable = data['deletable'] return ir diff --git a/mypyc/test-data/fixtures/ir.py b/mypyc/test-data/fixtures/ir.py index a661067845ac..a6914ccc36e5 100644 --- a/mypyc/test-data/fixtures/ir.py +++ b/mypyc/test-data/fixtures/ir.py @@ -294,7 +294,7 @@ def next(i: Iterator[T]) -> T: pass def next(i: Iterator[T], default: T) -> T: pass def hash(o: object) -> int: ... def globals() -> Dict[str, Any]: ... -def getattr(obj: object, name: str) -> Any: ... +def getattr(obj: object, name: str, default: Any = None) -> Any: ... def setattr(obj: object, name: str, value: Any) -> None: ... def enumerate(x: Iterable[T]) -> Iterator[Tuple[int, T]]: ... @overload diff --git a/mypyc/test-data/run-multimodule.test b/mypyc/test-data/run-multimodule.test index 20c9002cdf1d..6ffa166c57a1 100644 --- a/mypyc/test-data/run-multimodule.test +++ b/mypyc/test-data/run-multimodule.test @@ -798,3 +798,25 @@ def foo() -> int: return 10 import native [rechecked native, other_a] + +[case testIncrementalCompilationWithDeletable] +import other_a +[file other_a.py] +from other_b import C +[file other_a.py.2] +from other_b import C +c = C() +print(getattr(c, 'x', None)) +del c.x +print(getattr(c, 'x', None)) +[file other_b.py] +class C: + __deletable__ = ['x'] + def __init__(self) -> None: + self.x = 0 +[file driver.py] +import native +[out] +[out2] +0 +None diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index c990f52e74c8..caba9b73e594 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -5633,3 +5633,28 @@ main:5: error: Cannot override final attribute "x" (previously declared in base main:3: error: Cannot override writable attribute "x" with a final one main:4: error: Cannot extend enum with existing members: "FinalEnum" main:5: error: Cannot override final attribute "x" (previously declared in base class "FinalEnum") + +[case testSlotsSerialization] +import a +[file a.py] +from b import C + +class D(C): + pass +[file b.py] +class C: + __slots__ = ('x',) +[file a.py.2] +from b import C + +class D(C): + __slots__ = ('y',) + + def __init__(self) -> None: + self.x = 1 + self.y = 2 + self.z = 3 +[builtins fixtures/tuple.pyi] +[out] +[out2] +tmp/a.py:9: error: Trying to assign name "z" that is not in "__slots__" of type "a.D"