]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-130907: Treat all module-level annotations as conditional (#131550)
authorJelle Zijlstra <jelle.zijlstra@gmail.com>
Mon, 28 Apr 2025 13:10:28 +0000 (06:10 -0700)
committerGitHub <noreply@github.com>
Mon, 28 Apr 2025 13:10:28 +0000 (06:10 -0700)
21 files changed:
Include/internal/pycore_compile.h
Include/internal/pycore_instruction_sequence.h
Include/internal/pycore_opcode_metadata.h
Include/opcode_ids.h
Lib/_opcode_metadata.py
Lib/test/test_compiler_codegen.py
Lib/test/test_dis.py
Lib/test/test_grammar.py
Lib/test/test_type_annotations.py
Lib/test/typinganndata/partialexecution/__init__.py [new file with mode: 0644]
Lib/test/typinganndata/partialexecution/a.py [new file with mode: 0644]
Lib/test/typinganndata/partialexecution/b.py [new file with mode: 0644]
Makefile.pre.in
Misc/NEWS.d/next/Core_and_Builtins/2025-03-21-08-47-36.gh-issue-130907.rGg-ge.rst [new file with mode: 0644]
Objects/moduleobject.c
Python/bytecodes.c
Python/codegen.c
Python/compile.c
Python/flowgraph.c
Python/instruction_sequence.c
Python/symtable.c

index b98dfb0cebbd3f38fe8faa8c164889685c96b1f4..a606c2afe0a2344da548d6909f5f0a62989d085c 100644 (file)
@@ -133,6 +133,8 @@ int _PyCompile_EnterScope(struct _PyCompiler *c, identifier name, int scope_type
 void _PyCompile_ExitScope(struct _PyCompiler *c);
 Py_ssize_t _PyCompile_AddConst(struct _PyCompiler *c, PyObject *o);
 _PyInstructionSequence *_PyCompile_InstrSequence(struct _PyCompiler *c);
+int _PyCompile_StartAnnotationSetup(struct _PyCompiler *c);
+int _PyCompile_EndAnnotationSetup(struct _PyCompiler *c);
 int _PyCompile_FutureFeatures(struct _PyCompiler *c);
 void _PyCompile_DeferredAnnotations(
     struct _PyCompiler *c, PyObject **deferred_annotations,
index 099f2fd124007a2d2db2b1bd55d0d1f2f9842713..b5c927735374bebf4ff09d45f279c8c72db94144 100644 (file)
@@ -45,6 +45,9 @@ typedef struct instruction_sequence {
 
     /* PyList of instruction sequences of nested functions */
     PyObject *s_nested;
+
+    /* Code for creating annotations, spliced into the main sequence later */
+    struct instruction_sequence *s_annotations_code;
 } _PyInstructionSequence;
 
 typedef struct {
@@ -66,6 +69,8 @@ _PyJumpTargetLabel _PyInstructionSequence_NewLabel(_PyInstructionSequence *seq);
 int _PyInstructionSequence_ApplyLabelMap(_PyInstructionSequence *seq);
 int _PyInstructionSequence_InsertInstruction(_PyInstructionSequence *seq, int pos,
                                              int opcode, int oparg, _Py_SourceLocation loc);
+int _PyInstructionSequence_SetAnnotationsCode(_PyInstructionSequence *seq,
+                                              _PyInstructionSequence *annotations);
 int _PyInstructionSequence_AddNested(_PyInstructionSequence *seq, _PyInstructionSequence *nested);
 void PyInstructionSequence_Fini(_PyInstructionSequence *seq);
 
index c43d60175d5449cb916100a4954f664b85f3ea83..3b881b30b054f1f7670fa7929fcd553934859586 100644 (file)
@@ -20,6 +20,7 @@ extern "C" {
 #define IS_PSEUDO_INSTR(OP)  ( \
     ((OP) == LOAD_CLOSURE) || \
     ((OP) == STORE_FAST_MAYBE_NULL) || \
+    ((OP) == ANNOTATIONS_PLACEHOLDER) || \
     ((OP) == JUMP) || \
     ((OP) == JUMP_NO_INTERRUPT) || \
     ((OP) == JUMP_IF_FALSE) || \
@@ -35,6 +36,8 @@ extern int _PyOpcode_num_popped(int opcode, int oparg);
 #ifdef NEED_OPCODE_METADATA
 int _PyOpcode_num_popped(int opcode, int oparg)  {
     switch(opcode) {
+        case ANNOTATIONS_PLACEHOLDER:
+            return 0;
         case BINARY_OP:
             return 2;
         case BINARY_OP_ADD_FLOAT:
@@ -514,6 +517,8 @@ extern int _PyOpcode_num_pushed(int opcode, int oparg);
 #ifdef NEED_OPCODE_METADATA
 int _PyOpcode_num_pushed(int opcode, int oparg)  {
     switch(opcode) {
+        case ANNOTATIONS_PLACEHOLDER:
+            return 0;
         case BINARY_OP:
             return 1;
         case BINARY_OP_ADD_FLOAT:
@@ -1004,7 +1009,7 @@ enum InstructionFormat {
 };
 
 #define IS_VALID_OPCODE(OP) \
-    (((OP) >= 0) && ((OP) < 266) && \
+    (((OP) >= 0) && ((OP) < 267) && \
      (_PyOpcode_opcode_metadata[(OP)].valid_entry))
 
 #define HAS_ARG_FLAG (1)
@@ -1058,9 +1063,9 @@ struct opcode_metadata {
     uint16_t flags;
 };
 
-extern const struct opcode_metadata _PyOpcode_opcode_metadata[266];
+extern const struct opcode_metadata _PyOpcode_opcode_metadata[267];
 #ifdef NEED_OPCODE_METADATA
-const struct opcode_metadata _PyOpcode_opcode_metadata[266] = {
+const struct opcode_metadata _PyOpcode_opcode_metadata[267] = {
     [BINARY_OP] = { true, INSTR_FMT_IBC0000, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
     [BINARY_OP_ADD_FLOAT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG },
     [BINARY_OP_ADD_INT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
@@ -1285,6 +1290,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[266] = {
     [UNPACK_SEQUENCE_TWO_TUPLE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG },
     [WITH_EXCEPT_START] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
     [YIELD_VALUE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG },
+    [ANNOTATIONS_PLACEHOLDER] = { true, -1, HAS_PURE_FLAG },
     [JUMP] = { true, -1, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
     [JUMP_IF_FALSE] = { true, -1, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
     [JUMP_IF_TRUE] = { true, -1, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
@@ -1491,9 +1497,10 @@ _PyOpcode_macro_expansion[256] = {
 };
 #endif // NEED_OPCODE_METADATA
 
-extern const char *_PyOpcode_OpName[266];
+extern const char *_PyOpcode_OpName[267];
 #ifdef NEED_OPCODE_METADATA
-const char *_PyOpcode_OpName[266] = {
+const char *_PyOpcode_OpName[267] = {
+    [ANNOTATIONS_PLACEHOLDER] = "ANNOTATIONS_PLACEHOLDER",
     [BINARY_OP] = "BINARY_OP",
     [BINARY_OP_ADD_FLOAT] = "BINARY_OP_ADD_FLOAT",
     [BINARY_OP_ADD_INT] = "BINARY_OP_ADD_INT",
@@ -2025,11 +2032,12 @@ struct pseudo_targets {
     uint8_t as_sequence;
     uint8_t targets[4];
 };
-extern const struct pseudo_targets _PyOpcode_PseudoTargets[10];
+extern const struct pseudo_targets _PyOpcode_PseudoTargets[11];
 #ifdef NEED_OPCODE_METADATA
-const struct pseudo_targets _PyOpcode_PseudoTargets[10] = {
+const struct pseudo_targets _PyOpcode_PseudoTargets[11] = {
     [LOAD_CLOSURE-256] = { 0, { LOAD_FAST, 0, 0, 0 } },
     [STORE_FAST_MAYBE_NULL-256] = { 0, { STORE_FAST, 0, 0, 0 } },
+    [ANNOTATIONS_PLACEHOLDER-256] = { 0, { NOP, 0, 0, 0 } },
     [JUMP-256] = { 0, { JUMP_FORWARD, JUMP_BACKWARD, 0, 0 } },
     [JUMP_NO_INTERRUPT-256] = { 0, { JUMP_FORWARD, JUMP_BACKWARD_NO_INTERRUPT, 0, 0 } },
     [JUMP_IF_FALSE-256] = { 1, { COPY, TO_BOOL, POP_JUMP_IF_FALSE, 0 } },
@@ -2043,7 +2051,7 @@ const struct pseudo_targets _PyOpcode_PseudoTargets[10] = {
 #endif // NEED_OPCODE_METADATA
 static inline bool
 is_pseudo_target(int pseudo, int target) {
-    if (pseudo < 256 || pseudo >= 266) {
+    if (pseudo < 256 || pseudo >= 267) {
         return false;
     }
     for (int i = 0; _PyOpcode_PseudoTargets[pseudo-256].targets[i]; i++) {
index 898dc580f4148ea455b423fc4aeab18d5a6976e5..d93f028b7324063cc7cb8d6e55089d48875a30f3 100644 (file)
@@ -234,16 +234,17 @@ extern "C" {
 #define INSTRUMENTED_JUMP_BACKWARD             253
 #define INSTRUMENTED_LINE                      254
 #define ENTER_EXECUTOR                         255
-#define JUMP                                   256
-#define JUMP_IF_FALSE                          257
-#define JUMP_IF_TRUE                           258
-#define JUMP_NO_INTERRUPT                      259
-#define LOAD_CLOSURE                           260
-#define POP_BLOCK                              261
-#define SETUP_CLEANUP                          262
-#define SETUP_FINALLY                          263
-#define SETUP_WITH                             264
-#define STORE_FAST_MAYBE_NULL                  265
+#define ANNOTATIONS_PLACEHOLDER                256
+#define JUMP                                   257
+#define JUMP_IF_FALSE                          258
+#define JUMP_IF_TRUE                           259
+#define JUMP_NO_INTERRUPT                      260
+#define LOAD_CLOSURE                           261
+#define POP_BLOCK                              262
+#define SETUP_CLEANUP                          263
+#define SETUP_FINALLY                          264
+#define SETUP_WITH                             265
+#define STORE_FAST_MAYBE_NULL                  266
 
 #define HAVE_ARGUMENT                           42
 #define MIN_SPECIALIZED_OPCODE                 129
index 15900265a01270705d7132b10b0d71daf3ef1d78..4d30b6503fd1e9a0f9ba5e56a2b7fcefb23653fe 100644 (file)
@@ -350,16 +350,17 @@ opmap = {
     'INSTRUMENTED_CALL_KW': 251,
     'INSTRUMENTED_CALL_FUNCTION_EX': 252,
     'INSTRUMENTED_JUMP_BACKWARD': 253,
-    'JUMP': 256,
-    'JUMP_IF_FALSE': 257,
-    'JUMP_IF_TRUE': 258,
-    'JUMP_NO_INTERRUPT': 259,
-    'LOAD_CLOSURE': 260,
-    'POP_BLOCK': 261,
-    'SETUP_CLEANUP': 262,
-    'SETUP_FINALLY': 263,
-    'SETUP_WITH': 264,
-    'STORE_FAST_MAYBE_NULL': 265,
+    'ANNOTATIONS_PLACEHOLDER': 256,
+    'JUMP': 257,
+    'JUMP_IF_FALSE': 258,
+    'JUMP_IF_TRUE': 259,
+    'JUMP_NO_INTERRUPT': 260,
+    'LOAD_CLOSURE': 261,
+    'POP_BLOCK': 262,
+    'SETUP_CLEANUP': 263,
+    'SETUP_FINALLY': 264,
+    'SETUP_WITH': 265,
+    'STORE_FAST_MAYBE_NULL': 266,
 }
 
 HAVE_ARGUMENT = 42
index cc9ecc7e38917ba1bf33fbb23830139616b236a9..d02937c84d953495522bbf7869e0be9e015ed8b0 100644 (file)
@@ -26,6 +26,7 @@ class IsolatedCodeGenTests(CodegenTestCase):
         false_lbl = self.Label()
         expected = [
             ('RESUME', 0, 0),
+            ('ANNOTATIONS_PLACEHOLDER', None),
             ('LOAD_CONST', 0, 1),
             ('TO_BOOL', 0, 1),
             ('POP_JUMP_IF_FALSE', false_lbl := self.Label(), 1),
@@ -45,6 +46,7 @@ class IsolatedCodeGenTests(CodegenTestCase):
         false_lbl = self.Label()
         expected = [
             ('RESUME', 0, 0),
+            ('ANNOTATIONS_PLACEHOLDER', None),
             ('LOAD_NAME', 0, 1),
             ('GET_ITER', None, 1),
             loop_lbl := self.Label(),
@@ -73,6 +75,7 @@ class IsolatedCodeGenTests(CodegenTestCase):
         expected = [
             # Function definition
             ('RESUME', 0),
+            ('ANNOTATIONS_PLACEHOLDER', None),
             ('LOAD_CONST', 0),
             ('MAKE_FUNCTION', None),
             ('STORE_NAME', 0),
@@ -106,6 +109,7 @@ class IsolatedCodeGenTests(CodegenTestCase):
         expected = [
             # Function definition
             ('RESUME', 0),
+            ('ANNOTATIONS_PLACEHOLDER', None),
             ('LOAD_CONST', 0),
             ('MAKE_FUNCTION', None),
             ('STORE_NAME', 0),
index b53a365642930031fbe346131e24e48c022e7345..f2586fcee57d8714de15b55e9769f4e3797e1dfb 100644 (file)
@@ -381,24 +381,36 @@ lst[fun(0)]: int = 1
 # leading newline is for a reason (tests lineno)
 
 dis_annot_stmt_str = """\
-  0           RESUME                   0
+  --           MAKE_CELL                0 (__conditional_annotations__)
 
-  2           LOAD_SMALL_INT           1
-              STORE_NAME               0 (x)
+   0           RESUME                   0
 
-  4           LOAD_SMALL_INT           1
-              LOAD_NAME                1 (lst)
-              LOAD_NAME                2 (fun)
-              PUSH_NULL
-              LOAD_SMALL_INT           0
-              CALL                     1
-              STORE_SUBSCR
+   2           LOAD_CONST               1 (<code object __annotate__ at 0x..., file "<dis>", line 2>)
+               MAKE_FUNCTION
+               STORE_NAME               4 (__annotate__)
+               BUILD_SET                0
+               STORE_NAME               0 (__conditional_annotations__)
+               LOAD_SMALL_INT           1
+               STORE_NAME               1 (x)
+               LOAD_NAME                0 (__conditional_annotations__)
+               LOAD_SMALL_INT           0
+               SET_ADD                  1
+               POP_TOP
 
-  2           LOAD_CONST               1 (<code object __annotate__ at 0x..., file "<dis>", line 2>)
-              MAKE_FUNCTION
-              STORE_NAME               3 (__annotate__)
-              LOAD_CONST               2 (None)
-              RETURN_VALUE
+   3           LOAD_NAME                0 (__conditional_annotations__)
+               LOAD_SMALL_INT           1
+               SET_ADD                  1
+               POP_TOP
+
+   4           LOAD_SMALL_INT           1
+               LOAD_NAME                2 (lst)
+               LOAD_NAME                3 (fun)
+               PUSH_NULL
+               LOAD_SMALL_INT           0
+               CALL                     1
+               STORE_SUBSCR
+               LOAD_CONST               2 (None)
+               RETURN_VALUE
 """
 
 fn_with_annotate_str = """
@@ -995,7 +1007,8 @@ class DisTests(DisTestBase):
     def test_widths(self):
         long_opcodes = set(['JUMP_BACKWARD_NO_INTERRUPT',
                             'LOAD_FAST_BORROW_LOAD_FAST_BORROW',
-                            'INSTRUMENTED_CALL_FUNCTION_EX'])
+                            'INSTRUMENTED_CALL_FUNCTION_EX',
+                            'ANNOTATIONS_PLACEHOLDER'])
         for op, opname in enumerate(dis.opname):
             if opname in long_opcodes or opname.startswith("INSTRUMENTED"):
                 continue
index 3ea4e47ca50a164f4b2dbd2cb52ed07435fabf13..35cd6984267b3be36accb32d06626b9dd40cbbcf 100644 (file)
@@ -383,9 +383,10 @@ class GrammarTests(unittest.TestCase):
         gns = {}; lns = {}
         exec("'docstring'\n"
              "x: int = 5\n", gns, lns)
+        self.assertNotIn('__annotate__', gns)
+
+        gns.update(lns)  # __annotate__ looks at globals
         self.assertEqual(lns["__annotate__"](annotationlib.Format.VALUE), {'x': int})
-        with self.assertRaises(KeyError):
-            gns['__annotate__']
 
     def test_var_annot_rhs(self):
         ns = {}
index fb441976694b34900336fe668c10897444d83198..b72d3dbe5169744295ea99e09bb66670fe736609 100644 (file)
@@ -3,7 +3,7 @@ import inspect
 import textwrap
 import types
 import unittest
-from test.support import run_code, check_syntax_error, cpython_only
+from test.support import run_code, check_syntax_error, import_helper, cpython_only
 from test.test_inspect import inspect_stringized_annotations
 
 
@@ -151,6 +151,14 @@ class TypeAnnotationTests(unittest.TestCase):
             del D.__annotations__
         self.assertEqual(D.__annotations__, {})
 
+    def test_partially_executed_module(self):
+        partialexe = import_helper.import_fresh_module("test.typinganndata.partialexecution")
+        self.assertEqual(
+            partialexe.a.__annotations__,
+            {"v1": int, "v2": int},
+        )
+        self.assertEqual(partialexe.b.annos, {"v1": int})
+
     @cpython_only
     def test_no_cell(self):
         # gh-130924: Test that uses of annotations in local scopes do not
diff --git a/Lib/test/typinganndata/partialexecution/__init__.py b/Lib/test/typinganndata/partialexecution/__init__.py
new file mode 100644 (file)
index 0000000..c39074e
--- /dev/null
@@ -0,0 +1 @@
+from . import a
diff --git a/Lib/test/typinganndata/partialexecution/a.py b/Lib/test/typinganndata/partialexecution/a.py
new file mode 100644 (file)
index 0000000..ed0b8dc
--- /dev/null
@@ -0,0 +1,5 @@
+v1: int
+
+from . import b
+
+v2: int
diff --git a/Lib/test/typinganndata/partialexecution/b.py b/Lib/test/typinganndata/partialexecution/b.py
new file mode 100644 (file)
index 0000000..36b8d2e
--- /dev/null
@@ -0,0 +1,3 @@
+from . import a
+
+annos = a.__annotations__
index 8ec3173a8847b1512f9e85e9cd561ba89a633693..925e0a243c9e969188e0ebe6bf3d90d6784ccd90 100644 (file)
@@ -2679,6 +2679,7 @@ TESTSUBDIRS=      idlelib/idle_test \
                test/translationdata/getopt \
                test/translationdata/optparse \
                test/typinganndata \
+               test/typinganndata/partialexecution \
                test/wheeldata \
                test/xmltestdata \
                test/xmltestdata/c14n-20 \
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-03-21-08-47-36.gh-issue-130907.rGg-ge.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-03-21-08-47-36.gh-issue-130907.rGg-ge.rst
new file mode 100644 (file)
index 0000000..587627e
--- /dev/null
@@ -0,0 +1,3 @@
+If the ``__annotations__`` of a module object are accessed while the
+module is executing, return the annotations that have been defined so far,
+without caching them.
index 46dea1534cbcd62d109bc19d412eb1688db2290d..16f1b2cacd4f83ce0086e6c01fe26aa3a8817397 100644 (file)
@@ -1246,6 +1246,25 @@ module_get_annotations(PyObject *self, void *Py_UNUSED(ignored))
 
     PyObject *annotations;
     if (PyDict_GetItemRef(dict, &_Py_ID(__annotations__), &annotations) == 0) {
+        PyObject *spec;
+        if (PyDict_GetItemRef(m->md_dict, &_Py_ID(__spec__), &spec) < 0) {
+            Py_DECREF(dict);
+            return NULL;
+        }
+        bool is_initializing = false;
+        if (spec != NULL) {
+            int rc = _PyModuleSpec_IsInitializing(spec);
+            if (rc < 0) {
+                Py_DECREF(spec);
+                Py_DECREF(dict);
+                return NULL;
+            }
+            Py_DECREF(spec);
+            if (rc) {
+                is_initializing = true;
+            }
+        }
+
         PyObject *annotate;
         int annotate_result = PyDict_GetItemRef(dict, &_Py_ID(__annotate__), &annotate);
         if (annotate_result < 0) {
@@ -1273,7 +1292,8 @@ module_get_annotations(PyObject *self, void *Py_UNUSED(ignored))
             annotations = PyDict_New();
         }
         Py_XDECREF(annotate);
-        if (annotations) {
+        // Do not cache annotations if the module is still initializing
+        if (annotations && !is_initializing) {
             int result = PyDict_SetItem(
                     dict, &_Py_ID(__annotations__), annotations);
             if (result) {
index f5c6e734a7996009fb687659b8427533040b3274..40fb0d769afcca0ac45b8896248662d06070ac82 100644 (file)
@@ -2026,6 +2026,10 @@ dummy_func(
             }
         }
 
+        pseudo(ANNOTATIONS_PLACEHOLDER, (--)) = {
+            NOP,
+        };
+
         inst(DICT_UPDATE, (dict, unused[oparg - 1], update -- dict, unused[oparg - 1])) {
             PyObject *dict_o = PyStackRef_AsPyObjectBorrow(dict);
             PyObject *update_o = PyStackRef_AsPyObjectBorrow(update);
index 35b46dcdc4095086ac5ed5246fb1f43dc9d9cb15..a7e412f7032c4556d4fd09061fe94fdfb769eeab 100644 (file)
@@ -654,6 +654,9 @@ codegen_enter_scope(compiler *c, identifier name, int scope_type,
         loc.lineno = 0;
     }
     ADDOP_I(c, loc, RESUME, RESUME_AT_FUNC_START);
+    if (scope_type == COMPILE_SCOPE_MODULE) {
+        ADDOP(c, loc, ANNOTATIONS_PLACEHOLDER);
+    }
     return SUCCESS;
 }
 
@@ -792,6 +795,14 @@ codegen_process_deferred_annotations(compiler *c, location loc)
         return SUCCESS;
     }
 
+    int scope_type = SCOPE_TYPE(c);
+    bool need_separate_block = scope_type == COMPILE_SCOPE_MODULE;
+    if (need_separate_block) {
+        if (_PyCompile_StartAnnotationSetup(c) == ERROR) {
+            goto error;
+        }
+    }
+
     // It's possible that ste_annotations_block is set but
     // u_deferred_annotations is not, because the former is still
     // set if there are only non-simple annotations (i.e., annotations
@@ -800,7 +811,6 @@ codegen_process_deferred_annotations(compiler *c, location loc)
     PySTEntryObject *ste = SYMTABLE_ENTRY(c);
     assert(ste->ste_annotation_block != NULL);
     void *key = (void *)((uintptr_t)ste->ste_id + 1);
-    int scope_type = SCOPE_TYPE(c);
     if (codegen_setup_annotations_scope(c, loc, key,
                                         ste->ste_annotation_block->ste_name) < 0) {
         goto error;
@@ -820,6 +830,10 @@ codegen_process_deferred_annotations(compiler *c, location loc)
         ste->ste_type == ClassBlock ? &_Py_ID(__annotate_func__) : &_Py_ID(__annotate__),
         Store));
 
+    if (need_separate_block) {
+        RETURN_IF_ERROR(_PyCompile_EndAnnotationSetup(c));
+    }
+
     return SUCCESS;
 error:
     Py_XDECREF(deferred_anno);
index 430898b5966842e13559e9f1242eea46bc74137b..15ef7214d44b9c3b0f9841e238cf33938e140345 100644 (file)
@@ -64,6 +64,7 @@ struct compiler_unit {
     long u_next_conditional_annotation_index;  /* index of the next conditional annotation */
 
     instr_sequence *u_instr_sequence; /* codegen output */
+    instr_sequence *u_stashed_instr_sequence; /* temporarily stashed parent instruction sequence */
 
     int u_nfblocks;
     int u_in_inlined_comp;
@@ -178,6 +179,7 @@ static void
 compiler_unit_free(struct compiler_unit *u)
 {
     Py_CLEAR(u->u_instr_sequence);
+    Py_CLEAR(u->u_stashed_instr_sequence);
     Py_CLEAR(u->u_ste);
     Py_CLEAR(u->u_metadata.u_name);
     Py_CLEAR(u->u_metadata.u_qualname);
@@ -681,6 +683,7 @@ _PyCompile_EnterScope(compiler *c, identifier name, int scope_type,
         compiler_unit_free(u);
         return ERROR;
     }
+    u->u_stashed_instr_sequence = NULL;
 
     /* Push the old compiler_unit on the stack. */
     if (c->u) {
@@ -1154,7 +1157,7 @@ _PyCompile_AddDeferredAnnotation(compiler *c, stmt_ty s,
     }
     Py_DECREF(ptr);
     PyObject *index;
-    if (c->u->u_in_conditional_block) {
+    if (c->u->u_scope_type == COMPILE_SCOPE_MODULE || c->u->u_in_conditional_block) {
         index = PyLong_FromLong(c->u->u_next_conditional_annotation_index);
         if (index == NULL) {
             return ERROR;
@@ -1231,6 +1234,35 @@ _PyCompile_InstrSequence(compiler *c)
     return c->u->u_instr_sequence;
 }
 
+int
+_PyCompile_StartAnnotationSetup(struct _PyCompiler *c)
+{
+    instr_sequence *new_seq = (instr_sequence *)_PyInstructionSequence_New();
+    if (new_seq == NULL) {
+        return ERROR;
+    }
+    assert(c->u->u_stashed_instr_sequence == NULL);
+    c->u->u_stashed_instr_sequence = c->u->u_instr_sequence;
+    c->u->u_instr_sequence = new_seq;
+    return SUCCESS;
+}
+
+int
+_PyCompile_EndAnnotationSetup(struct _PyCompiler *c)
+{
+    assert(c->u->u_stashed_instr_sequence != NULL);
+    instr_sequence *parent_seq = c->u->u_stashed_instr_sequence;
+    instr_sequence *anno_seq = c->u->u_instr_sequence;
+    c->u->u_stashed_instr_sequence = NULL;
+    c->u->u_instr_sequence = parent_seq;
+    if (_PyInstructionSequence_SetAnnotationsCode(parent_seq, anno_seq) == ERROR) {
+        Py_DECREF(anno_seq);
+        return ERROR;
+    }
+    return SUCCESS;
+}
+
+
 int
 _PyCompile_FutureFeatures(compiler *c)
 {
index a0d5690250cffbaadfd6face9e68ee28e8938056..9e714bf6c4c54dfa2218cb4428c354cada67acfd 100644 (file)
@@ -3847,16 +3847,38 @@ _PyCfg_FromInstructionSequence(_PyInstructionSequence *seq)
             seq->s_instrs[instr->i_oparg].i_target = 1;
         }
     }
+    int offset = 0;
     for (int i = 0; i < seq->s_used; i++) {
         _PyInstruction *instr = &seq->s_instrs[i];
+        if (instr->i_opcode == ANNOTATIONS_PLACEHOLDER) {
+            if (seq->s_annotations_code != NULL) {
+                assert(seq->s_annotations_code->s_labelmap_size == 0
+                    && seq->s_annotations_code->s_nested == NULL);
+                for (int j = 0; j < seq->s_annotations_code->s_used; j++) {
+                    _PyInstruction *ann_instr = &seq->s_annotations_code->s_instrs[j];
+                    assert(!HAS_TARGET(ann_instr->i_opcode));
+                    if (_PyCfgBuilder_Addop(g, ann_instr->i_opcode, ann_instr->i_oparg, ann_instr->i_loc) < 0) {
+                        goto error;
+                    }
+                }
+                offset += seq->s_annotations_code->s_used - 1;
+            }
+            else {
+                offset -= 1;
+            }
+            continue;
+        }
         if (instr->i_target) {
-            jump_target_label lbl_ = {i};
+            jump_target_label lbl_ = {i + offset};
             if (_PyCfgBuilder_UseLabel(g, lbl_) < 0) {
                 goto error;
             }
         }
         int opcode = instr->i_opcode;
         int oparg = instr->i_oparg;
+        if (HAS_TARGET(opcode)) {
+            oparg += offset;
+        }
         if (_PyCfgBuilder_Addop(g, opcode, oparg, instr->i_loc) < 0) {
             goto error;
         }
index 4ca85eec345d38a5b710585fda615dac6bde95e1..b068e4fa3dbf4317c0a1249776c693f393c15196 100644 (file)
@@ -154,6 +154,15 @@ _PyInstructionSequence_InsertInstruction(instr_sequence *seq, int pos,
     return SUCCESS;
 }
 
+int
+_PyInstructionSequence_SetAnnotationsCode(instr_sequence *seq,
+                                          instr_sequence *annotations)
+{
+    assert(seq->s_annotations_code == NULL);
+    seq->s_annotations_code = annotations;
+    return SUCCESS;
+}
+
 int
 _PyInstructionSequence_AddNested(instr_sequence *seq, instr_sequence *nested)
 {
@@ -178,6 +187,12 @@ PyInstructionSequence_Fini(instr_sequence *seq) {
 
     PyMem_Free(seq->s_instrs);
     seq->s_instrs = NULL;
+
+    if (seq->s_annotations_code != NULL) {
+        PyInstructionSequence_Fini(seq->s_annotations_code);
+        Py_CLEAR(seq->s_annotations_code);
+    }
+
 }
 
 /*[clinic input]
@@ -200,6 +215,7 @@ inst_seq_create(void)
     seq->s_labelmap = NULL;
     seq->s_labelmap_size = 0;
     seq->s_nested = NULL;
+    seq->s_annotations_code = NULL;
 
     PyObject_GC_Track(seq);
     return seq;
@@ -414,6 +430,7 @@ inst_seq_traverse(PyObject *op, visitproc visit, void *arg)
 {
     _PyInstructionSequence *seq = (_PyInstructionSequence *)op;
     Py_VISIT(seq->s_nested);
+    Py_VISIT((PyObject *)seq->s_annotations_code);
     return 0;
 }
 
@@ -422,6 +439,7 @@ inst_seq_clear(PyObject *op)
 {
     _PyInstructionSequence *seq = (_PyInstructionSequence *)op;
     Py_CLEAR(seq->s_nested);
+    Py_CLEAR(seq->s_annotations_code);
     return 0;
 }
 
index 66f6c4a89aaee30cfef496806d71009b3dd2b259..2f13f35072aa5ef7cfbc80deba744ec6338e63f0 100644 (file)
@@ -2749,8 +2749,10 @@ symtable_visit_annotation(struct symtable *st, expr_ty annotation, void *key)
     // Annotations in local scopes are not executed and should not affect the symtable
     bool is_unevaluated = st->st_cur->ste_type == FunctionBlock;
 
-    if ((st->st_cur->ste_type == ClassBlock || st->st_cur->ste_type == ModuleBlock)
-            && st->st_cur->ste_in_conditional_block
+    // Module-level annotations are always considered conditional because the module
+    // may be partially executed.
+    if ((((st->st_cur->ste_type == ClassBlock && st->st_cur->ste_in_conditional_block)
+            || st->st_cur->ste_type == ModuleBlock))
             && !st->st_cur->ste_has_conditional_annotations)
     {
         st->st_cur->ste_has_conditional_annotations = 1;