From 80deddbc9f3720c1ca49afaec1bb801728b9ffa3 Mon Sep 17 00:00:00 2001 From: Jun Komoda <45822440+junkmd@users.noreply.github.com> Date: Thu, 24 Oct 2024 12:49:19 +0000 Subject: [PATCH 1/5] Add a test. Such an implementation is used in `IUnknown` of `comtypes`. --- .../test_ctypes/test_c_simple_type_meta.py | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 Lib/test/test_ctypes/test_c_simple_type_meta.py diff --git a/Lib/test/test_ctypes/test_c_simple_type_meta.py b/Lib/test/test_ctypes/test_c_simple_type_meta.py new file mode 100644 index 00000000000000..3da701d39432e7 --- /dev/null +++ b/Lib/test/test_ctypes/test_c_simple_type_meta.py @@ -0,0 +1,49 @@ +import unittest +import ctypes +from ctypes import POINTER, c_void_p + +from ._support import PyCSimpleType + + +class PyCSimpleTypeAsMetaclassTest(unittest.TestCase): + def tearDown(self): + # to not leak references, we must clean _pointer_type_cache + ctypes._reset_cache() + + def test_early_return_in_dunder_new_1(self): + # Such an implementation is used in `IUnknown` of `comtypes`. + + class _ct_meta(type): + def __new__(cls, name, bases, namespace): + self = super().__new__(cls, name, bases, namespace) + if bases == (c_void_p,): + return self + if issubclass(self, _PtrBase): + return self + if bases == (object,): + _ptr_bases = (self, _PtrBase) + else: + _ptr_bases = (self, POINTER(bases[0])) + p = _p_meta(f"POINTER({self.__name__})", _ptr_bases, {}) + ctypes._pointer_type_cache[self] = p + return self + + class _p_meta(PyCSimpleType, _ct_meta): + pass + + class _PtrBase(c_void_p, metaclass=_p_meta): + pass + + class _CtBase(object, metaclass=_ct_meta): + pass + + class _Sub(_CtBase): + pass + + class _Sub2(_Sub): + pass + + self.assertIsInstance(POINTER(_Sub2), _p_meta) + self.assertTrue(issubclass(POINTER(_Sub2), _Sub2)) + self.assertTrue(issubclass(POINTER(_Sub2), POINTER(_Sub))) + self.assertTrue(issubclass(POINTER(_Sub), POINTER(_CtBase))) From cf81f0313a3b8ba990a324ab39a29caf2ac1d3ba Mon Sep 17 00:00:00 2001 From: Jun Komoda <45822440+junkmd@users.noreply.github.com> Date: Thu, 24 Oct 2024 12:49:19 +0000 Subject: [PATCH 2/5] Add a test. Such an implementation is used in `CoClass` of `comtypes`. --- .../test_ctypes/test_c_simple_type_meta.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Lib/test/test_ctypes/test_c_simple_type_meta.py b/Lib/test/test_ctypes/test_c_simple_type_meta.py index 3da701d39432e7..de97ca690f8421 100644 --- a/Lib/test/test_ctypes/test_c_simple_type_meta.py +++ b/Lib/test/test_ctypes/test_c_simple_type_meta.py @@ -47,3 +47,32 @@ class _Sub2(_Sub): self.assertTrue(issubclass(POINTER(_Sub2), _Sub2)) self.assertTrue(issubclass(POINTER(_Sub2), POINTER(_Sub))) self.assertTrue(issubclass(POINTER(_Sub), POINTER(_CtBase))) + + def test_early_return_in_dunder_new_2(self): + # Such an implementation is used in `CoClass` of `comtypes`. + + class _ct_meta(type): + def __new__(cls, name, bases, namespace): + self = super().__new__(cls, name, bases, namespace) + if isinstance(self, _p_meta): + return self + p = _p_meta( + f"POINTER({self.__name__})", (self, c_void_p), {} + ) + ctypes._pointer_type_cache[self] = p + return self + + class _p_meta(PyCSimpleType, _ct_meta): + pass + + class _Core(object): + pass + + class _CtBase(_Core, metaclass=_ct_meta): + pass + + class _Sub(_CtBase): + pass + + self.assertIsInstance(POINTER(_Sub), _p_meta) + self.assertTrue(issubclass(POINTER(_Sub), _Sub)) From 69032777017df344105199f571fc17ddb61d5958 Mon Sep 17 00:00:00 2001 From: Jun Komoda <45822440+junkmd@users.noreply.github.com> Date: Thu, 24 Oct 2024 13:18:37 +0000 Subject: [PATCH 3/5] Remove the `_` prefix from local names. --- .../test_ctypes/test_c_simple_type_meta.py | 48 +++++++++---------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/Lib/test/test_ctypes/test_c_simple_type_meta.py b/Lib/test/test_ctypes/test_c_simple_type_meta.py index de97ca690f8421..5dddad81bdfaf3 100644 --- a/Lib/test/test_ctypes/test_c_simple_type_meta.py +++ b/Lib/test/test_ctypes/test_c_simple_type_meta.py @@ -13,66 +13,64 @@ def tearDown(self): def test_early_return_in_dunder_new_1(self): # Such an implementation is used in `IUnknown` of `comtypes`. - class _ct_meta(type): + class ct_meta(type): def __new__(cls, name, bases, namespace): self = super().__new__(cls, name, bases, namespace) if bases == (c_void_p,): return self - if issubclass(self, _PtrBase): + if issubclass(self, PtrBase): return self if bases == (object,): - _ptr_bases = (self, _PtrBase) + ptr_bases = (self, PtrBase) else: - _ptr_bases = (self, POINTER(bases[0])) - p = _p_meta(f"POINTER({self.__name__})", _ptr_bases, {}) + ptr_bases = (self, POINTER(bases[0])) + p = p_meta(f"POINTER({self.__name__})", ptr_bases, {}) ctypes._pointer_type_cache[self] = p return self - class _p_meta(PyCSimpleType, _ct_meta): + class p_meta(PyCSimpleType, ct_meta): pass - class _PtrBase(c_void_p, metaclass=_p_meta): + class PtrBase(c_void_p, metaclass=p_meta): pass - class _CtBase(object, metaclass=_ct_meta): + class CtBase(object, metaclass=ct_meta): pass - class _Sub(_CtBase): + class Sub(CtBase): pass - class _Sub2(_Sub): + class Sub2(Sub): pass - self.assertIsInstance(POINTER(_Sub2), _p_meta) - self.assertTrue(issubclass(POINTER(_Sub2), _Sub2)) - self.assertTrue(issubclass(POINTER(_Sub2), POINTER(_Sub))) - self.assertTrue(issubclass(POINTER(_Sub), POINTER(_CtBase))) + self.assertIsInstance(POINTER(Sub2), p_meta) + self.assertTrue(issubclass(POINTER(Sub2), Sub2)) + self.assertTrue(issubclass(POINTER(Sub2), POINTER(Sub))) + self.assertTrue(issubclass(POINTER(Sub), POINTER(CtBase))) def test_early_return_in_dunder_new_2(self): # Such an implementation is used in `CoClass` of `comtypes`. - class _ct_meta(type): + class ct_meta(type): def __new__(cls, name, bases, namespace): self = super().__new__(cls, name, bases, namespace) - if isinstance(self, _p_meta): + if isinstance(self, p_meta): return self - p = _p_meta( - f"POINTER({self.__name__})", (self, c_void_p), {} - ) + p = p_meta(f"POINTER({self.__name__})", (self, c_void_p), {}) ctypes._pointer_type_cache[self] = p return self - class _p_meta(PyCSimpleType, _ct_meta): + class p_meta(PyCSimpleType, ct_meta): pass - class _Core(object): + class Core(object): pass - class _CtBase(_Core, metaclass=_ct_meta): + class CtBase(Core, metaclass=ct_meta): pass - class _Sub(_CtBase): + class Sub(CtBase): pass - self.assertIsInstance(POINTER(_Sub), _p_meta) - self.assertTrue(issubclass(POINTER(_Sub), _Sub)) + self.assertIsInstance(POINTER(Sub), p_meta) + self.assertTrue(issubclass(POINTER(Sub), Sub)) From 47ec482cd96c8c8f1c0c4596eb16055568ec58d4 Mon Sep 17 00:00:00 2001 From: Jun Komoda <45822440+junkmd@users.noreply.github.com> Date: Sat, 26 Oct 2024 00:23:16 +0900 Subject: [PATCH 4/5] Apply suggestions from code review Co-authored-by: Petr Viktorin --- .../test_ctypes/test_c_simple_type_meta.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_ctypes/test_c_simple_type_meta.py b/Lib/test/test_ctypes/test_c_simple_type_meta.py index 5dddad81bdfaf3..b76238a5c530cd 100644 --- a/Lib/test/test_ctypes/test_c_simple_type_meta.py +++ b/Lib/test/test_ctypes/test_c_simple_type_meta.py @@ -10,16 +10,26 @@ def tearDown(self): # to not leak references, we must clean _pointer_type_cache ctypes._reset_cache() - def test_early_return_in_dunder_new_1(self): - # Such an implementation is used in `IUnknown` of `comtypes`. + def test_creating_pointer_in_dunder_new_1(self): + # Test metaclass whose instances are C types; when the type is + # created it automatically creates a pointer type for itself. + # The pointer type is also an instance of the metaclass. + # Such an implementation is used in `IUnknown` of the `comtypes` + # project. See gh-124520. class ct_meta(type): def __new__(cls, name, bases, namespace): self = super().__new__(cls, name, bases, namespace) + + # Avoid recursion: don't set up a pointer to + # a pointer (to a pointer...) if bases == (c_void_p,): + # When creating PtrBase itself, the name + # is not yet available return self if issubclass(self, PtrBase): return self + if bases == (object,): ptr_bases = (self, PtrBase) else: @@ -48,8 +58,8 @@ class Sub2(Sub): self.assertTrue(issubclass(POINTER(Sub2), POINTER(Sub))) self.assertTrue(issubclass(POINTER(Sub), POINTER(CtBase))) - def test_early_return_in_dunder_new_2(self): - # Such an implementation is used in `CoClass` of `comtypes`. + def test_creating_pointer_in_dunder_new_2(self): + # A simpler variant of the above, used in `CoClass` of `comtypes`. class ct_meta(type): def __new__(cls, name, bases, namespace): From 4f1b74c2be28a4d98f56ffa3f6a7789acacfa207 Mon Sep 17 00:00:00 2001 From: Jun Komoda <45822440+junkmd@users.noreply.github.com> Date: Fri, 25 Oct 2024 15:35:35 +0000 Subject: [PATCH 5/5] Update comments. --- Lib/test/test_ctypes/test_c_simple_type_meta.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_ctypes/test_c_simple_type_meta.py b/Lib/test/test_ctypes/test_c_simple_type_meta.py index b76238a5c530cd..fa5144a3ca01bb 100644 --- a/Lib/test/test_ctypes/test_c_simple_type_meta.py +++ b/Lib/test/test_ctypes/test_c_simple_type_meta.py @@ -59,7 +59,8 @@ class Sub2(Sub): self.assertTrue(issubclass(POINTER(Sub), POINTER(CtBase))) def test_creating_pointer_in_dunder_new_2(self): - # A simpler variant of the above, used in `CoClass` of `comtypes`. + # A simpler variant of the above, used in `CoClass` of the `comtypes` + # project. class ct_meta(type): def __new__(cls, name, bases, namespace):