Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-119180: PEP 649 compiler changes #119361

Merged
merged 77 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
985f8df
Add __annotate__ descriptors
JelleZijlstra May 20, 2024
c822ffa
fix refleaks
JelleZijlstra May 20, 2024
e80095e
blurb
JelleZijlstra May 20, 2024
90ff2c4
Fix some tests
JelleZijlstra May 20, 2024
026c0ff
regen globals
JelleZijlstra May 20, 2024
7968744
Some initial work
JelleZijlstra May 20, 2024
4e54197
Add bytecode for adding annotate
JelleZijlstra May 20, 2024
4469b32
compiler changes
JelleZijlstra May 20, 2024
47d672e
Functions work
JelleZijlstra May 21, 2024
ab9359c
Raise AssertionError on the wrong format
JelleZijlstra May 21, 2024
e65d55a
Merge remote-tracking branch 'upstream/main' into pep649-compile
JelleZijlstra May 21, 2024
e50cd62
Modules and classes
JelleZijlstra May 21, 2024
afae5c0
attempts
JelleZijlstra May 21, 2024
e5a7b1a
Fixes
JelleZijlstra May 21, 2024
3f26d44
Merge remote-tracking branch 'upstream/main' into pep649-compile
JelleZijlstra May 21, 2024
31a4471
Fix code object name
JelleZijlstra May 21, 2024
fbb1d88
Start fixing test_type_annotations
JelleZijlstra May 22, 2024
f452eb2
Fix class scoping
JelleZijlstra May 22, 2024
8c4b4e3
unyielding
JelleZijlstra May 22, 2024
cbf9a3d
Fix test_typing
JelleZijlstra May 22, 2024
5d182fc
Fix test_type_parmas
JelleZijlstra May 22, 2024
ce98c19
Add test, add to inspect
JelleZijlstra May 22, 2024
e0578fc
Use inspect constants
JelleZijlstra May 22, 2024
ed16167
test_grammar tweaks
JelleZijlstra May 22, 2024
f38de20
fix test_positional_only_arg
JelleZijlstra May 22, 2024
87baca2
Fix test_module
JelleZijlstra May 22, 2024
355d3df
Fix symtable tests
JelleZijlstra May 22, 2024
f9d81b6
fix test_pydoc
JelleZijlstra May 22, 2024
dd1f64a
fix test_traceback
JelleZijlstra May 22, 2024
62f5b3b
fix test_opcodes
JelleZijlstra May 22, 2024
b66ad8b
Merge branch 'main' into pep649-compile
JelleZijlstra May 22, 2024
1a63f5d
Raise NotImplementedError
JelleZijlstra May 22, 2024
82c0dbc
Merge remote-tracking branch 'upstream/main' into pep649-compile
JelleZijlstra May 22, 2024
a0c39b5
blurb
JelleZijlstra May 22, 2024
083bbc5
Fix test_dis
JelleZijlstra May 22, 2024
de1b235
Remove broken tests
JelleZijlstra May 22, 2024
1c98fe5
Merge remote-tracking branch 'upstream/main' into pep649-compile
JelleZijlstra May 24, 2024
5f5cf11
No deferred evaluation in interactive mode
JelleZijlstra May 24, 2024
77f3b1c
gh-119443: Turn off from __future__ import annotations in REPL
JelleZijlstra May 24, 2024
4217830
Fix refleak
JelleZijlstra May 24, 2024
13f5d76
Fix another refleak
JelleZijlstra May 24, 2024
a121e1a
Merge remote-tracking branch 'upstream/main' into pep649-compile
JelleZijlstra May 24, 2024
242301c
Exit scope correctly on error
JelleZijlstra May 24, 2024
239ba23
fix test
JelleZijlstra May 24, 2024
b62e04c
Initialize field
JelleZijlstra May 25, 2024
c8a9294
Merge remote-tracking branch 'upstream/main' into pep649-compile
JelleZijlstra May 25, 2024
5ae206d
self-review
JelleZijlstra May 25, 2024
24fd328
Fix crash found by CIFuzz
JelleZijlstra May 25, 2024
0f9d0c5
Merge remote-tracking branch 'upstream/main' into pep649-compile
JelleZijlstra May 29, 2024
c181864
fix merge
JelleZijlstra May 29, 2024
0befff5
Name the function as __annotate__
JelleZijlstra May 29, 2024
ada6573
Merge remote-tracking branch 'upstream/main' into pep649-compile
JelleZijlstra May 29, 2024
ae7714c
Replace find_ann()
JelleZijlstra May 29, 2024
431811a
fix test
JelleZijlstra May 29, 2024
7ca24d3
Remove second compiler pass
JelleZijlstra May 29, 2024
2ab5d07
Fix refleak
JelleZijlstra May 29, 2024
3b4a645
Fix a test
JelleZijlstra May 29, 2024
1dfd02b
Fix bug when there are only non-simple annotations
JelleZijlstra May 29, 2024
daba318
Fix more tests
JelleZijlstra May 29, 2024
0daf0b1
Merge remote-tracking branch 'upstream/main' into pep649-compile
JelleZijlstra May 29, 2024
b066b3d
Remove REPL special case
JelleZijlstra May 29, 2024
7669361
no docstrings in the repl
JelleZijlstra May 29, 2024
c6a1b80
Fix pyrepl test
JelleZijlstra May 29, 2024
5cdbdd7
CR feedback
JelleZijlstra May 30, 2024
e748feb
Merge remote-tracking branch 'upstream/main' into pep649-compile
JelleZijlstra May 31, 2024
a2b4f9e
Merge remote-tracking branch 'upstream/main' into pep649-compile
JelleZijlstra Jun 1, 2024
bd469ab
Still generate __annotate__ if "from __future__ import annotations" i…
JelleZijlstra Jun 1, 2024
278de22
Regen globals
JelleZijlstra Jun 1, 2024
487ea34
Merge branch 'main' into pep649-compile
JelleZijlstra Jun 7, 2024
8f486ba
Merge remote-tracking branch 'upstream/main' into pep649-compile
JelleZijlstra Jun 8, 2024
6b563db
Feedback from Bénédikt Tran
JelleZijlstra Jun 8, 2024
0058b82
add test
JelleZijlstra Jun 9, 2024
517fb56
Merge remote-tracking branch 'upstream/main' into pep649-compile
JelleZijlstra Jun 9, 2024
21f93b6
Merge remote-tracking branch 'upstream/main' into pep649-compile
JelleZijlstra Jun 11, 2024
f72dbca
Undo changes to inspect that we do not want
JelleZijlstra Jun 11, 2024
8674eab
fix
JelleZijlstra Jun 11, 2024
ee11fd9
Fix more tests
JelleZijlstra Jun 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Include/internal/pycore_global_objects_fini_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ struct _Py_global_strings {
STRUCT_FOR_STR(dot, ".")
STRUCT_FOR_STR(dot_locals, ".<locals>")
STRUCT_FOR_STR(empty, "")
STRUCT_FOR_STR(format, ".format")
STRUCT_FOR_STR(generic_base, ".generic_base")
STRUCT_FOR_STR(json_decoder, "json.decoder")
STRUCT_FOR_STR(kwdefaults, ".kwdefaults")
Expand Down Expand Up @@ -234,7 +235,6 @@ struct _Py_global_strings {
STRUCT_FOR_ID(_abstract_)
STRUCT_FOR_ID(_active)
STRUCT_FOR_ID(_align_)
STRUCT_FOR_ID(_annotation)
STRUCT_FOR_ID(_anonymous_)
STRUCT_FOR_ID(_argtypes_)
STRUCT_FOR_ID(_as_parameter_)
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_opcode_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ extern "C" {
#define MAKE_FUNCTION_KWDEFAULTS 0x02
#define MAKE_FUNCTION_ANNOTATIONS 0x04
#define MAKE_FUNCTION_CLOSURE 0x08
#define MAKE_FUNCTION_ANNOTATE 0x10

/* Values used as the oparg for LOAD_COMMON_CONSTANT */
#define CONSTANT_ASSERTIONERROR 0
Expand Down
2 changes: 1 addition & 1 deletion Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions Include/internal/pycore_symtable.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ struct _mod; // Type defined in pycore_ast.h

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

Expand All @@ -126,6 +129,7 @@ extern struct symtable* _PySymtable_Build(
PyObject *filename,
_PyFutureFeatures *future);
extern PySTEntryObject* _PySymtable_Lookup(struct symtable *, void *);
extern int _PySymtable_LookupOptional(struct symtable *, void *, PySTEntryObject **);

extern void _PySymtable_Free(struct symtable *);

Expand Down
3 changes: 0 additions & 3 deletions Include/internal/pycore_unicodeobject_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 1 addition & 7 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,13 +220,7 @@ def get_annotations(obj, *, globals=None, locals=None, eval_str=False):
"""
if isinstance(obj, type):
# class
obj_dict = getattr(obj, '__dict__', None)
if obj_dict and hasattr(obj_dict, 'get'):
ann = obj_dict.get('__annotations__', None)
if isinstance(ann, types.GetSetDescriptorType):
ann = None
else:
ann = None
ann = obj.__annotations__

obj_globals = None
module_name = getattr(obj, '__module__', None)
Expand Down
2 changes: 2 additions & 0 deletions Lib/symtable.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@ def get_methods(self):
if self.__methods is None:
d = {}
for st in self._table.children:
if st.type == _symtable.TYPE_ANNOTATION:
carljm marked this conversation as resolved.
Show resolved Hide resolved
continue
d[st.name] = 1
self.__methods = tuple(d)
return self.__methods
Expand Down
29 changes: 9 additions & 20 deletions Lib/test/test_dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,32 +352,21 @@ def wrap_func_w_kwargs():
dis_annot_stmt_str = """\
0 RESUME 0

2 SETUP_ANNOTATIONS
LOAD_CONST 0 (1)
2 LOAD_CONST 0 (1)
STORE_NAME 0 (x)
LOAD_NAME 1 (int)
LOAD_NAME 2 (__annotations__)
LOAD_CONST 1 ('x')
STORE_SUBSCR

3 LOAD_NAME 3 (fun)
PUSH_NULL
LOAD_CONST 0 (1)
CALL 1
LOAD_NAME 2 (__annotations__)
LOAD_CONST 2 ('y')
STORE_SUBSCR

4 LOAD_CONST 0 (1)
LOAD_NAME 4 (lst)
LOAD_NAME 3 (fun)
LOAD_NAME 1 (lst)
LOAD_NAME 2 (fun)
PUSH_NULL
LOAD_CONST 3 (0)
LOAD_CONST 1 (0)
CALL 1
STORE_SUBSCR
LOAD_NAME 1 (int)
POP_TOP
RETURN_CONST 4 (None)

2 LOAD_CONST 2 (<code object __annotate__ at 0x..., file "<dis>", line 2>)
MAKE_FUNCTION
STORE_NAME 3 (__annotate__)
RETURN_CONST 3 (None)
"""

compound_stmt_str = """\
Expand Down
69 changes: 5 additions & 64 deletions Lib/test/test_grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,16 +306,6 @@ def test_eof_error(self):

var_annot_global: int # a global annotated is necessary for test_var_annot

# custom namespace for testing __annotations__

class CNS:
def __init__(self):
self._dct = {}
def __setitem__(self, item, value):
self._dct[item.lower()] = value
def __getitem__(self, item):
return self._dct[item]


class GrammarTests(unittest.TestCase):

Expand Down Expand Up @@ -446,22 +436,12 @@ class F(C, A):
self.assertEqual(E.__annotations__, {})
self.assertEqual(F.__annotations__, {})


def test_var_annot_metaclass_semantics(self):
class CMeta(type):
@classmethod
def __prepare__(metacls, name, bases, **kwds):
return {'__annotations__': CNS()}
class CC(metaclass=CMeta):
XX: 'ANNOT'
self.assertEqual(CC.__annotations__['xx'], 'ANNOT')

def test_var_annot_module_semantics(self):
self.assertEqual(test.__annotations__, {})
self.assertEqual(ann_module.__annotations__,
{1: 2, 'x': int, 'y': str, 'f': typing.Tuple[int, int], 'u': int | float})
{'x': int, 'y': str, 'f': typing.Tuple[int, int], 'u': int | float})
self.assertEqual(ann_module.M.__annotations__,
{'123': 123, 'o': type})
{'o': type})
self.assertEqual(ann_module2.__annotations__, {})

def test_var_annot_in_module(self):
Expand All @@ -476,51 +456,12 @@ def test_var_annot_in_module(self):
ann_module3.D_bad_ann(5)

def test_var_annot_simple_exec(self):
gns = {}; lns= {}
gns = {}; lns = {}
exec("'docstring'\n"
"__annotations__[1] = 2\n"
"x: int = 5\n", gns, lns)
self.assertEqual(lns["__annotations__"], {1: 2, 'x': int})
with self.assertRaises(KeyError):
gns['__annotations__']

def test_var_annot_custom_maps(self):
# tests with custom locals() and __annotations__
ns = {'__annotations__': CNS()}
exec('X: int; Z: str = "Z"; (w): complex = 1j', ns)
self.assertEqual(ns['__annotations__']['x'], int)
self.assertEqual(ns['__annotations__']['z'], str)
self.assertEqual(lns["__annotate__"](1), {'x': int})
with self.assertRaises(KeyError):
ns['__annotations__']['w']
nonloc_ns = {}
class CNS2:
def __init__(self):
self._dct = {}
def __setitem__(self, item, value):
nonlocal nonloc_ns
self._dct[item] = value
nonloc_ns[item] = value
def __getitem__(self, item):
return self._dct[item]
exec('x: int = 1', {}, CNS2())
self.assertEqual(nonloc_ns['__annotations__']['x'], int)

def test_var_annot_refleak(self):
# complex case: custom locals plus custom __annotations__
# this was causing refleak
cns = CNS()
nonloc_ns = {'__annotations__': cns}
class CNS2:
def __init__(self):
self._dct = {'__annotations__': cns}
def __setitem__(self, item, value):
nonlocal nonloc_ns
self._dct[item] = value
nonloc_ns[item] = value
def __getitem__(self, item):
return self._dct[item]
exec('X: str', {}, CNS2())
self.assertEqual(nonloc_ns['__annotations__']['x'], str)
gns['__annotate__']

def test_var_annot_rhs(self):
ns = {}
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_module/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,8 @@ def test_annotations_are_created_correctly(self):
ann_module4 = import_helper.import_fresh_module(
'test.typinganndata.ann_module4',
)
self.assertFalse("__annotations__" in ann_module4.__dict__)
self.assertEqual(ann_module4.__annotations__, {"a": int, "b": str})
self.assertTrue("__annotations__" in ann_module4.__dict__)
del ann_module4.__annotations__
self.assertFalse("__annotations__" in ann_module4.__dict__)
Expand Down
13 changes: 8 additions & 5 deletions Lib/test/test_opcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,19 @@ class C: pass
def test_use_existing_annotations(self):
ns = {'__annotations__': {1: 2}}
exec('x: int', ns)
self.assertEqual(ns['__annotations__'], {'x': int, 1: 2})
self.assertEqual(ns['__annotations__'], {1: 2})

def test_do_not_recreate_annotations(self):
# Don't rely on the existence of the '__annotations__' global.
with support.swap_item(globals(), '__annotations__', {}):
del globals()['__annotations__']
globals().pop('__annotations__', None)
class C:
del __annotations__
with self.assertRaises(NameError):
x: int
try:
del __annotations__
except NameError:
pass
x: int
self.assertEqual(C.__annotations__, {"x": int})

def test_raise_class_exceptions(self):

Expand Down
5 changes: 4 additions & 1 deletion Lib/test/test_positional_only_arg.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import dis
import pickle
import types
import unittest

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

Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_pyclbr.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ def ismethod(oclass, obj, name):

actualMethods = []
for m in py_item.__dict__.keys():
if m == "__annotate__":
continue
if ismethod(py_item, getattr(py_item, m), m):
actualMethods.append(m)
foundMethods = []
Expand Down
11 changes: 8 additions & 3 deletions Lib/test/test_pydoc/test_pydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ class A(builtins.object)
| __weakref__%s

class B(builtins.object)
| Methods defined here:
|
| __annotate__(...)
Copy link
Member

@AlexWaygood AlexWaygood Jun 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(It would be great if we could generate a nice __text_signature__ for these generated methods, but that definitely doesn't need to be done in this PR)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're actually Python function, so I wonder why pydoc doesn't pick them up. Possibly because the parameter is called .format.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed:

>>> inspect.signature(f.__annotate__)
Traceback (most recent call last):
  File "<python-input-2>", line 1, in <module>
    inspect.signature(f.__annotate__)
    ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
  File "/Users/jelle/py/cpython/Lib/inspect.py", line 3329, in signature
    return Signature.from_callable(obj, follow_wrapped=follow_wrapped,
           ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                   globals=globals, locals=locals, eval_str=eval_str)
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jelle/py/cpython/Lib/inspect.py", line 3055, in from_callable
    return _signature_from_callable(obj, sigcls=cls,
                                    follow_wrapper_chains=follow_wrapped,
                                    globals=globals, locals=locals, eval_str=eval_str)
  File "/Users/jelle/py/cpython/Lib/inspect.py", line 2558, in _signature_from_callable
    return _signature_from_function(sigcls, obj,
                                    skip_bound_arg=skip_bound_arg,
                                    globals=globals, locals=locals, eval_str=eval_str)
  File "/Users/jelle/py/cpython/Lib/inspect.py", line 2403, in _signature_from_function
    parameters.append(Parameter(name, annotation=annotation,
                      ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                kind=kind))
                                ^^^^^^^^^^
  File "/Users/jelle/py/cpython/Lib/inspect.py", line 2750, in __init__
    raise ValueError('{!r} is not a valid parameter name'.format(name))
ValueError: '.format' is not a valid parameter name

Maybe in a followup PR we can figure out a way to make this work. The parameter has to have an illegal name so it doesn't shadow any symbol name that the user might have used.

Copy link
Member

@AlexWaygood AlexWaygood Jun 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter has to have an illegal name so it doesn't shadow any symbol name that the user might have used.

I'm not sure I 100% understand this point, and no tests seem to fail if I make this change locally to your PR branch and recompile (and I verified that it means that __annotate__ methods have valid signatures as per inspect.signature):

diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h
index 009802c4416..899b19ae941 100644
--- a/Include/internal/pycore_global_strings.h
+++ b/Include/internal/pycore_global_strings.h
@@ -45,7 +45,7 @@ struct _Py_global_strings {
         STRUCT_FOR_STR(dot, ".")
         STRUCT_FOR_STR(dot_locals, ".<locals>")
         STRUCT_FOR_STR(empty, "")
-        STRUCT_FOR_STR(format, ".format")
+        STRUCT_FOR_STR(format, "format")
         STRUCT_FOR_STR(generic_base, ".generic_base")
         STRUCT_FOR_STR(json_decoder, "json.decoder")
         STRUCT_FOR_STR(kwdefaults, ".kwdefaults")
diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h
index ff5b6ee8e0f..e8b5173a457 100644
--- a/Include/internal/pycore_runtime_init_generated.h
+++ b/Include/internal/pycore_runtime_init_generated.h
@@ -554,7 +554,7 @@ extern "C" {
     INIT_STR(dot, "."), \
     INIT_STR(dot_locals, ".<locals>"), \
     INIT_STR(empty, ""), \
-    INIT_STR(format, ".format"), \
+    INIT_STR(format, "format"), \
     INIT_STR(generic_base, ".generic_base"), \
     INIT_STR(json_decoder, "json.decoder"), \
     INIT_STR(kwdefaults, ".kwdefaults"), \
diff --git a/Python/compile.c b/Python/compile.c
index c3372766d0b..7535067ded0 100644
--- a/Python/compile.c
+++ b/Python/compile.c
@@ -1447,7 +1447,7 @@ compiler_setup_annotations_scope(struct compiler *c, location loc,
     }
     c->u->u_metadata.u_posonlyargcount = 1;
     // if .format != 1: raise NotImplementedError
-    _Py_DECLARE_STR(format, ".format");
+    _Py_DECLARE_STR(format, "format");
     ADDOP_I(c, loc, LOAD_FAST, 0);
     ADDOP_LOAD_CONST(c, loc, _PyLong_GetOne());
     ADDOP_I(c, loc, COMPARE_OP, (Py_NE << 5) | compare_masks[Py_NE]);
diff --git a/Python/symtable.c b/Python/symtable.c
index 23fc4a0ec03..0154092a905 100644
--- a/Python/symtable.c
+++ b/Python/symtable.c
@@ -2511,7 +2511,7 @@ symtable_visit_annotation(struct symtable *st, expr_ty annotation, void *key)
             }
         }
 
-        _Py_DECLARE_STR(format, ".format");
+        _Py_DECLARE_STR(format, "format");
         // The generated __annotate__ function takes a single parameter with the
         // internal name ".format".
         if (!symtable_add_def(st, &_Py_STR(format), DEF_PARAM,
@@ -2570,7 +2570,7 @@ symtable_visit_annotations(struct symtable *st, stmt_ty o, arguments_ty a, expr_
             return 0;
         }
     }
-    _Py_DECLARE_STR(format, ".format");
+    _Py_DECLARE_STR(format, "format");
     // We need to insert code that reads this "parameter" to the function.
     if (!symtable_add_def(st, &_Py_STR(format), DEF_PARAM, LOCATION(o))) {
         return 0;

This also might be worth a brief mention in PEP-749, since PEP-649 seems to specify that the signature of __annotate__ should be __annotate__(format: int).

In any event, this can definitely be tackled in a followup; I don't want to block this PR!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try something like this:

class format: pass

def f(x: format): pass

print(f.__annotations__)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, makes sense! Worth adding a test like that?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, the problem is that any such test can only tell us about one specific name.

Copy link
Member

@AlexWaygood AlexWaygood Jun 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, it doesn't verify any fundamental invariants about __annotate__ — but I still think it's useful to have such a test, so that the CI goes obviously red if someone else comes along in the future and tries to do the "obvious fix" to get inspect.signature() working for these methods.

Thanks for adding it!

|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__%s
Expand All @@ -87,8 +92,6 @@ class B(builtins.object)
| Data and other attributes defined here:
|
| NO_MEANING = 'eggs'
|
| __annotations__ = {'NO_MEANING': <class 'str'>}

class C(builtins.object)
| Methods defined here:
Expand Down Expand Up @@ -176,6 +179,9 @@ class A(builtins.object)
list of weak references to the object

class B(builtins.object)
Methods defined here:
__annotate__(...)
----------------------------------------------------------------------
Data descriptors defined here:
__dict__
dictionary for instance variables
Expand All @@ -184,7 +190,6 @@ class B(builtins.object)
----------------------------------------------------------------------
Data and other attributes defined here:
NO_MEANING = 'eggs'
__annotations__ = {'NO_MEANING': <class 'str'>}


class C(builtins.object)
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_pyrepl/test_interact.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def test_runsource_shows_syntax_error_for_failed_compilation(self):

def test_no_active_future(self):
console = InteractiveColoredConsole()
source = "x: int = 1; print(__annotations__)"
source = "x: int = 1; print(__annotate__(1))"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does printing __annotations__ not work here? Shouldn't it call __annotate__ under the hood anyway?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Printing a global doesn't go through the descriptor protocol, so printing __annotations__ will just throw a NameError; it is added to the namespace only when the __annotate__ descriptor is called.

f = io.StringIO()
with contextlib.redirect_stdout(f):
result = console.runsource(source)
Expand Down
6 changes: 4 additions & 2 deletions Lib/test/test_symtable.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,14 @@ def test_assigned(self):

def test_annotated(self):
st1 = symtable.symtable('def f():\n x: int\n', 'test', 'exec')
st2 = st1.get_children()[0]
st2 = st1.get_children()[1]
carljm marked this conversation as resolved.
Show resolved Hide resolved
self.assertEqual(st2.get_type(), "function")
self.assertTrue(st2.lookup('x').is_local())
self.assertTrue(st2.lookup('x').is_annotated())
self.assertFalse(st2.lookup('x').is_global())
st3 = symtable.symtable('def f():\n x = 1\n', 'test', 'exec')
st4 = st3.get_children()[0]
st4 = st3.get_children()[1]
self.assertEqual(st4.get_type(), "function")
self.assertTrue(st4.lookup('x').is_local())
self.assertFalse(st4.lookup('x').is_annotated())

Expand Down
Loading
Loading