diff --git a/Lib/codeop.py b/Lib/codeop.py
index 2213b69f231f92..e64911ee5ad417 100644
--- a/Lib/codeop.py
+++ b/Lib/codeop.py
@@ -70,8 +70,7 @@ def _maybe_compile(compiler, source, filename, symbol):
return None
# fallthrough
- return compiler(source, filename, symbol)
-
+ return compiler(source, filename, symbol, incomplete_input=False)
def _is_syntax_error(err1, err2):
rep1 = repr(err1)
@@ -82,8 +81,12 @@ def _is_syntax_error(err1, err2):
return True
return False
-def _compile(source, filename, symbol):
- return compile(source, filename, symbol, PyCF_DONT_IMPLY_DEDENT | PyCF_ALLOW_INCOMPLETE_INPUT)
+def _compile(source, filename, symbol, incomplete_input=True):
+ flags = 0
+ if incomplete_input:
+ flags |= PyCF_ALLOW_INCOMPLETE_INPUT
+ flags |= PyCF_DONT_IMPLY_DEDENT
+ return compile(source, filename, symbol, flags)
def compile_command(source, filename="", symbol="single"):
r"""Compile a command and determine whether it is incomplete.
@@ -114,8 +117,12 @@ class Compile:
def __init__(self):
self.flags = PyCF_DONT_IMPLY_DEDENT | PyCF_ALLOW_INCOMPLETE_INPUT
- def __call__(self, source, filename, symbol):
- codeob = compile(source, filename, symbol, self.flags, True)
+ def __call__(self, source, filename, symbol, **kwargs):
+ flags = self.flags
+ if kwargs.get('incomplete_input', True) is False:
+ flags &= ~PyCF_DONT_IMPLY_DEDENT
+ flags &= ~PyCF_ALLOW_INCOMPLETE_INPUT
+ codeob = compile(source, filename, symbol, flags, True)
for feature in _features:
if codeob.co_flags & feature.compiler_flag:
self.flags |= feature.compiler_flag
diff --git a/Lib/test/test_codeop.py b/Lib/test/test_codeop.py
index 133096d25a44bc..94ea06f79d3618 100644
--- a/Lib/test/test_codeop.py
+++ b/Lib/test/test_codeop.py
@@ -7,6 +7,7 @@
import warnings
from test import support
from test.support import warnings_helper
+from textwrap import dedent
from codeop import compile_command, PyCF_DONT_IMPLY_DEDENT
import io
@@ -341,6 +342,19 @@ def test_invalid_warning(self):
self.assertRegex(str(w[0].message), 'invalid escape sequence')
self.assertEqual(w[0].filename, '')
+ def assertSyntaxErrorMatches(self, code, message):
+ with self.subTest(code):
+ with self.assertRaisesRegex(SyntaxError, message):
+ compile_command(code, symbol='exec')
+
+ def test_syntax_errors(self):
+ self.assertSyntaxErrorMatches(
+ dedent("""\
+ def foo(x,x):
+ pass
+ """), "duplicate argument 'x' in function definition")
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-10-27-12-17-49.gh-issue-111366._TSknV.rst b/Misc/NEWS.d/next/Core and Builtins/2023-10-27-12-17-49.gh-issue-111366._TSknV.rst
new file mode 100644
index 00000000000000..7e76ce916ea714
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2023-10-27-12-17-49.gh-issue-111366._TSknV.rst
@@ -0,0 +1,3 @@
+Fix an issue in the :mod:`codeop` that was causing :exc:`SyntaxError`
+exceptions raised in the presence of invalid syntax to not contain precise
+error messages. Patch by Pablo Galindo