diff --git a/Doc/library/winsound.rst b/Doc/library/winsound.rst index 799fb3dea19501..925984c3cdb0cb 100644 --- a/Doc/library/winsound.rst +++ b/Doc/library/winsound.rst @@ -142,6 +142,27 @@ provided by Windows platforms. It includes functions and several constants. to specify an application-defined sound alias. +.. data:: SND_SENTRY + + Triggers a SoundSentry event when the sound is played. + + .. versionadded:: 3.14 + + +.. data:: SND_SYNC + + The sound is played synchronously. This is the default behavior. + + .. versionadded:: 3.14 + + +.. data:: SND_SYSTEM + + Assign the sound to the audio session for system notification sounds. + + .. versionadded:: 3.14 + + .. data:: MB_ICONASTERISK Play the ``SystemDefault`` sound. @@ -166,3 +187,30 @@ provided by Windows platforms. It includes functions and several constants. Play the ``SystemDefault`` sound. + +.. data:: MB_ICONERROR + + Play the ``SystemHand`` sound. + + .. versionadded:: 3.14 + + +.. data:: MB_ICONINFORMATION + + Play the ``SystemDefault`` sound. + + .. versionadded:: 3.14 + + +.. data:: MB_ICONSTOP + + Play the ``SystemHand`` sound. + + .. versionadded:: 3.14 + + +.. data:: MB_ICONWARNING + + Play the ``SystemExclamation`` sound. + + .. versionadded:: 3.14 diff --git a/Doc/tools/extensions/grammar_snippet.py b/Doc/tools/extensions/grammar_snippet.py index 03c7e7ce2f4228..1e059f111e4091 100644 --- a/Doc/tools/extensions/grammar_snippet.py +++ b/Doc/tools/extensions/grammar_snippet.py @@ -13,8 +13,8 @@ from sphinx.util.nodes import make_id if TYPE_CHECKING: - from collections.abc import Sequence - from typing import Any + from collections.abc import Iterable, Iterator, Sequence + from typing import Any, Final from docutils.nodes import Node from sphinx.application import Sphinx @@ -41,98 +41,140 @@ class GrammarSnippetBase(SphinxDirective): # The option/argument handling is left to the individual classes. + grammar_re: Final = re.compile( + r""" + (?P^[a-zA-Z0-9_]+) # identifier at start of line + (?=:) # ... followed by a colon + | + (?P`[^\s`]+`) # identifier in backquotes + | + (?P'[^']*') # string in 'quotes' + | + (?P"[^"]*") # string in "quotes" + """, + re.VERBOSE, + ) + def make_grammar_snippet( self, options: dict[str, Any], content: Sequence[str] - ) -> list[nodes.paragraph]: + ) -> list[addnodes.productionlist]: """Create a literal block from options & content.""" group_name = options['group'] - - # Docutils elements have a `rawsource` attribute that is supposed to be - # set to the original ReST source. - # Sphinx does the following with it: - # - if it's empty, set it to `self.astext()` - # - if it matches `self.astext()` when generating the output, - # apply syntax highlighting (which is based on the plain-text content - # and thus discards internal formatting, like references). - # To get around this, we set it to this non-empty string: - rawsource = 'You should not see this.' - - literal = nodes.literal_block( - rawsource, + node_location = self.get_location() + production_nodes = [] + for rawsource, production_defs in self.production_definitions(content): + production = self.make_production( + rawsource, + production_defs, + group_name=group_name, + location=node_location, + ) + production_nodes.append(production) + + node = addnodes.productionlist( '', + *production_nodes, + support_smartquotes=False, classes=['highlight'], ) + self.set_source_info(node) + return [node] - grammar_re = re.compile( - r""" - (?P^[a-zA-Z0-9_]+) # identifier at start of line - (?=:) # ... followed by a colon - | - (?P`[^\s`]+`) # identifier in backquotes - | - (?P'[^']*') # string in 'quotes' - | - (?P"[^"]*") # string in "quotes" - """, - re.VERBOSE, - ) - - for line in content: + def production_definitions( + self, lines: Iterable[str], / + ) -> Iterator[tuple[str, list[tuple[str, str]]]]: + """Yield pairs of rawsource and production content dicts.""" + production_lines: list[str] = [] + production_content: list[tuple[str, str]] = [] + for line in lines: + # If this line is the start of a new rule (text in the column 1), + # emit the current production and start a new one. + if not line[:1].isspace(): + rawsource = '\n'.join(production_lines) + production_lines.clear() + if production_content: + yield rawsource, production_content + production_content = [] + + # Append the current line for the raw source + production_lines.append(line) + + # Parse the line into constituent parts last_pos = 0 - for match in grammar_re.finditer(line): + for match in self.grammar_re.finditer(line): # Handle text between matches if match.start() > last_pos: - literal += nodes.Text(line[last_pos : match.start()]) + unmatched_text = line[last_pos : match.start()] + production_content.append(('text', unmatched_text)) last_pos = match.end() - # Handle matches - group_dict = { - name: content - for name, content in match.groupdict().items() + # Handle matches. + # After filtering None (non-matches), exactly one groupdict() + # entry should remain. + [(re_group_name, content)] = ( + (re_group_name, content) + for re_group_name, content in match.groupdict().items() if content is not None - } - match group_dict: - case {'rule_name': name}: - literal += self.make_link_target_for_token( - group_name, name - ) - case {'rule_ref': ref_text}: - literal += token_xrefs(ref_text, group_name) - case {'single_quoted': name} | {'double_quoted': name}: - literal += snippet_string_node('', name) - case _: - raise ValueError('unhandled match') - literal += nodes.Text(line[last_pos:] + '\n') - - node = nodes.paragraph( - '', - '', - literal, - ) + ) + production_content.append((re_group_name, content)) + production_content.append(('text', line[last_pos:] + '\n')) - return [node] + # Emit the final production + if production_content: + rawsource = '\n'.join(production_lines) + yield rawsource, production_content - def make_link_target_for_token( - self, group_name: str, name: str + def make_production( + self, + rawsource: str, + production_defs: list[tuple[str, str]], + *, + group_name: str, + location: str, + ) -> addnodes.production: + """Create a production node from a list of parts.""" + production_node = addnodes.production(rawsource) + for re_group_name, content in production_defs: + match re_group_name: + case 'rule_name': + production_node += self.make_name_target( + name=content, + production_group=group_name, + location=location, + ) + case 'rule_ref': + production_node += token_xrefs(content, group_name) + case 'single_quoted' | 'double_quoted': + production_node += snippet_string_node('', content) + case 'text': + production_node += nodes.Text(content) + case _: + raise ValueError(f'unhandled match: {re_group_name!r}') + return production_node + + def make_name_target( + self, + *, + name: str, + production_group: str, + location: str, ) -> addnodes.literal_strong: - """Return a literal node which is a link target for the given token.""" - name_node = addnodes.literal_strong() + """Make a link target for the given production.""" # Cargo-culted magic to make `name_node` a link target # similar to Sphinx `production`. # This needs to be the same as what Sphinx does # to avoid breaking existing links. - domain = self.env.domains['std'] - obj_name = f"{group_name}:{name}" - prefix = f'grammar-token-{group_name}' + + name_node = addnodes.literal_strong(name, name) + prefix = f'grammar-token-{production_group}' node_id = make_id(self.env, self.state.document, prefix, name) name_node['ids'].append(node_id) self.state.document.note_implicit_target(name_node, name_node) - domain.note_object('token', obj_name, node_id, location=name_node) - - text_node = nodes.Text(name) - name_node += text_node + obj_name = f'{production_group}:{name}' if production_group else name + std = self.env.domains.standard_domain + std.note_object('token', obj_name, node_id, location=location) return name_node @@ -168,7 +210,7 @@ class GrammarSnippetDirective(GrammarSnippetBase): optional_arguments = 1 final_argument_whitespace = True - def run(self) -> list[nodes.paragraph]: + def run(self) -> list[addnodes.productionlist]: return self.make_grammar_snippet(self.options, self.content) @@ -187,7 +229,7 @@ class CompatProductionList(GrammarSnippetBase): final_argument_whitespace = True option_spec = {} - def run(self) -> list[nodes.paragraph]: + def run(self) -> list[addnodes.productionlist]: # The "content" of a productionlist is actually the first and only # argument. The first line is the group; the rest is the content lines. lines = self.arguments[0].splitlines() diff --git a/Include/internal/pycore_jit.h b/Include/internal/pycore_jit.h index 4d6cc35a7a3de7..8a88cbf607ba4b 100644 --- a/Include/internal/pycore_jit.h +++ b/Include/internal/pycore_jit.h @@ -5,6 +5,10 @@ extern "C" { #endif +#include "pycore_interp.h" +#include "pycore_optimizer.h" +#include "pycore_stackref.h" + #ifndef Py_BUILD_CORE # error "this header requires Py_BUILD_CORE define" #endif diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 08cbfe46b0daff..30b88404bbe204 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -445,9 +445,6 @@ static inline void Py_DECREF_MORTAL(const char *filename, int lineno, PyObject * _Py_DECREF_DecRefTotal(); } if (--op->ob_refcnt == 0) { -#ifdef Py_TRACE_REFS - _Py_ForgetReference(op); -#endif _Py_Dealloc(op); } } diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index 03c2708e0a0505..4af1fa63ac1f1a 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -202,7 +202,7 @@ typedef struct _jit_opt_tuple { typedef struct { uint8_t tag; - bool not; + bool invert; uint16_t value; } JitOptTruthiness; diff --git a/Lib/_colorize.py b/Lib/_colorize.py index 41e818f2a747ff..9eb6f0933b8150 100644 --- a/Lib/_colorize.py +++ b/Lib/_colorize.py @@ -1,23 +1,64 @@ +from __future__ import annotations import io import os import sys COLORIZE = True +# types +if False: + from typing import IO + class ANSIColors: - BACKGROUND_YELLOW = "\x1b[43m" - BOLD_GREEN = "\x1b[1;32m" - BOLD_MAGENTA = "\x1b[1;35m" - BOLD_RED = "\x1b[1;31m" + RESET = "\x1b[0m" + BLACK = "\x1b[30m" + BLUE = "\x1b[34m" + CYAN = "\x1b[36m" GREEN = "\x1b[32m" - GREY = "\x1b[90m" MAGENTA = "\x1b[35m" RED = "\x1b[31m" - RESET = "\x1b[0m" + WHITE = "\x1b[37m" # more like LIGHT GRAY YELLOW = "\x1b[33m" + BOLD_BLACK = "\x1b[1;30m" # DARK GRAY + BOLD_BLUE = "\x1b[1;34m" + BOLD_CYAN = "\x1b[1;36m" + BOLD_GREEN = "\x1b[1;32m" + BOLD_MAGENTA = "\x1b[1;35m" + BOLD_RED = "\x1b[1;31m" + BOLD_WHITE = "\x1b[1;37m" # actual WHITE + BOLD_YELLOW = "\x1b[1;33m" + + # intense = like bold but without being bold + INTENSE_BLACK = "\x1b[90m" + INTENSE_BLUE = "\x1b[94m" + INTENSE_CYAN = "\x1b[96m" + INTENSE_GREEN = "\x1b[92m" + INTENSE_MAGENTA = "\x1b[95m" + INTENSE_RED = "\x1b[91m" + INTENSE_WHITE = "\x1b[97m" + INTENSE_YELLOW = "\x1b[93m" + + BACKGROUND_BLACK = "\x1b[40m" + BACKGROUND_BLUE = "\x1b[44m" + BACKGROUND_CYAN = "\x1b[46m" + BACKGROUND_GREEN = "\x1b[42m" + BACKGROUND_MAGENTA = "\x1b[45m" + BACKGROUND_RED = "\x1b[41m" + BACKGROUND_WHITE = "\x1b[47m" + BACKGROUND_YELLOW = "\x1b[43m" + + INTENSE_BACKGROUND_BLACK = "\x1b[100m" + INTENSE_BACKGROUND_BLUE = "\x1b[104m" + INTENSE_BACKGROUND_CYAN = "\x1b[106m" + INTENSE_BACKGROUND_GREEN = "\x1b[102m" + INTENSE_BACKGROUND_MAGENTA = "\x1b[105m" + INTENSE_BACKGROUND_RED = "\x1b[101m" + INTENSE_BACKGROUND_WHITE = "\x1b[107m" + INTENSE_BACKGROUND_YELLOW = "\x1b[103m" + NoColors = ANSIColors() @@ -26,14 +67,16 @@ class ANSIColors: setattr(NoColors, attr, "") -def get_colors(colorize: bool = False, *, file=None) -> ANSIColors: +def get_colors( + colorize: bool = False, *, file: IO[str] | IO[bytes] | None = None +) -> ANSIColors: if colorize or can_colorize(file=file): return ANSIColors() else: return NoColors -def can_colorize(*, file=None) -> bool: +def can_colorize(*, file: IO[str] | IO[bytes] | None = None) -> bool: if file is None: file = sys.stdout @@ -66,4 +109,4 @@ def can_colorize(*, file=None) -> bool: try: return os.isatty(file.fileno()) except io.UnsupportedOperation: - return file.isatty() + return hasattr(file, "isatty") and file.isatty() diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py index 26bcd1e491d78c..50e21a12335611 100644 --- a/Lib/_pydatetime.py +++ b/Lib/_pydatetime.py @@ -576,7 +576,7 @@ def _check_date_fields(year, month, day): raise ValueError(f"month must be in 1..12, not {month}") dim = _days_in_month(year, month) if not 1 <= day <= dim: - raise ValueError(f"day must be in 1..{dim}, not {day}") + raise ValueError(f"day {day} must be in range 1..{dim} for month {month} in year {year}") return year, month, day def _check_time_fields(hour, minute, second, microsecond, fold): diff --git a/Lib/_pyrepl/commands.py b/Lib/_pyrepl/commands.py index 503ca1da329eaa..cbb6d85f683257 100644 --- a/Lib/_pyrepl/commands.py +++ b/Lib/_pyrepl/commands.py @@ -456,7 +456,7 @@ def do(self) -> None: class show_history(Command): def do(self) -> None: from .pager import get_pager - from site import gethistoryfile # type: ignore[attr-defined] + from site import gethistoryfile history = os.linesep.join(self.reader.history[:]) self.reader.console.restore() diff --git a/Lib/_pyrepl/console.py b/Lib/_pyrepl/console.py index 7916638921bbf2..8956fb1242e52a 100644 --- a/Lib/_pyrepl/console.py +++ b/Lib/_pyrepl/console.py @@ -19,7 +19,7 @@ from __future__ import annotations -import _colorize # type: ignore[import-not-found] +import _colorize from abc import ABC, abstractmethod import ast @@ -162,7 +162,7 @@ def __init__( *, local_exit: bool = False, ) -> None: - super().__init__(locals=locals, filename=filename, local_exit=local_exit) # type: ignore[call-arg] + super().__init__(locals=locals, filename=filename, local_exit=local_exit) self.can_colorize = _colorize.can_colorize() def showsyntaxerror(self, filename=None, **kwargs): diff --git a/Lib/_pyrepl/mypy.ini b/Lib/_pyrepl/mypy.ini index 395f5945ab740b..eabd0e9b440bf4 100644 --- a/Lib/_pyrepl/mypy.ini +++ b/Lib/_pyrepl/mypy.ini @@ -4,8 +4,9 @@ [mypy] files = Lib/_pyrepl +mypy_path = $MYPY_CONFIG_FILE_DIR/../../Misc/mypy explicit_package_bases = True -python_version = 3.12 +python_version = 3.13 platform = linux pretty = True @@ -22,3 +23,7 @@ check_untyped_defs = False # Various internal modules that typeshed deliberately doesn't have stubs for: [mypy-_abc.*,_opcode.*,_overlapped.*,_testcapi.*,_testinternalcapi.*,test.*] ignore_missing_imports = True + +# Other untyped parts of the stdlib +[mypy-idlelib.*] +ignore_missing_imports = True diff --git a/Lib/_pyrepl/reader.py b/Lib/_pyrepl/reader.py index 4795c51296a500..7fc2422dac9c3f 100644 --- a/Lib/_pyrepl/reader.py +++ b/Lib/_pyrepl/reader.py @@ -25,12 +25,11 @@ from contextlib import contextmanager from dataclasses import dataclass, field, fields -import unicodedata -from _colorize import can_colorize, ANSIColors # type: ignore[import-not-found] +from _colorize import can_colorize, ANSIColors from . import commands, console, input -from .utils import ANSI_ESCAPE_SEQUENCE, wlen, str_width +from .utils import wlen, unbracket, disp_str from .trace import trace @@ -39,36 +38,6 @@ from .types import Callback, SimpleContextManager, KeySpec, CommandName -def disp_str(buffer: str) -> tuple[str, list[int]]: - """disp_str(buffer:string) -> (string, [int]) - - Return the string that should be the printed representation of - |buffer| and a list detailing where the characters of |buffer| - get used up. E.g.: - - >>> disp_str(chr(3)) - ('^C', [1, 0]) - - """ - b: list[int] = [] - s: list[str] = [] - for c in buffer: - if c == '\x1a': - s.append(c) - b.append(2) - elif ord(c) < 128: - s.append(c) - b.append(1) - elif unicodedata.category(c).startswith("C"): - c = r"\u%04x" % ord(c) - s.append(c) - b.append(len(c)) - else: - s.append(c) - b.append(str_width(c)) - return "".join(s), b - - # syntax classes: SYNTAX_WHITESPACE, SYNTAX_WORD, SYNTAX_SYMBOL = range(3) @@ -347,14 +316,12 @@ def calc_screen(self) -> list[str]: pos -= offset prompt_from_cache = (offset and self.buffer[offset - 1] != "\n") - lines = "".join(self.buffer[offset:]).split("\n") - cursor_found = False lines_beyond_cursor = 0 for ln, line in enumerate(lines, num_common_lines): - ll = len(line) - if 0 <= pos <= ll: + line_len = len(line) + if 0 <= pos <= line_len: self.lxy = pos, ln cursor_found = True elif cursor_found: @@ -368,34 +335,34 @@ def calc_screen(self) -> list[str]: prompt_from_cache = False prompt = "" else: - prompt = self.get_prompt(ln, ll >= pos >= 0) + prompt = self.get_prompt(ln, line_len >= pos >= 0) while "\n" in prompt: pre_prompt, _, prompt = prompt.partition("\n") last_refresh_line_end_offsets.append(offset) screen.append(pre_prompt) screeninfo.append((0, [])) - pos -= ll + 1 - prompt, lp = self.process_prompt(prompt) - l, l2 = disp_str(line) - wrapcount = (wlen(l) + lp) // self.console.width - if wrapcount == 0: - offset += ll + 1 # Takes all of the line plus the newline + pos -= line_len + 1 + prompt, prompt_len = self.process_prompt(prompt) + chars, char_widths = disp_str(line) + wrapcount = (sum(char_widths) + prompt_len) // self.console.width + trace("wrapcount = {wrapcount}", wrapcount=wrapcount) + if wrapcount == 0 or not char_widths: + offset += line_len + 1 # Takes all of the line plus the newline last_refresh_line_end_offsets.append(offset) - screen.append(prompt + l) - screeninfo.append((lp, l2)) + screen.append(prompt + "".join(chars)) + screeninfo.append((prompt_len, char_widths)) else: - i = 0 - while l: - prelen = lp if i == 0 else 0 + pre = prompt + prelen = prompt_len + for wrap in range(wrapcount + 1): index_to_wrap_before = 0 column = 0 - for character_width in l2: - if column + character_width >= self.console.width - prelen: + for char_width in char_widths: + if column + char_width + prelen >= self.console.width: break index_to_wrap_before += 1 - column += character_width - pre = prompt if i == 0 else "" - if len(l) > index_to_wrap_before: + column += char_width + if len(chars) > index_to_wrap_before: offset += index_to_wrap_before post = "\\" after = [1] @@ -404,11 +371,14 @@ def calc_screen(self) -> list[str]: post = "" after = [] last_refresh_line_end_offsets.append(offset) - screen.append(pre + l[:index_to_wrap_before] + post) - screeninfo.append((prelen, l2[:index_to_wrap_before] + after)) - l = l[index_to_wrap_before:] - l2 = l2[index_to_wrap_before:] - i += 1 + render = pre + "".join(chars[:index_to_wrap_before]) + post + render_widths = char_widths[:index_to_wrap_before] + after + screen.append(render) + screeninfo.append((prelen, render_widths)) + chars = chars[index_to_wrap_before:] + char_widths = char_widths[index_to_wrap_before:] + pre = "" + prelen = 0 self.screeninfo = screeninfo self.cxy = self.pos2xy() if self.msg: @@ -421,42 +391,15 @@ def calc_screen(self) -> list[str]: @staticmethod def process_prompt(prompt: str) -> tuple[str, int]: - """Process the prompt. - - This means calculate the length of the prompt. The character \x01 - and \x02 are used to bracket ANSI control sequences and need to be - excluded from the length calculation. So also a copy of the prompt - is returned with these control characters removed.""" - - # The logic below also ignores the length of common escape - # sequences if they were not explicitly within \x01...\x02. - # They are CSI (or ANSI) sequences ( ESC [ ... LETTER ) - - # wlen from utils already excludes ANSI_ESCAPE_SEQUENCE chars, - # which breaks the logic below so we redefine it here. - def wlen(s: str) -> int: - return sum(str_width(i) for i in s) + r"""Return a tuple with the prompt string and its visible length. - out_prompt = "" - l = wlen(prompt) - pos = 0 - while True: - s = prompt.find("\x01", pos) - if s == -1: - break - e = prompt.find("\x02", s) - if e == -1: - break - # Found start and end brackets, subtract from string length - l = l - (e - s + 1) - keep = prompt[pos:s] - l -= sum(map(wlen, ANSI_ESCAPE_SEQUENCE.findall(keep))) - out_prompt += keep + prompt[s + 1 : e] - pos = e + 1 - keep = prompt[pos:] - l -= sum(map(wlen, ANSI_ESCAPE_SEQUENCE.findall(keep))) - out_prompt += keep - return out_prompt, l + The prompt string has the zero-width brackets recognized by shells + (\x01 and \x02) removed. The length ignores anything between those + brackets as well as any ANSI escape sequences. + """ + out_prompt = unbracket(prompt, including_content=False) + visible_prompt = unbracket(prompt, including_content=True) + return out_prompt, wlen(visible_prompt) def bow(self, p: int | None = None) -> int: """Return the 0-based index of the word break preceding p most @@ -564,9 +507,9 @@ def setpos_from_xy(self, x: int, y: int) -> None: pos = 0 i = 0 while i < y: - prompt_len, character_widths = self.screeninfo[i] - offset = len(character_widths) - character_widths.count(0) - in_wrapped_line = prompt_len + sum(character_widths) >= self.console.width + prompt_len, char_widths = self.screeninfo[i] + offset = len(char_widths) + in_wrapped_line = prompt_len + sum(char_widths) >= self.console.width if in_wrapped_line: pos += offset - 1 # -1 cause backslash is not in buffer else: @@ -587,29 +530,33 @@ def setpos_from_xy(self, x: int, y: int) -> None: def pos2xy(self) -> tuple[int, int]: """Return the x, y coordinates of position 'pos'.""" - # this *is* incomprehensible, yes. - p, y = 0, 0 - l2: list[int] = [] + + prompt_len, y = 0, 0 + char_widths: list[int] = [] pos = self.pos assert 0 <= pos <= len(self.buffer) + + # optimize for the common case: typing at the end of the buffer if pos == len(self.buffer) and len(self.screeninfo) > 0: y = len(self.screeninfo) - 1 - p, l2 = self.screeninfo[y] - return p + sum(l2) + l2.count(0), y + prompt_len, char_widths = self.screeninfo[y] + return prompt_len + sum(char_widths), y + + for prompt_len, char_widths in self.screeninfo: + offset = len(char_widths) + in_wrapped_line = prompt_len + sum(char_widths) >= self.console.width + if in_wrapped_line: + offset -= 1 # need to remove line-wrapping backslash - for p, l2 in self.screeninfo: - l = len(l2) - l2.count(0) - in_wrapped_line = p + sum(l2) >= self.console.width - offset = l - 1 if in_wrapped_line else l # need to remove backslash if offset >= pos: break - if p + sum(l2) >= self.console.width: - pos -= l - 1 # -1 cause backslash is not in buffer - else: - pos -= l + 1 # +1 cause newline is in buffer + if not in_wrapped_line: + offset += 1 # there's a newline in buffer + + pos -= offset y += 1 - return p + sum(l2[:pos]), y + return prompt_len + sum(char_widths[:pos]), y def insert(self, text: str | list[str]) -> None: """Insert 'text' at the insertion point.""" diff --git a/Lib/_pyrepl/readline.py b/Lib/_pyrepl/readline.py index 888185eb03be66..be229488e54e37 100644 --- a/Lib/_pyrepl/readline.py +++ b/Lib/_pyrepl/readline.py @@ -32,7 +32,7 @@ from dataclasses import dataclass, field import os -from site import gethistoryfile # type: ignore[attr-defined] +from site import gethistoryfile import sys from rlcompleter import Completer as RLCompleter diff --git a/Lib/_pyrepl/types.py b/Lib/_pyrepl/types.py index f9d48b828c720b..c5b7ebc1a406bd 100644 --- a/Lib/_pyrepl/types.py +++ b/Lib/_pyrepl/types.py @@ -1,8 +1,10 @@ from collections.abc import Callable, Iterator -Callback = Callable[[], object] -SimpleContextManager = Iterator[None] -KeySpec = str # like r"\C-c" -CommandName = str # like "interrupt" -EventTuple = tuple[CommandName, str] -Completer = Callable[[str, int], str | None] +type Callback = Callable[[], object] +type SimpleContextManager = Iterator[None] +type KeySpec = str # like r"\C-c" +type CommandName = str # like "interrupt" +type EventTuple = tuple[CommandName, str] +type Completer = Callable[[str, int], str | None] +type CharBuffer = list[str] +type CharWidths = list[int] diff --git a/Lib/_pyrepl/utils.py b/Lib/_pyrepl/utils.py index 4651717bd7e121..7437fbe1ab9371 100644 --- a/Lib/_pyrepl/utils.py +++ b/Lib/_pyrepl/utils.py @@ -2,7 +2,12 @@ import unicodedata import functools +from .types import CharBuffer, CharWidths +from .trace import trace + ANSI_ESCAPE_SEQUENCE = re.compile(r"\x1b\[[ -@]*[A-~]") +ZERO_WIDTH_BRACKET = re.compile(r"\x01.*?\x02") +ZERO_WIDTH_TRANS = str.maketrans({"\x01": "", "\x02": ""}) @functools.cache @@ -10,16 +15,63 @@ def str_width(c: str) -> int: if ord(c) < 128: return 1 w = unicodedata.east_asian_width(c) - if w in ('N', 'Na', 'H', 'A'): + if w in ("N", "Na", "H", "A"): return 1 return 2 def wlen(s: str) -> int: - if len(s) == 1 and s != '\x1a': + if len(s) == 1 and s != "\x1a": return str_width(s) length = sum(str_width(i) for i in s) # remove lengths of any escape sequences sequence = ANSI_ESCAPE_SEQUENCE.findall(s) - ctrl_z_cnt = s.count('\x1a') + ctrl_z_cnt = s.count("\x1a") return length - sum(len(i) for i in sequence) + ctrl_z_cnt + + +def unbracket(s: str, including_content: bool = False) -> str: + r"""Return `s` with \001 and \002 characters removed. + + If `including_content` is True, content between \001 and \002 is also + stripped. + """ + if including_content: + return ZERO_WIDTH_BRACKET.sub("", s) + return s.translate(ZERO_WIDTH_TRANS) + + +def disp_str(buffer: str) -> tuple[CharBuffer, CharWidths]: + r"""Decompose the input buffer into a printable variant. + + Returns a tuple of two lists: + - the first list is the input buffer, character by character; + - the second list is the visible width of each character in the input + buffer. + + Examples: + >>> utils.disp_str("a = 9") + (['a', ' ', '=', ' ', '9'], [1, 1, 1, 1, 1]) + """ + chars: CharBuffer = [] + char_widths: CharWidths = [] + + if not buffer: + return chars, char_widths + + for c in buffer: + if c == "\x1a": # CTRL-Z on Windows + chars.append(c) + char_widths.append(2) + elif ord(c) < 128: + chars.append(c) + char_widths.append(1) + elif unicodedata.category(c).startswith("C"): + c = r"\u%04x" % ord(c) + chars.append(c) + char_widths.append(len(c)) + else: + chars.append(c) + char_widths.append(str_width(c)) + trace("disp_str({buffer}) = {s}, {b}", buffer=repr(buffer), s=chars, b=char_widths) + return chars, char_widths diff --git a/Lib/bdb.py b/Lib/bdb.py index d32a05f59ad692..ba5cacc2a54cbc 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -342,7 +342,12 @@ def dispatch_call(self, frame, arg): self.botframe = frame.f_back # (CT) Note that this may also be None! return self.trace_dispatch if not (self.stop_here(frame) or self.break_anywhere(frame)): - # No need to trace this function + # We already know there's no breakpoint in this function + # If it's a next/until/return command, we don't need any CALL event + # and we don't need to set the f_trace on any new frame. + # If it's a step command, it must either hit stop_here, or skip the + # whole module. Either way, we don't need the CALL event here. + self.disable_current_event() return # None # Ignore call events in generator except when stepping. if self.stopframe and frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS: diff --git a/Lib/gzip.py b/Lib/gzip.py index c9c088783bea65..2a6eea1b3939b7 100644 --- a/Lib/gzip.py +++ b/Lib/gzip.py @@ -202,51 +202,58 @@ def __init__(self, filename=None, mode=None, raise ValueError("Invalid mode: {!r}".format(mode)) if mode and 'b' not in mode: mode += 'b' - if fileobj is None: - fileobj = self.myfileobj = builtins.open(filename, mode or 'rb') - if filename is None: - filename = getattr(fileobj, 'name', '') - if not isinstance(filename, (str, bytes)): - filename = '' - else: - filename = os.fspath(filename) - origmode = mode - if mode is None: - mode = getattr(fileobj, 'mode', 'rb') - - - if mode.startswith('r'): - self.mode = READ - raw = _GzipReader(fileobj) - self._buffer = io.BufferedReader(raw) - self.name = filename - - elif mode.startswith(('w', 'a', 'x')): - if origmode is None: - import warnings - warnings.warn( - "GzipFile was opened for writing, but this will " - "change in future Python releases. " - "Specify the mode argument for opening it for writing.", - FutureWarning, 2) - self.mode = WRITE - self._init_write(filename) - self.compress = zlib.compressobj(compresslevel, - zlib.DEFLATED, - -zlib.MAX_WBITS, - zlib.DEF_MEM_LEVEL, - 0) - self._write_mtime = mtime - self._buffer_size = _WRITE_BUFFER_SIZE - self._buffer = io.BufferedWriter(_WriteBufferStream(self), - buffer_size=self._buffer_size) - else: - raise ValueError("Invalid mode: {!r}".format(mode)) - self.fileobj = fileobj + try: + if fileobj is None: + fileobj = self.myfileobj = builtins.open(filename, mode or 'rb') + if filename is None: + filename = getattr(fileobj, 'name', '') + if not isinstance(filename, (str, bytes)): + filename = '' + else: + filename = os.fspath(filename) + origmode = mode + if mode is None: + mode = getattr(fileobj, 'mode', 'rb') + + + if mode.startswith('r'): + self.mode = READ + raw = _GzipReader(fileobj) + self._buffer = io.BufferedReader(raw) + self.name = filename + + elif mode.startswith(('w', 'a', 'x')): + if origmode is None: + import warnings + warnings.warn( + "GzipFile was opened for writing, but this will " + "change in future Python releases. " + "Specify the mode argument for opening it for writing.", + FutureWarning, 2) + self.mode = WRITE + self._init_write(filename) + self.compress = zlib.compressobj(compresslevel, + zlib.DEFLATED, + -zlib.MAX_WBITS, + zlib.DEF_MEM_LEVEL, + 0) + self._write_mtime = mtime + self._buffer_size = _WRITE_BUFFER_SIZE + self._buffer = io.BufferedWriter(_WriteBufferStream(self), + buffer_size=self._buffer_size) + else: + raise ValueError("Invalid mode: {!r}".format(mode)) + + self.fileobj = fileobj - if self.mode == WRITE: - self._write_gzip_header(compresslevel) + if self.mode == WRITE: + self._write_gzip_header(compresslevel) + except: + # Avoid a ResourceWarning if the write fails, + # eg read-only file or KeyboardInterrupt + self._close() + raise @property def mtime(self): @@ -387,11 +394,14 @@ def close(self): elif self.mode == READ: self._buffer.close() finally: - self.fileobj = None - myfileobj = self.myfileobj - if myfileobj: - self.myfileobj = None - myfileobj.close() + self._close() + + def _close(self): + self.fileobj = None + myfileobj = self.myfileobj + if myfileobj is not None: + self.myfileobj = None + myfileobj.close() def flush(self,zlib_mode=zlib.Z_SYNC_FLUSH): self._check_not_closed() diff --git a/Lib/multiprocessing/resource_tracker.py b/Lib/multiprocessing/resource_tracker.py index 90e036ae905afa..05633ac21a259c 100644 --- a/Lib/multiprocessing/resource_tracker.py +++ b/Lib/multiprocessing/resource_tracker.py @@ -75,29 +75,53 @@ def _reentrant_call_error(self): raise ReentrantCallError( "Reentrant call into the multiprocessing resource tracker") - def _stop(self): - with self._lock: - # This should not happen (_stop() isn't called by a finalizer) - # but we check for it anyway. - if self._lock._recursion_count() > 1: - return self._reentrant_call_error() - if self._fd is None: - # not running - return - - # closing the "alive" file descriptor stops main() - os.close(self._fd) - self._fd = None + def __del__(self): + # making sure child processess are cleaned before ResourceTracker + # gets destructed. + # see https://github.com/python/cpython/issues/88887 + self._stop(use_blocking_lock=False) + + def _stop(self, use_blocking_lock=True): + if use_blocking_lock: + with self._lock: + self._stop_locked() + else: + acquired = self._lock.acquire(blocking=False) + try: + self._stop_locked() + finally: + if acquired: + self._lock.release() + + def _stop_locked( + self, + close=os.close, + waitpid=os.waitpid, + waitstatus_to_exitcode=os.waitstatus_to_exitcode, + ): + # This shouldn't happen (it might when called by a finalizer) + # so we check for it anyway. + if self._lock._recursion_count() > 1: + return self._reentrant_call_error() + if self._fd is None: + # not running + return + if self._pid is None: + return + + # closing the "alive" file descriptor stops main() + close(self._fd) + self._fd = None - _, status = os.waitpid(self._pid, 0) + _, status = waitpid(self._pid, 0) - self._pid = None + self._pid = None - try: - self._exitcode = os.waitstatus_to_exitcode(status) - except ValueError: - # os.waitstatus_to_exitcode may raise an exception for invalid values - self._exitcode = None + try: + self._exitcode = waitstatus_to_exitcode(status) + except ValueError: + # os.waitstatus_to_exitcode may raise an exception for invalid values + self._exitcode = None def getfd(self): self.ensure_running() diff --git a/Lib/pydoc_data/topics.py b/Lib/pydoc_data/topics.py index d392efec5c12c2..f949b96aa56bb1 100644 --- a/Lib/pydoc_data/topics.py +++ b/Lib/pydoc_data/topics.py @@ -1,4 +1,4 @@ -# Autogenerated by Sphinx on Fri Mar 14 15:32:05 2025 +# Autogenerated by Sphinx on Wed Mar 19 18:40:00 2025 # as part of the release process. topics = { @@ -8,7 +8,7 @@ Assert statements are a convenient way to insert debugging assertions into a program: - **assert_stmt**: "assert" "expression" ["," "expression"] + assert_stmt: "assert" expression ["," expression] The simple form, "assert expression", is equivalent to @@ -39,15 +39,15 @@ Assignment statements are used to (re)bind names to values and to modify attributes or items of mutable objects: - **assignment_stmt**: ("target_list" "=")+ ("starred_expression" | "yield_expression") - **target_list**: "target" ("," "target")* [","] - **target**: "identifier" - | "(" ["target_list"] ")" - | "[" ["target_list"] "]" - | "attributeref" - | "subscription" - | "slicing" - | "*" "target" + assignment_stmt: (target_list "=")+ (starred_expression | yield_expression) + target_list: target ("," target)* [","] + target: identifier + | "(" [target_list] ")" + | "[" [target_list] "]" + | attributeref + | subscription + | slicing + | "*" target (See section Primaries for the syntax definitions for *attributeref*, *subscription*, and *slicing*.) @@ -195,9 +195,9 @@ class Cls: Augmented assignment is the combination, in a single statement, of a binary operation and an assignment statement: - **augmented_assignment_stmt**: "augtarget" "augop" ("expression_list" | "yield_expression") - **augtarget**: "identifier" | "attributeref" | "subscription" | "slicing" - **augop**: "+=" | "-=" | "*=" | "@=" | "/=" | "//=" | "%=" | "**=" + augmented_assignment_stmt: augtarget augop (expression_list | yield_expression) + augtarget: identifier | attributeref | subscription | slicing + augop: "+=" | "-=" | "*=" | "@=" | "/=" | "//=" | "%=" | "**=" | ">>=" | "<<=" | "&=" | "^=" | "|=" (See section Primaries for the syntax definitions of the last three @@ -239,8 +239,8 @@ class and instance attributes applies as for regular assignments. a variable or attribute annotation and an optional assignment statement: - **annotated_assignment_stmt**: "augtarget" ":" "expression" - ["=" ("starred_expression" | "yield_expression")] + annotated_assignment_stmt: augtarget ":" expression + ["=" (starred_expression | yield_expression)] The difference from normal Assignment statements is that only a single target is allowed. @@ -289,7 +289,7 @@ class and instance attributes applies as for regular assignments. 'assignment-expressions': r'''Assignment expressions ********************** - **assignment_expression**: ["identifier" ":="] "expression" + assignment_expression: [identifier ":="] expression An assignment expression (sometimes also called a “named expression” or “walrus”) assigns an "expression" to an "identifier", while also @@ -324,8 +324,8 @@ class and instance attributes applies as for regular assignments. Coroutine function definition ============================= - **async_funcdef**: ["decorators"] "async" "def" "funcname" "(" ["parameter_list"] ")" - ["->" "expression"] ":" "suite" + async_funcdef: [decorators] "async" "def" funcname "(" [parameter_list] ")" + ["->" expression] ":" suite Execution of Python coroutines can be suspended and resumed at many points (see *coroutine*). "await" expressions, "async for" and "async @@ -351,7 +351,7 @@ async def func(param1, param2): The "async for" statement ========================= - **async_for_stmt**: "async" "for_stmt" + async_for_stmt: "async" for_stmt An *asynchronous iterable* provides an "__aiter__" method that directly returns an *asynchronous iterator*, which can call @@ -392,7 +392,7 @@ async def func(param1, param2): The "async with" statement ========================== - **async_with_stmt**: "async" "with_stmt" + async_with_stmt: "async" with_stmt An *asynchronous context manager* is a *context manager* that is able to suspend execution in its *enter* and *exit* methods. @@ -492,8 +492,8 @@ async def func(param1, param2): Python supports string and bytes literals and various numeric literals: - **literal**: "stringliteral" | "bytesliteral" - | "integer" | "floatnumber" | "imagnumber" + literal: stringliteral | bytesliteral + | integer | floatnumber | imagnumber Evaluation of a literal yields an object of the given type (string, bytes, integer, floating-point number, complex number) with the given @@ -842,7 +842,7 @@ class derived from a ""variable-length" built-in type" such as An attribute reference is a primary followed by a period and a name: - **attributeref**: "primary" "." "identifier" + attributeref: primary "." identifier The primary must evaluate to an object of a type that supports attribute references, which most objects do. This object is then @@ -864,9 +864,9 @@ class derived from a ""variable-length" built-in type" such as Augmented assignment is the combination, in a single statement, of a binary operation and an assignment statement: - **augmented_assignment_stmt**: "augtarget" "augop" ("expression_list" | "yield_expression") - **augtarget**: "identifier" | "attributeref" | "subscription" | "slicing" - **augop**: "+=" | "-=" | "*=" | "@=" | "/=" | "//=" | "%=" | "**=" + augmented_assignment_stmt: augtarget augop (expression_list | yield_expression) + augtarget: identifier | attributeref | subscription | slicing + augop: "+=" | "-=" | "*=" | "@=" | "/=" | "//=" | "%=" | "**=" | ">>=" | "<<=" | "&=" | "^=" | "|=" (See section Primaries for the syntax definitions of the last three @@ -906,7 +906,7 @@ class and instance attributes applies as for regular assignments. Suspend the execution of *coroutine* on an *awaitable* object. Can only be used inside a *coroutine function*. - **await_expr**: "await" "primary" + await_expr: "await" primary Added in version 3.5. ''', @@ -919,10 +919,10 @@ class and instance attributes applies as for regular assignments. levels, one for multiplicative operators and one for additive operators: - **m_expr**: "u_expr" | "m_expr" "*" "u_expr" | "m_expr" "@" "m_expr" | - "m_expr" "//" "u_expr" | "m_expr" "/" "u_expr" | - "m_expr" "%" "u_expr" - **a_expr**: "m_expr" | "a_expr" "+" "m_expr" | "a_expr" "-" "m_expr" + m_expr: u_expr | m_expr "*" u_expr | m_expr "@" m_expr | + m_expr "//" u_expr | m_expr "/" u_expr | + m_expr "%" u_expr + a_expr: m_expr | a_expr "+" m_expr | a_expr "-" m_expr The "*" (multiplication) operator yields the product of its arguments. The arguments must either both be numbers, or one argument must be an @@ -1010,9 +1010,9 @@ class and instance attributes applies as for regular assignments. Each of the three bitwise operations has a different priority level: - **and_expr**: "shift_expr" | "and_expr" "&" "shift_expr" - **xor_expr**: "and_expr" | "xor_expr" "^" "and_expr" - **or_expr**: "xor_expr" | "or_expr" "|" "xor_expr" + and_expr: shift_expr | and_expr "&" shift_expr + xor_expr: and_expr | xor_expr "^" and_expr + or_expr: xor_expr | or_expr "|" xor_expr The "&" operator yields the bitwise AND of its arguments, which must be integers or one of them must be a custom object overriding @@ -1077,9 +1077,9 @@ class and instance attributes applies as for regular assignments. 'booleans': r'''Boolean operations ****************** - **or_test**: "and_test" | "or_test" "or" "and_test" - **and_test**: "not_test" | "and_test" "and" "not_test" - **not_test**: "comparison" | "not" "not_test" + or_test: and_test | or_test "or" and_test + and_test: not_test | and_test "and" not_test + not_test: comparison | "not" not_test In the context of Boolean operations, and also when expressions are used by control flow statements, the following values are interpreted @@ -1111,7 +1111,7 @@ class and instance attributes applies as for regular assignments. 'break': r'''The "break" statement ********************* - **break_stmt**: "break" + break_stmt: "break" "break" may only occur syntactically nested in a "for" or "while" loop, but not nested in a function or class definition within that @@ -1143,18 +1143,18 @@ class and instance attributes applies as for regular assignments. A call calls a callable object (e.g., a *function*) with a possibly empty series of *arguments*: - **call**: "primary" "(" ["argument_list" [","] | "comprehension"] ")" - **argument_list**: "positional_arguments" ["," "starred_and_keywords"] - ["," "keywords_arguments"] - | "starred_and_keywords" ["," "keywords_arguments"] - | "keywords_arguments" - **positional_arguments**: positional_item ("," positional_item)* - **positional_item**: "assignment_expression" | "*" "expression" - **starred_and_keywords**: ("*" "expression" | "keyword_item") - ("," "*" "expression" | "," "keyword_item")* - **keywords_arguments**: ("keyword_item" | "**" "expression") - ("," "keyword_item" | "," "**" "expression")* - **keyword_item**: "identifier" "=" "expression" + call: primary "(" [argument_list [","] | comprehension] ")" + argument_list: positional_arguments ["," starred_and_keywords] + ["," keywords_arguments] + | starred_and_keywords ["," keywords_arguments] + | keywords_arguments + positional_arguments: positional_item ("," positional_item)* + positional_item: assignment_expression | "*" expression + starred_and_keywords: ("*" expression | keyword_item) + ("," "*" expression | "," keyword_item)* + keywords_arguments: (keyword_item | "**" expression) + ("," keyword_item | "," "**" expression)* + keyword_item: identifier "=" expression An optional trailing comma may be present after the positional and keyword arguments but does not affect the semantics. @@ -1296,9 +1296,9 @@ class and instance attributes applies as for regular assignments. A class definition defines a class object (see section The standard type hierarchy): - **classdef**: ["decorators"] "class" "classname" ["type_params"] ["inheritance"] ":" "suite" - **inheritance**: "(" ["argument_list"] ")" - **classname**: "identifier" + classdef: [decorators] "class" classname [type_params] [inheritance] ":" suite + inheritance: "(" [argument_list] ")" + classname: identifier A class definition is an executable statement. The inheritance list usually gives a list of base classes (see Metaclasses for more @@ -1386,8 +1386,8 @@ class attributes; they are shared by instances. Instance attributes operation. Also unlike C, expressions like "a < b < c" have the interpretation that is conventional in mathematics: - **comparison**: "or_expr" ("comp_operator" "or_expr")* - **comp_operator**: "<" | ">" | "==" | ">=" | "<=" | "!=" + comparison: or_expr (comp_operator or_expr)* + comp_operator: "<" | ">" | "==" | ">=" | "<=" | "!=" | "is" ["not"] | ["not"] "in" Comparisons yield boolean values: "True" or "False". Custom *rich @@ -1656,20 +1656,20 @@ class attributes; they are shared by instances. Instance attributes Summarizing: - **compound_stmt**: "if_stmt" - | "while_stmt" - | "for_stmt" - | "try_stmt" - | "with_stmt" - | "match_stmt" - | "funcdef" - | "classdef" - | "async_with_stmt" - | "async_for_stmt" - | "async_funcdef" - **suite**: "stmt_list" NEWLINE | NEWLINE INDENT "statement"+ DEDENT - **statement**: "stmt_list" NEWLINE | "compound_stmt" - **stmt_list**: "simple_stmt" (";" "simple_stmt")* [";"] + compound_stmt: if_stmt + | while_stmt + | for_stmt + | try_stmt + | with_stmt + | match_stmt + | funcdef + | classdef + | async_with_stmt + | async_for_stmt + | async_funcdef + suite: stmt_list NEWLINE | NEWLINE INDENT statement+ DEDENT + statement: stmt_list NEWLINE | compound_stmt + stmt_list: simple_stmt (";" simple_stmt)* [";"] Note that statements always end in a "NEWLINE" possibly followed by a "DEDENT". Also note that optional continuation clauses always begin @@ -1686,9 +1686,9 @@ class attributes; they are shared by instances. Instance attributes The "if" statement is used for conditional execution: - **if_stmt**: "if" "assignment_expression" ":" "suite" - ("elif" "assignment_expression" ":" "suite")* - ["else" ":" "suite"] + if_stmt: "if" assignment_expression ":" suite + ("elif" assignment_expression ":" suite)* + ["else" ":" suite] It selects exactly one of the suites by evaluating the expressions one by one until one is found to be true (see section Boolean operations @@ -1704,8 +1704,8 @@ class attributes; they are shared by instances. Instance attributes The "while" statement is used for repeated execution as long as an expression is true: - **while_stmt**: "while" "assignment_expression" ":" "suite" - ["else" ":" "suite"] + while_stmt: "while" assignment_expression ":" suite + ["else" ":" suite] This repeatedly tests the expression and, if it is true, executes the first suite; if the expression is false (which may be the first time @@ -1724,8 +1724,8 @@ class attributes; they are shared by instances. Instance attributes The "for" statement is used to iterate over the elements of a sequence (such as a string, tuple or list) or other iterable object: - **for_stmt**: "for" "target_list" "in" "starred_list" ":" "suite" - ["else" ":" "suite"] + for_stmt: "for" target_list "in" starred_list ":" suite + ["else" ":" suite] The "starred_list" expression is evaluated once; it should yield an *iterable* object. An *iterator* is created for that iterable. The @@ -1768,17 +1768,17 @@ class attributes; they are shared by instances. Instance attributes The "try" statement specifies exception handlers and/or cleanup code for a group of statements: - **try_stmt**: "try1_stmt" | "try2_stmt" | "try3_stmt" - **try1_stmt**: "try" ":" "suite" - ("except" ["expression" ["as" "identifier"]] ":" "suite")+ - ["else" ":" "suite"] - ["finally" ":" "suite"] - **try2_stmt**: "try" ":" "suite" - ("except" "*" "expression" ["as" "identifier"] ":" "suite")+ - ["else" ":" "suite"] - ["finally" ":" "suite"] - **try3_stmt**: "try" ":" "suite" - "finally" ":" "suite" + try_stmt: try1_stmt | try2_stmt | try3_stmt + try1_stmt: "try" ":" suite + ("except" [expression ["as" identifier]] ":" suite)+ + ["else" ":" suite] + ["finally" ":" suite] + try2_stmt: "try" ":" suite + ("except" "*" expression ["as" identifier] ":" suite)+ + ["else" ":" suite] + ["finally" ":" suite] + try3_stmt: "try" ":" suite + "finally" ":" suite Additional information on exceptions can be found in section Exceptions, and information on using the "raise" statement to generate @@ -1940,16 +1940,13 @@ class attributes; they are shared by instances. Instance attributes clause. If the "finally" clause raises another exception, the saved exception is set as the context of the new exception. If the "finally" clause executes a "return", "break" or "continue" statement, the saved -exception is discarded: +exception is discarded. For example, this function returns 42. - >>> def f(): - ... try: - ... 1/0 - ... finally: - ... return 42 - ... - >>> f() - 42 + def f(): + try: + 1/0 + finally: + return 42 The exception information is not available to the program during execution of the "finally" clause. @@ -1961,21 +1958,22 @@ class attributes; they are shared by instances. Instance attributes The return value of a function is determined by the last "return" statement executed. Since the "finally" clause always executes, a "return" statement executed in the "finally" clause will always be the -last one executed: +last one executed. The following function returns ‘finally’. - >>> def foo(): - ... try: - ... return 'try' - ... finally: - ... return 'finally' - ... - >>> foo() - 'finally' + def foo(): + try: + return 'try' + finally: + return 'finally' Changed in version 3.8: Prior to Python 3.8, a "continue" statement was illegal in the "finally" clause due to a problem with the implementation. +Changed in version 3.14.0a6 (unreleased): The compiler emits a +"SyntaxWarning" when a "return", "break" or "continue" appears in a +"finally" block (see **PEP 765**). + The "with" statement ==================== @@ -1985,9 +1983,9 @@ class attributes; they are shared by instances. Instance attributes Context Managers). This allows common "try"…"except"…"finally" usage patterns to be encapsulated for convenient reuse. - **with_stmt**: "with" ( "(" "with_stmt_contents" ","? ")" | "with_stmt_contents" ) ":" "suite" - **with_stmt_contents**: "with_item" ("," "with_item")* - **with_item**: "expression" ["as" "target"] + with_stmt: "with" ( "(" with_stmt_contents ","? ")" | with_stmt_contents ) ":" suite + with_stmt_contents: with_item ("," with_item)* + with_item: expression ["as" target] The execution of the "with" statement with one “item” proceeds as follows: @@ -2093,10 +2091,10 @@ class attributes; they are shared by instances. Instance attributes The match statement is used for pattern matching. Syntax: - **match_stmt**: 'match' "subject_expr" ":" NEWLINE INDENT "case_block"+ DEDENT - **subject_expr**: "star_named_expression" "," "star_named_expressions"? - | "named_expression" - **case_block**: 'case' "patterns" ["guard"] ":" "block" + match_stmt: 'match' subject_expr ":" NEWLINE INDENT case_block+ DEDENT + subject_expr: star_named_expression "," star_named_expressions? + | named_expression + case_block: 'case' patterns [guard] ":" block Note: @@ -2187,7 +2185,7 @@ class attributes; they are shared by instances. Instance attributes Guards ------ - **guard**: "if" "named_expression" + guard: "if" named_expression A "guard" (which is part of the "case") must succeed for code inside the "case" block to execute. It takes the form: "if" followed by an @@ -2254,16 +2252,16 @@ class attributes; they are shared by instances. Instance attributes The top-level syntax for "patterns" is: - **patterns**: "open_sequence_pattern" | "pattern" - **pattern**: "as_pattern" | "or_pattern" - **closed_pattern**: | "literal_pattern" - | "capture_pattern" - | "wildcard_pattern" - | "value_pattern" - | "group_pattern" - | "sequence_pattern" - | "mapping_pattern" - | "class_pattern" + patterns: open_sequence_pattern | pattern + pattern: as_pattern | or_pattern + closed_pattern: | literal_pattern + | capture_pattern + | wildcard_pattern + | value_pattern + | group_pattern + | sequence_pattern + | mapping_pattern + | class_pattern The descriptions below will include a description “in simple terms” of what a pattern does for illustration purposes (credits to Raymond @@ -2279,7 +2277,7 @@ class attributes; they are shared by instances. Instance attributes An OR pattern is two or more patterns separated by vertical bars "|". Syntax: - **or_pattern**: "|"."closed_pattern"+ + or_pattern: "|".closed_pattern+ Only the final subpattern may be irrefutable, and each subpattern must bind the same set of names to avoid ambiguity. @@ -2300,7 +2298,7 @@ class attributes; they are shared by instances. Instance attributes An AS pattern matches an OR pattern on the left of the "as" keyword against a subject. Syntax: - **as_pattern**: "or_pattern" "as" "capture_pattern" + as_pattern: or_pattern "as" capture_pattern If the OR pattern fails, the AS pattern fails. Otherwise, the AS pattern binds the subject to the name on the right of the as keyword @@ -2315,14 +2313,14 @@ class attributes; they are shared by instances. Instance attributes A literal pattern corresponds to most literals in Python. Syntax: - **literal_pattern**: "signed_number" - | "signed_number" "+" NUMBER - | "signed_number" "-" NUMBER - | "strings" + literal_pattern: signed_number + | signed_number "+" NUMBER + | signed_number "-" NUMBER + | strings | "None" | "True" | "False" - **signed_number**: ["-"] NUMBER + signed_number: ["-"] NUMBER The rule "strings" and the token "NUMBER" are defined in the standard Python grammar. Triple-quoted strings are supported. Raw strings and @@ -2342,7 +2340,7 @@ class attributes; they are shared by instances. Instance attributes A capture pattern binds the subject value to a name. Syntax: - **capture_pattern**: !'_' NAME + capture_pattern: !'_' NAME A single underscore "_" is not a capture pattern (this is what "!'_'" expresses). It is instead treated as a "wildcard_pattern". @@ -2365,7 +2363,7 @@ class attributes; they are shared by instances. Instance attributes A wildcard pattern always succeeds (matches anything) and binds no name. Syntax: - **wildcard_pattern**: '_' + wildcard_pattern: '_' "_" is a soft keyword within any pattern, but only within patterns. It is an identifier, as usual, even within "match" subject @@ -2379,9 +2377,9 @@ class attributes; they are shared by instances. Instance attributes A value pattern represents a named value in Python. Syntax: - **value_pattern**: "attr" - **attr**: "name_or_attr" "." NAME - **name_or_attr**: "attr" | NAME + value_pattern: attr + attr: name_or_attr "." NAME + name_or_attr: attr | NAME The dotted name in the pattern is looked up using standard Python name resolution rules. The pattern succeeds if the value found compares @@ -2405,7 +2403,7 @@ class attributes; they are shared by instances. Instance attributes emphasize the intended grouping. Otherwise, it has no additional syntax. Syntax: - **group_pattern**: "(" "pattern" ")" + group_pattern: "(" pattern ")" In simple terms "(P)" has the same effect as "P". @@ -2417,12 +2415,12 @@ class attributes; they are shared by instances. Instance attributes sequence elements. The syntax is similar to the unpacking of a list or tuple. - **sequence_pattern**: "[" ["maybe_sequence_pattern"] "]" - | "(" ["open_sequence_pattern"] ")" - **open_sequence_pattern**: "maybe_star_pattern" "," ["maybe_sequence_pattern"] - **maybe_sequence_pattern**: ","."maybe_star_pattern"+ ","? - **maybe_star_pattern**: "star_pattern" | "pattern" - **star_pattern**: "*" ("capture_pattern" | "wildcard_pattern") + sequence_pattern: "[" [maybe_sequence_pattern] "]" + | "(" [open_sequence_pattern] ")" + open_sequence_pattern: maybe_star_pattern "," [maybe_sequence_pattern] + maybe_sequence_pattern: ",".maybe_star_pattern+ ","? + maybe_star_pattern: star_pattern | pattern + star_pattern: "*" (capture_pattern | wildcard_pattern) There is no difference if parentheses or square brackets are used for sequence patterns (i.e. "(...)" vs "[...]" ). @@ -2505,11 +2503,11 @@ class attributes; they are shared by instances. Instance attributes A mapping pattern contains one or more key-value patterns. The syntax is similar to the construction of a dictionary. Syntax: - **mapping_pattern**: "{" ["items_pattern"] "}" - **items_pattern**: ","."key_value_pattern"+ ","? - **key_value_pattern**: ("literal_pattern" | "value_pattern") ":" "pattern" - | "double_star_pattern" - **double_star_pattern**: "**" "capture_pattern" + mapping_pattern: "{" [items_pattern] "}" + items_pattern: ",".key_value_pattern+ ","? + key_value_pattern: (literal_pattern | value_pattern) ":" pattern + | double_star_pattern + double_star_pattern: "**" capture_pattern At most one double star pattern may be in a mapping pattern. The double star pattern must be the last subpattern in the mapping @@ -2558,12 +2556,12 @@ class attributes; they are shared by instances. Instance attributes A class pattern represents a class and its positional and keyword arguments (if any). Syntax: - **class_pattern**: "name_or_attr" "(" ["pattern_arguments" ","?] ")" - **pattern_arguments**: "positional_patterns" ["," "keyword_patterns"] - | "keyword_patterns" - **positional_patterns**: ","."pattern"+ - **keyword_patterns**: ","."keyword_pattern"+ - **keyword_pattern**: NAME "=" "pattern" + class_pattern: name_or_attr "(" [pattern_arguments ","?] ")" + pattern_arguments: positional_patterns ["," keyword_patterns] + | keyword_patterns + positional_patterns: ",".pattern+ + keyword_patterns: ",".keyword_pattern+ + keyword_pattern: NAME "=" pattern The same keyword should not be repeated in class patterns. @@ -2690,22 +2688,22 @@ class attributes; they are shared by instances. Instance attributes A function definition defines a user-defined function object (see section The standard type hierarchy): - **funcdef**: ["decorators"] "def" "funcname" ["type_params"] "(" ["parameter_list"] ")" - ["->" "expression"] ":" "suite" - **decorators**: "decorator"+ - **decorator**: "@" "assignment_expression" NEWLINE - **parameter_list**: "defparameter" ("," "defparameter")* "," "/" ["," ["parameter_list_no_posonly"]] - | "parameter_list_no_posonly" - **parameter_list_no_posonly**: "defparameter" ("," "defparameter")* ["," ["parameter_list_starargs"]] - | "parameter_list_starargs" - **parameter_list_starargs**: "*" ["star_parameter"] ("," "defparameter")* ["," ["parameter_star_kwargs"]] - "*" ("," "defparameter")+ ["," ["parameter_star_kwargs"]] - | "parameter_star_kwargs" - **parameter_star_kwargs**: "**" "parameter" [","] - **parameter**: "identifier" [":" "expression"] - **star_parameter**: "identifier" [":" ["*"] "expression"] - **defparameter**: "parameter" ["=" "expression"] - **funcname**: "identifier" + funcdef: [decorators] "def" funcname [type_params] "(" [parameter_list] ")" + ["->" expression] ":" suite + decorators: decorator+ + decorator: "@" assignment_expression NEWLINE + parameter_list: defparameter ("," defparameter)* "," "/" ["," [parameter_list_no_posonly]] + | parameter_list_no_posonly + parameter_list_no_posonly: defparameter ("," defparameter)* ["," [parameter_list_starargs]] + | parameter_list_starargs + parameter_list_starargs: "*" [star_parameter] ("," defparameter)* ["," [parameter_star_kwargs]] + "*" ("," defparameter)+ ["," [parameter_star_kwargs]] + | parameter_star_kwargs + parameter_star_kwargs: "**" parameter [","] + parameter: identifier [":" expression] + star_parameter: identifier [":" ["*"] expression] + defparameter: parameter ["=" expression] + funcname: identifier A function definition is an executable statement. Its execution binds the function name in the current local namespace to a function object @@ -2845,9 +2843,9 @@ def whats_on_the_telly(penguin=None): A class definition defines a class object (see section The standard type hierarchy): - **classdef**: ["decorators"] "class" "classname" ["type_params"] ["inheritance"] ":" "suite" - **inheritance**: "(" ["argument_list"] ")" - **classname**: "identifier" + classdef: [decorators] "class" classname [type_params] [inheritance] ":" suite + inheritance: "(" [argument_list] ")" + classname: identifier A class definition is an executable statement. The inheritance list usually gives a list of base classes (see Metaclasses for more @@ -2937,8 +2935,8 @@ class attributes; they are shared by instances. Instance attributes Coroutine function definition ----------------------------- - **async_funcdef**: ["decorators"] "async" "def" "funcname" "(" ["parameter_list"] ")" - ["->" "expression"] ":" "suite" + async_funcdef: [decorators] "async" "def" funcname "(" [parameter_list] ")" + ["->" expression] ":" suite Execution of Python coroutines can be suspended and resumed at many points (see *coroutine*). "await" expressions, "async for" and "async @@ -2964,7 +2962,7 @@ async def func(param1, param2): The "async for" statement ------------------------- - **async_for_stmt**: "async" "for_stmt" + async_for_stmt: "async" for_stmt An *asynchronous iterable* provides an "__aiter__" method that directly returns an *asynchronous iterator*, which can call @@ -3005,7 +3003,7 @@ async def func(param1, param2): The "async with" statement -------------------------- - **async_with_stmt**: "async" "with_stmt" + async_with_stmt: "async" with_stmt An *asynchronous context manager* is a *context manager* that is able to suspend execution in its *enter* and *exit* methods. @@ -3054,11 +3052,11 @@ async def func(param1, param2): Changed in version 3.13: Support for default values was added (see **PEP 696**). - **type_params**: "[" "type_param" ("," "type_param")* "]" - **type_param**: "typevar" | "typevartuple" | "paramspec" - **typevar**: "identifier" (":" "expression")? ("=" "expression")? - **typevartuple**: "*" "identifier" ("=" "expression")? - **paramspec**: "**" "identifier" ("=" "expression")? + type_params: "[" type_param ("," type_param)* "]" + type_param: typevar | typevartuple | paramspec + typevar: identifier (":" expression)? ("=" expression)? + typevartuple: "*" identifier ("=" expression)? + paramspec: "**" identifier ("=" expression)? Functions (including coroutines), classes and type aliases may contain a type parameter list: @@ -3424,7 +3422,7 @@ def f() -> annotation: ... 'continue': r'''The "continue" statement ************************ - **continue_stmt**: "continue" + continue_stmt: "continue" "continue" may only occur syntactically nested in a "for" or "while" loop, but not nested in a function or class definition within that @@ -3942,11 +3940,31 @@ def double(x): Enter post-mortem debugging of the exception found in "sys.last_exc". +pdb.set_default_backend(backend) + + There are two supported backends for pdb: "'settrace'" and + "'monitoring'". See "bdb.Bdb" for details. The user can set the + default backend to use if none is specified when instantiating + "Pdb". If no backend is specified, the default is "'settrace'". + + Note: + + "breakpoint()" and "set_trace()" will not be affected by this + function. They always use "'monitoring'" backend. + + Added in version 3.14. + +pdb.get_default_backend() + + Returns the default backend for pdb. + + Added in version 3.14. + The "run*" functions and "set_trace()" are aliases for instantiating the "Pdb" class and calling the method of the same name. If you want to access further features, you have to do this yourself: -class pdb.Pdb(completekey='tab', stdin=None, stdout=None, skip=None, nosigint=False, readrc=True, mode=None) +class pdb.Pdb(completekey='tab', stdin=None, stdout=None, skip=None, nosigint=False, readrc=True, mode=None, backend=None) "Pdb" is the debugger class. @@ -3972,6 +3990,11 @@ class pdb.Pdb(completekey='tab', stdin=None, stdout=None, skip=None, nosigint=Fa command line invocation) or "None" (for backwards compatible behaviour, as before the *mode* argument was added). + The *backend* argument specifies the backend to use for the + debugger. If "None" is passed, the default backend will be used. + See "set_default_backend()". Otherwise the supported backends are + "'settrace'" and "'monitoring'". + Example call to enable tracing with *skip*: import pdb; pdb.Pdb(skip=['django.*']).set_trace() @@ -3987,6 +4010,8 @@ class pdb.Pdb(completekey='tab', stdin=None, stdout=None, skip=None, nosigint=Fa Added in version 3.14: Added the *mode* argument. + Added in version 3.14: Added the *backend* argument. + Changed in version 3.14: Inline breakpoints like "breakpoint()" or "pdb.set_trace()" will always stop the program at calling frame, ignoring the *skip* pattern (if any). @@ -4045,7 +4070,7 @@ class pdb.Pdb(completekey='tab', stdin=None, stdout=None, skip=None, nosigint=Fa the program resumes execution so it’s less likely to interfere with your program compared to using normal variables like "foo = 1". -There are three preset *convenience variables*: +There are four preset *convenience variables*: * "$_frame": the current frame you are debugging @@ -4053,8 +4078,12 @@ class pdb.Pdb(completekey='tab', stdin=None, stdout=None, skip=None, nosigint=Fa * "$_exception": the exception if the frame is raising an exception +* "$_asynctask": the asyncio task if pdb stops in an async function + Added in version 3.12: Added the *convenience variable* feature. +Added in version 3.14: Added the "$_asynctask" convenience variable. + If a file ".pdbrc" exists in the user’s home directory or in the current directory, it is read with "'utf-8'" encoding and executed as if it had been typed at the debugger prompt, with the exception that @@ -4510,7 +4539,7 @@ def inner(x): 'del': r'''The "del" statement ******************* - **del_stmt**: "del" "target_list" + del_stmt: "del" target_list Deletion is recursively defined very similar to the way assignment is defined. Rather than spelling it out in full details, here are some @@ -4539,10 +4568,10 @@ def inner(x): A dictionary display is a possibly empty series of dict items (key/value pairs) enclosed in curly braces: - **dict_display**: "{" ["dict_item_list" | "dict_comprehension"] "}" - **dict_item_list**: "dict_item" ("," "dict_item")* [","] - **dict_item**: "expression" ":" "expression" | "**" "or_expr" - **dict_comprehension**: "expression" ":" "expression" "comp_for" + dict_display: "{" [dict_item_list | dict_comprehension] "}" + dict_item_list: dict_item ("," dict_item)* [","] + dict_item: expression ":" expression | "**" or_expr + dict_comprehension: expression ":" expression comp_for A dictionary display yields a new dictionary object. @@ -4603,9 +4632,9 @@ def f(): The "if" statement is used for conditional execution: - **if_stmt**: "if" "assignment_expression" ":" "suite" - ("elif" "assignment_expression" ":" "suite")* - ["else" ":" "suite"] + if_stmt: "if" assignment_expression ":" suite + ("elif" assignment_expression ":" suite)* + ["else" ":" suite] It selects exactly one of the suites by evaluating the expressions one by one until one is found to be true (see section Boolean operations @@ -5024,12 +5053,12 @@ class of the instance or a *non-virtual base class* thereof. The 'exprlists': r'''Expression lists **************** - **starred_expression**: ["*"] "or_expr" - **flexible_expression**: "assignment_expression" | "starred_expression" - **flexible_expression_list**: "flexible_expression" ("," "flexible_expression")* [","] - **starred_expression_list**: "starred_expression" ("," "starred_expression")* [","] - **expression_list**: "expression" ("," "expression")* [","] - **yield_list**: "expression_list" | "starred_expression" "," ["starred_expression_list"] + starred_expression: ["*"] or_expr + flexible_expression: assignment_expression | starred_expression + flexible_expression_list: flexible_expression ("," flexible_expression)* [","] + starred_expression_list: starred_expression ("," starred_expression)* [","] + expression_list: expression ("," expression)* [","] + yield_list: expression_list | starred_expression "," [starred_expression_list] Except when part of a list or set display, an expression list containing at least one comma yields a tuple. The length of the tuple @@ -5059,12 +5088,12 @@ class of the instance or a *non-virtual base class* thereof. The Floating-point literals are described by the following lexical definitions: - **floatnumber**: "pointfloat" | "exponentfloat" - **pointfloat**: ["digitpart"] "fraction" | "digitpart" "." - **exponentfloat**: ("digitpart" | "pointfloat") "exponent" - **digitpart**: "digit" (["_"] "digit")* - **fraction**: "." "digitpart" - **exponent**: ("e" | "E") ["+" | "-"] "digitpart" + floatnumber: pointfloat | exponentfloat + pointfloat: [digitpart] fraction | digitpart "." + exponentfloat: (digitpart | pointfloat) exponent + digitpart: digit (["_"] digit)* + fraction: "." digitpart + exponent: ("e" | "E") ["+" | "-"] digitpart Note that the integer and exponent parts are always interpreted using radix 10. For example, "077e010" is legal, and denotes the same number @@ -5085,8 +5114,8 @@ class of the instance or a *non-virtual base class* thereof. The The "for" statement is used to iterate over the elements of a sequence (such as a string, tuple or list) or other iterable object: - **for_stmt**: "for" "target_list" "in" "starred_list" ":" "suite" - ["else" ":" "suite"] + for_stmt: "for" target_list "in" starred_list ":" suite + ["else" ":" suite] The "starred_list" expression is evaluated once; it should yield an *iterable* object. An *iterator* is created for that iterable. The @@ -5140,14 +5169,14 @@ class of the instance or a *non-virtual base class* thereof. The The grammar for a replacement field is as follows: - **replacement_field**: "{" ["field_name"] ["!" "conversion"] [":" "format_spec"] "}" - **field_name**: "arg_name" ("." "attribute_name" | "[" "element_index" "]")* - **arg_name**: ["identifier" | "digit"+] - **attribute_name**: "identifier" - **element_index**: "digit"+ | "index_string" - **index_string**: + - **conversion**: "r" | "s" | "a" - **format_spec**: "format-spec:format_spec" + replacement_field: "{" [field_name] ["!" conversion] [":" format_spec] "}" + field_name: arg_name ("." attribute_name | "[" element_index "]")* + arg_name: [identifier | digit+] + attribute_name: identifier + element_index: digit+ | index_string + index_string: + + conversion: "r" | "s" | "a" + format_spec: format-spec:format_spec In less formal terms, the replacement field can start with a *field_name* that specifies the object whose value is to be formatted @@ -5244,18 +5273,18 @@ class of the instance or a *non-virtual base class* thereof. The The general form of a *standard format specifier* is: - **format_spec**: ["options"]["width_and_precision"]["type"] - **options**: [["fill"]"align"]["sign"]["z"]["#"]["0"] - **fill**: - **align**: "<" | ">" | "=" | "^" - **sign**: "+" | "-" | " " - **width_and_precision**: ["width_with_grouping"]["precision_with_grouping"] - **width_with_grouping**: ["width"]["grouping_option"] - **precision_with_grouping**: "." ["precision"]"grouping_option" - **width**: "digit"+ - **grouping_option**: "_" | "," - **precision**: "digit"+ - **type**: "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" + format_spec: [options][width_and_precision][type] + options: [[fill]align][sign]["z"]["#"]["0"] + fill: + align: "<" | ">" | "=" | "^" + sign: "+" | "-" | " " + width_and_precision: [width_with_grouping][precision_with_grouping] + width_with_grouping: [width][grouping_option] + precision_with_grouping: "." [precision]grouping_option + width: digit+ + grouping_option: "_" | "," + precision: digit+ + type: "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "n" | "o" | "s" | "x" | "X" | "%" If a valid *align* value is specified, it can be preceded by a *fill* @@ -5665,22 +5694,22 @@ class of the instance or a *non-virtual base class* thereof. The A function definition defines a user-defined function object (see section The standard type hierarchy): - **funcdef**: ["decorators"] "def" "funcname" ["type_params"] "(" ["parameter_list"] ")" - ["->" "expression"] ":" "suite" - **decorators**: "decorator"+ - **decorator**: "@" "assignment_expression" NEWLINE - **parameter_list**: "defparameter" ("," "defparameter")* "," "/" ["," ["parameter_list_no_posonly"]] - | "parameter_list_no_posonly" - **parameter_list_no_posonly**: "defparameter" ("," "defparameter")* ["," ["parameter_list_starargs"]] - | "parameter_list_starargs" - **parameter_list_starargs**: "*" ["star_parameter"] ("," "defparameter")* ["," ["parameter_star_kwargs"]] - "*" ("," "defparameter")+ ["," ["parameter_star_kwargs"]] - | "parameter_star_kwargs" - **parameter_star_kwargs**: "**" "parameter" [","] - **parameter**: "identifier" [":" "expression"] - **star_parameter**: "identifier" [":" ["*"] "expression"] - **defparameter**: "parameter" ["=" "expression"] - **funcname**: "identifier" + funcdef: [decorators] "def" funcname [type_params] "(" [parameter_list] ")" + ["->" expression] ":" suite + decorators: decorator+ + decorator: "@" assignment_expression NEWLINE + parameter_list: defparameter ("," defparameter)* "," "/" ["," [parameter_list_no_posonly]] + | parameter_list_no_posonly + parameter_list_no_posonly: defparameter ("," defparameter)* ["," [parameter_list_starargs]] + | parameter_list_starargs + parameter_list_starargs: "*" [star_parameter] ("," defparameter)* ["," [parameter_star_kwargs]] + "*" ("," defparameter)+ ["," [parameter_star_kwargs]] + | parameter_star_kwargs + parameter_star_kwargs: "**" parameter [","] + parameter: identifier [":" expression] + star_parameter: identifier [":" ["*"] expression] + defparameter: parameter ["=" expression] + funcname: identifier A function definition is an executable statement. Its execution binds the function name in the current local namespace to a function object @@ -5816,7 +5845,7 @@ def whats_on_the_telly(penguin=None): 'global': r'''The "global" statement ********************** - **global_stmt**: "global" "identifier" ("," "identifier")* + global_stmt: "global" identifier ("," identifier)* The "global" statement causes the listed identifiers to be interpreted as globals. It would be impossible to assign to a global variable @@ -5899,11 +5928,11 @@ class body. A "SyntaxError" is raised if a variable is used or Identifiers are unlimited in length. Case is significant. - **identifier**: "xid_start" "xid_continue"* - **id_start**: - **id_continue**: - **xid_start**: - **xid_continue**: + identifier: xid_start xid_continue* + id_start: + id_continue: + xid_start: + xid_continue: The Unicode category codes mentioned above stand for: @@ -6024,9 +6053,9 @@ class body. A "SyntaxError" is raised if a variable is used or The "if" statement is used for conditional execution: - **if_stmt**: "if" "assignment_expression" ":" "suite" - ("elif" "assignment_expression" ":" "suite")* - ["else" ":" "suite"] + if_stmt: "if" assignment_expression ":" suite + ("elif" assignment_expression ":" suite)* + ["else" ":" suite] It selects exactly one of the suites by evaluating the expressions one by one until one is found to be true (see section Boolean operations @@ -6040,7 +6069,7 @@ class body. A "SyntaxError" is raised if a variable is used or Imaginary literals are described by the following lexical definitions: - **imagnumber**: ("floatnumber" | "digitpart") ("j" | "J") + imagnumber: (floatnumber | digitpart) ("j" | "J") An imaginary literal yields a complex number with a real part of 0.0. Complex numbers are represented as a pair of floating-point numbers @@ -6053,14 +6082,14 @@ class body. A "SyntaxError" is raised if a variable is used or 'import': r'''The "import" statement ********************** - **import_stmt**: "import" "module" ["as" "identifier"] ("," "module" ["as" "identifier"])* - | "from" "relative_module" "import" "identifier" ["as" "identifier"] - ("," "identifier" ["as" "identifier"])* - | "from" "relative_module" "import" "(" "identifier" ["as" "identifier"] - ("," "identifier" ["as" "identifier"])* [","] ")" - | "from" "relative_module" "import" "*" - **module**: ("identifier" ".")* "identifier" - **relative_module**: "."* "module" | "."+ + import_stmt: "import" module ["as" identifier] ("," module ["as" identifier])* + | "from" relative_module "import" identifier ["as" identifier] + ("," identifier ["as" identifier])* + | "from" relative_module "import" "(" identifier ["as" identifier] + ("," identifier ["as" identifier])* [","] ")" + | "from" relative_module "import" "*" + module: (identifier ".")* identifier + relative_module: "."* module | "."+ The basic import statement (no "from" clause) is executed in two steps: @@ -6179,11 +6208,11 @@ class body. A "SyntaxError" is raised if a variable is used or allows use of the new features on a per-module basis before the release in which the feature becomes standard. - **future_stmt**: "from" "__future__" "import" "feature" ["as" "identifier"] - ("," "feature" ["as" "identifier"])* - | "from" "__future__" "import" "(" "feature" ["as" "identifier"] - ("," "feature" ["as" "identifier"])* [","] ")" - **feature**: "identifier" + future_stmt: "from" "__future__" "import" feature ["as" identifier] + ("," feature ["as" identifier])* + | "from" "__future__" "import" "(" feature ["as" identifier] + ("," feature ["as" identifier])* [","] ")" + feature: identifier A future statement must appear near the top of the module. The only lines that can appear before a future statement are: @@ -6292,16 +6321,16 @@ class body. A "SyntaxError" is raised if a variable is used or Integer literals are described by the following lexical definitions: - **integer**: "decinteger" | "bininteger" | "octinteger" | "hexinteger" - **decinteger**: "nonzerodigit" (["_"] "digit")* | "0"+ (["_"] "0")* - **bininteger**: "0" ("b" | "B") (["_"] "bindigit")+ - **octinteger**: "0" ("o" | "O") (["_"] "octdigit")+ - **hexinteger**: "0" ("x" | "X") (["_"] "hexdigit")+ - **nonzerodigit**: "1"..."9" - **digit**: "0"..."9" - **bindigit**: "0" | "1" - **octdigit**: "0"..."7" - **hexdigit**: "digit" | "a"..."f" | "A"..."F" + integer: decinteger | bininteger | octinteger | hexinteger + decinteger: nonzerodigit (["_"] digit)* | "0"+ (["_"] "0")* + bininteger: "0" ("b" | "B") (["_"] bindigit)+ + octinteger: "0" ("o" | "O") (["_"] octdigit)+ + hexinteger: "0" ("x" | "X") (["_"] hexdigit)+ + nonzerodigit: "1"..."9" + digit: "0"..."9" + bindigit: "0" | "1" + octdigit: "0"..."7" + hexdigit: digit | "a"..."f" | "A"..."F" There is no limit for the length of integer literals apart from what can be stored in available memory. @@ -6327,7 +6356,7 @@ class body. A "SyntaxError" is raised if a variable is used or 'lambda': r'''Lambdas ******* - **lambda_expr**: "lambda" ["parameter_list"] ":" "expression" + lambda_expr: "lambda" [parameter_list] ":" expression Lambda expressions (sometimes called lambda forms) are used to create anonymous functions. The expression "lambda parameters: expression" @@ -6347,7 +6376,7 @@ def (parameters): A list display is a possibly empty series of expressions enclosed in square brackets: - **list_display**: "[" ["flexible_expression_list" | "comprehension"] "]" + list_display: "[" [flexible_expression_list | comprehension] "]" A list display yields a new list object, the contents being specified by either a list of expressions or a comprehension. When a comma- @@ -6640,7 +6669,7 @@ def f(): 'nonlocal': r'''The "nonlocal" statement ************************ - **nonlocal_stmt**: "nonlocal" "identifier" ("," "identifier")* + nonlocal_stmt: "nonlocal" identifier ("," identifier)* When the definition of a function or class is nested (enclosed) within the definitions of other functions, its nonlocal scopes are the local @@ -7027,7 +7056,7 @@ class that has an "__rsub__()" method, "type(y).__rsub__(y, x)" is 'pass': r'''The "pass" statement ******************** - **pass_stmt**: "pass" + pass_stmt: "pass" "pass" is a null operation — when it is executed, nothing happens. It is useful as a placeholder when a statement is required syntactically, @@ -7044,7 +7073,7 @@ class C: pass # a class with no methods (yet) left; it binds less tightly than unary operators on its right. The syntax is: - **power**: ("await_expr" | "primary") ["**" "u_expr"] + power: (await_expr | primary) ["**" u_expr] Thus, in an unparenthesized sequence of power and unary operators, the operators are evaluated from right to left (this does not constrain @@ -7070,7 +7099,7 @@ class C: pass # a class with no methods (yet) 'raise': r'''The "raise" statement ********************* - **raise_stmt**: "raise" ["expression" ["from" "expression"]] + raise_stmt: "raise" [expression ["from" expression]] If no expressions are present, "raise" re-raises the exception that is currently being handled, which is also known as the *active @@ -7172,7 +7201,7 @@ class C: pass # a class with no methods (yet) 'return': r'''The "return" statement ********************** - **return_stmt**: "return" ["expression_list"] + return_stmt: "return" [expression_list] "return" may only occur syntactically nested in a function definition, not within a nested class definition. @@ -7357,7 +7386,7 @@ class C: pass # a class with no methods (yet) The shifting operations have lower priority than the arithmetic operations: - **shift_expr**: "a_expr" | "shift_expr" ("<<" | ">>") "a_expr" + shift_expr: a_expr | shift_expr ("<<" | ">>") a_expr These operators accept integers as arguments. They shift the first argument to the left or right by the number of bits given by the @@ -7378,13 +7407,13 @@ class C: pass # a class with no methods (yet) string, tuple or list). Slicings may be used as expressions or as targets in assignment or "del" statements. The syntax for a slicing: - **slicing**: "primary" "[" "slice_list" "]" - **slice_list**: "slice_item" ("," "slice_item")* [","] - **slice_item**: "expression" | "proper_slice" - **proper_slice**: ["lower_bound"] ":" ["upper_bound"] [ ":" ["stride"] ] - **lower_bound**: "expression" - **upper_bound**: "expression" - **stride**: "expression" + slicing: primary "[" slice_list "]" + slice_list: slice_item ("," slice_item)* [","] + slice_item: expression | proper_slice + proper_slice: [lower_bound] ":" [upper_bound] [ ":" [stride] ] + lower_bound: expression + upper_bound: expression + stride: expression There is ambiguity in the formal syntax here: anything that looks like an expression list also looks like a slice list, so any subscription @@ -9776,26 +9805,26 @@ class is used in a class pattern with positional arguments, each String literals are described by the following lexical definitions: - **stringliteral**: ["stringprefix"]("shortstring" | "longstring") - **stringprefix**: "r" | "u" | "R" | "U" | "f" | "F" + stringliteral: [stringprefix](shortstring | longstring) + stringprefix: "r" | "u" | "R" | "U" | "f" | "F" | "fr" | "Fr" | "fR" | "FR" | "rf" | "rF" | "Rf" | "RF" - **shortstring**: "'" "shortstringitem"* "'" | '"' "shortstringitem"* '"' - **longstring**: "\'\'\'" "longstringitem"* "\'\'\'" | '"""' "longstringitem"* '"""' - **shortstringitem**: "shortstringchar" | "stringescapeseq" - **longstringitem**: "longstringchar" | "stringescapeseq" - **shortstringchar**: - **longstringchar**: - **stringescapeseq**: "\\" - - **bytesliteral**: "bytesprefix"("shortbytes" | "longbytes") - **bytesprefix**: "b" | "B" | "br" | "Br" | "bR" | "BR" | "rb" | "rB" | "Rb" | "RB" - **shortbytes**: "'" "shortbytesitem"* "'" | '"' "shortbytesitem"* '"' - **longbytes**: "\'\'\'" "longbytesitem"* "\'\'\'" | '"""' "longbytesitem"* '"""' - **shortbytesitem**: "shortbyteschar" | "bytesescapeseq" - **longbytesitem**: "longbyteschar" | "bytesescapeseq" - **shortbyteschar**: - **longbyteschar**: - **bytesescapeseq**: "\\" + shortstring: "'" shortstringitem* "'" | '"' shortstringitem* '"' + longstring: "\'\'\'" longstringitem* "\'\'\'" | '"""' longstringitem* '"""' + shortstringitem: shortstringchar | stringescapeseq + longstringitem: longstringchar | stringescapeseq + shortstringchar: + longstringchar: + stringescapeseq: "\\" + + bytesliteral: bytesprefix(shortbytes | longbytes) + bytesprefix: "b" | "B" | "br" | "Br" | "bR" | "BR" | "rb" | "rB" | "Rb" | "RB" + shortbytes: "'" shortbytesitem* "'" | '"' shortbytesitem* '"' + longbytes: "\'\'\'" longbytesitem* "\'\'\'" | '"""' longbytesitem* '"""' + shortbytesitem: shortbyteschar | bytesescapeseq + longbytesitem: longbyteschar | bytesescapeseq + shortbyteschar: + longbyteschar: + bytesescapeseq: "\\" One syntactic restriction not indicated by these productions is that whitespace is not allowed between the "stringprefix" or "bytesprefix" @@ -9959,7 +9988,7 @@ class is used in a class pattern with positional arguments, each select an element from the container. The subscription of a *generic class* will generally return a GenericAlias object. - **subscription**: "primary" "[" "flexible_expression_list" "]" + subscription: primary "[" flexible_expression_list "]" When an object is subscripted, the interpreter will evaluate the primary and the expression list. @@ -10039,17 +10068,17 @@ class is used in a class pattern with positional arguments, each The "try" statement specifies exception handlers and/or cleanup code for a group of statements: - **try_stmt**: "try1_stmt" | "try2_stmt" | "try3_stmt" - **try1_stmt**: "try" ":" "suite" - ("except" ["expression" ["as" "identifier"]] ":" "suite")+ - ["else" ":" "suite"] - ["finally" ":" "suite"] - **try2_stmt**: "try" ":" "suite" - ("except" "*" "expression" ["as" "identifier"] ":" "suite")+ - ["else" ":" "suite"] - ["finally" ":" "suite"] - **try3_stmt**: "try" ":" "suite" - "finally" ":" "suite" + try_stmt: try1_stmt | try2_stmt | try3_stmt + try1_stmt: "try" ":" suite + ("except" [expression ["as" identifier]] ":" suite)+ + ["else" ":" suite] + ["finally" ":" suite] + try2_stmt: "try" ":" suite + ("except" "*" expression ["as" identifier] ":" suite)+ + ["else" ":" suite] + ["finally" ":" suite] + try3_stmt: "try" ":" suite + "finally" ":" suite Additional information on exceptions can be found in section Exceptions, and information on using the "raise" statement to generate @@ -10211,16 +10240,13 @@ class is used in a class pattern with positional arguments, each clause. If the "finally" clause raises another exception, the saved exception is set as the context of the new exception. If the "finally" clause executes a "return", "break" or "continue" statement, the saved -exception is discarded: +exception is discarded. For example, this function returns 42. - >>> def f(): - ... try: - ... 1/0 - ... finally: - ... return 42 - ... - >>> f() - 42 + def f(): + try: + 1/0 + finally: + return 42 The exception information is not available to the program during execution of the "finally" clause. @@ -10232,20 +10258,21 @@ class is used in a class pattern with positional arguments, each The return value of a function is determined by the last "return" statement executed. Since the "finally" clause always executes, a "return" statement executed in the "finally" clause will always be the -last one executed: +last one executed. The following function returns ‘finally’. - >>> def foo(): - ... try: - ... return 'try' - ... finally: - ... return 'finally' - ... - >>> foo() - 'finally' + def foo(): + try: + return 'try' + finally: + return 'finally' Changed in version 3.8: Prior to Python 3.8, a "continue" statement was illegal in the "finally" clause due to a problem with the implementation. + +Changed in version 3.14.0a6 (unreleased): The compiler emits a +"SyntaxWarning" when a "return", "break" or "continue" appears in a +"finally" block (see **PEP 765**). ''', 'types': r'''The standard type hierarchy *************************** @@ -12658,7 +12685,7 @@ class range(start, stop[, step]) All unary arithmetic and bitwise operations have the same priority: - **u_expr**: "power" | "-" "u_expr" | "+" "u_expr" | "~" "u_expr" + u_expr: power | "-" u_expr | "+" u_expr | "~" u_expr The unary "-" (minus) operator yields the negation of its numeric argument; the operation can be overridden with the "__neg__()" special @@ -12681,8 +12708,8 @@ class range(start, stop[, step]) The "while" statement is used for repeated execution as long as an expression is true: - **while_stmt**: "while" "assignment_expression" ":" "suite" - ["else" ":" "suite"] + while_stmt: "while" assignment_expression ":" suite + ["else" ":" suite] This repeatedly tests the expression and, if it is true, executes the first suite; if the expression is false (which may be the first time @@ -12702,9 +12729,9 @@ class range(start, stop[, step]) Context Managers). This allows common "try"…"except"…"finally" usage patterns to be encapsulated for convenient reuse. - **with_stmt**: "with" ( "(" "with_stmt_contents" ","? ")" | "with_stmt_contents" ) ":" "suite" - **with_stmt_contents**: "with_item" ("," "with_item")* - **with_item**: "expression" ["as" "target"] + with_stmt: "with" ( "(" with_stmt_contents ","? ")" | with_stmt_contents ) ":" suite + with_stmt_contents: with_item ("," with_item)* + with_item: expression ["as" target] The execution of the "with" statement with one “item” proceeds as follows: @@ -12805,7 +12832,7 @@ class range(start, stop[, step]) 'yield': r'''The "yield" statement ********************* - **yield_stmt**: "yield_expression" + yield_stmt: yield_expression A "yield" statement is semantically equivalent to a yield expression. The "yield" statement can be used to omit the parentheses that would diff --git a/Lib/random.py b/Lib/random.py index 4d9a047b027974..5e5d0c4c694a1c 100644 --- a/Lib/random.py +++ b/Lib/random.py @@ -245,11 +245,10 @@ def __init_subclass__(cls, /, **kwargs): def _randbelow_with_getrandbits(self, n): "Return a random int in the range [0,n). Defined for n > 0." - getrandbits = self.getrandbits k = n.bit_length() - r = getrandbits(k) # 0 <= r < 2**k + r = self.getrandbits(k) # 0 <= r < 2**k while r >= n: - r = getrandbits(k) + r = self.getrandbits(k) return r def _randbelow_without_getrandbits(self, n, maxsize=1<instr_ptr = next_instr; - next_instr += 1; - INSTRUCTION_STATS(OP); - _PyStackRef aa; - _PyStackRef input = PyStackRef_NULL; - _PyStackRef cc; - _PyStackRef xx; - _PyStackRef output = PyStackRef_NULL; - _PyStackRef zz; - cc = stack_pointer[-1]; - if ((oparg & 1) == 1) { input = stack_pointer[-1 - (((oparg & 1) == 1) ? 1 : 0)]; } - aa = stack_pointer[-2 - (((oparg & 1) == 1) ? 1 : 0)]; - output = SPAM(oparg, aa, cc, input); - xx = 0; - zz = 0; - stack_pointer[-2 - (((oparg & 1) == 1) ? 1 : 0)] = xx; - if (oparg & 2) stack_pointer[-1 - (((oparg & 1) == 1) ? 1 : 0)] = output; - stack_pointer[-1 - (((oparg & 1) == 1) ? 1 : 0) + ((oparg & 2) ? 1 : 0)] = zz; - stack_pointer += -(((oparg & 1) == 1) ? 1 : 0) + ((oparg & 2) ? 1 : 0); - assert(WITHIN_STACK_BOUNDS()); - DISPATCH(); - } - """ - self.run_cases_test(input, output) - - def test_macro_cond_effect(self): - input = """ - op(A, (left, middle, right --)) { - USE(left, middle, right); - INPUTS_DEAD(); - } - op(B, (-- deep, extra if (oparg), res)) { - deep = -1; - res = 0; - extra = 1; - INPUTS_DEAD(); - } - macro(M) = A + B; - """ - output = """ - TARGET(M) { - #if Py_TAIL_CALL_INTERP - int opcode = M; - (void)(opcode); - #endif - frame->instr_ptr = next_instr; - next_instr += 1; - INSTRUCTION_STATS(M); - _PyStackRef left; - _PyStackRef middle; - _PyStackRef right; - _PyStackRef deep; - _PyStackRef extra = PyStackRef_NULL; - _PyStackRef res; - // A - { - right = stack_pointer[-1]; - middle = stack_pointer[-2]; - left = stack_pointer[-3]; - USE(left, middle, right); - } - // B - { - deep = -1; - res = 0; - extra = 1; - } - stack_pointer[-3] = deep; - if (oparg) stack_pointer[-2] = extra; - stack_pointer[-2 + ((oparg) ? 1 : 0)] = res; - stack_pointer += -1 + ((oparg) ? 1 : 0); - assert(WITHIN_STACK_BOUNDS()); - DISPATCH(); - } - """ - self.run_cases_test(input, output) - def test_macro_push_push(self): input = """ op(A, (-- val1)) { @@ -1930,6 +1838,50 @@ def test_multiple_labels(self): """ self.run_cases_test(input, output) + def test_reassigning_live_inputs(self): + input = """ + inst(OP, (in -- )) { + in = 0; + DEAD(in); + } + """ + with self.assertRaises(SyntaxError): + self.run_cases_test(input, "") + + def test_reassigning_dead_inputs(self): + input = """ + inst(OP, (in -- )) { + temp = use(in); + DEAD(in); + in = temp; + PyStackRef_CLOSE(in); + } + """ + output = """ + TARGET(OP) { + #if Py_TAIL_CALL_INTERP + int opcode = OP; + (void)(opcode); + #endif + frame->instr_ptr = next_instr; + next_instr += 1; + INSTRUCTION_STATS(OP); + _PyStackRef in; + in = stack_pointer[-1]; + _PyFrame_SetStackPointer(frame, stack_pointer); + temp = use(in); + stack_pointer = _PyFrame_GetStackPointer(frame); + in = temp; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); + _PyFrame_SetStackPointer(frame, stack_pointer); + PyStackRef_CLOSE(in); + stack_pointer = _PyFrame_GetStackPointer(frame); + DISPATCH(); + } + """ + self.run_cases_test(input, output) + class TestGeneratedAbstractCases(unittest.TestCase): def setUp(self) -> None: diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py index bde0b45b3ceb93..bee2c06e274a96 100644 --- a/Lib/test/test_grammar.py +++ b/Lib/test/test_grammar.py @@ -7,6 +7,7 @@ import inspect import unittest import sys +import textwrap import warnings # testing import * from sys import * @@ -916,189 +917,292 @@ def g3(): self.assertEqual(y, (1, 2, 3), "unparenthesized star expr return") check_syntax_error(self, "class foo:return 1") - def test_break_in_finally(self): - with warnings.catch_warnings(): - warnings.simplefilter('ignore', SyntaxWarning) + def test_control_flow_in_finally(self): - count = 0 - while count < 2: - count += 1 + def run_case(self, src, expected): + with warnings.catch_warnings(): + warnings.simplefilter('ignore', SyntaxWarning) + g, l = {}, { 'self': self } + exec(textwrap.dedent(src), g, l) + self.assertEqual(expected, l['result']) + + + # *********** Break in finally *********** + + run_case( + self, + """ + result = 0 + while result < 2: + result += 1 + try: + pass + finally: + break + """, + 1) + + run_case( + self, + """ + result = 0 + while result < 2: + result += 1 + try: + continue + finally: + break + """, + 1) + + run_case( + self, + """ + result = 0 + while result < 2: + result += 1 + try: + 1/0 + finally: + break + """, + 1) + + run_case( + self, + """ + for result in [0, 1]: + self.assertEqual(result, 0) try: pass finally: break - self.assertEqual(count, 1) - - count = 0 - while count < 2: - count += 1 + """, + 0) + + run_case( + self, + """ + for result in [0, 1]: + self.assertEqual(result, 0) try: continue finally: break - self.assertEqual(count, 1) - - count = 0 - while count < 2: - count += 1 + """, + 0) + + run_case( + self, + """ + for result in [0, 1]: + self.assertEqual(result, 0) try: 1/0 finally: break - self.assertEqual(count, 1) + """, + 0) - for count in [0, 1]: - self.assertEqual(count, 0) + + # *********** Continue in finally *********** + + run_case( + self, + """ + result = 0 + while result < 2: + result += 1 try: pass finally: - break - self.assertEqual(count, 0) + continue + break + """, + 2) + - for count in [0, 1]: - self.assertEqual(count, 0) + run_case( + self, + """ + result = 0 + while result < 2: + result += 1 try: - continue - finally: break - self.assertEqual(count, 0) - - for count in [0, 1]: - self.assertEqual(count, 0) + finally: + continue + """, + 2) + + run_case( + self, + """ + result = 0 + while result < 2: + result += 1 try: 1/0 finally: - break - self.assertEqual(count, 0) - - def test_continue_in_finally(self): - count = 0 - while count < 2: - count += 1 - try: - pass - finally: - continue - break - self.assertEqual(count, 2) - - count = 0 - while count < 2: - count += 1 - try: + continue break - finally: - continue - self.assertEqual(count, 2) + """, + 2) - count = 0 - while count < 2: - count += 1 - try: - 1/0 - finally: - continue - break - self.assertEqual(count, 2) + run_case( + self, + """ + for result in [0, 1]: + try: + pass + finally: + continue + break + """, + 1) - for count in [0, 1]: - try: - pass - finally: - continue - break - self.assertEqual(count, 1) + run_case( + self, + """ + for result in [0, 1]: + try: + break + finally: + continue + """, + 1) - for count in [0, 1]: - try: + run_case( + self, + """ + for result in [0, 1]: + try: + 1/0 + finally: + continue break - finally: - continue - self.assertEqual(count, 1) + """, + 1) - for count in [0, 1]: - try: - 1/0 - finally: - continue - break - self.assertEqual(count, 1) - def test_return_in_finally(self): - def g1(): - try: - pass - finally: - return 1 - self.assertEqual(g1(), 1) + # *********** Return in finally *********** - def g2(): - try: - return 2 - finally: - return 3 - self.assertEqual(g2(), 3) + run_case( + self, + """ + def f(): + try: + pass + finally: + return 1 + result = f() + """, + 1) + + run_case( + self, + """ + def f(): + try: + return 2 + finally: + return 3 + result = f() + """, + 3) + + run_case( + self, + """ + def f(): + try: + 1/0 + finally: + return 4 + result = f() + """, + 4) - def g3(): - try: - 1/0 - finally: - return 4 - self.assertEqual(g3(), 4) + # See issue #37830 + run_case( + self, + """ + def break_in_finally_after_return1(x): + for count in [0, 1]: + count2 = 0 + while count2 < 20: + count2 += 10 + try: + return count + count2 + finally: + if x: + break + return 'end', count, count2 + + self.assertEqual(break_in_finally_after_return1(False), 10) + self.assertEqual(break_in_finally_after_return1(True), ('end', 1, 10)) + result = True + """, + True) + + + run_case( + self, + """ + def break_in_finally_after_return2(x): + for count in [0, 1]: + for count2 in [10, 20]: + try: + return count + count2 + finally: + if x: + break + return 'end', count, count2 + + self.assertEqual(break_in_finally_after_return2(False), 10) + self.assertEqual(break_in_finally_after_return2(True), ('end', 1, 10)) + result = True + """, + True) - def test_break_in_finally_after_return(self): # See issue #37830 - def g1(x): - for count in [0, 1]: - count2 = 0 - while count2 < 20: - count2 += 10 + run_case( + self, + """ + def continue_in_finally_after_return1(x): + count = 0 + while count < 100: + count += 1 try: - return count + count2 + return count finally: if x: - break - return 'end', count, count2 - self.assertEqual(g1(False), 10) - self.assertEqual(g1(True), ('end', 1, 10)) - - def g2(x): - for count in [0, 1]: - for count2 in [10, 20]: + continue + return 'end', count + + self.assertEqual(continue_in_finally_after_return1(False), 1) + self.assertEqual(continue_in_finally_after_return1(True), ('end', 100)) + result = True + """, + True) + + run_case( + self, + """ + def continue_in_finally_after_return2(x): + for count in [0, 1]: try: - return count + count2 + return count finally: if x: - break - return 'end', count, count2 - self.assertEqual(g2(False), 10) - self.assertEqual(g2(True), ('end', 1, 10)) - - def test_continue_in_finally_after_return(self): - # See issue #37830 - def g1(x): - count = 0 - while count < 100: - count += 1 - try: - return count - finally: - if x: - continue - return 'end', count - self.assertEqual(g1(False), 1) - self.assertEqual(g1(True), ('end', 100)) - - def g2(x): - for count in [0, 1]: - try: - return count - finally: - if x: - continue - return 'end', count - self.assertEqual(g2(False), 0) - self.assertEqual(g2(True), ('end', 1)) + continue + return 'end', count + + self.assertEqual(continue_in_finally_after_return2(False), 0) + self.assertEqual(continue_in_finally_after_return2(True), ('end', 1)) + result = True + """, + True) def test_yield(self): # Allowed as standalone statement diff --git a/Lib/test/test_pyrepl/support.py b/Lib/test/test_pyrepl/support.py index db7c68681f62c4..3692e164cb9254 100644 --- a/Lib/test/test_pyrepl/support.py +++ b/Lib/test/test_pyrepl/support.py @@ -6,14 +6,24 @@ from _pyrepl.console import Console, Event from _pyrepl.readline import ReadlineAlikeReader, ReadlineConfig from _pyrepl.simple_interact import _strip_final_indent +from _pyrepl.utils import unbracket, ANSI_ESCAPE_SEQUENCE + + +class ScreenEqualMixin: + def assert_screen_equal( + self, reader: ReadlineAlikeReader, expected: str, clean: bool = False + ): + actual = clean_screen(reader) if clean else reader.screen + expected = expected.split("\n") + self.assertListEqual(actual, expected) def multiline_input(reader: ReadlineAlikeReader, namespace: dict | None = None): saved = reader.more_lines try: reader.more_lines = partial(more_lines, namespace=namespace) - reader.ps1 = reader.ps2 = ">>>" - reader.ps3 = reader.ps4 = "..." + reader.ps1 = reader.ps2 = ">>> " + reader.ps3 = reader.ps4 = "... " return reader.readline() finally: reader.more_lines = saved @@ -38,18 +48,22 @@ def code_to_events(code: str): yield Event(evt="key", data=c, raw=bytearray(c.encode("utf-8"))) -def clean_screen(screen: Iterable[str]): +def clean_screen(reader: ReadlineAlikeReader) -> list[str]: """Cleans color and console characters out of a screen output. This is useful for screen testing, it increases the test readability since it strips out all the unreadable side of the screen. """ output = [] - for line in screen: - if line.startswith(">>>") or line.startswith("..."): - line = line[3:] + for line in reader.screen: + line = unbracket(line, including_content=True) + line = ANSI_ESCAPE_SEQUENCE.sub("", line) + for prefix in (reader.ps1, reader.ps2, reader.ps3, reader.ps4): + if line.startswith(prefix): + line = line[len(prefix):] + break output.append(line) - return "\n".join(output).strip() + return output def prepare_reader(console: Console, **kwargs): @@ -99,6 +113,9 @@ def handle_all_events( prepare_console=partial(prepare_console, width=10), ) +reader_no_colors = partial(prepare_reader, can_colorize=False) +reader_force_colors = partial(prepare_reader, can_colorize=True) + class FakeConsole(Console): def __init__(self, events, encoding="utf-8") -> None: diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index a6a66162d6b25a..8b063a25913b0b 100644 --- a/Lib/test/test_pyrepl/test_pyrepl.py +++ b/Lib/test/test_pyrepl/test_pyrepl.py @@ -17,12 +17,12 @@ from .support import ( FakeConsole, + ScreenEqualMixin, handle_all_events, handle_events_narrow_console, more_lines, multiline_input, code_to_events, - clean_screen, ) from _pyrepl.console import Event from _pyrepl.readline import (ReadlineAlikeReader, ReadlineConfig, @@ -587,7 +587,7 @@ def test_auto_indent_ignore_comments(self): self.assertEqual(output, output_code) -class TestPyReplOutput(TestCase): +class TestPyReplOutput(ScreenEqualMixin, TestCase): def prepare_reader(self, events): console = FakeConsole(events) config = ReadlineConfig(readline_completer=None) @@ -620,7 +620,7 @@ def test_basic(self): output = multiline_input(reader) self.assertEqual(output, "1+1") - self.assertEqual(clean_screen(reader.screen), "1+1") + self.assert_screen_equal(reader, "1+1", clean=True) def test_get_line_buffer_returns_str(self): reader = self.prepare_reader(code_to_events("\n")) @@ -654,11 +654,13 @@ def test_multiline_edit(self): reader = self.prepare_reader(events) output = multiline_input(reader) - self.assertEqual(output, "def f():\n ...\n ") - self.assertEqual(clean_screen(reader.screen), "def f():\n ...") + expected = "def f():\n ...\n " + self.assertEqual(output, expected) + self.assert_screen_equal(reader, expected, clean=True) output = multiline_input(reader) - self.assertEqual(output, "def g():\n pass\n ") - self.assertEqual(clean_screen(reader.screen), "def g():\n pass") + expected = "def g():\n pass\n " + self.assertEqual(output, expected) + self.assert_screen_equal(reader, expected, clean=True) def test_history_navigation_with_up_arrow(self): events = itertools.chain( @@ -677,16 +679,16 @@ def test_history_navigation_with_up_arrow(self): output = multiline_input(reader) self.assertEqual(output, "1+1") - self.assertEqual(clean_screen(reader.screen), "1+1") + self.assert_screen_equal(reader, "1+1", clean=True) output = multiline_input(reader) self.assertEqual(output, "2+2") - self.assertEqual(clean_screen(reader.screen), "2+2") + self.assert_screen_equal(reader, "2+2", clean=True) output = multiline_input(reader) self.assertEqual(output, "2+2") - self.assertEqual(clean_screen(reader.screen), "2+2") + self.assert_screen_equal(reader, "2+2", clean=True) output = multiline_input(reader) self.assertEqual(output, "1+1") - self.assertEqual(clean_screen(reader.screen), "1+1") + self.assert_screen_equal(reader, "1+1", clean=True) def test_history_with_multiline_entries(self): code = "def foo():\nx = 1\ny = 2\nz = 3\n\ndef bar():\nreturn 42\n\n" @@ -705,11 +707,9 @@ def test_history_with_multiline_entries(self): output = multiline_input(reader) output = multiline_input(reader) output = multiline_input(reader) - self.assertEqual( - clean_screen(reader.screen), - 'def foo():\n x = 1\n y = 2\n z = 3' - ) - self.assertEqual(output, "def foo():\n x = 1\n y = 2\n z = 3\n ") + expected = "def foo():\n x = 1\n y = 2\n z = 3\n " + self.assert_screen_equal(reader, expected, clean=True) + self.assertEqual(output, expected) def test_history_navigation_with_down_arrow(self): @@ -728,7 +728,7 @@ def test_history_navigation_with_down_arrow(self): output = multiline_input(reader) self.assertEqual(output, "1+1") - self.assertEqual(clean_screen(reader.screen), "1+1") + self.assert_screen_equal(reader, "1+1", clean=True) def test_history_search(self): events = itertools.chain( @@ -745,23 +745,23 @@ def test_history_search(self): output = multiline_input(reader) self.assertEqual(output, "1+1") - self.assertEqual(clean_screen(reader.screen), "1+1") + self.assert_screen_equal(reader, "1+1", clean=True) output = multiline_input(reader) self.assertEqual(output, "2+2") - self.assertEqual(clean_screen(reader.screen), "2+2") + self.assert_screen_equal(reader, "2+2", clean=True) output = multiline_input(reader) self.assertEqual(output, "3+3") - self.assertEqual(clean_screen(reader.screen), "3+3") + self.assert_screen_equal(reader, "3+3", clean=True) output = multiline_input(reader) self.assertEqual(output, "1+1") - self.assertEqual(clean_screen(reader.screen), "1+1") + self.assert_screen_equal(reader, "1+1", clean=True) def test_control_character(self): events = code_to_events("c\x1d\n") reader = self.prepare_reader(events) output = multiline_input(reader) self.assertEqual(output, "c\x1d") - self.assertEqual(clean_screen(reader.screen), "c") + self.assert_screen_equal(reader, "c\x1d", clean=True) def test_history_search_backward(self): # Test history search backward with "imp" input @@ -781,7 +781,7 @@ def test_history_search_backward(self): # search for "imp" in history output = multiline_input(reader) self.assertEqual(output, "import os") - self.assertEqual(clean_screen(reader.screen), "import os") + self.assert_screen_equal(reader, "import os", clean=True) def test_history_search_backward_empty(self): # Test history search backward with an empty input @@ -800,7 +800,7 @@ def test_history_search_backward_empty(self): # search backward in history output = multiline_input(reader) self.assertEqual(output, "import os") - self.assertEqual(clean_screen(reader.screen), "import os") + self.assert_screen_equal(reader, "import os", clean=True) class TestPyReplCompleter(TestCase): diff --git a/Lib/test/test_pyrepl/test_reader.py b/Lib/test/test_pyrepl/test_reader.py index 468c0c5e68d165..109cb603ae88b6 100644 --- a/Lib/test/test_pyrepl/test_reader.py +++ b/Lib/test/test_pyrepl/test_reader.py @@ -4,31 +4,28 @@ from unittest import TestCase from unittest.mock import MagicMock -from .support import handle_all_events, handle_events_narrow_console, code_to_events, prepare_reader, prepare_console +from .support import handle_all_events, handle_events_narrow_console +from .support import ScreenEqualMixin, code_to_events +from .support import prepare_reader, prepare_console from _pyrepl.console import Event from _pyrepl.reader import Reader -class TestReader(TestCase): - def assert_screen_equals(self, reader, expected): - actual = reader.screen - expected = expected.split("\n") - self.assertListEqual(actual, expected) - +class TestReader(ScreenEqualMixin, TestCase): def test_calc_screen_wrap_simple(self): events = code_to_events(10 * "a") reader, _ = handle_events_narrow_console(events) - self.assert_screen_equals(reader, f"{9*"a"}\\\na") + self.assert_screen_equal(reader, f"{9*"a"}\\\na") def test_calc_screen_wrap_wide_characters(self): events = code_to_events(8 * "a" + "樂") reader, _ = handle_events_narrow_console(events) - self.assert_screen_equals(reader, f"{8*"a"}\\\n樂") + self.assert_screen_equal(reader, f"{8*"a"}\\\n樂") def test_calc_screen_wrap_three_lines(self): events = code_to_events(20 * "a") reader, _ = handle_events_narrow_console(events) - self.assert_screen_equals(reader, f"{9*"a"}\\\n{9*"a"}\\\naa") + self.assert_screen_equal(reader, f"{9*"a"}\\\n{9*"a"}\\\naa") def test_calc_screen_prompt_handling(self): def prepare_reader_keep_prompts(*args, **kwargs): @@ -48,7 +45,7 @@ def prepare_reader_keep_prompts(*args, **kwargs): prepare_reader=prepare_reader_keep_prompts, ) # fmt: off - self.assert_screen_equals( + self.assert_screen_equal( reader, ( ">>> if so\\\n" @@ -74,13 +71,17 @@ def test_calc_screen_wrap_three_lines_mixed_character(self): reader, _ = handle_events_narrow_console(events) # fmt: off - self.assert_screen_equals(reader, ( - "def f():\n" - f" {7*"a"}\\\n" - "a\n" - f" {3*"樂"}\\\n" - "樂樂" - )) + self.assert_screen_equal( + reader, + ( + "def f():\n" + f" {7*"a"}\\\n" + "a\n" + f" {3*"樂"}\\\n" + "樂樂" + ), + clean=True, + ) # fmt: on def test_calc_screen_backspace(self): @@ -91,7 +92,7 @@ def test_calc_screen_backspace(self): ], ) reader, _ = handle_all_events(events) - self.assert_screen_equals(reader, "aa") + self.assert_screen_equal(reader, "aa") def test_calc_screen_wrap_removes_after_backspace(self): events = itertools.chain( @@ -101,7 +102,7 @@ def test_calc_screen_wrap_removes_after_backspace(self): ], ) reader, _ = handle_events_narrow_console(events) - self.assert_screen_equals(reader, 9 * "a") + self.assert_screen_equal(reader, 9 * "a") def test_calc_screen_backspace_in_second_line_after_wrap(self): events = itertools.chain( @@ -111,7 +112,7 @@ def test_calc_screen_backspace_in_second_line_after_wrap(self): ], ) reader, _ = handle_events_narrow_console(events) - self.assert_screen_equals(reader, f"{9*"a"}\\\na") + self.assert_screen_equal(reader, f"{9*"a"}\\\na") def test_setpos_for_xy_simple(self): events = code_to_events("11+11") @@ -123,7 +124,7 @@ def test_control_characters(self): code = 'flag = "🏳️‍🌈"' events = code_to_events(code) reader, _ = handle_all_events(events) - self.assert_screen_equals(reader, 'flag = "🏳️\\u200d🌈"') + self.assert_screen_equal(reader, 'flag = "🏳️\\u200d🌈"', clean=True) def test_setpos_from_xy_multiple_lines(self): # fmt: off @@ -173,7 +174,7 @@ def test_up_arrow_after_ctrl_r(self): ) reader, _ = handle_all_events(events) - self.assert_screen_equals(reader, "") + self.assert_screen_equal(reader, "") def test_newline_within_block_trailing_whitespace(self): # fmt: off @@ -212,13 +213,14 @@ def test_newline_within_block_trailing_whitespace(self): " \n" " a = 1\n" " \n" - " " # HistoricalReader will trim trailing whitespace + " " # HistoricalReader will trim trailing whitespace ) - self.assert_screen_equals(reader, expected) + self.assert_screen_equal(reader, expected, clean=True) self.assertTrue(reader.finished) def test_input_hook_is_called_if_set(self): input_hook = MagicMock() + def _prepare_console(events): console = MagicMock() console.get_event.side_effect = events @@ -235,18 +237,35 @@ def _prepare_console(events): def test_keyboard_interrupt_clears_screen(self): namespace = {"itertools": itertools} code = "import itertools\nitertools." - events = itertools.chain(code_to_events(code), [ - Event(evt='key', data='\t', raw=bytearray(b'\t')), # Two tabs for completion - Event(evt='key', data='\t', raw=bytearray(b'\t')), - Event(evt='key', data='\x03', raw=bytearray(b'\x03')), # Ctrl-C - ]) - - completing_reader = functools.partial( - prepare_reader, - readline_completer=rlcompleter.Completer(namespace).complete + events = itertools.chain( + code_to_events(code), + [ + # Two tabs for completion + Event(evt="key", data="\t", raw=bytearray(b"\t")), + Event(evt="key", data="\t", raw=bytearray(b"\t")), + Event(evt="key", data="\x03", raw=bytearray(b"\x03")), # Ctrl-C + ], ) - reader, _ = handle_all_events(events, prepare_reader=completing_reader) - self.assertEqual(reader.calc_screen(), code.split("\n")) + console = prepare_console(events) + reader = prepare_reader( + console, + readline_completer=rlcompleter.Completer(namespace).complete, + ) + try: + # we're not using handle_all_events() here to be able to + # follow the KeyboardInterrupt sequence of events. Normally this + # happens in simple_interact.run_multiline_interactive_console. + while True: + reader.handle1() + except KeyboardInterrupt: + # at this point the completions are still visible + self.assertTrue(len(reader.screen) > 2) + reader.refresh() + # after the refresh, they are gone + self.assertEqual(len(reader.screen), 2) + self.assert_screen_equal(reader, code, clean=True) + else: + self.fail("KeyboardInterrupt not raised.") def test_prompt_length(self): # Handles simple ASCII prompt @@ -282,14 +301,19 @@ def test_prompt_length(self): def test_completions_updated_on_key_press(self): namespace = {"itertools": itertools} code = "itertools." - events = itertools.chain(code_to_events(code), [ - Event(evt='key', data='\t', raw=bytearray(b'\t')), # Two tabs for completion - Event(evt='key', data='\t', raw=bytearray(b'\t')), - ], code_to_events("a")) + events = itertools.chain( + code_to_events(code), + [ + # Two tabs for completion + Event(evt="key", data="\t", raw=bytearray(b"\t")), + Event(evt="key", data="\t", raw=bytearray(b"\t")), + ], + code_to_events("a"), + ) completing_reader = functools.partial( prepare_reader, - readline_completer=rlcompleter.Completer(namespace).complete + readline_completer=rlcompleter.Completer(namespace).complete, ) reader, _ = handle_all_events(events, prepare_reader=completing_reader) @@ -301,17 +325,21 @@ def test_completions_updated_on_key_press(self): def test_key_press_on_tab_press_once(self): namespace = {"itertools": itertools} code = "itertools." - events = itertools.chain(code_to_events(code), [ - Event(evt='key', data='\t', raw=bytearray(b'\t')), - ], code_to_events("a")) + events = itertools.chain( + code_to_events(code), + [ + Event(evt="key", data="\t", raw=bytearray(b"\t")), + ], + code_to_events("a"), + ) completing_reader = functools.partial( prepare_reader, - readline_completer=rlcompleter.Completer(namespace).complete + readline_completer=rlcompleter.Completer(namespace).complete, ) reader, _ = handle_all_events(events, prepare_reader=completing_reader) - self.assert_screen_equals(reader, f"{code}a") + self.assert_screen_equal(reader, f"{code}a") def test_pos2xy_with_no_columns(self): console = prepare_console([]) diff --git a/Lib/test/test_pyrepl/test_unix_console.py b/Lib/test/test_pyrepl/test_unix_console.py index 15dbf48bcf0f1c..057cdd112852dc 100644 --- a/Lib/test/test_pyrepl/test_unix_console.py +++ b/Lib/test/test_pyrepl/test_unix_console.py @@ -7,7 +7,7 @@ from unittest import TestCase from unittest.mock import MagicMock, call, patch, ANY -from .support import handle_all_events, code_to_events +from .support import handle_all_events, code_to_events, reader_no_colors try: from _pyrepl.console import Event @@ -252,7 +252,9 @@ def test_resize_bigger_on_multiline_function(self, _os_write): # fmt: on events = itertools.chain(code_to_events(code)) - reader, console = handle_events_short_unix_console(events) + reader, console = handle_events_short_unix_console( + events, prepare_reader=reader_no_colors + ) console.height = 2 console.getheightwidth = MagicMock(lambda _: (2, 80)) diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index 3e4cf8dd56db08..fcbaf854cc294f 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -19,6 +19,7 @@ from test import support from test.support import os_helper from test.support import script_helper +from test.support import warnings_helper # Check for our compression modules. try: @@ -1638,10 +1639,13 @@ def write(self, data): raise exctype f = BadFile() - with self.assertRaises(exctype): - tar = tarfile.open(tmpname, self.mode, fileobj=f, - format=tarfile.PAX_FORMAT, - pax_headers={'non': 'empty'}) + with ( + warnings_helper.check_no_resource_warning(self), + self.assertRaises(exctype), + ): + tarfile.open(tmpname, self.mode, fileobj=f, + format=tarfile.PAX_FORMAT, + pax_headers={'non': 'empty'}) self.assertFalse(f.closed) def test_missing_fileobj(self): diff --git a/Lib/test/test_winsound.py b/Lib/test/test_winsound.py index 870ab7bd41d8ce..9724d830ade0b4 100644 --- a/Lib/test/test_winsound.py +++ b/Lib/test/test_winsound.py @@ -82,6 +82,18 @@ def test_hand(self): def test_question(self): safe_MessageBeep(winsound.MB_ICONQUESTION) + def test_error(self): + safe_MessageBeep(winsound.MB_ICONERROR) + + def test_information(self): + safe_MessageBeep(winsound.MB_ICONINFORMATION) + + def test_stop(self): + safe_MessageBeep(winsound.MB_ICONSTOP) + + def test_warning(self): + safe_MessageBeep(winsound.MB_ICONWARNING) + def test_keyword_args(self): safe_MessageBeep(type=winsound.MB_OK) @@ -161,6 +173,15 @@ def test_stopasync(self): # does not raise on systems without a sound card. winsound.PlaySound(None, winsound.SND_PURGE) + def test_sound_sentry(self): + safe_PlaySound("SystemExit", winsound.SND_ALIAS | winsound.SND_SENTRY) + + def test_sound_sync(self): + safe_PlaySound("SystemExit", winsound.SND_ALIAS | winsound.SND_SYNC) + + def test_sound_system(self): + safe_PlaySound("SystemExit", winsound.SND_ALIAS | winsound.SND_SYSTEM) + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-02-21-14-47-46.gh-issue-88887.V3U0CV.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-21-14-47-46.gh-issue-88887.V3U0CV.rst new file mode 100644 index 00000000000000..1a6c9483e2a35e --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-21-14-47-46.gh-issue-88887.V3U0CV.rst @@ -0,0 +1 @@ +Fixing multiprocessing Resource Tracker process leaking, usually observed when running Python as PID 1. diff --git a/Misc/NEWS.d/next/Library/2025-03-16-18-30-00.gh-issue-70647.1qq2r3.rst b/Misc/NEWS.d/next/Library/2025-03-16-18-30-00.gh-issue-70647.1qq2r3.rst new file mode 100644 index 00000000000000..33fb244cd563ba --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-03-16-18-30-00.gh-issue-70647.1qq2r3.rst @@ -0,0 +1,2 @@ +When creating a :mod:`datetime` object with an out of range date a more informative +error is raised. diff --git a/Misc/NEWS.d/next/Library/2025-03-18-02-11-33.gh-issue-120144.dBLFkI.rst b/Misc/NEWS.d/next/Library/2025-03-18-02-11-33.gh-issue-120144.dBLFkI.rst new file mode 100644 index 00000000000000..35d577e235102f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-03-18-02-11-33.gh-issue-120144.dBLFkI.rst @@ -0,0 +1 @@ +Disable ``CALL`` event in :mod:`bdb` in ``monitoring`` backend when we don't need any new events on the code object to get a better performance. diff --git a/Misc/NEWS.d/next/Library/2025-03-19-14-36-54.gh-issue-131461.uDUmdY.rst b/Misc/NEWS.d/next/Library/2025-03-19-14-36-54.gh-issue-131461.uDUmdY.rst new file mode 100644 index 00000000000000..735b3ca429ca75 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-03-19-14-36-54.gh-issue-131461.uDUmdY.rst @@ -0,0 +1 @@ +Fix :exc:`ResourceWarning` when constructing a :class:`gzip.GzipFile` in write mode with a broken file object. diff --git a/Misc/NEWS.d/next/Library/2025-03-19-20-37-07.gh-issue-131435.y8KMae.rst b/Misc/NEWS.d/next/Library/2025-03-19-20-37-07.gh-issue-131435.y8KMae.rst new file mode 100644 index 00000000000000..1a9810a8fed7d5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-03-19-20-37-07.gh-issue-131435.y8KMae.rst @@ -0,0 +1 @@ +10-20% performance improvement of :func:`random.randint`. diff --git a/Misc/NEWS.d/next/Library/2025-03-20-08-32-49.gh-issue-131492.saC2cA.rst b/Misc/NEWS.d/next/Library/2025-03-20-08-32-49.gh-issue-131492.saC2cA.rst new file mode 100644 index 00000000000000..0f52dec7ce8a83 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-03-20-08-32-49.gh-issue-131492.saC2cA.rst @@ -0,0 +1 @@ +Fix a resource leak when constructing a :class:`gzip.GzipFile` with a filename fails, for example when passing an invalid ``compresslevel``. diff --git a/Misc/NEWS.d/next/Windows/2025-03-19-21-58-16.gh-issue-131453.qQ4J5H.rst b/Misc/NEWS.d/next/Windows/2025-03-19-21-58-16.gh-issue-131453.qQ4J5H.rst new file mode 100644 index 00000000000000..4f44c091745700 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2025-03-19-21-58-16.gh-issue-131453.qQ4J5H.rst @@ -0,0 +1 @@ +Some :data:`!SND_*` and :data:`!MB_*` constants are added to :mod:`winsound`. diff --git a/Misc/mypy/README.md b/Misc/mypy/README.md new file mode 100644 index 00000000000000..05eda6c0b82f0a --- /dev/null +++ b/Misc/mypy/README.md @@ -0,0 +1,16 @@ +# Mypy path symlinks + +This directory stores symlinks to standard library modules and packages +that are fully type-annotated and ready to be used in type checking of +the rest of the stdlib or Tools/ and so on. + +Due to most of the standard library being untyped, we prefer not to +point mypy directly at `Lib/` for type checking. Additionally, mypy +as a tool does not support shadowing typing-related standard libraries +like `types`, `typing`, and `collections.abc`. + +So instead, we set `mypy_path` to include this directory, +which only links modules and packages we know are safe to be +type-checked themselves and used as dependencies. + +See `Lib/_pyrepl/mypy.ini` for an example. \ No newline at end of file diff --git a/Misc/mypy/_colorize.py b/Misc/mypy/_colorize.py new file mode 120000 index 00000000000000..9b7304769ec30b --- /dev/null +++ b/Misc/mypy/_colorize.py @@ -0,0 +1 @@ +../../Lib/_colorize.py \ No newline at end of file diff --git a/Misc/mypy/_pyrepl b/Misc/mypy/_pyrepl new file mode 120000 index 00000000000000..bd7b69909663b6 --- /dev/null +++ b/Misc/mypy/_pyrepl @@ -0,0 +1 @@ +../../Lib/_pyrepl \ No newline at end of file diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 70b59d67f56bda..9bba0e3354b26b 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -664,7 +664,8 @@ check_date_args(int year, int month, int day) int dim = days_in_month(year, month); if (day < 1 || day > dim) { PyErr_Format(PyExc_ValueError, - "day must be in 1..%d, not %d", dim, day); + "day %i must be in range 1..%d for month %i in year %i", + day, dim, month, year); return -1; } return 0; diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 5f39ed11b57e5c..56e3408652a6a0 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -28,6 +28,7 @@ #include "pycore_instruction_sequence.h" // _PyInstructionSequence_New() #include "pycore_interpframe.h" // _PyFrame_GetFunction() #include "pycore_object.h" // _PyObject_IsFreed() +#include "pycore_optimizer.h" // _Py_Executor_DependsOn #include "pycore_pathconfig.h" // _PyPathConfig_ClearGlobal() #include "pycore_pyerrors.h" // _PyErr_ChainExceptions1() #include "pycore_pylifecycle.h" // _PyInterpreterConfig_InitFromDict() diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 643f5dc47ab7a7..53b2e685a577db 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -2392,8 +2392,16 @@ PyDoc_STRVAR(thread__get_main_thread_ident_doc, Internal only. Return a non-zero integer that uniquely identifies the main thread\n\ of the main interpreter."); +#if defined(__OpenBSD__) + /* pthread_*_np functions, especially pthread_{get,set}_name_np(). + pthread_np.h exists on both OpenBSD and FreeBSD but the latter declares + pthread_getname_np() and pthread_setname_np() in pthread.h as long as + __BSD_VISIBLE remains set. + */ +# include +#endif -#if defined(HAVE_PTHREAD_GETNAME_NP) || defined(MS_WINDOWS) +#if defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP) || defined(MS_WINDOWS) /*[clinic input] _thread._get_name @@ -2408,7 +2416,12 @@ _thread__get_name_impl(PyObject *module) // Linux and macOS are limited to respectively 16 and 64 bytes char name[100]; pthread_t thread = pthread_self(); +#ifdef HAVE_PTHREAD_GETNAME_NP int rc = pthread_getname_np(thread, name, Py_ARRAY_LENGTH(name)); +#else /* defined(HAVE_PTHREAD_GET_NAME_NP) */ + int rc = 0; /* pthread_get_name_np() returns void */ + pthread_get_name_np(thread, name, Py_ARRAY_LENGTH(name)); +#endif if (rc) { errno = rc; return PyErr_SetFromErrno(PyExc_OSError); @@ -2435,10 +2448,10 @@ _thread__get_name_impl(PyObject *module) return name_obj; #endif } -#endif // HAVE_PTHREAD_GETNAME_NP +#endif // HAVE_PTHREAD_GETNAME_NP || HAVE_PTHREAD_GET_NAME_NP || MS_WINDOWS -#if defined(HAVE_PTHREAD_SETNAME_NP) || defined(MS_WINDOWS) +#if defined(HAVE_PTHREAD_SETNAME_NP) || defined(HAVE_PTHREAD_SET_NAME_NP) || defined(MS_WINDOWS) /*[clinic input] _thread.set_name @@ -2487,9 +2500,13 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) #elif defined(__NetBSD__) pthread_t thread = pthread_self(); int rc = pthread_setname_np(thread, "%s", (void *)name); -#else +#elif defined(HAVE_PTHREAD_SETNAME_NP) pthread_t thread = pthread_self(); int rc = pthread_setname_np(thread, name); +#else /* defined(HAVE_PTHREAD_SET_NAME_NP) */ + pthread_t thread = pthread_self(); + int rc = 0; /* pthread_set_name_np() returns void */ + pthread_set_name_np(thread, name); #endif Py_DECREF(name_encoded); if (rc) { @@ -2527,7 +2544,7 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) Py_RETURN_NONE; #endif } -#endif // HAVE_PTHREAD_SETNAME_NP +#endif // HAVE_PTHREAD_SETNAME_NP || HAVE_PTHREAD_SET_NAME_NP || MS_WINDOWS static PyMethodDef thread_methods[] = { diff --git a/Modules/clinic/_threadmodule.c.h b/Modules/clinic/_threadmodule.c.h index 09b7afebd6d8d9..367831c25a96c6 100644 --- a/Modules/clinic/_threadmodule.c.h +++ b/Modules/clinic/_threadmodule.c.h @@ -8,7 +8,7 @@ preserve #endif #include "pycore_modsupport.h" // _PyArg_UnpackKeywords() -#if (defined(HAVE_PTHREAD_GETNAME_NP) || defined(MS_WINDOWS)) +#if (defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP) || defined(MS_WINDOWS)) PyDoc_STRVAR(_thread__get_name__doc__, "_get_name($module, /)\n" @@ -28,9 +28,9 @@ _thread__get_name(PyObject *module, PyObject *Py_UNUSED(ignored)) return _thread__get_name_impl(module); } -#endif /* (defined(HAVE_PTHREAD_GETNAME_NP) || defined(MS_WINDOWS)) */ +#endif /* (defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP) || defined(MS_WINDOWS)) */ -#if (defined(HAVE_PTHREAD_SETNAME_NP) || defined(MS_WINDOWS)) +#if (defined(HAVE_PTHREAD_SETNAME_NP) || defined(HAVE_PTHREAD_SET_NAME_NP) || defined(MS_WINDOWS)) PyDoc_STRVAR(_thread_set_name__doc__, "set_name($module, /, name)\n" @@ -92,7 +92,7 @@ _thread_set_name(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyOb return return_value; } -#endif /* (defined(HAVE_PTHREAD_SETNAME_NP) || defined(MS_WINDOWS)) */ +#endif /* (defined(HAVE_PTHREAD_SETNAME_NP) || defined(HAVE_PTHREAD_SET_NAME_NP) || defined(MS_WINDOWS)) */ #ifndef _THREAD__GET_NAME_METHODDEF #define _THREAD__GET_NAME_METHODDEF @@ -101,4 +101,4 @@ _thread_set_name(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyOb #ifndef _THREAD_SET_NAME_METHODDEF #define _THREAD_SET_NAME_METHODDEF #endif /* !defined(_THREAD_SET_NAME_METHODDEF) */ -/*[clinic end generated code: output=6e88ef6b126cece8 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=c7922811558d314f input=a9049054013a1b77]*/ diff --git a/Objects/codeobject.c b/Objects/codeobject.c index c55ab6b9b28e9e..635da094e37a29 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -9,6 +9,7 @@ #include "pycore_interpframe.h" // FRAME_SPECIALS_SIZE #include "pycore_opcode_metadata.h" // _PyOpcode_Caches #include "pycore_opcode_utils.h" // RESUME_AT_FUNC_START +#include "pycore_optimizer.h" // _Py_ExecutorDetach #include "pycore_pymem.h" // _PyMem_FreeDelayed() #include "pycore_pystate.h" // _PyInterpreterState_GET() #include "pycore_setobject.h" // _PySet_NextEntry() diff --git a/PC/winsound.c b/PC/winsound.c index 1cb42f2f3bdfc6..f485c2c5972b01 100644 --- a/PC/winsound.c +++ b/PC/winsound.c @@ -56,7 +56,10 @@ PyDoc_STRVAR(sound_module_doc, "SND_NODEFAULT - Do not play a default beep if the sound can not be found\n" "SND_NOSTOP - Do not interrupt any sounds currently playing\n" // Raising RuntimeError if needed "SND_NOWAIT - Return immediately if the sound driver is busy\n" // Without any errors -"SND_APPLICATION - sound is an application-specific alias in the registry." +"SND_APPLICATION - sound is an application-specific alias in the registry.\n" +"SND_SENTRY - Triggers a SoundSentry event when the sound is played.\n" +"SND_SYNC - Play the sound synchronously, default behavior.\n" +"SND_SYSTEM - Assign sound to the audio session for system notification sounds.\n" "\n" "Beep(frequency, duration) - Make a beep through the PC speaker.\n" "MessageBeep(type) - Call Windows MessageBeep."); @@ -232,12 +235,19 @@ exec_module(PyObject *module) ADD_DEFINE(SND_PURGE); ADD_DEFINE(SND_LOOP); ADD_DEFINE(SND_APPLICATION); + ADD_DEFINE(SND_SENTRY); + ADD_DEFINE(SND_SYNC); + ADD_DEFINE(SND_SYSTEM); ADD_DEFINE(MB_OK); ADD_DEFINE(MB_ICONASTERISK); ADD_DEFINE(MB_ICONEXCLAMATION); ADD_DEFINE(MB_ICONHAND); ADD_DEFINE(MB_ICONQUESTION); + ADD_DEFINE(MB_ICONERROR); + ADD_DEFINE(MB_ICONINFORMATION); + ADD_DEFINE(MB_ICONSTOP); + ADD_DEFINE(MB_ICONWARNING); #undef ADD_DEFINE diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 66546080b1f5fe..cdd4d5bdd46b43 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -59,7 +59,6 @@ #define guard #define override #define specializing -#define split #define replicate(TIMES) #define tier1 #define no_save_ip @@ -1686,8 +1685,10 @@ dummy_func( ERROR_IF(PyStackRef_IsNull(*res), error); } - op(_PUSH_NULL_CONDITIONAL, ( -- null if (oparg & 1))) { - null = PyStackRef_NULL; + op(_PUSH_NULL_CONDITIONAL, ( -- null[oparg & 1])) { + if (oparg & 1) { + null[0] = PyStackRef_NULL; + } } macro(LOAD_GLOBAL) = diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index ff9f33b6db0187..42a3d6d4be9ba4 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -2285,10 +2285,12 @@ } case _PUSH_NULL_CONDITIONAL: { - _PyStackRef null = PyStackRef_NULL; + _PyStackRef *null; oparg = CURRENT_OPARG(); - null = PyStackRef_NULL; - if (oparg & 1) stack_pointer[0] = null; + null = &stack_pointer[0]; + if (oparg & 1) { + null[0] = PyStackRef_NULL; + } stack_pointer += (oparg & 1); assert(WITHIN_STACK_BOUNDS()); break; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 558b0b48ceaa71..9ce2b633d5e506 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -6982,7 +6982,7 @@ _PyStackRef class_st; _PyStackRef self_st; _PyStackRef attr; - _PyStackRef null = PyStackRef_NULL; + _PyStackRef *null; /* Skip 1 cache entry */ // _LOAD_SUPER_ATTR { @@ -7078,10 +7078,12 @@ } // _PUSH_NULL_CONDITIONAL { - null = PyStackRef_NULL; + null = &stack_pointer[1]; + if (oparg & 1) { + null[0] = PyStackRef_NULL; + } } stack_pointer[0] = attr; - if (oparg & 1) stack_pointer[1] = null; stack_pointer += 1 + (oparg & 1); assert(WITHIN_STACK_BOUNDS()); DISPATCH(); @@ -7840,7 +7842,7 @@ static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); _PyStackRef owner; _PyStackRef attr; - _PyStackRef null = PyStackRef_NULL; + _PyStackRef *null; /* Skip 1 cache entry */ // _CHECK_ATTR_CLASS { @@ -7876,9 +7878,11 @@ } // _PUSH_NULL_CONDITIONAL { - null = PyStackRef_NULL; + null = &stack_pointer[0]; + if (oparg & 1) { + null[0] = PyStackRef_NULL; + } } - if (oparg & 1) stack_pointer[0] = null; stack_pointer += (oparg & 1); assert(WITHIN_STACK_BOUNDS()); DISPATCH(); @@ -7897,7 +7901,7 @@ static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); _PyStackRef owner; _PyStackRef attr; - _PyStackRef null = PyStackRef_NULL; + _PyStackRef *null; /* Skip 1 cache entry */ // _CHECK_ATTR_CLASS { @@ -7943,9 +7947,11 @@ } // _PUSH_NULL_CONDITIONAL { - null = PyStackRef_NULL; + null = &stack_pointer[0]; + if (oparg & 1) { + null[0] = PyStackRef_NULL; + } } - if (oparg & 1) stack_pointer[0] = null; stack_pointer += (oparg & 1); assert(WITHIN_STACK_BOUNDS()); DISPATCH(); @@ -8022,7 +8028,7 @@ static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); _PyStackRef owner; _PyStackRef attr; - _PyStackRef null = PyStackRef_NULL; + _PyStackRef *null; /* Skip 1 cache entry */ // _GUARD_TYPE_VERSION { @@ -8078,9 +8084,11 @@ /* Skip 5 cache entries */ // _PUSH_NULL_CONDITIONAL { - null = PyStackRef_NULL; + null = &stack_pointer[0]; + if (oparg & 1) { + null[0] = PyStackRef_NULL; + } } - if (oparg & 1) stack_pointer[0] = null; stack_pointer += (oparg & 1); assert(WITHIN_STACK_BOUNDS()); DISPATCH(); @@ -8270,7 +8278,7 @@ static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); _PyStackRef owner; _PyStackRef attr; - _PyStackRef null = PyStackRef_NULL; + _PyStackRef *null; /* Skip 1 cache entry */ // _LOAD_ATTR_MODULE { @@ -8321,9 +8329,11 @@ /* Skip 5 cache entries */ // _PUSH_NULL_CONDITIONAL { - null = PyStackRef_NULL; + null = &stack_pointer[0]; + if (oparg & 1) { + null[0] = PyStackRef_NULL; + } } - if (oparg & 1) stack_pointer[0] = null; stack_pointer += (oparg & 1); assert(WITHIN_STACK_BOUNDS()); DISPATCH(); @@ -8552,7 +8562,7 @@ static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); _PyStackRef owner; _PyStackRef attr; - _PyStackRef null = PyStackRef_NULL; + _PyStackRef *null; /* Skip 1 cache entry */ // _GUARD_TYPE_VERSION { @@ -8599,9 +8609,11 @@ /* Skip 5 cache entries */ // _PUSH_NULL_CONDITIONAL { - null = PyStackRef_NULL; + null = &stack_pointer[0]; + if (oparg & 1) { + null[0] = PyStackRef_NULL; + } } - if (oparg & 1) stack_pointer[0] = null; stack_pointer += (oparg & 1); assert(WITHIN_STACK_BOUNDS()); DISPATCH(); @@ -8620,7 +8632,7 @@ static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); _PyStackRef owner; _PyStackRef attr; - _PyStackRef null = PyStackRef_NULL; + _PyStackRef *null; /* Skip 1 cache entry */ // _GUARD_TYPE_VERSION { @@ -8700,9 +8712,11 @@ /* Skip 5 cache entries */ // _PUSH_NULL_CONDITIONAL { - null = PyStackRef_NULL; + null = &stack_pointer[0]; + if (oparg & 1) { + null[0] = PyStackRef_NULL; + } } - if (oparg & 1) stack_pointer[0] = null; stack_pointer += (oparg & 1); assert(WITHIN_STACK_BOUNDS()); DISPATCH(); @@ -9080,7 +9094,7 @@ _Py_CODEUNIT* const this_instr = next_instr - 5; (void)this_instr; _PyStackRef *res; - _PyStackRef null = PyStackRef_NULL; + _PyStackRef *null; // _SPECIALIZE_LOAD_GLOBAL { uint16_t counter = read_u16(&this_instr[1].cache); @@ -9114,9 +9128,11 @@ } // _PUSH_NULL_CONDITIONAL { - null = PyStackRef_NULL; + null = &stack_pointer[1]; + if (oparg & 1) { + null[0] = PyStackRef_NULL; + } } - if (oparg & 1) stack_pointer[1] = null; stack_pointer += 1 + (oparg & 1); assert(WITHIN_STACK_BOUNDS()); DISPATCH(); @@ -9134,7 +9150,7 @@ INSTRUCTION_STATS(LOAD_GLOBAL_BUILTIN); static_assert(INLINE_CACHE_ENTRIES_LOAD_GLOBAL == 4, "incorrect cache size"); _PyStackRef res; - _PyStackRef null = PyStackRef_NULL; + _PyStackRef *null; /* Skip 1 cache entry */ // _GUARD_GLOBALS_VERSION { @@ -9191,10 +9207,12 @@ } // _PUSH_NULL_CONDITIONAL { - null = PyStackRef_NULL; + null = &stack_pointer[1]; + if (oparg & 1) { + null[0] = PyStackRef_NULL; + } } stack_pointer[0] = res; - if (oparg & 1) stack_pointer[1] = null; stack_pointer += 1 + (oparg & 1); assert(WITHIN_STACK_BOUNDS()); DISPATCH(); @@ -9212,7 +9230,7 @@ INSTRUCTION_STATS(LOAD_GLOBAL_MODULE); static_assert(INLINE_CACHE_ENTRIES_LOAD_GLOBAL == 4, "incorrect cache size"); _PyStackRef res; - _PyStackRef null = PyStackRef_NULL; + _PyStackRef *null; /* Skip 1 cache entry */ // _NOP { @@ -9256,10 +9274,12 @@ } // _PUSH_NULL_CONDITIONAL { - null = PyStackRef_NULL; + null = &stack_pointer[1]; + if (oparg & 1) { + null[0] = PyStackRef_NULL; + } } stack_pointer[0] = res; - if (oparg & 1) stack_pointer[1] = null; stack_pointer += 1 + (oparg & 1); assert(WITHIN_STACK_BOUNDS()); DISPATCH(); @@ -9387,7 +9407,7 @@ _PyStackRef class_st; _PyStackRef self_st; _PyStackRef attr; - _PyStackRef null = PyStackRef_NULL; + _PyStackRef *null; // _SPECIALIZE_LOAD_SUPER_ATTR { class_st = stack_pointer[-2]; @@ -9499,10 +9519,12 @@ } // _PUSH_NULL_CONDITIONAL { - null = PyStackRef_NULL; + null = &stack_pointer[1]; + if (oparg & 1) { + null[0] = PyStackRef_NULL; + } } stack_pointer[0] = attr; - if (oparg & 1) stack_pointer[1] = null; stack_pointer += 1 + (oparg & 1); assert(WITHIN_STACK_BOUNDS()); DISPATCH(); diff --git a/Python/jit.c b/Python/jit.c index 95b5a1b52b8b65..1f4873ee63a88f 100644 --- a/Python/jit.c +++ b/Python/jit.c @@ -8,7 +8,11 @@ #include "pycore_ceval.h" #include "pycore_critical_section.h" #include "pycore_dict.h" +#include "pycore_floatobject.h" +#include "pycore_frame.h" +#include "pycore_interpframe.h" #include "pycore_intrinsics.h" +#include "pycore_list.h" #include "pycore_long.h" #include "pycore_opcode_metadata.h" #include "pycore_opcode_utils.h" @@ -16,6 +20,9 @@ #include "pycore_pyerrors.h" #include "pycore_setobject.h" #include "pycore_sliceobject.h" +#include "pycore_tuple.h" +#include "pycore_unicodeobject.h" + #include "pycore_jit.h" // Memory management stuff: //////////////////////////////////////////////////// diff --git a/Python/optimizer.c b/Python/optimizer.c index 6fc5eabdf8b44e..e2fe0f6cff7464 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -6,11 +6,15 @@ #include "pycore_interp.h" #include "pycore_backoff.h" #include "pycore_bitutils.h" // _Py_popcount32() +#include "pycore_code.h" // _Py_GetBaseCodeUnit +#include "pycore_interpframe.h" #include "pycore_object.h" // _PyObject_GC_UNTRACK() #include "pycore_opcode_metadata.h" // _PyOpcode_OpName[] #include "pycore_opcode_utils.h" // MAX_REAL_OPCODE #include "pycore_optimizer.h" // _Py_uop_analyze_and_optimize() #include "pycore_pystate.h" // _PyInterpreterState_GET() +#include "pycore_tuple.h" // _PyTuple_FromArraySteal +#include "pycore_unicodeobject.h" // _PyUnicode_FromASCII #include "pycore_uop_ids.h" #include "pycore_jit.h" #include @@ -1226,11 +1230,7 @@ uop_optimize( for (int pc = 0; pc < length; pc++) { int opcode = buffer[pc].opcode; int oparg = buffer[pc].oparg; - if (_PyUop_Flags[opcode] & HAS_OPARG_AND_1_FLAG) { - buffer[pc].opcode = opcode + 1 + (oparg & 1); - assert(strncmp(_PyOpcode_uop_name[buffer[pc].opcode], _PyOpcode_uop_name[opcode], strlen(_PyOpcode_uop_name[opcode])) == 0); - } - else if (oparg < _PyUop_Replication[opcode]) { + if (oparg < _PyUop_Replication[opcode]) { buffer[pc].opcode = opcode + oparg + 1; assert(strncmp(_PyOpcode_uop_name[buffer[pc].opcode], _PyOpcode_uop_name[opcode], strlen(_PyOpcode_uop_name[opcode])) == 0); } diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index 67bf8d11b3f9ac..017a2eeca0741e 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -21,6 +21,7 @@ #include "pycore_uop_metadata.h" #include "pycore_dict.h" #include "pycore_long.h" +#include "pycore_interpframe.h" // _PyFrame_GetCode #include "pycore_optimizer.h" #include "pycore_object.h" #include "pycore_dict.h" diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index ea7c39bd01ea07..cfa0a733cda21d 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -546,10 +546,14 @@ dummy_func(void) { } } - op (_PUSH_NULL_CONDITIONAL, ( -- null if (oparg & 1))) { - int opcode = (oparg & 1) ? _PUSH_NULL : _NOP; - REPLACE_OP(this_instr, opcode, 0, 0); - null = sym_new_null(ctx); + op (_PUSH_NULL_CONDITIONAL, ( -- null[oparg & 1])) { + if (oparg & 1) { + REPLACE_OP(this_instr, _PUSH_NULL, 0, 0); + null[0] = sym_new_null(ctx); + } + else { + REPLACE_OP(this_instr, _NOP, 0, 0); + } } op(_LOAD_ATTR, (owner -- attr, self_or_null[oparg&1])) { @@ -765,7 +769,7 @@ dummy_func(void) { Py_UNREACHABLE(); } - op(_PUSH_FRAME, (new_frame: _Py_UOpsAbstractFrame * -- unused if (0))) { + op(_PUSH_FRAME, (new_frame: _Py_UOpsAbstractFrame * -- )) { SYNC_SP(); ctx->frame->stack_pointer = stack_pointer; ctx->frame = new_frame; diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 3f315901a5beb8..fc70ee31a80002 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -921,11 +921,15 @@ } case _PUSH_NULL_CONDITIONAL: { - JitOptSymbol *null = NULL; - int opcode = (oparg & 1) ? _PUSH_NULL : _NOP; - REPLACE_OP(this_instr, opcode, 0, 0); - null = sym_new_null(ctx); - if (oparg & 1) stack_pointer[0] = null; + JitOptSymbol **null; + null = &stack_pointer[0]; + if (oparg & 1) { + REPLACE_OP(this_instr, _PUSH_NULL, 0, 0); + null[0] = sym_new_null(ctx); + } + else { + REPLACE_OP(this_instr, _NOP, 0, 0); + } stack_pointer += (oparg & 1); assert(WITHIN_STACK_BOUNDS()); break; diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index 8445546ffdf716..e8a4f87031b76a 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -6,6 +6,7 @@ #include "pycore_frame.h" #include "pycore_long.h" #include "pycore_optimizer.h" +#include "pycore_stats.h" #include "pycore_tuple.h" // _PyTuple_FromArray() #include @@ -114,7 +115,7 @@ _Py_uop_sym_is_const(JitOptContext *ctx, JitOptSymbol *sym) if (truthiness < 0) { return false; } - make_const(sym, (truthiness ^ sym->truthiness.not) ? Py_True : Py_False); + make_const(sym, (truthiness ^ sym->truthiness.invert) ? Py_True : Py_False); return true; } return false; @@ -139,7 +140,7 @@ _Py_uop_sym_get_const(JitOptContext *ctx, JitOptSymbol *sym) if (truthiness < 0) { return NULL; } - PyObject *res = (truthiness ^ sym->truthiness.not) ? Py_True : Py_False; + PyObject *res = (truthiness ^ sym->truthiness.invert) ? Py_True : Py_False; make_const(sym, res); return res; } @@ -290,7 +291,7 @@ _Py_uop_sym_set_const(JitOptContext *ctx, JitOptSymbol *sym, PyObject *const_val } JitOptSymbol *value = allocation_base(ctx) + sym->truthiness.value; PyTypeObject *type = _Py_uop_sym_get_type(value); - if (const_val == (sym->truthiness.not ? Py_False : Py_True)) { + if (const_val == (sym->truthiness.invert ? Py_False : Py_True)) { // value is truthy. This is only useful for bool: if (type == &PyBool_Type) { _Py_uop_sym_set_const(ctx, value, Py_True); @@ -497,7 +498,7 @@ _Py_uop_sym_truthiness(JitOptContext *ctx, JitOptSymbol *sym) if (truthiness < 0) { return truthiness; } - truthiness ^= sym->truthiness.not; + truthiness ^= sym->truthiness.invert; make_const(sym, truthiness ? Py_True : Py_False); return truthiness; } @@ -591,8 +592,8 @@ JitOptSymbol * _Py_uop_sym_new_truthiness(JitOptContext *ctx, JitOptSymbol *value, bool truthy) { // It's clearer to invert this in the signature: - bool not = !truthy; - if (value->tag == JIT_SYM_TRUTHINESS_TAG && value->truthiness.not == not) { + bool invert = !truthy; + if (value->tag == JIT_SYM_TRUTHINESS_TAG && value->truthiness.invert == invert) { return value; } JitOptSymbol *res = sym_new(ctx); @@ -602,11 +603,11 @@ _Py_uop_sym_new_truthiness(JitOptContext *ctx, JitOptSymbol *value, bool truthy) int truthiness = _Py_uop_sym_truthiness(ctx, value); if (truthiness < 0) { res->tag = JIT_SYM_TRUTHINESS_TAG; - res->truthiness.not = not; + res->truthiness.invert = invert; res->truthiness.value = (uint16_t)(value - allocation_base(ctx)); } else { - make_const(res, (truthiness ^ not) ? Py_True : Py_False); + make_const(res, (truthiness ^ invert) ? Py_True : Py_False); } return res; } diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 8fe58c320a33b2..ed21fce335c99d 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -18,6 +18,7 @@ #include "pycore_long.h" // _PyLong_InitTypes() #include "pycore_object.h" // _PyDebug_PrintTotalRefs() #include "pycore_obmalloc.h" // _PyMem_init_obmalloc() +#include "pycore_optimizer.h" // _Py_Executors_InvalidateAll #include "pycore_pathconfig.h" // _PyPathConfig_UpdateGlobal() #include "pycore_pyerrors.h" // _PyErr_Occurred() #include "pycore_pylifecycle.h" // _PyErr_Print() diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 664bf31e9ecd9b..1b2019a9f74d42 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -1614,7 +1614,7 @@ sys_getrecursionlimit_impl(PyObject *module) #ifdef MS_WINDOWS -static PyTypeObject WindowsVersionType = {0, 0, 0, 0, 0, 0}; +static PyTypeObject WindowsVersionType = { 0 }; static PyStructSequence_Field windows_version_fields[] = { {"major", "Major version number"}, diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index e0ef198c1646c2..491b5d127cf0f5 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -33,7 +33,6 @@ class Properties: pure: bool uses_opcode: bool tier: int | None = None - oparg_and_1: bool = False const_oparg: int = -1 needs_prev: bool = False no_save_ip: bool = False @@ -136,16 +135,14 @@ def size(self) -> int: class StackItem: name: str type: str | None - condition: str | None size: str peek: bool = False used: bool = False def __str__(self) -> str: - cond = f" if ({self.condition})" if self.condition else "" size = f"[{self.size}]" if self.size else "" type = "" if self.type is None else f"{self.type} " - return f"{type}{self.name}{size}{cond} {self.peek}" + return f"{type}{self.name}{size} {self.peek}" def is_array(self) -> bool: return self.size != "" @@ -180,7 +177,7 @@ class Uop: stack: StackEffect caches: list[CacheEntry] deferred_refs: dict[lexer.Token, str | None] - output_stores: list[lexer.Token] + local_stores: list[lexer.Token] body: list[lexer.Token] properties: Properties _size: int = -1 @@ -239,7 +236,7 @@ def __init__(self, name: str, spilled: bool, body: list[lexer.Token], properties self.properties = properties size:int = 0 - output_stores: list[lexer.Token] = [] + local_stores: list[lexer.Token] = [] instruction_size = None def __str__(self) -> str: @@ -348,10 +345,7 @@ def override_error( def convert_stack_item( item: parser.StackEffect, replace_op_arg_1: str | None ) -> StackItem: - cond = item.cond - if replace_op_arg_1 and OPARG_AND_1.match(item.cond): - cond = replace_op_arg_1 - return StackItem(item.name, item.type, cond, item.size) + return StackItem(item.name, item.type, item.size) def check_unused(stack: list[StackItem], input_names: dict[str, lexer.Token]) -> None: "Unused items cannot be on the stack above used, non-peek items" @@ -437,7 +431,7 @@ def find_assignment_target(node: parser.InstDef, idx: int) -> list[lexer.Token]: return [] -def find_stores_outputs(node: parser.InstDef) -> list[lexer.Token]: +def find_variable_stores(node: parser.InstDef) -> list[lexer.Token]: res: list[lexer.Token] = [] outnames = { out.name for out in node.outputs } innames = { out.name for out in node.inputs } @@ -455,9 +449,7 @@ def find_stores_outputs(node: parser.InstDef) -> list[lexer.Token]: if len(lhs) != 1 or lhs[0].kind != "IDENTIFIER": continue name = lhs[0] - if name.text in innames: - raise analysis_error(f"Cannot assign to input variable '{name.text}'", name) - if name.text in outnames: + if name.text in outnames or name.text in innames: res.append(name) return res @@ -815,33 +807,12 @@ def stack_effect_only_peeks(instr: parser.InstDef) -> bool: return False if len(stack_inputs) == 0: return False - if any(s.cond for s in stack_inputs) or any(s.cond for s in instr.outputs): - return False return all( (s.name == other.name and s.type == other.type and s.size == other.size) for s, other in zip(stack_inputs, instr.outputs) ) -OPARG_AND_1 = re.compile("\\(*oparg *& *1") - - -def effect_depends_on_oparg_1(op: parser.InstDef) -> bool: - for effect in op.inputs: - if isinstance(effect, parser.CacheEffect): - continue - if not effect.cond: - continue - if OPARG_AND_1.match(effect.cond): - return True - for effect in op.outputs: - if not effect.cond: - continue - if OPARG_AND_1.match(effect.cond): - return True - return False - - def compute_properties(op: parser.CodeDef) -> Properties: escaping_calls = find_escaping_api_calls(op) has_free = ( @@ -904,33 +875,10 @@ def make_uop( stack=analyze_stack(op), caches=analyze_caches(inputs), deferred_refs=analyze_deferred_refs(op), - output_stores=find_stores_outputs(op), + local_stores=find_variable_stores(op), body=op.block.tokens, properties=compute_properties(op), ) - if effect_depends_on_oparg_1(op) and "split" in op.annotations: - result.properties.oparg_and_1 = True - for bit in ("0", "1"): - name_x = name + "_" + bit - properties = compute_properties(op) - if properties.oparg: - # May not need oparg anymore - properties.oparg = any( - token.text == "oparg" for token in op.block.tokens - ) - rep = Uop( - name=name_x, - context=op.context, - annotations=op.annotations, - stack=analyze_stack(op, bit), - caches=analyze_caches(inputs), - deferred_refs=analyze_deferred_refs(op), - output_stores=find_stores_outputs(op), - body=op.block.tokens, - properties=properties, - ) - rep.replicates = result - uops[name_x] = rep for anno in op.annotations: if anno.startswith("replicate"): result.replicated = int(anno[10:-1]) @@ -949,7 +897,7 @@ def make_uop( stack=analyze_stack(op), caches=analyze_caches(inputs), deferred_refs=analyze_deferred_refs(op), - output_stores=find_stores_outputs(op), + local_stores=find_variable_stores(op), body=op.block.tokens, properties=properties, ) diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index 8e6bc5a8995dc9..cc1ea5c7da5455 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -262,7 +262,7 @@ def kill_inputs( next(tkn_iter) next(tkn_iter) for var in storage.inputs: - var.defined = False + var.kill() return True def kill( @@ -280,7 +280,7 @@ def kill( next(tkn_iter) for var in storage.inputs: if var.name == name: - var.defined = False + var.kill() break else: raise analysis_error( @@ -301,7 +301,7 @@ def stackref_kill( raise analysis_error( f"Cannot close '{name.text}' when " f"'{live}' is still live", name) - var.defined = False + var.kill() break if var.defined: live = var.name @@ -526,7 +526,7 @@ def _emit_block( ) -> tuple[bool, Token, Storage]: """ Returns (reachable?, closing '}', stack).""" braces = 1 - out_stores = set(uop.output_stores) + local_stores = set(uop.local_stores) tkn = next(tkn_iter) reload: Token | None = None try: @@ -574,11 +574,19 @@ def _emit_block( if not self._replacers[tkn.text](tkn, tkn_iter, uop, storage, inst): reachable = False else: - if tkn in out_stores: - for out in storage.outputs: - if out.name == tkn.text: - out.defined = True - out.in_memory = False + if tkn in local_stores: + for var in storage.inputs: + if var.name == tkn.text: + if var.defined or var.in_memory: + msg = f"Cannot assign to already defined input variable '{tkn.text}'" + raise analysis_error(msg, tkn) + var.defined = True + var.in_memory = False + break + for var in storage.outputs: + if var.name == tkn.text: + var.defined = True + var.in_memory = False break if tkn.text.startswith("DISPATCH"): self._print_storage(storage) @@ -648,8 +656,6 @@ def cflags(p: Properties) -> str: flags.append("HAS_PURE_FLAG") if p.no_save_ip: flags.append("HAS_NO_SAVE_IP_FLAG") - if p.oparg_and_1: - flags.append("HAS_OPARG_AND_1_FLAG") if flags: return " | ".join(flags) else: diff --git a/Tools/cases_generator/lexer.py b/Tools/cases_generator/lexer.py index 6afca750be9b19..b4bcd73fdbfe52 100644 --- a/Tools/cases_generator/lexer.py +++ b/Tools/cases_generator/lexer.py @@ -227,7 +227,6 @@ def choice(*opts: str) -> str: "register", "replaced", "pure", - "split", "replicate", "tier1", "tier2", diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index b265847a26c971..15be7608e93937 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -48,19 +48,13 @@ def declare_variables(uop: Uop, out: CWriter, skip_inputs: bool) -> None: for var in reversed(uop.stack.inputs): if var.used and var.name not in variables: variables.add(var.name) - if var.condition: - out.emit(f"{type_name(var)}{var.name} = NULL;\n") - else: - out.emit(f"{type_name(var)}{var.name};\n") + out.emit(f"{type_name(var)}{var.name};\n") for var in uop.stack.outputs: if var.peek: continue if var.name not in variables: variables.add(var.name) - if var.condition: - out.emit(f"{type_name(var)}{var.name} = NULL;\n") - else: - out.emit(f"{type_name(var)}{var.name};\n") + out.emit(f"{type_name(var)}{var.name};\n") def decref_inputs( diff --git a/Tools/cases_generator/parsing.py b/Tools/cases_generator/parsing.py index 011f34de288871..84aed49d491e01 100644 --- a/Tools/cases_generator/parsing.py +++ b/Tools/cases_generator/parsing.py @@ -77,12 +77,11 @@ class Block(Node): class StackEffect(Node): name: str = field(compare=False) # __eq__ only uses type, cond, size type: str = "" # Optional `:type` - cond: str = "" # Optional `if (cond)` size: str = "" # Optional `[size]` # Note: size cannot be combined with type or cond def __repr__(self) -> str: - items = [self.name, self.type, self.cond, self.size] + items = [self.name, self.type, self.size] while items and items[-1] == "": del items[-1] return f"StackEffect({', '.join(repr(item) for item in items)})" @@ -299,22 +298,15 @@ def stack_effect(self) -> StackEffect | None: type_text = self.require(lx.IDENTIFIER).text.strip() if self.expect(lx.TIMES): type_text += " *" - cond_text = "" - if self.expect(lx.IF): - self.require(lx.LPAREN) - if not (cond := self.expression()): - raise self.make_syntax_error("Expected condition") - self.require(lx.RPAREN) - cond_text = cond.text.strip() size_text = "" if self.expect(lx.LBRACKET): - if type_text or cond_text: + if type_text: raise self.make_syntax_error("Unexpected [") if not (size := self.expression()): raise self.make_syntax_error("Expected expression") self.require(lx.RBRACKET) size_text = size.text.strip() - return StackEffect(tkn.text, type_text, cond_text, size_text) + return StackEffect(tkn.text, type_text, size_text) return None @contextual diff --git a/Tools/cases_generator/stack.py b/Tools/cases_generator/stack.py index 51c4c810e20714..67d0418a114977 100644 --- a/Tools/cases_generator/stack.py +++ b/Tools/cases_generator/stack.py @@ -23,17 +23,7 @@ def maybe_parenthesize(sym: str) -> str: def var_size(var: StackItem) -> str: - if var.condition: - # Special case simplifications - if var.condition == "0": - return "0" - elif var.condition == "1": - return var.get_size() - elif var.condition == "oparg & 1" and not var.size: - return f"({var.condition})" - else: - return f"(({var.condition}) ? {var.get_size()} : 0)" - elif var.size: + if var.size: return var.size else: return "1" @@ -73,6 +63,10 @@ def redefinition(var: StackItem, prev: "Local") -> "Local": def from_memory(defn: StackItem) -> "Local": return Local(defn, True, True, True) + def kill(self) -> None: + self.defined = False + self.in_memory = False + def copy(self) -> "Local": return Local( self.item, @@ -89,10 +83,6 @@ def size(self) -> str: def name(self) -> str: return self.item.name - @property - def condition(self) -> str | None: - return self.item.condition - def is_array(self) -> bool: return self.item.is_array() @@ -275,15 +265,7 @@ def pop(self, var: StackItem) -> tuple[str, Local]: cast = f"({var.type})" if (not indirect and var.type) else "" bits = ".bits" if cast and self.extract_bits else "" assign = f"{var.name} = {cast}{indirect}stack_pointer[{self.base_offset.to_c()}]{bits};" - if var.condition: - if var.condition == "1": - assign = f"{assign}\n" - elif var.condition == "0": - return "", Local.unused(var) - else: - assign = f"if ({var.condition}) {{ {assign} }}\n" - else: - assign = f"{assign}\n" + assign = f"{assign}\n" return assign, Local.from_memory(var) def push(self, var: Local) -> None: @@ -303,10 +285,6 @@ def _do_emit( ) -> None: cast = f"({cast_type})" if var.type else "" bits = ".bits" if cast and extract_bits else "" - if var.condition == "0": - return - if var.condition and var.condition != "1": - out.emit(f"if ({var.condition}) ") out.emit(f"stack_pointer[{base_offset.to_c()}]{bits} = {cast}{var.name};\n") def _adjust_stack_pointer(self, out: CWriter, number: str) -> None: @@ -655,7 +633,7 @@ def close_named(close: str, name: str, overwrite: str) -> None: def close_variable(var: Local, overwrite: str) -> None: nonlocal tmp_defined close = "PyStackRef_CLOSE" - if "null" in var.name or var.condition and var.condition != "1": + if "null" in var.name: close = "PyStackRef_XCLOSE" if var.size: if var.size == "1": @@ -668,8 +646,6 @@ def close_variable(var: Local, overwrite: str) -> None: close_named(close, f"{var.name}[_i]", overwrite) out.emit("}\n") else: - if var.condition and var.condition == "0": - return close_named(close, var.name, overwrite) self.clear_dead_inputs() diff --git a/Tools/cases_generator/tier1_generator.py b/Tools/cases_generator/tier1_generator.py index 0f0addb3d99589..ee375681b50f0b 100644 --- a/Tools/cases_generator/tier1_generator.py +++ b/Tools/cases_generator/tier1_generator.py @@ -40,10 +40,7 @@ def declare_variable(var: StackItem, out: CWriter) -> None: type, null = type_and_null(var) space = " " if type[-1].isalnum() else "" - if var.condition: - out.emit(f"{type}{space}{var.name} = {null};\n") - else: - out.emit(f"{type}{space}{var.name};\n") + out.emit(f"{type}{space}{var.name};\n") def declare_variables(inst: Instruction, out: CWriter) -> None: diff --git a/Tools/cases_generator/tier2_generator.py b/Tools/cases_generator/tier2_generator.py index d378815f6af391..572c636e84c0ca 100644 --- a/Tools/cases_generator/tier2_generator.py +++ b/Tools/cases_generator/tier2_generator.py @@ -41,14 +41,7 @@ def declare_variable( required.remove(var.name) type, null = type_and_null(var) space = " " if type[-1].isalnum() else "" - if var.condition: - out.emit(f"{type}{space}{var.name} = {null};\n") - if uop.replicates: - # Replicas may not use all their conditional variables - # So avoid a compiler warning with a fake use - out.emit(f"(void){var.name};\n") - else: - out.emit(f"{type}{space}{var.name};\n") + out.emit(f"{type}{space}{var.name};\n") def declare_variables(uop: Uop, out: CWriter) -> None: @@ -189,9 +182,6 @@ def generate_tier2( for name, uop in analysis.uops.items(): if uop.properties.tier == 1: continue - if uop.properties.oparg_and_1: - out.emit(f"/* {uop.name} is split on (oparg & 1) */\n\n") - continue if uop.is_super(): continue why_not_viable = uop.why_not_viable() diff --git a/Tools/jit/template.c b/Tools/jit/template.c index 0b7d077d78ce7d..adc08f3cc5f2a5 100644 --- a/Tools/jit/template.c +++ b/Tools/jit/template.c @@ -4,10 +4,16 @@ #include "pycore_call.h" #include "pycore_ceval.h" #include "pycore_cell.h" +#include "pycore_code.h" #include "pycore_dict.h" +#include "pycore_floatobject.h" #include "pycore_emscripten_signal.h" +#include "pycore_frame.h" +#include "pycore_genobject.h" +#include "pycore_interpframe.h" #include "pycore_intrinsics.h" #include "pycore_jit.h" +#include "pycore_list.h" #include "pycore_long.h" #include "pycore_opcode_metadata.h" #include "pycore_opcode_utils.h" @@ -18,6 +24,8 @@ #include "pycore_sliceobject.h" #include "pycore_descrobject.h" #include "pycore_stackref.h" +#include "pycore_tuple.h" +#include "pycore_unicodeobject.h" #include "ceval_macros.h" diff --git a/configure b/configure index c4b5e074254332..a058553480ca5a 100755 --- a/configure +++ b/configure @@ -19608,12 +19608,24 @@ if test "x$ac_cv_func_pthread_kill" = xyes then : printf "%s\n" "#define HAVE_PTHREAD_KILL 1" >>confdefs.h +fi +ac_fn_c_check_func "$LINENO" "pthread_get_name_np" "ac_cv_func_pthread_get_name_np" +if test "x$ac_cv_func_pthread_get_name_np" = xyes +then : + printf "%s\n" "#define HAVE_PTHREAD_GET_NAME_NP 1" >>confdefs.h + fi ac_fn_c_check_func "$LINENO" "pthread_getname_np" "ac_cv_func_pthread_getname_np" if test "x$ac_cv_func_pthread_getname_np" = xyes then : printf "%s\n" "#define HAVE_PTHREAD_GETNAME_NP 1" >>confdefs.h +fi +ac_fn_c_check_func "$LINENO" "pthread_set_name_np" "ac_cv_func_pthread_set_name_np" +if test "x$ac_cv_func_pthread_set_name_np" = xyes +then : + printf "%s\n" "#define HAVE_PTHREAD_SET_NAME_NP 1" >>confdefs.h + fi ac_fn_c_check_func "$LINENO" "pthread_setname_np" "ac_cv_func_pthread_setname_np" if test "x$ac_cv_func_pthread_setname_np" = xyes @@ -30455,6 +30467,7 @@ case "$ac_sys_system" in Darwin) _PYTHREAD_NAME_MAXLEN=63;; iOS) _PYTHREAD_NAME_MAXLEN=63;; FreeBSD*) _PYTHREAD_NAME_MAXLEN=19;; # gh-131268 + OpenBSD*) _PYTHREAD_NAME_MAXLEN=23;; # gh-131268 *) _PYTHREAD_NAME_MAXLEN=;; esac if test -n "$_PYTHREAD_NAME_MAXLEN"; then diff --git a/configure.ac b/configure.ac index a9ebfc7883120f..23bd81ed4431b9 100644 --- a/configure.ac +++ b/configure.ac @@ -5147,7 +5147,8 @@ AC_CHECK_FUNCS([ \ posix_spawn_file_actions_addclosefrom_np \ pread preadv preadv2 process_vm_readv \ pthread_cond_timedwait_relative_np pthread_condattr_setclock pthread_init \ - pthread_kill pthread_getname_np pthread_setname_np pthread_getattr_np \ + pthread_kill pthread_get_name_np pthread_getname_np pthread_set_name_np + pthread_setname_np pthread_getattr_np \ ptsname ptsname_r pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \ rtpSpawn sched_get_priority_max sched_rr_get_interval sched_setaffinity \ sched_setparam sched_setscheduler sem_clockwait sem_getvalue sem_open \ @@ -7563,6 +7564,7 @@ case "$ac_sys_system" in Darwin) _PYTHREAD_NAME_MAXLEN=63;; iOS) _PYTHREAD_NAME_MAXLEN=63;; FreeBSD*) _PYTHREAD_NAME_MAXLEN=19;; # gh-131268 + OpenBSD*) _PYTHREAD_NAME_MAXLEN=23;; # gh-131268 *) _PYTHREAD_NAME_MAXLEN=;; esac if test -n "$_PYTHREAD_NAME_MAXLEN"; then diff --git a/pyconfig.h.in b/pyconfig.h.in index f12517301811dc..dbf7865447bc2e 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -993,6 +993,9 @@ /* Define to 1 if you have the 'pthread_getname_np' function. */ #undef HAVE_PTHREAD_GETNAME_NP +/* Define to 1 if you have the 'pthread_get_name_np' function. */ +#undef HAVE_PTHREAD_GET_NAME_NP + /* Define to 1 if you have the header file. */ #undef HAVE_PTHREAD_H @@ -1005,6 +1008,9 @@ /* Define to 1 if you have the 'pthread_setname_np' function. */ #undef HAVE_PTHREAD_SETNAME_NP +/* Define to 1 if you have the 'pthread_set_name_np' function. */ +#undef HAVE_PTHREAD_SET_NAME_NP + /* Define to 1 if you have the 'pthread_sigmask' function. */ #undef HAVE_PTHREAD_SIGMASK