https://github.com/python/cpython/commit/83674483563c83550c87311adb557f8565916ce0
commit: 83674483563c83550c87311adb557f8565916ce0
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-07-19T12:05:22+02:00
summary:

gh-104533: Fix `@ctypes.util.struct` with string annotations (#154040)

Fix `@ctypes.util.struct` on structures declared in a module which uses
`from __future__ import annotations`. The decorator now calls
get_annotations() with eval_str=True.

files:
A Lib/test/test_ctypes/struct_str_ann.py
M Lib/ctypes/util.py
M Lib/test/test_ctypes/test_structures.py

diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py
index c65fc3032fdfad..e8db8c578bb360 100644
--- a/Lib/ctypes/util.py
+++ b/Lib/ctypes/util.py
@@ -512,7 +512,8 @@ def _process_struct(decorated_class, /, *, align, layout, 
endian, pack):
         fields.extend(decorated_class._fields_)
         anonymous.extend(decorated_class._anonymous_)
 
-    for name, hint in annotationlib.get_annotations(decorated_class).items():
+    annotations = annotationlib.get_annotations(decorated_class, eval_str=True)
+    for name, hint in annotations.items():
         if get_origin(hint) is ClassVar:
             continue
 
diff --git a/Lib/test/test_ctypes/struct_str_ann.py 
b/Lib/test/test_ctypes/struct_str_ann.py
new file mode 100644
index 00000000000000..7a924825dd81ee
--- /dev/null
+++ b/Lib/test/test_ctypes/struct_str_ann.py
@@ -0,0 +1,15 @@
+from __future__ import annotations
+from ctypes.util import struct
+from ctypes import c_int
+
+class TestAnn:
+    x: c_int
+
+# Check that "from __future__ import annotations" works as expected
+if not isinstance(TestAnn.__annotations__['x'], str):
+    raise Exception("annotations must be strings")
+
+@struct
+class Point:
+    x: c_int
+    y: c_int
diff --git a/Lib/test/test_ctypes/test_structures.py 
b/Lib/test/test_ctypes/test_structures.py
index 4dc1ea867c0700..04c3682e8308ec 100644
--- a/Lib/test/test_ctypes/test_structures.py
+++ b/Lib/test/test_ctypes/test_structures.py
@@ -88,13 +88,14 @@ class X(Structure):
         self.assertEqual(sizeof(X), min(8, longlong_align) + longlong_size)
         self.assertEqual(X.b.offset, min(8, longlong_align))
 
-        with self.assertRaises(ValueError):
-            if use_struct_util:
+        if use_struct_util:
+            with self.assertRaises(NameError):
                 @struct_util(pack=-1, layout='ms')
                 class X:
                     a: "b"
                     b: "q"
-            else:
+        else:
+            with self.assertRaises(ValueError):
                 class X(Structure):
                     _fields_ = [("a", "b"), ("b", "q")]
                     _pack_ = -1
@@ -954,6 +955,12 @@ class Foo:
 
         self.assertEqual(Foo.__name__, "Foo")
 
+    def test_string_annotations(self):
+        from test.test_ctypes import struct_str_ann
+        Point = struct_str_ann.Point
+        fields = [['x', c_int], ['y', c_int]]
+        self.assertEqual(Point._fields_, fields)
+
 
 if __name__ == '__main__':
     unittest.main()

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to