Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 898e6b3

Browse filesBrowse files
gh-130881: Handle conditionally defined annotations (#130935)
1 parent 7bb41ae commit 898e6b3
Copy full SHA for 898e6b3

11 files changed

+505
-73
lines changed

‎Include/internal/pycore_compile.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_compile.h
+9-4Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,9 @@ void _PyCompile_ExitScope(struct _PyCompiler *c);
134134
Py_ssize_t _PyCompile_AddConst(struct _PyCompiler *c, PyObject *o);
135135
_PyInstructionSequence *_PyCompile_InstrSequence(struct _PyCompiler *c);
136136
int _PyCompile_FutureFeatures(struct _PyCompiler *c);
137-
PyObject *_PyCompile_DeferredAnnotations(struct _PyCompiler *c);
137+
void _PyCompile_DeferredAnnotations(
138+
struct _PyCompiler *c, PyObject **deferred_annotations,
139+
PyObject **conditional_annotation_indices);
138140
PyObject *_PyCompile_Mangle(struct _PyCompiler *c, PyObject *name);
139141
PyObject *_PyCompile_MaybeMangle(struct _PyCompiler *c, PyObject *name);
140142
int _PyCompile_MaybeAddStaticAttributeToClass(struct _PyCompiler *c, expr_ty e);
@@ -178,13 +180,16 @@ int _PyCompile_TweakInlinedComprehensionScopes(struct _PyCompiler *c, _Py_Source
178180
_PyCompile_InlinedComprehensionState *state);
179181
int _PyCompile_RevertInlinedComprehensionScopes(struct _PyCompiler *c, _Py_SourceLocation loc,
180182
_PyCompile_InlinedComprehensionState *state);
181-
int _PyCompile_AddDeferredAnnotaion(struct _PyCompiler *c, stmt_ty s);
183+
int _PyCompile_AddDeferredAnnotation(struct _PyCompiler *c, stmt_ty s,
184+
PyObject **conditional_annotation_index);
185+
void _PyCompile_EnterConditionalBlock(struct _PyCompiler *c);
186+
void _PyCompile_LeaveConditionalBlock(struct _PyCompiler *c);
182187

183188
int _PyCodegen_AddReturnAtEnd(struct _PyCompiler *c, int addNone);
184189
int _PyCodegen_EnterAnonymousScope(struct _PyCompiler* c, mod_ty mod);
185190
int _PyCodegen_Expression(struct _PyCompiler *c, expr_ty e);
186-
int _PyCodegen_Body(struct _PyCompiler *c, _Py_SourceLocation loc, asdl_stmt_seq *stmts,
187-
bool is_interactive);
191+
int _PyCodegen_Module(struct _PyCompiler *c, _Py_SourceLocation loc, asdl_stmt_seq *stmts,
192+
bool is_interactive);
188193

189194
int _PyCompile_ConstCacheMergeOne(PyObject *const_cache, PyObject **obj);
190195

‎Include/internal/pycore_global_objects_fini_generated.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_global_objects_fini_generated.h
+1Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Include/internal/pycore_global_strings.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_global_strings.h
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ struct _Py_global_strings {
9595
STRUCT_FOR_ID(__classdict__)
9696
STRUCT_FOR_ID(__classdictcell__)
9797
STRUCT_FOR_ID(__complex__)
98+
STRUCT_FOR_ID(__conditional_annotations__)
9899
STRUCT_FOR_ID(__contains__)
99100
STRUCT_FOR_ID(__ctypes_from_outparam__)
100101
STRUCT_FOR_ID(__del__)

‎Include/internal/pycore_runtime_init_generated.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_runtime_init_generated.h
+1Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Include/internal/pycore_symtable.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_symtable.h
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ typedef struct _symtable_entry {
124124
enclosing class scope */
125125
unsigned ste_has_docstring : 1; /* true if docstring present */
126126
unsigned ste_method : 1; /* true if block is a function block defined in class scope */
127+
unsigned ste_has_conditional_annotations : 1; /* true if block has conditionally executed annotations */
128+
unsigned ste_in_conditional_block : 1; /* set while we are inside a conditionally executed block */
127129
int ste_comp_iter_expr; /* non-zero if visiting a comprehension range expression */
128130
_Py_SourceLocation ste_loc; /* source location of block */
129131
struct _symtable_entry *ste_annotation_block; /* symbol table entry for this entry's annotations */

‎Include/internal/pycore_unicodeobject_generated.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_unicodeobject_generated.h
+4Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Lib/test/test_type_annotations.py

Copy file name to clipboardExpand all lines: Lib/test/test_type_annotations.py
+199Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,3 +457,202 @@ class format: pass
457457
"cannot access free variable 'format' where it is not associated with a value in enclosing scope",
458458
):
459459
ns["f"].__annotations__
460+
461+
462+
class ConditionalAnnotationTests(unittest.TestCase):
463+
def check_scopes(self, code, true_annos, false_annos):
464+
for scope in ("class", "module"):
465+
for (cond, expected) in (
466+
# Constants (so code might get optimized out)
467+
(True, true_annos), (False, false_annos),
468+
# Non-constant expressions
469+
("not not len", true_annos), ("not len", false_annos),
470+
):
471+
with self.subTest(scope=scope, cond=cond):
472+
code_to_run = code.format(cond=cond)
473+
if scope == "class":
474+
code_to_run = "class Cls:\n" + textwrap.indent(textwrap.dedent(code_to_run), " " * 4)
475+
ns = run_code(code_to_run)
476+
if scope == "class":
477+
self.assertEqual(ns["Cls"].__annotations__, expected)
478+
else:
479+
self.assertEqual(ns["__annotate__"](annotationlib.Format.VALUE),
480+
expected)
481+
482+
def test_with(self):
483+
code = """
484+
class Swallower:
485+
def __enter__(self):
486+
pass
487+
488+
def __exit__(self, *args):
489+
return True
490+
491+
with Swallower():
492+
if {cond}:
493+
about_to_raise: int
494+
raise Exception
495+
in_with: "with"
496+
"""
497+
self.check_scopes(code, {"about_to_raise": int}, {"in_with": "with"})
498+
499+
def test_simple_if(self):
500+
code = """
501+
if {cond}:
502+
in_if: "if"
503+
else:
504+
in_if: "else"
505+
"""
506+
self.check_scopes(code, {"in_if": "if"}, {"in_if": "else"})
507+
508+
def test_if_elif(self):
509+
code = """
510+
if not len:
511+
in_if: "if"
512+
elif {cond}:
513+
in_elif: "elif"
514+
else:
515+
in_else: "else"
516+
"""
517+
self.check_scopes(
518+
code,
519+
{"in_elif": "elif"},
520+
{"in_else": "else"}
521+
)
522+
523+
def test_try(self):
524+
code = """
525+
try:
526+
if {cond}:
527+
raise Exception
528+
in_try: "try"
529+
except Exception:
530+
in_except: "except"
531+
finally:
532+
in_finally: "finally"
533+
"""
534+
self.check_scopes(
535+
code,
536+
{"in_except": "except", "in_finally": "finally"},
537+
{"in_try": "try", "in_finally": "finally"}
538+
)
539+
540+
def test_try_star(self):
541+
code = """
542+
try:
543+
if {cond}:
544+
raise Exception
545+
in_try_star: "try"
546+
except* Exception:
547+
in_except_star: "except"
548+
finally:
549+
in_finally: "finally"
550+
"""
551+
self.check_scopes(
552+
code,
553+
{"in_except_star": "except", "in_finally": "finally"},
554+
{"in_try_star": "try", "in_finally": "finally"}
555+
)
556+
557+
def test_while(self):
558+
code = """
559+
while {cond}:
560+
in_while: "while"
561+
break
562+
else:
563+
in_else: "else"
564+
"""
565+
self.check_scopes(
566+
code,
567+
{"in_while": "while"},
568+
{"in_else": "else"}
569+
)
570+
571+
def test_for(self):
572+
code = """
573+
for _ in ([1] if {cond} else []):
574+
in_for: "for"
575+
else:
576+
in_else: "else"
577+
"""
578+
self.check_scopes(
579+
code,
580+
{"in_for": "for", "in_else": "else"},
581+
{"in_else": "else"}
582+
)
583+
584+
def test_match(self):
585+
code = """
586+
match {cond}:
587+
case True:
588+
x: "true"
589+
case False:
590+
x: "false"
591+
"""
592+
self.check_scopes(
593+
code,
594+
{"x": "true"},
595+
{"x": "false"}
596+
)
597+
598+
def test_nesting_override(self):
599+
code = """
600+
if {cond}:
601+
x: "foo"
602+
if {cond}:
603+
x: "bar"
604+
"""
605+
self.check_scopes(
606+
code,
607+
{"x": "bar"},
608+
{}
609+
)
610+
611+
def test_nesting_outer(self):
612+
code = """
613+
if {cond}:
614+
outer_before: "outer_before"
615+
if len:
616+
inner_if: "inner_if"
617+
else:
618+
inner_else: "inner_else"
619+
outer_after: "outer_after"
620+
"""
621+
self.check_scopes(
622+
code,
623+
{"outer_before": "outer_before", "inner_if": "inner_if",
624+
"outer_after": "outer_after"},
625+
{}
626+
)
627+
628+
def test_nesting_inner(self):
629+
code = """
630+
if len:
631+
outer_before: "outer_before"
632+
if {cond}:
633+
inner_if: "inner_if"
634+
else:
635+
inner_else: "inner_else"
636+
outer_after: "outer_after"
637+
"""
638+
self.check_scopes(
639+
code,
640+
{"outer_before": "outer_before", "inner_if": "inner_if",
641+
"outer_after": "outer_after"},
642+
{"outer_before": "outer_before", "inner_else": "inner_else",
643+
"outer_after": "outer_after"},
644+
)
645+
646+
def test_non_name_annotations(self):
647+
code = """
648+
before: "before"
649+
if {cond}:
650+
a = "x"
651+
a[0]: int
652+
else:
653+
a = object()
654+
a.b: str
655+
after: "after"
656+
"""
657+
expected = {"before": "before", "after": "after"}
658+
self.check_scopes(code, expected, expected)
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Annotations at the class and module level that are conditionally defined are
2+
now only reflected in ``__annotations__`` if the block they are in is
3+
executed. Patch by Jelle Zijlstra.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.