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

vstinner <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
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]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.