Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
43013f7
gh-138779: Use the dev_t converter for st_rdev (GH-138780)
serhiy-storchaka Sep 15, 2025
f07ae27
gh-129813, PEP 782: Use PyBytesWriter in fcntl (#138921)
vstinner Sep 15, 2025
7c6efc3
gh-129813, PEP 782: Init small_buffer in PyBytesWriter_Create() (#138…
vstinner Sep 15, 2025
21c80ca
gh-129813, PEP 782: Use PyBytesWriter in _curses (#138920)
vstinner Sep 15, 2025
67cc1cf
gh-129813, PEP 782: Use PyBytesWriter in _codecs.escape_decode() (#13…
vstinner Sep 15, 2025
a5b9d0b
gh-134953: Expand theming for `True`/`False`/`None` (#135000)
StanFromIreland Sep 15, 2025
a003112
gh-138712: Add os.NODEV (GH-138728)
serhiy-storchaka Sep 15, 2025
5c4bb9b
gh-137992: fix `PyRefTracer_SetTracer` to start world before returnin…
kumaraditya303 Sep 15, 2025
9c9a0f7
GH-132732: Use pure op machinery to optimize various instructions wit…
savannahostrowski Sep 15, 2025
fa12c6b
GH-132732: Remove textwrap import (#138933)
savannahostrowski Sep 15, 2025
07d0b95
gh-137490: Fix signal.sigwaitinfo() on NetBSD (GH-137523)
serhiy-storchaka Sep 15, 2025
26cfb17
gh-138239: Fix incorrect highlighting of "type" in type statements in…
00ll00 Sep 15, 2025
46f823b
gh-132732: Clear errors in JIT optimizer on error (GH-136048)
Fidget-Spinner Sep 15, 2025
8ef7735
gh-128636: Fix crash in PyREPL when os.environ is overwritten with an…
yihong0618 Sep 15, 2025
811acc8
gh-134953: Make the True/False/None check more efficient (GH-138931)
ambv Sep 15, 2025
29d026f
gh-37817: Allow assignment to __bases__ of direct subclasses of built…
serhiy-storchaka Sep 15, 2025
a68efdf
gh-129813, PEP 782: Use PyBytesWriter in _hashopenssl (#138922)
vstinner Sep 15, 2025
537133d
gh-69605: Hardcode some stdlib submodules in PyREPL module completion…
loic-simon Sep 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Doc/library/os.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2630,6 +2630,13 @@ features:
Compose a raw device number from the major and minor device numbers.


.. data:: NODEV

Non-existent device.

.. versionadded:: next


.. function:: pathconf(path, name)

Return system configuration information relevant to a named file. *name*
Expand Down
22 changes: 16 additions & 6 deletions Lib/_colorize.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ class Difflib(ThemeSection):
class Syntax(ThemeSection):
prompt: str = ANSIColors.BOLD_MAGENTA
keyword: str = ANSIColors.BOLD_BLUE
keyword_constant: str = ANSIColors.BOLD_BLUE
builtin: str = ANSIColors.CYAN
comment: str = ANSIColors.RED
string: str = ANSIColors.GREEN
Expand Down Expand Up @@ -287,21 +288,29 @@ def decolor(text: str) -> str:


def can_colorize(*, file: IO[str] | IO[bytes] | None = None) -> bool:

def _safe_getenv(k: str, fallback: str | None = None) -> str | None:
"""Exception-safe environment retrieval. See gh-128636."""
try:
return os.environ.get(k, fallback)
except Exception:
return fallback

if file is None:
file = sys.stdout

if not sys.flags.ignore_environment:
if os.environ.get("PYTHON_COLORS") == "0":
if _safe_getenv("PYTHON_COLORS") == "0":
return False
if os.environ.get("PYTHON_COLORS") == "1":
if _safe_getenv("PYTHON_COLORS") == "1":
return True
if os.environ.get("NO_COLOR"):
if _safe_getenv("NO_COLOR"):
return False
if not COLORIZE:
return False
if os.environ.get("FORCE_COLOR"):
if _safe_getenv("FORCE_COLOR"):
return True
if os.environ.get("TERM") == "dumb":
if _safe_getenv("TERM") == "dumb":
return False

if not hasattr(file, "fileno"):
Expand Down Expand Up @@ -344,7 +353,8 @@ def get_theme(
environment (including environment variable state and console configuration
on Windows) can also change in the course of the application life cycle.
"""
if force_color or (not force_no_color and can_colorize(file=tty_file)):
if force_color or (not force_no_color and
can_colorize(file=tty_file)):
return _theme
return theme_no_color

Expand Down
31 changes: 29 additions & 2 deletions Lib/_pyrepl/_module_completer.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from __future__ import annotations

import importlib
import os
import pkgutil
import sys
import token
import tokenize
from importlib.machinery import FileFinder
from io import StringIO
from contextlib import contextmanager
from dataclasses import dataclass
Expand All @@ -16,6 +19,15 @@
from typing import Any, Iterable, Iterator, Mapping


HARDCODED_SUBMODULES = {
# Standard library submodules that are not detected by pkgutil.iter_modules
# but can be imported, so should be proposed in completion
"collections": ["abc"],
"os": ["path"],
"xml.parsers.expat": ["errors", "model"],
}


def make_default_module_completer() -> ModuleCompleter:
# Inside pyrepl, __package__ is set to None by default
return ModuleCompleter(namespace={'__package__': None})
Expand All @@ -41,6 +53,7 @@ def __init__(self, namespace: Mapping[str, Any] | None = None) -> None:
self.namespace = namespace or {}
self._global_cache: list[pkgutil.ModuleInfo] = []
self._curr_sys_path: list[str] = sys.path[:]
self._stdlib_path = os.path.dirname(importlib.__path__[0])

def get_completions(self, line: str) -> list[str] | None:
"""Return the next possible import completions for 'line'."""
Expand Down Expand Up @@ -95,12 +108,26 @@ def _find_modules(self, path: str, prefix: str) -> list[str]:
return []

modules: Iterable[pkgutil.ModuleInfo] = self.global_cache
is_stdlib_import: bool | None = None
for segment in path.split('.'):
modules = [mod_info for mod_info in modules
if mod_info.ispkg and mod_info.name == segment]
if is_stdlib_import is None:
# Top-level import decide if we import from stdlib or not
is_stdlib_import = all(
self._is_stdlib_module(mod_info) for mod_info in modules
)
modules = self.iter_submodules(modules)
return [module.name for module in modules
if self.is_suggestion_match(module.name, prefix)]

module_names = [module.name for module in modules]
if is_stdlib_import:
module_names.extend(HARDCODED_SUBMODULES.get(path, ()))
return [module_name for module_name in module_names
if self.is_suggestion_match(module_name, prefix)]

def _is_stdlib_module(self, module_info: pkgutil.ModuleInfo) -> bool:
return (isinstance(module_info.module_finder, FileFinder)
and module_info.module_finder.path == self._stdlib_path)

def is_suggestion_match(self, module_name: str, prefix: str) -> bool:
if prefix:
Expand Down
8 changes: 6 additions & 2 deletions Lib/_pyrepl/unix_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ def __init__(
self.pollob.register(self.input_fd, select.POLLIN)
self.terminfo = terminfo.TermInfo(term or None)
self.term = term
self.is_apple_terminal = (
platform.system() == "Darwin"
and os.getenv("TERM_PROGRAM") == "Apple_Terminal"
)

@overload
def _my_getstr(cap: str, optional: Literal[False] = False) -> bytes: ...
Expand Down Expand Up @@ -339,7 +343,7 @@ def prepare(self):
tcsetattr(self.input_fd, termios.TCSADRAIN, raw)

# In macOS terminal we need to deactivate line wrap via ANSI escape code
if platform.system() == "Darwin" and os.getenv("TERM_PROGRAM") == "Apple_Terminal":
if self.is_apple_terminal:
os.write(self.output_fd, b"\033[?7l")

self.screen = []
Expand Down Expand Up @@ -370,7 +374,7 @@ def restore(self):
self.flushoutput()
tcsetattr(self.input_fd, termios.TCSADRAIN, self.__svtermstate)

if platform.system() == "Darwin" and os.getenv("TERM_PROGRAM") == "Apple_Terminal":
if self.is_apple_terminal:
os.write(self.output_fd, b"\033[?7h")

if hasattr(self, "old_sigwinch"):
Expand Down
12 changes: 11 additions & 1 deletion Lib/_pyrepl/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
ZERO_WIDTH_BRACKET = re.compile(r"\x01.*?\x02")
ZERO_WIDTH_TRANS = str.maketrans({"\x01": "", "\x02": ""})
IDENTIFIERS_AFTER = {"def", "class"}
KEYWORD_CONSTANTS = {"True", "False", "None"}
BUILTINS = {str(name) for name in dir(builtins) if not name.startswith('_')}


Expand Down Expand Up @@ -197,8 +198,11 @@ def gen_colors_from_token_stream(
span = Span.from_token(token, line_lengths)
yield ColorSpan(span, "definition")
elif keyword.iskeyword(token.string):
span_cls = "keyword"
if token.string in KEYWORD_CONSTANTS:
span_cls = "keyword_constant"
span = Span.from_token(token, line_lengths)
yield ColorSpan(span, "keyword")
yield ColorSpan(span, span_cls)
if token.string in IDENTIFIERS_AFTER:
is_def_name = True
elif (
Expand Down Expand Up @@ -260,6 +264,12 @@ def is_soft_keyword_used(*tokens: TI | None) -> bool:
return True
case (TI(string="case"), TI(string="_"), TI(string=":")):
return True
case (
None | TI(T.NEWLINE) | TI(T.INDENT) | TI(T.DEDENT) | TI(string=":"),
TI(string="type"),
TI(T.NAME, string=s)
):
return not keyword.iskeyword(s)
case _:
return False

Expand Down
2 changes: 1 addition & 1 deletion Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2898,7 +2898,7 @@ def force_color(color: bool):
from .os_helper import EnvironmentVarGuard

with (
swap_attr(_colorize, "can_colorize", lambda file=None: color),
swap_attr(_colorize, "can_colorize", lambda *, file=None: color),
EnvironmentVarGuard() as env,
):
env.unset("FORCE_COLOR", "NO_COLOR", "PYTHON_COLORS")
Expand Down
85 changes: 85 additions & 0 deletions Lib/test/test_capi/test_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -1614,6 +1614,74 @@ def f(n):
# But all of the appends we care about are still there:
self.assertEqual(uops.count("_CALL_LIST_APPEND"), len("ABCDEFG"))

def test_unary_negative_pop_top_load_const_inline_borrow(self):
def testfunc(n):
x = 0
for i in range(n):
a = 1
result = -a
if result < 0:
x += 1
return x

res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
self.assertEqual(res, TIER2_THRESHOLD)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertNotIn("_UNARY_NEGATIVE", uops)
self.assertNotIn("_POP_TOP_LOAD_CONST_INLINE_BORROW", uops)

def test_unary_not_pop_top_load_const_inline_borrow(self):
def testfunc(n):
x = 0
for i in range(n):
a = 42
result = not a
if result:
x += 1
return x

res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
self.assertEqual(res, 0)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertNotIn("_UNARY_NOT", uops)
self.assertNotIn("_POP_TOP_LOAD_CONST_INLINE_BORROW", uops)

def test_unary_invert_pop_top_load_const_inline_borrow(self):
def testfunc(n):
x = 0
for i in range(n):
a = 0
result = ~a
if result < 0:
x += 1
return x

res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
self.assertEqual(res, TIER2_THRESHOLD)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertNotIn("_UNARY_INVERT", uops)
self.assertNotIn("_POP_TOP_LOAD_CONST_INLINE_BORROW", uops)

def test_compare_op_pop_two_load_const_inline_borrow(self):
def testfunc(n):
x = 0
for _ in range(n):
a = 10
b = 10.0
if a == b:
x += 1
return x

res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
self.assertEqual(res, TIER2_THRESHOLD)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertNotIn("_COMPARE_OP", uops)
self.assertNotIn("_POP_TWO_LOAD_CONST_INLINE_BORROW", uops)

def test_compare_op_int_pop_two_load_const_inline_borrow(self):
def testfunc(n):
x = 0
Expand Down Expand Up @@ -1665,6 +1733,23 @@ def testfunc(n):
self.assertNotIn("_COMPARE_OP_FLOAT", uops)
self.assertNotIn("_POP_TWO_LOAD_CONST_INLINE_BORROW", uops)

def test_contains_op_pop_two_load_const_inline_borrow(self):
def testfunc(n):
x = 0
for _ in range(n):
a = "foo"
s = "foo bar baz"
if a in s:
x += 1
return x

res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
self.assertEqual(res, TIER2_THRESHOLD)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertNotIn("_CONTAINS_OP", uops)
self.assertNotIn("_POP_TWO_LOAD_CONST_INLINE_BORROW", uops)

def test_to_bool_bool_contains_op_set(self):
"""
Test that _TO_BOOL_BOOL is removed from code like:
Expand Down
Loading
Loading