https://github.com/python/cpython/commit/922049b613d155ade4c4a8f83452767bea003a9f commit: 922049b613d155ade4c4a8f83452767bea003a9f branch: main author: Jelle Zijlstra <jelle.zijls...@gmail.com> committer: JelleZijlstra <jelle.zijls...@gmail.com> date: 2025-04-28T06:10:28-07:00 summary:
gh-130907: Treat all module-level annotations as conditional (#131550) files: A Lib/test/typinganndata/partialexecution/__init__.py A Lib/test/typinganndata/partialexecution/a.py A Lib/test/typinganndata/partialexecution/b.py A Misc/NEWS.d/next/Core_and_Builtins/2025-03-21-08-47-36.gh-issue-130907.rGg-ge.rst M Include/internal/pycore_compile.h M Include/internal/pycore_instruction_sequence.h M Include/internal/pycore_opcode_metadata.h M Include/opcode_ids.h M Lib/_opcode_metadata.py M Lib/test/test_compiler_codegen.py M Lib/test/test_dis.py M Lib/test/test_grammar.py M Lib/test/test_type_annotations.py M Makefile.pre.in M Objects/moduleobject.c M Python/bytecodes.c M Python/codegen.c M Python/compile.c M Python/flowgraph.c M Python/instruction_sequence.c M Python/symtable.c diff --git a/Include/internal/pycore_compile.h b/Include/internal/pycore_compile.h index b98dfb0cebbd3f..a606c2afe0a234 100644 --- a/Include/internal/pycore_compile.h +++ b/Include/internal/pycore_compile.h @@ -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, diff --git a/Include/internal/pycore_instruction_sequence.h b/Include/internal/pycore_instruction_sequence.h index 099f2fd124007a..b5c927735374be 100644 --- a/Include/internal/pycore_instruction_sequence.h +++ b/Include/internal/pycore_instruction_sequence.h @@ -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); diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index c43d60175d5449..3b881b30b054f1 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -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++) { diff --git a/Include/opcode_ids.h b/Include/opcode_ids.h index 898dc580f4148e..d93f028b732406 100644 --- a/Include/opcode_ids.h +++ b/Include/opcode_ids.h @@ -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 diff --git a/Lib/_opcode_metadata.py b/Lib/_opcode_metadata.py index 15900265a01270..4d30b6503fd1e9 100644 --- a/Lib/_opcode_metadata.py +++ b/Lib/_opcode_metadata.py @@ -350,16 +350,17 @@ '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 diff --git a/Lib/test/test_compiler_codegen.py b/Lib/test/test_compiler_codegen.py index cc9ecc7e38917b..d02937c84d9534 100644 --- a/Lib/test/test_compiler_codegen.py +++ b/Lib/test/test_compiler_codegen.py @@ -26,6 +26,7 @@ def test_if_expression(self): 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 @@ def test_for_loop(self): 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 @@ def f(x): expected = [ # Function definition ('RESUME', 0), + ('ANNOTATIONS_PLACEHOLDER', None), ('LOAD_CONST', 0), ('MAKE_FUNCTION', None), ('STORE_NAME', 0), @@ -106,6 +109,7 @@ def g(): expected = [ # Function definition ('RESUME', 0), + ('ANNOTATIONS_PLACEHOLDER', None), ('LOAD_CONST', 0), ('MAKE_FUNCTION', None), ('STORE_NAME', 0), diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index b53a3656429300..f2586fcee57d87 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -381,24 +381,36 @@ def wrap_func_w_kwargs(): # 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 @@ def test_boundaries(self): 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 diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py index 3ea4e47ca50a16..35cd6984267b3b 100644 --- a/Lib/test/test_grammar.py +++ b/Lib/test/test_grammar.py @@ -383,9 +383,10 @@ def test_var_annot_simple_exec(self): 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 = {} diff --git a/Lib/test/test_type_annotations.py b/Lib/test/test_type_annotations.py index fb441976694b34..b72d3dbe516974 100644 --- a/Lib/test/test_type_annotations.py +++ b/Lib/test/test_type_annotations.py @@ -3,7 +3,7 @@ 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 D(metaclass=C): 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 index 00000000000000..c39074ea84b7e6 --- /dev/null +++ b/Lib/test/typinganndata/partialexecution/__init__.py @@ -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 index 00000000000000..ed0b8dcbd55530 --- /dev/null +++ b/Lib/test/typinganndata/partialexecution/a.py @@ -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 index 00000000000000..36b8d2e52a3c63 --- /dev/null +++ b/Lib/test/typinganndata/partialexecution/b.py @@ -0,0 +1,3 @@ +from . import a + +annos = a.__annotations__ diff --git a/Makefile.pre.in b/Makefile.pre.in index 8ec3173a8847b1..925e0a243c9e96 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -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 index 00000000000000..587627e979adbb --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-03-21-08-47-36.gh-issue-130907.rGg-ge.rst @@ -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. diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c index 46dea1534cbcd6..16f1b2cacd4f83 100644 --- a/Objects/moduleobject.c +++ b/Objects/moduleobject.c @@ -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) { diff --git a/Python/bytecodes.c b/Python/bytecodes.c index f5c6e734a79960..40fb0d769afcca 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -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); diff --git a/Python/codegen.c b/Python/codegen.c index 35b46dcdc40950..a7e412f7032c45 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -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); diff --git a/Python/compile.c b/Python/compile.c index 430898b5966842..15ef7214d44b9c 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -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) { diff --git a/Python/flowgraph.c b/Python/flowgraph.c index a0d5690250cffb..9e714bf6c4c54d 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -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; } diff --git a/Python/instruction_sequence.c b/Python/instruction_sequence.c index 4ca85eec345d38..b068e4fa3dbf43 100644 --- a/Python/instruction_sequence.c +++ b/Python/instruction_sequence.c @@ -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; } diff --git a/Python/symtable.c b/Python/symtable.c index 66f6c4a89aaee3..2f13f35072aa5e 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -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; _______________________________________________ Python-checkins mailing list -- python-checkins@python.org To unsubscribe send an email to python-checkins-le...@python.org https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: arch...@mail-archive.com