diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py index 802f0248e4e4594..2725043b4ed9253 100644 --- a/Lib/idlelib/run.py +++ b/Lib/idlelib/run.py @@ -1,6 +1,6 @@ """ idlelib.run -Simplified, pyshell.ModifiedInterpreter spawns a subprocess with +Simplified: pyshell.ModifiedInterpreter spawns a subprocess with f'''{sys.executable} -c "__import__('idlelib.run').run.main()"''' '.run' is needed because __import__ returns idlelib, not idlelib.run. """ @@ -26,11 +26,12 @@ from idlelib import rpc # multiple objects from idlelib import stackviewer # StackTreeItem from idlelib import util # fix_scaling -import __main__ +import __main__ # self.locals in Executive.__init__. import tkinter # Use tcl and, if startup fails, messagebox. -if not hasattr(sys.modules['idlelib.run'], 'firstrun'): - # Undo modifications of tkinter by idlelib imports; see bpo-25507. + +def scrub_tkinter_submodules(): # Call in main when starting user process. + # Undo modifications of tkinter by idlelib imports; see gh-69693. # Which of these submodules got imported (and thus added as a tkinter # attribute) depends on what idlelib pulled in, so tolerate missing # ones rather than assuming a fixed set; see gh-59396. @@ -42,8 +43,6 @@ del sys.modules['tkinter.' + mod] except (AttributeError, KeyError): pass - # Avoid AttributeError if run again; see bpo-37038. - sys.modules['idlelib.run'].firstrun = False LOCALHOST = '127.0.0.1' @@ -139,6 +138,9 @@ def main(del_exitfunc=False): register and unregister themselves. """ + + scrub_tkinter_submodules() + global exit_now global quitting global no_exitfunc @@ -705,8 +707,7 @@ def stackviewer(self, flist_oid=None): item = stackviewer.StackTreeItem(exc, flist) return debugobj_r.remote_object_tree_item(item) - -if __name__ == '__main__': +if __name__ == '__main__': # __name__ is 'idlelib.run' in user subprocess. from unittest import main main('idlelib.idle_test.test_run', verbosity=2) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index 4cc9eea1f27214d..3d02bb6ec2389ba 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -366,6 +366,16 @@ def test_pow(self): self.assertRaises(TypeError, pow, None, 1j) self.assertAlmostEqual(pow(1j, 0.5), 0.7071067811865476+0.7071067811865475j) + # gh-156886: an infinite phase is not a zero base. + for base, exp in [(complex(INF), 1j), + (complex(INF, 1), 1j), + (1e300, 1e308j), + (complex(2), complex(0, INF))]: + with self.subTest(base=base, exponent=exp): + r = base ** exp + self.assertTrue(isnan(r.real)) + self.assertTrue(isnan(r.imag)) + a = 3.33+4.43j self.assertEqual(a ** 0j, 1) self.assertEqual(a ** 0.+0.j, 1) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index c34cf44d722456c..0c02b38dd3c0a89 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -233,6 +233,23 @@ def test_error_offset_continuation_characters(self): check = self.check check('"\\\n"(1 for c in I,\\\n\\', 2, 2) + def testSyntaxErrorRange(self): + # gh-156894: the position was reported in bytes, not in characters, + # for the errors which cover a range + for source, offset, end_offset in [ + ('abcd = 00010', 8, 11), + ('\u03b1\u03b2\u03b3\u03b4 = 00010', 8, 11), + ('a\u0301b\u0308c\u20d7d\u1ab0 = 00010', 12, 15), + ("abcd = ub'a'", 8, 10), + ("\u03b1\u03b2\u03b3\u03b4 = ub'a'", 8, 10), + ("a\u0301b\u0308c\u20d7d\u1ab0 = ub'a'", 12, 14), + ]: + with self.subTest(source=source): + with self.assertRaises(SyntaxError) as cm: + compile(source, '', 'exec') + self.assertEqual(cm.exception.offset, offset) + self.assertEqual(cm.exception.end_offset, end_offset) + def testSyntaxErrorOffset(self): check = self.check check('def fact(x):\n\treturn x!\n', 2, 10) diff --git a/Lib/test/test_tokenize.py b/Lib/test/test_tokenize.py index 7e02191db86be5a..c471f857660ec90 100644 --- a/Lib/test/test_tokenize.py +++ b/Lib/test/test_tokenize.py @@ -2566,7 +2566,7 @@ def test_tolerant_incompatible_prefix_position_after_non_ascii(self): self._get_tokens('bé )tf"2 ', extra_tokens=True) self.assertEqual( caught.exception.args, - ("'f' and 't' prefixes are incompatible", (1, 6)), + ("'f' and 't' prefixes are incompatible", (1, 5)), ) def test_tolerant_fstring_closer_at_expression_entry_depth(self): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-09-03-12-40-00.gh-issue-156886.Kx7mQt.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-03-12-40-00.gh-issue-156886.Kx7mQt.rst new file mode 100644 index 000000000000000..69fbc33992100e3 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-03-12-40-00.gh-issue-156886.Kx7mQt.rst @@ -0,0 +1,3 @@ +Fix :exc:`ZeroDivisionError` spuriously raised for a complex power +with a non-zero base when the phase of the result is infinite, +e.g. ``1e300**1e308j`` or ``complex('inf')**1j``. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-09-03-15-20-00.gh-issue-156894.Rt9Bx4.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-03-15-20-00.gh-issue-156894.Rt9Bx4.rst new file mode 100644 index 000000000000000..9b279ab27947867 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-03-15-20-00.gh-issue-156894.Rt9Bx4.rst @@ -0,0 +1,2 @@ +Fix the position of syntax errors which cover a range if the line contains +non-ASCII characters before the error. diff --git a/Misc/NEWS.d/next/IDLE/2026-09-03-17-16-26.gh-issue-156896.XTQJlq.rst b/Misc/NEWS.d/next/IDLE/2026-09-03-17-16-26.gh-issue-156896.XTQJlq.rst new file mode 100644 index 000000000000000..fd759cdbe92c2ff --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2026-09-03-17-16-26.gh-issue-156896.XTQJlq.rst @@ -0,0 +1 @@ +Stop deleting tkinter submodules when idlelib.run is imported. diff --git a/Modules/_testlimitedcapi.c b/Modules/_testlimitedcapi.c index de8bed77d7d3275..1f7392dd430d4cb 100644 --- a/Modules/_testlimitedcapi.c +++ b/Modules/_testlimitedcapi.c @@ -2,122 +2,113 @@ * Test the limited C API. * * The 'test_*' functions exported by this module are run as part of the - * standard Python regression test, via Lib/test/test_capi.py. + * standard Python regression test, via Lib/test/test_capi/test_misc.py. */ -#include "pyconfig.h" // Py_GIL_DISABLED - -#ifdef Py_GIL_DISABLED - // Cannot test the limited C API -#else - // Use the oldest limited C API version -# define Py_LIMITED_API 0x03020000 -#endif - #include "_testlimitedcapi/parts.h" -static PyMethodDef TestMethods[] = { - {NULL, NULL} /* sentinel */ -}; - -static struct PyModuleDef _testlimitedcapimodule = { - PyModuleDef_HEAD_INIT, - .m_name = "_testlimitedcapi", - .m_size = 0, - .m_methods = TestMethods, -}; - -PyMODINIT_FUNC -PyInit__testlimitedcapi(void) +static int +module_exec(PyObject *mod) { - PyObject *mod = PyModule_Create(&_testlimitedcapimodule); - if (mod == NULL) { - return NULL; - } -#ifdef Py_GIL_DISABLED - PyUnstable_Module_SetGIL(mod, Py_MOD_GIL_NOT_USED); -#endif - if (_PyTestLimitedCAPI_Init_Abstract(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_ByteArray(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_Bytes(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_Capsule(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_Codec(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_Complex(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_Dict(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_Eval(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_Float(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_HeaptypeRelative(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_Import(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_List(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_Long(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_Object(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_PyOS(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_Set(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_Slots(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_Sys(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_ThreadState(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_Tuple(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_Unicode(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_VectorcallLimited(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_Version(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_File(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_Weakref(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_Run(mod) < 0) { - return NULL; + return -1; } if (_PyTestLimitedCAPI_Init_Type(mod) < 0) { - return NULL; + return -1; } - return mod; + return 0; +} + +static struct PyModuleDef _testlimitedcapimodule_def = { + PyModuleDef_HEAD_INIT, + .m_name = "_testlimitedcapi", + .m_size = 0, + .m_slots = (PyModuleDef_Slot[]){ + {Py_mod_exec, module_exec}, +#ifdef Py_GIL_DISABLED + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, +#endif + {0} + } +}; + +PyMODINIT_FUNC +PyInit__testlimitedcapi(void) +{ + return PyModuleDef_Init(&_testlimitedcapimodule_def); } diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 5f7acdeb7cfd8df..9328baf013c972a 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -331,6 +331,9 @@ _Py_c_pow(Py_complex a, Py_complex b) r.real = len*cos(phase); r.imag = len*sin(phase); + /* Don't rely on errno set by the math functions above, for + example cos() and sin() set EDOM for an infinite phase. */ + errno = 0; if (isfinite(a.real) && isfinite(a.imag) && isfinite(b.real) && isfinite(b.imag)) { diff --git a/Parser/tokenizer/helpers.c b/Parser/tokenizer/helpers.c index bbd64760a18a664..e99be70ea5e9801 100644 --- a/Parser/tokenizer/helpers.c +++ b/Parser/tokenizer/helpers.c @@ -9,6 +9,20 @@ /* ############## ERRORS ############## */ +/* Convert a 1-based column in bytes into a 1-based column in characters. + The line is UTF-8 encoded, so it is enough to skip continuation bytes. */ +static int +byte_col_to_char_col(const char *line, int byte_col) +{ + int char_col = 1; + for (int i = 0; i < byte_col - 1; i++) { + if ((line[i] & 0xC0) != 0x80) { + char_col++; + } + } + return char_col; +} + static int _syntaxerror_range(struct tok_state *tok, const char *format, int col_offset, int end_col_offset, @@ -35,9 +49,15 @@ _syntaxerror_range(struct tok_state *tok, const char *format, if (col_offset == -1) { col_offset = (int)PyUnicode_GET_LENGTH(errtext); } + else if (col_offset > 0) { + col_offset = byte_col_to_char_col(tok->line_start, col_offset); + } if (end_col_offset == -1) { end_col_offset = col_offset; } + else if (end_col_offset > 0) { + end_col_offset = byte_col_to_char_col(tok->line_start, end_col_offset); + } Py_ssize_t line_len = strcspn(tok->line_start, "\n"); if (line_len != tok->cur - tok->line_start) {