Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork34.1k
gh-130881: Handle conditionally defined annotations#130935
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes from9 commits
189d4f13d5184d840c35f3408c110109e75ed79a88e0249cc611de40db54ed23eb088da076a0dFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.
Uh oh!
There was an error while loading.Please reload this page.
Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.
Uh oh!
There was an error while loading.Please reload this page.
Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.
Uh oh!
There was an error while loading.Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -457,3 +457,202 @@ class format: pass | ||
| "cannot access free variable 'format' where it is not associated with a value in enclosing scope", | ||
| ): | ||
| ns["f"].__annotations__ | ||
| class ConditionalAnnotationTests(unittest.TestCase): | ||
| def check_scopes(self, code, true_annos, false_annos): | ||
| for scope in ("class", "module"): | ||
| for (cond, expected) in ( | ||
| # Constants (so code might get optimized out) | ||
| (True, true_annos), (False, false_annos), | ||
| # Non-constant expressions | ||
| ("not not len", true_annos), ("not len", false_annos), | ||
| ): | ||
| with self.subTest(scope=scope, cond=cond): | ||
| code_to_run = code.format(cond=cond) | ||
| if scope == "class": | ||
| code_to_run = "class Cls:\n" + textwrap.indent(textwrap.dedent(code_to_run), " " * 4) | ||
| ns = run_code(code_to_run) | ||
| if scope == "class": | ||
| self.assertEqual(ns["Cls"].__annotations__, expected) | ||
| else: | ||
| self.assertEqual(ns["__annotate__"](annotationlib.Format.VALUE), | ||
| expected) | ||
tomasr8 marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| def test_with(self): | ||
| code = """ | ||
| class Swallower: | ||
| def __enter__(self): | ||
| pass | ||
| def __exit__(self, *args): | ||
| return True | ||
| with Swallower(): | ||
| if {cond}: | ||
| about_to_raise: int | ||
| raise Exception | ||
| in_with: "with" | ||
| """ | ||
| self.check_scopes(code, {"about_to_raise": int}, {"in_with": "with"}) | ||
| def test_simple_if(self): | ||
tomasr8 marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| code = """ | ||
| if {cond}: | ||
| in_if: "if" | ||
| else: | ||
| in_if: "else" | ||
| """ | ||
| self.check_scopes(code, {"in_if": "if"}, {"in_if": "else"}) | ||
| def test_if_elif(self): | ||
| code = """ | ||
| if not len: | ||
| in_if: "if" | ||
| elif {cond}: | ||
| in_elif: "elif" | ||
| else: | ||
| in_else: "else" | ||
| """ | ||
| self.check_scopes( | ||
| code, | ||
| {"in_elif": "elif"}, | ||
| {"in_else": "else"} | ||
| ) | ||
| def test_try(self): | ||
| code = """ | ||
| try: | ||
| if {cond}: | ||
| raise Exception | ||
| in_try: "try" | ||
| except Exception: | ||
| in_except: "except" | ||
| finally: | ||
| in_finally: "finally" | ||
| """ | ||
| self.check_scopes( | ||
| code, | ||
| {"in_except": "except", "in_finally": "finally"}, | ||
| {"in_try": "try", "in_finally": "finally"} | ||
| ) | ||
| def test_try_star(self): | ||
| code = """ | ||
| try: | ||
| if {cond}: | ||
| raise Exception | ||
| in_try_star: "try" | ||
| except* Exception: | ||
| in_except_star: "except" | ||
| finally: | ||
| in_finally: "finally" | ||
| """ | ||
| self.check_scopes( | ||
| code, | ||
| {"in_except_star": "except", "in_finally": "finally"}, | ||
| {"in_try_star": "try", "in_finally": "finally"} | ||
| ) | ||
| def test_while(self): | ||
| code = """ | ||
| while {cond}: | ||
| in_while: "while" | ||
| break | ||
| else: | ||
| in_else: "else" | ||
| """ | ||
| self.check_scopes( | ||
| code, | ||
| {"in_while": "while"}, | ||
| {"in_else": "else"} | ||
| ) | ||
| def test_for(self): | ||
| code = """ | ||
| for _ in ([1] if {cond} else []): | ||
| in_for: "for" | ||
| else: | ||
| in_else: "else" | ||
| """ | ||
| self.check_scopes( | ||
| code, | ||
| {"in_for": "for", "in_else": "else"}, | ||
| {"in_else": "else"} | ||
| ) | ||
| def test_match(self): | ||
| code = """ | ||
| match {cond}: | ||
| case True: | ||
| x: "true" | ||
| case False: | ||
| x: "false" | ||
| """ | ||
| self.check_scopes( | ||
| code, | ||
| {"x": "true"}, | ||
| {"x": "false"} | ||
| ) | ||
| def test_nesting_override(self): | ||
| code = """ | ||
| if {cond}: | ||
| x: "foo" | ||
| if {cond}: | ||
| x: "bar" | ||
| """ | ||
| self.check_scopes( | ||
| code, | ||
| {"x": "bar"}, | ||
| {} | ||
| ) | ||
| def test_nesting_outer(self): | ||
tomasr8 marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| code = """ | ||
| if {cond}: | ||
| outer_before: "outer_before" | ||
| if len: | ||
| inner_if: "inner_if" | ||
| else: | ||
| inner_else: "inner_else" | ||
| outer_after: "outer_after" | ||
| """ | ||
| self.check_scopes( | ||
| code, | ||
| {"outer_before": "outer_before", "inner_if": "inner_if", | ||
| "outer_after": "outer_after"}, | ||
| {} | ||
| ) | ||
| def test_nesting_inner(self): | ||
| code = """ | ||
| if len: | ||
tomasr8 marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| outer_before: "outer_before" | ||
| if {cond}: | ||
| inner_if: "inner_if" | ||
| else: | ||
| inner_else: "inner_else" | ||
| outer_after: "outer_after" | ||
| """ | ||
| self.check_scopes( | ||
| code, | ||
| {"outer_before": "outer_before", "inner_if": "inner_if", | ||
| "outer_after": "outer_after"}, | ||
| {"outer_before": "outer_before", "inner_else": "inner_else", | ||
| "outer_after": "outer_after"}, | ||
| ) | ||
| def test_non_name_annotations(self): | ||
| code = """ | ||
| before: "before" | ||
| if {cond}: | ||
| a = "x" | ||
| a[0]: int | ||
| else: | ||
| a = object() | ||
| a.b: str | ||
| after: "after" | ||
| """ | ||
| expected = {"before": "before", "after": "after"} | ||
| self.check_scopes(code, expected, expected) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| Annotations at the class and module level that are conditionally defined are | ||
| now only reflected in ``__annotations__`` if the block they are in is | ||
| executed. Patch by Jelle Zijlstra. |
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.