def test_type_freeze(self): class C: pass cinder.freeze_type(C) with self.assertRaisesRegex( TypeError, "type 'C' has been frozen and cannot be modified" ): C.foo = 42 class D: x = 42 cinder.freeze_type(D) with self.assertRaisesRegex( TypeError, "type 'D' has been frozen and cannot be modified" ): D.foo = 42 with self.assertRaisesRegex( TypeError, "type 'D' has been frozen and cannot be modified" ): del D.foo
def test_cached_class_prop_frozen_type_inst(self): class C: @cached_classproperty def f(cls): return 42 cinder.freeze_type(C) self.assertEqual(C().f, 42)
async def test_cached_class_prop_frozen_type(self): class C: @async_cached_classproperty async def f(cls): return 42 cinder.freeze_type(C) self.assertEqual(await C.f, 42)
def _replace_types(gen_type: Type[object], subs: Tuple[Type[object], ...]) -> Type[object]: existing_inst = gen_type.__origin__.__insts__.get(subs) if existing_inst is not None: return existing_inst # Check if we have a full instantation, and verify the constraints new_dict = dict(gen_type.__dict__) has_params = False for sub in subs: if isinstance(sub, TypeVar) or hasattr(sub, "__parameters__"): has_params = True continue # Remove the existing StaticGeneric base... bases = tuple(base for base in gen_type.__orig_bases__ if not isinstance(base, StaticGeneric)) new_dict["__args__"] = subs if not has_params: # Instantiated types don't have generic parameters anymore. del new_dict["__parameters__"] else: new_vars = _collect_type_vars(subs) new_gen = StaticGeneric() new_gen.__parameters__ = new_vars new_dict["__orig_bases__"] = bases + (new_gen, ) bases += (StaticGeneric, ) new_dict["__parameters__"] = new_vars # Eventually we'll want to have some processing of the members here to # bind the generics through. That may be an actual process which creates # new objects with the generics bound, or a virtual process. For now # we just propagate the members to the new type. param_names = ", ".join(param.__name__ for param in subs) res = type(f"{gen_type.__origin__.__name__}[{param_names}]", bases, new_dict) res.__origin__ = gen_type if not has_params: # specialize the type for name, value in new_dict.items(): if isinstance(value, FunctionType): if hasattr(value, "__runtime_impl__"): setattr( res, name, _static.specialize_function(res, value.__qualname__, subs), ) if cinder is not None: cinder.freeze_type(res) gen_type.__origin__.__insts__[subs] = res return res
def test_warn_on_frozen_type(self): class C: pass cinder.freeze_type(C) with self.assertRaisesRegex( TypeError, "can't call warn_on_inst_dict on a frozen type"): cinder.warn_on_inst_dict(C)
def test_init_frozen_type(self): codestr = """ class C: def __init__(self, a: int) -> None: self.value = a def f(x: int) -> C: return C(x) """ with self.in_module(codestr) as mod: C = mod.C freeze_type(C) f = mod.f self.assertEqual(f(42).value, 42)
def test_invoke_with_freevars(self): codestr = """ class C: def __init__(self) -> None: super().__init__() def f() -> C: return C() """ code = self.compile(codestr) with self.in_module(codestr) as mod: f = mod.f C = mod.C freeze_type(C) self.assertInBytecode(f, "INVOKE_FUNCTION") self.assertTrue(isinstance(f(), C))
def test_warn_on_type(self): class C: pass msg = type = attr = None def cb(*args): nonlocal msg, type, attr msg = args[0] type = args[1] attr = args[2] cinder.warn_on_inst_dict(C) cinder.freeze_type(C) cinder.cinder_set_warn_handler(cb) C.foo = 42 self.assertEqual( msg, "WARN002: Type modified that was flagged for immutability") self.assertEqual(type, C) self.assertEqual(attr, "foo")
def test_type_freeze_bad_arg(self): with self.assertRaisesRegex(TypeError, "freeze_type requires a type"): cinder.freeze_type(42)