Skip to content

Commit 43065e7

Browse files
committed
Align compile module snippet with merged canonical coverage
Use the complete snippet merged through #8947 rather than the older mega-only subset. Preserve all existing cases and the additional canonical warning paths. Assisted-by: OpenAI Codex:model identifier unavailable
1 parent cc05fc8 commit 43065e7

1 file changed

Lines changed: 170 additions & 1 deletion

File tree

‎extra_tests/snippets/builtin_compile_module.py‎

Lines changed: 170 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,108 @@
55

66

77
def test_compile_module():
8+
class ModuleName(str):
9+
pass
10+
11+
for module in (None, "", "package.module", ModuleName("package.module"), "\ud800"):
12+
code = compile("value = 42", "<module-context>", "exec", module=module)
13+
assert code.co_filename == "<module-context>"
14+
namespace = {}
15+
exec(code, namespace)
16+
assert namespace["value"] == 42
17+
18+
for module in (1, False, b"package.module", [], {}, object()):
19+
with assert_raises(TypeError) as caught:
20+
compile("", "<module-context>", "exec", module=module)
21+
assert "argument 'module' must be str or None" in str(caught.exception)
22+
23+
# module is keyword-only; _feature_version stays keyword-only too.
24+
with assert_raises(TypeError):
25+
compile("", "<module-context>", "exec", 0, False, -1, "package.module")
26+
27+
lexical_sources = [
28+
"x = 1or 0",
29+
r"'\z'",
30+
r"'\400'",
31+
r'f"{1:\z}"',
32+
r'f"\{1}"',
33+
]
34+
codegen_sources = [
35+
"assert (1, 2)",
36+
"1 is 1",
37+
"def f():\n try:\n pass\n finally:\n return 1\n",
38+
]
39+
40+
# Default source compilation uses module for matching, while diagnostics retain
41+
# the real source filename. Test both text and bytes compilation.
42+
for source in lexical_sources + codegen_sources:
43+
for input_source in (source, source.encode()):
44+
with warnings.catch_warnings(record=True) as caught:
45+
warnings.simplefilter("error", SyntaxWarning)
46+
warnings.filterwarnings(
47+
"always", category=SyntaxWarning, module=r"^package\.module$"
48+
)
49+
compile(
50+
input_source, "<module-context>", "exec", module="package.module"
51+
)
52+
assert len(caught) == 1, (source, caught)
53+
assert caught[0].filename == "<module-context>"
54+
assert caught[0].category is SyntaxWarning
55+
with warnings.catch_warnings():
56+
warnings.simplefilter("ignore", SyntaxWarning)
57+
warnings.filterwarnings(
58+
"error", category=SyntaxWarning, module=r"^package\.module$"
59+
)
60+
with assert_raises(SyntaxError):
61+
compile(
62+
input_source,
63+
"<module-context>",
64+
"exec",
65+
module="package.module",
66+
)
67+
68+
# Compiling a pre-existing AST must use the module too.
69+
for source in codegen_sources:
70+
tree = ast.parse(source)
71+
with warnings.catch_warnings(record=True) as caught:
72+
warnings.simplefilter("error", SyntaxWarning)
73+
warnings.filterwarnings(
74+
"always", category=SyntaxWarning, module=r"^package\.module$"
75+
)
76+
compile(tree, "<module-context>", "exec", module="package.module")
77+
assert len(caught) == 1, (source, caught)
78+
assert caught[0].filename == "<module-context>"
79+
80+
# A following compile, with omitted or None module, must not inherit the override.
81+
for kwargs in ({}, {"module": None}):
82+
with warnings.catch_warnings(record=True) as caught:
83+
warnings.simplefilter("error", SyntaxWarning)
84+
warnings.filterwarnings(
85+
"always", category=SyntaxWarning, module=r"^<module-context>$"
86+
)
87+
compile("1 is 1", "<module-context>", "exec", **kwargs)
88+
assert len(caught) == 1
89+
90+
# Failed parses use the fallback string scanner, which must retain the module.
91+
for source in (r"b'\z' )", r'f"{1:\z}" )'):
92+
with warnings.catch_warnings(record=True) as caught:
93+
warnings.simplefilter("error", SyntaxWarning)
94+
warnings.filterwarnings(
95+
"always", category=SyntaxWarning, module=r"^package\.module$"
96+
)
97+
with assert_raises(SyntaxError):
98+
compile(source, "<module-context>", "exec", module="package.module")
99+
assert len(caught) == 1, (source, caught)
100+
assert caught[0].filename == "<module-context>"
101+
8102
# AST conversion must use the same fallible, source-located warning boundary.
9103
# TYPE_COMMENTS validates through the AST parser before compiling the source;
10104
# the later compiler must not filter or emit the same escape a second time.
11105
for flags in (
12106
ast.PyCF_ONLY_AST,
13107
ast.PyCF_TYPE_COMMENTS,
108+
ast.PyCF_ONLY_AST | ast.PyCF_TYPE_COMMENTS,
109+
ast.PyCF_ONLY_AST | ast.PyCF_OPTIMIZED_AST,
14110
):
15111
source = '\nπ = f"{1:\\z}"\n'
16112
for kwargs, expected_module in (
@@ -34,6 +130,27 @@ def match(self, module):
34130
assert modules == [expected_module], (flags, kwargs, modules)
35131
assert len(caught) == 1, (flags, kwargs, caught)
36132
assert (caught[0].filename, caught[0].lineno) == ("<module-context>", 2)
133+
134+
with warnings.catch_warnings():
135+
warnings.simplefilter("ignore", SyntaxWarning)
136+
warnings.filterwarnings(
137+
"error", category=SyntaxWarning, module=r"^package\.module$"
138+
)
139+
with assert_raises(SyntaxError) as caught:
140+
compile(
141+
source, "<module-context>", "exec", flags, module="package.module"
142+
)
143+
error = caught.exception
144+
assert error.filename == "<module-context>"
145+
assert (error.lineno, error.offset, error.end_lineno, error.end_offset) == (
146+
2,
147+
10,
148+
2,
149+
12,
150+
)
151+
assert error.text == 'π = f"{1:\\z}"\n'
152+
assert "Such sequences will not work in the future" not in error.msg
153+
37154
# Warning handlers may raise arbitrary exceptions, which must survive intact.
38155
failure = RuntimeError("warning handler failed")
39156

@@ -78,7 +195,27 @@ def showwarning(message, category, filename, lineno, *args, **kwargs):
78195
)
79196
assert modules == ["package.outer", "package.inner", "package.outer"], modules
80197
assert locations == [("<outer>", 1), ("<inner>", 1), ("<outer>", 2)], locations
81-
for flags in (ast.PyCF_ONLY_AST,):
198+
199+
# These warnings already came from AST conversion. Retain them instead of
200+
# replacing that path with a broader scanner before every AST parse.
201+
for source in ('''rf"{1:{'\\z'}}"''', '''f"{1:{t'\\z'}}"'''):
202+
for flags in (
203+
ast.PyCF_ONLY_AST,
204+
ast.PyCF_TYPE_COMMENTS,
205+
ast.PyCF_ONLY_AST | ast.PyCF_TYPE_COMMENTS,
206+
):
207+
with warnings.catch_warnings(record=True) as caught:
208+
warnings.simplefilter("error", SyntaxWarning)
209+
warnings.filterwarnings(
210+
"always", category=SyntaxWarning, module=r"^package\.module$"
211+
)
212+
compile(
213+
source, "<module-context>", "exec", flags, module="package.module"
214+
)
215+
assert len(caught) == 1, (source, flags, caught)
216+
assert (caught[0].filename, caught[0].lineno) == ("<module-context>", 1)
217+
218+
for flags in (ast.PyCF_ONLY_AST, ast.PyCF_ONLY_AST | ast.PyCF_TYPE_COMMENTS):
82219
with warnings.catch_warnings(record=True) as caught:
83220
warnings.simplefilter("always", SyntaxWarning)
84221
with assert_raises(SyntaxError):
@@ -110,6 +247,38 @@ def showwarning(*args, **kwargs):
110247
)
111248
assert caught.exception.lineno == 2
112249
assert "\\q" in caught.exception.msg
250+
251+
# Raw literal text is not an invalid escape. Nested nonraw expressions still
252+
# retain the diagnostics previously emitted during AST conversion.
253+
for flags in (
254+
ast.PyCF_ONLY_AST,
255+
ast.PyCF_TYPE_COMMENTS,
256+
ast.PyCF_ONLY_AST | ast.PyCF_TYPE_COMMENTS,
257+
):
258+
for source in (
259+
r'rf"{1:\z}"',
260+
r'rf"{1:{2:\z}}"',
261+
r'tr"{1:{2:\z}}"',
262+
"f\"{1:{r'\\z'}}\"",
263+
):
264+
with warnings.catch_warnings(record=True) as caught:
265+
warnings.simplefilter("error", SyntaxWarning)
266+
compile(
267+
source, "<module-context>", "exec", flags, module="package.module"
268+
)
269+
assert not caught, (source, flags, caught)
270+
for source in ("rf\"{f'{2:\\z}'}\"", "rf\"{1:{'\\z'}}\""):
271+
with warnings.catch_warnings():
272+
warnings.simplefilter("error", SyntaxWarning)
273+
with assert_raises(SyntaxError):
274+
compile(
275+
source,
276+
"<module-context>",
277+
"exec",
278+
flags,
279+
module="package.module",
280+
)
281+
113282
# Func-type parsing trims its source before converting the parsed nodes.
114283
# Already-detected escapes must still point into the original source.
115284
for prefix in ("", "\n\n"):

0 commit comments

Comments
 (0)