Skip to content

Commit 9b8611e

Browse files
gh-119180: PEP 649 compiler changes (#119361)
1 parent 02c1dff commit 9b8611e

28 files changed

+609
-328
lines changed

Include/internal/pycore_global_objects_fini_generated.h

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_global_strings.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ struct _Py_global_strings {
4545
STRUCT_FOR_STR(dot, ".")
4646
STRUCT_FOR_STR(dot_locals, ".<locals>")
4747
STRUCT_FOR_STR(empty, "")
48+
STRUCT_FOR_STR(format, ".format")
4849
STRUCT_FOR_STR(generic_base, ".generic_base")
4950
STRUCT_FOR_STR(json_decoder, "json.decoder")
5051
STRUCT_FOR_STR(kwdefaults, ".kwdefaults")
@@ -234,7 +235,6 @@ struct _Py_global_strings {
234235
STRUCT_FOR_ID(_abstract_)
235236
STRUCT_FOR_ID(_active)
236237
STRUCT_FOR_ID(_align_)
237-
STRUCT_FOR_ID(_annotation)
238238
STRUCT_FOR_ID(_anonymous_)
239239
STRUCT_FOR_ID(_argtypes_)
240240
STRUCT_FOR_ID(_as_parameter_)

Include/internal/pycore_opcode_utils.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ extern "C" {
5757
#define MAKE_FUNCTION_KWDEFAULTS 0x02
5858
#define MAKE_FUNCTION_ANNOTATIONS 0x04
5959
#define MAKE_FUNCTION_CLOSURE 0x08
60+
#define MAKE_FUNCTION_ANNOTATE 0x10
6061

6162
/* Values used as the oparg for LOAD_COMMON_CONSTANT */
6263
#define CONSTANT_ASSERTIONERROR 0

Include/internal/pycore_runtime_init_generated.h

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_symtable.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ struct _mod; // Type defined in pycore_ast.h
1212

1313
typedef enum _block_type {
1414
FunctionBlock, ClassBlock, ModuleBlock,
15-
// Used for annotations if 'from __future__ import annotations' is active.
16-
// Annotation blocks cannot bind names and are not evaluated.
15+
// Used for annotations. If 'from __future__ import annotations' is active,
16+
// annotation blocks cannot bind names and are not evaluated. Otherwise, they
17+
// are lazily evaluated (see PEP 649).
1718
AnnotationBlock,
1819
// Used for generics and type aliases. These work mostly like functions
1920
// (see PEP 695 for details). The three different blocks function identically;
@@ -89,6 +90,7 @@ typedef struct _symtable_entry {
8990
including free refs to globals */
9091
unsigned ste_generator : 1; /* true if namespace is a generator */
9192
unsigned ste_coroutine : 1; /* true if namespace is a coroutine */
93+
unsigned ste_annotations_used : 1; /* true if there are any annotations in this scope */
9294
_Py_comprehension_ty ste_comprehension; /* Kind of comprehension (if any) */
9395
unsigned ste_varargs : 1; /* true if block has varargs */
9496
unsigned ste_varkeywords : 1; /* true if block has varkeywords */
@@ -110,6 +112,7 @@ typedef struct _symtable_entry {
110112
int ste_end_col_offset; /* end offset of first line of block */
111113
int ste_opt_lineno; /* lineno of last exec or import * */
112114
int ste_opt_col_offset; /* offset of last exec or import * */
115+
struct _symtable_entry *ste_annotation_block; /* symbol table entry for this entry's annotations */
113116
struct symtable *ste_table;
114117
} PySTEntryObject;
115118

@@ -126,6 +129,7 @@ extern struct symtable* _PySymtable_Build(
126129
PyObject *filename,
127130
_PyFutureFeatures *future);
128131
extern PySTEntryObject* _PySymtable_Lookup(struct symtable *, void *);
132+
extern int _PySymtable_LookupOptional(struct symtable *, void *, PySTEntryObject **);
129133

130134
extern void _PySymtable_Free(struct symtable *);
131135

Include/internal/pycore_unicodeobject_generated.h

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/inspect.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -220,13 +220,7 @@ def get_annotations(obj, *, globals=None, locals=None, eval_str=False):
220220
"""
221221
if isinstance(obj, type):
222222
# class
223-
obj_dict = getattr(obj, '__dict__', None)
224-
if obj_dict and hasattr(obj_dict, 'get'):
225-
ann = obj_dict.get('__annotations__', None)
226-
if isinstance(ann, types.GetSetDescriptorType):
227-
ann = None
228-
else:
229-
ann = None
223+
ann = obj.__annotations__
230224

231225
obj_globals = None
232226
module_name = getattr(obj, '__module__', None)

Lib/symtable.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,8 @@ def get_methods(self):
222222
if self.__methods is None:
223223
d = {}
224224
for st in self._table.children:
225+
if st.type == _symtable.TYPE_ANNOTATION:
226+
continue
225227
d[st.name] = 1
226228
self.__methods = tuple(d)
227229
return self.__methods

Lib/test/test_dis.py

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -352,32 +352,21 @@ def wrap_func_w_kwargs():
352352
dis_annot_stmt_str = """\
353353
0 RESUME 0
354354
355-
2 SETUP_ANNOTATIONS
356-
LOAD_CONST 0 (1)
355+
2 LOAD_CONST 0 (1)
357356
STORE_NAME 0 (x)
358-
LOAD_NAME 1 (int)
359-
LOAD_NAME 2 (__annotations__)
360-
LOAD_CONST 1 ('x')
361-
STORE_SUBSCR
362-
363-
3 LOAD_NAME 3 (fun)
364-
PUSH_NULL
365-
LOAD_CONST 0 (1)
366-
CALL 1
367-
LOAD_NAME 2 (__annotations__)
368-
LOAD_CONST 2 ('y')
369-
STORE_SUBSCR
370357
371358
4 LOAD_CONST 0 (1)
372-
LOAD_NAME 4 (lst)
373-
LOAD_NAME 3 (fun)
359+
LOAD_NAME 1 (lst)
360+
LOAD_NAME 2 (fun)
374361
PUSH_NULL
375-
LOAD_CONST 3 (0)
362+
LOAD_CONST 1 (0)
376363
CALL 1
377364
STORE_SUBSCR
378-
LOAD_NAME 1 (int)
379-
POP_TOP
380-
RETURN_CONST 4 (None)
365+
366+
2 LOAD_CONST 2 (<code object __annotate__ at 0x..., file "<dis>", line 2>)
367+
MAKE_FUNCTION
368+
STORE_NAME 3 (__annotate__)
369+
RETURN_CONST 3 (None)
381370
"""
382371

383372
compound_stmt_str = """\

Lib/test/test_grammar.py

Lines changed: 5 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -306,16 +306,6 @@ def test_eof_error(self):
306306

307307
var_annot_global: int # a global annotated is necessary for test_var_annot
308308

309-
# custom namespace for testing __annotations__
310-
311-
class CNS:
312-
def __init__(self):
313-
self._dct = {}
314-
def __setitem__(self, item, value):
315-
self._dct[item.lower()] = value
316-
def __getitem__(self, item):
317-
return self._dct[item]
318-
319309

320310
class GrammarTests(unittest.TestCase):
321311

@@ -446,22 +436,12 @@ class F(C, A):
446436
self.assertEqual(E.__annotations__, {})
447437
self.assertEqual(F.__annotations__, {})
448438

449-
450-
def test_var_annot_metaclass_semantics(self):
451-
class CMeta(type):
452-
@classmethod
453-
def __prepare__(metacls, name, bases, **kwds):
454-
return {'__annotations__': CNS()}
455-
class CC(metaclass=CMeta):
456-
XX: 'ANNOT'
457-
self.assertEqual(CC.__annotations__['xx'], 'ANNOT')
458-
459439
def test_var_annot_module_semantics(self):
460440
self.assertEqual(test.__annotations__, {})
461441
self.assertEqual(ann_module.__annotations__,
462-
{1: 2, 'x': int, 'y': str, 'f': typing.Tuple[int, int], 'u': int | float})
442+
{'x': int, 'y': str, 'f': typing.Tuple[int, int], 'u': int | float})
463443
self.assertEqual(ann_module.M.__annotations__,
464-
{'123': 123, 'o': type})
444+
{'o': type})
465445
self.assertEqual(ann_module2.__annotations__, {})
466446

467447
def test_var_annot_in_module(self):
@@ -476,51 +456,12 @@ def test_var_annot_in_module(self):
476456
ann_module3.D_bad_ann(5)
477457

478458
def test_var_annot_simple_exec(self):
479-
gns = {}; lns= {}
459+
gns = {}; lns = {}
480460
exec("'docstring'\n"
481-
"__annotations__[1] = 2\n"
482461
"x: int = 5\n", gns, lns)
483-
self.assertEqual(lns["__annotations__"], {1: 2, 'x': int})
484-
with self.assertRaises(KeyError):
485-
gns['__annotations__']
486-
487-
def test_var_annot_custom_maps(self):
488-
# tests with custom locals() and __annotations__
489-
ns = {'__annotations__': CNS()}
490-
exec('X: int; Z: str = "Z"; (w): complex = 1j', ns)
491-
self.assertEqual(ns['__annotations__']['x'], int)
492-
self.assertEqual(ns['__annotations__']['z'], str)
462+
self.assertEqual(lns["__annotate__"](1), {'x': int})
493463
with self.assertRaises(KeyError):
494-
ns['__annotations__']['w']
495-
nonloc_ns = {}
496-
class CNS2:
497-
def __init__(self):
498-
self._dct = {}
499-
def __setitem__(self, item, value):
500-
nonlocal nonloc_ns
501-
self._dct[item] = value
502-
nonloc_ns[item] = value
503-
def __getitem__(self, item):
504-
return self._dct[item]
505-
exec('x: int = 1', {}, CNS2())
506-
self.assertEqual(nonloc_ns['__annotations__']['x'], int)
507-
508-
def test_var_annot_refleak(self):
509-
# complex case: custom locals plus custom __annotations__
510-
# this was causing refleak
511-
cns = CNS()
512-
nonloc_ns = {'__annotations__': cns}
513-
class CNS2:
514-
def __init__(self):
515-
self._dct = {'__annotations__': cns}
516-
def __setitem__(self, item, value):
517-
nonlocal nonloc_ns
518-
self._dct[item] = value
519-
nonloc_ns[item] = value
520-
def __getitem__(self, item):
521-
return self._dct[item]
522-
exec('X: str', {}, CNS2())
523-
self.assertEqual(nonloc_ns['__annotations__']['x'], str)
464+
gns['__annotate__']
524465

525466
def test_var_annot_rhs(self):
526467
ns = {}

Lib/test/test_module/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,8 @@ def test_annotations_are_created_correctly(self):
360360
ann_module4 = import_helper.import_fresh_module(
361361
'test.typinganndata.ann_module4',
362362
)
363+
self.assertFalse("__annotations__" in ann_module4.__dict__)
364+
self.assertEqual(ann_module4.__annotations__, {"a": int, "b": str})
363365
self.assertTrue("__annotations__" in ann_module4.__dict__)
364366
del ann_module4.__annotations__
365367
self.assertFalse("__annotations__" in ann_module4.__dict__)

Lib/test/test_opcodes.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,19 @@ class C: pass
3939
def test_use_existing_annotations(self):
4040
ns = {'__annotations__': {1: 2}}
4141
exec('x: int', ns)
42-
self.assertEqual(ns['__annotations__'], {'x': int, 1: 2})
42+
self.assertEqual(ns['__annotations__'], {1: 2})
4343

4444
def test_do_not_recreate_annotations(self):
4545
# Don't rely on the existence of the '__annotations__' global.
4646
with support.swap_item(globals(), '__annotations__', {}):
47-
del globals()['__annotations__']
47+
globals().pop('__annotations__', None)
4848
class C:
49-
del __annotations__
50-
with self.assertRaises(NameError):
51-
x: int
49+
try:
50+
del __annotations__
51+
except NameError:
52+
pass
53+
x: int
54+
self.assertEqual(C.__annotations__, {"x": int})
5255

5356
def test_raise_class_exceptions(self):
5457

Lib/test/test_positional_only_arg.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import dis
44
import pickle
5+
import types
56
import unittest
67

78
from test.support import check_syntax_error
@@ -440,7 +441,9 @@ def f(x: not (int is int), /): ...
440441
# without constant folding we end up with
441442
# COMPARE_OP(is), IS_OP (0)
442443
# with constant folding we should expect a IS_OP (1)
443-
codes = [(i.opname, i.argval) for i in dis.get_instructions(g)]
444+
code_obj = next(const for const in g.__code__.co_consts
445+
if isinstance(const, types.CodeType) and const.co_name == "__annotate__")
446+
codes = [(i.opname, i.argval) for i in dis.get_instructions(code_obj)]
444447
self.assertNotIn(('UNARY_NOT', None), codes)
445448
self.assertIn(('IS_OP', 1), codes)
446449

Lib/test/test_pyclbr.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ def ismethod(oclass, obj, name):
109109

110110
actualMethods = []
111111
for m in py_item.__dict__.keys():
112+
if m == "__annotate__":
113+
continue
112114
if ismethod(py_item, getattr(py_item, m), m):
113115
actualMethods.append(m)
114116
foundMethods = []

Lib/test/test_pydoc/test_pydoc.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ class A(builtins.object)
7777
| __weakref__%s
7878
7979
class B(builtins.object)
80+
| Methods defined here:
81+
|
82+
| __annotate__(...)
83+
|
84+
| ----------------------------------------------------------------------
8085
| Data descriptors defined here:
8186
|
8287
| __dict__%s
@@ -87,8 +92,6 @@ class B(builtins.object)
8792
| Data and other attributes defined here:
8893
|
8994
| NO_MEANING = 'eggs'
90-
|
91-
| __annotations__ = {'NO_MEANING': <class 'str'>}
9295
9396
class C(builtins.object)
9497
| Methods defined here:
@@ -176,6 +179,9 @@ class A(builtins.object)
176179
list of weak references to the object
177180
178181
class B(builtins.object)
182+
Methods defined here:
183+
__annotate__(...)
184+
----------------------------------------------------------------------
179185
Data descriptors defined here:
180186
__dict__
181187
dictionary for instance variables
@@ -184,7 +190,6 @@ class B(builtins.object)
184190
----------------------------------------------------------------------
185191
Data and other attributes defined here:
186192
NO_MEANING = 'eggs'
187-
__annotations__ = {'NO_MEANING': <class 'str'>}
188193
189194
190195
class C(builtins.object)

Lib/test/test_pyrepl/test_interact.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def test_runsource_shows_syntax_error_for_failed_compilation(self):
105105

106106
def test_no_active_future(self):
107107
console = InteractiveColoredConsole()
108-
source = "x: int = 1; print(__annotations__)"
108+
source = "x: int = 1; print(__annotate__(1))"
109109
f = io.StringIO()
110110
with contextlib.redirect_stdout(f):
111111
result = console.runsource(source)

Lib/test/test_symtable.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,12 +205,14 @@ def test_assigned(self):
205205

206206
def test_annotated(self):
207207
st1 = symtable.symtable('def f():\n x: int\n', 'test', 'exec')
208-
st2 = st1.get_children()[0]
208+
st2 = st1.get_children()[1]
209+
self.assertEqual(st2.get_type(), "function")
209210
self.assertTrue(st2.lookup('x').is_local())
210211
self.assertTrue(st2.lookup('x').is_annotated())
211212
self.assertFalse(st2.lookup('x').is_global())
212213
st3 = symtable.symtable('def f():\n x = 1\n', 'test', 'exec')
213-
st4 = st3.get_children()[0]
214+
st4 = st3.get_children()[1]
215+
self.assertEqual(st4.get_type(), "function")
214216
self.assertTrue(st4.lookup('x').is_local())
215217
self.assertFalse(st4.lookup('x').is_annotated())
216218

0 commit comments

Comments
 (0)