|
3 | 3 | import dis |
4 | 4 | import enum |
5 | 5 | import os |
| 6 | +import re |
6 | 7 | import sys |
7 | 8 | import types |
8 | 9 | import unittest |
@@ -862,6 +863,32 @@ def test_null_bytes(self): |
862 | 863 | msg="source code string cannot contain null bytes"): |
863 | 864 | ast.parse("a\0b") |
864 | 865 |
|
| 866 | + def assert_none_check(self, node: type[ast.AST], attr: str, source: str) -> None: |
| 867 | + with self.subTest(f"{node.__name__}.{attr}"): |
| 868 | + tree = ast.parse(source) |
| 869 | + found = 0 |
| 870 | + for child in ast.walk(tree): |
| 871 | + if isinstance(child, node): |
| 872 | + setattr(child, attr, None) |
| 873 | + found += 1 |
| 874 | + self.assertEqual(found, 1) |
| 875 | + e = re.escape(f"field '{attr}' is required for {node.__name__}") |
| 876 | + with self.assertRaisesRegex(ValueError, f"^{e}$"): |
| 877 | + compile(tree, "<test>", "exec") |
| 878 | + |
| 879 | + def test_none_checks(self) -> None: |
| 880 | + tests = [ |
| 881 | + (ast.alias, "name", "import spam as SPAM"), |
| 882 | + (ast.arg, "arg", "def spam(SPAM): spam"), |
| 883 | + (ast.comprehension, "target", "[spam for SPAM in spam]"), |
| 884 | + (ast.comprehension, "iter", "[spam for spam in SPAM]"), |
| 885 | + (ast.keyword, "value", "spam(**SPAM)"), |
| 886 | + (ast.match_case, "pattern", "match spam:\n case SPAM: spam"), |
| 887 | + (ast.withitem, "context_expr", "with SPAM: spam"), |
| 888 | + ] |
| 889 | + for node, attr, source in tests: |
| 890 | + self.assert_none_check(node, attr, source) |
| 891 | + |
865 | 892 | class ASTHelpers_Test(unittest.TestCase): |
866 | 893 | maxDiff = None |
867 | 894 |
|
|
0 commit comments