Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 6 additions & 6 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,12 @@ Lib/test/test_build_details.py @FFY00
InternalDocs/ @AA-Turner

# Tools, Configuration, etc
Doc/Makefile @AA-Turner @hugovk
Doc/_static/ @AA-Turner @hugovk
Doc/conf.py @AA-Turner @hugovk
Doc/make.bat @AA-Turner @hugovk
Doc/requirements.txt @AA-Turner @hugovk
Doc/tools/ @AA-Turner @hugovk
Doc/Makefile @AA-Turner @hugovk @StanFromIreland
Doc/_static/ @AA-Turner @hugovk @StanFromIreland
Doc/conf.py @AA-Turner @hugovk @StanFromIreland
Doc/make.bat @AA-Turner @hugovk @StanFromIreland
Doc/requirements.txt @AA-Turner @hugovk @StanFromIreland
Doc/tools/ @AA-Turner @hugovk @StanFromIreland

# PR Previews
.readthedocs.yml @AA-Turner
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/stale.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ jobs:
if: github.repository_owner == 'python'
runs-on: ubuntu-latest
permissions:
actions: write
pull-requests: write
timeout-minutes: 10

Expand Down
7 changes: 7 additions & 0 deletions Include/internal/pycore_code.h
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,13 @@ typedef struct {
int oparg;
binaryopguardfunc guard;
binaryopactionfunc action;
/* Static type of the result, or NULL if unknown. Used by the tier 2
optimizer to propagate type information through _BINARY_OP_EXTEND. */
PyTypeObject *result_type;
/* Nonzero iff `action` always returns a freshly allocated object (not
aliased to either operand). Used by the tier 2 optimizer to enable
inplace follow-up ops. */
int result_unique;
} _PyBinaryOpSpecializationDescr;

/* Comparison bit masks. */
Expand Down
6 changes: 6 additions & 0 deletions Include/internal/pycore_crossinterp.h
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,12 @@ typedef struct {
// heap types
PyObject *PyExc_NotShareableError;
} exceptions;

// Cached references to pickle.dumps/loads (per-interpreter).
struct {
PyObject *dumps;
PyObject *loads;
} pickle;
} _PyXI_state_t;

#define _PyXI_GET_GLOBAL_STATE(interp) (&(interp)->runtime->xi)
Expand Down
23 changes: 7 additions & 16 deletions Lib/_pyrepl/fancycompleter.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,6 @@ def attr_matches(self, text):
names = [f'{expr}.{name}' for name in names]
if self.use_colors:
return self.colorize_matches(names, values)

if prefix:
names.append(' ')
return names

def _attr_matches(self, text):
Expand Down Expand Up @@ -173,21 +170,15 @@ def _attr_matches(self, text):
return expr, attr, names, values

def colorize_matches(self, names, values):
matches = [self._color_for_obj(i, name, obj)
for i, (name, obj)
in enumerate(zip(names, values))]
# We add a space at the end to prevent the automatic completion of the
# common prefix, which is the ANSI escape sequence.
matches.append(' ')
return matches

def _color_for_obj(self, i, name, value):
return [
self._color_for_obj(name, obj)
for name, obj in zip(names, values)
]

def _color_for_obj(self, name, value):
t = type(value)
color = self._color_by_type(t)
# Encode the match index into a fake escape sequence that
# stripcolor() can still remove once i reaches four digits.
N = f"\x1b[{i // 100:03d};{i % 100:02d}m"
return f"{N}{color}{name}{ANSIColors.RESET}"
return f"{color}{name}{ANSIColors.RESET}"

def _color_by_type(self, t):
typename = t.__name__
Expand Down
8 changes: 4 additions & 4 deletions Lib/_pyrepl/readline.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
from rlcompleter import Completer as RLCompleter

from . import commands, historical_reader
from .completing_reader import CompletingReader
from .completing_reader import CompletingReader, stripcolor
from .console import Console as ConsoleType
from ._module_completer import ModuleCompleter, make_default_module_completer
from .fancycompleter import Completer as FancyCompleter
Expand Down Expand Up @@ -163,9 +163,9 @@ def get_completions(self, stem: str) -> tuple[list[str], CompletionAction | None
break
result.append(next)
state += 1
# emulate the behavior of the standard readline that sorts
# the completions before displaying them.
result.sort()
# Emulate readline's sorting using the visible text rather than
# the raw ANSI escape sequences used for colorized matches.
result.sort(key=stripcolor)
return result, None

def get_module_completions(self) -> tuple[list[str], CompletionAction | None] | None:
Expand Down
23 changes: 23 additions & 0 deletions Lib/test/test_capi/test_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -3813,6 +3813,29 @@ def f(n):
self.assertIn("_UNPACK_SEQUENCE_TWO_TUPLE", uops)
self.assertNotIn("_GUARD_TOS_TUPLE", uops)

def test_binary_op_extend_float_result_enables_inplace_multiply(self):
# (2 + x) * y with x, y floats: `2 + x` goes through _BINARY_OP_EXTEND
# (int + float). The result_type/result_unique info should let the
# subsequent float multiply use the inplace variant.
def testfunc(n):
x = 3.5
y = 2.0
res = 0.0
for _ in range(n):
res = (2 + x) * y
return res

res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
self.assertEqual(res, 11.0)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertIn("_BINARY_OP_EXTEND", uops)
self.assertIn("_BINARY_OP_MULTIPLY_FLOAT_INPLACE", uops)
self.assertNotIn("_BINARY_OP_MULTIPLY_FLOAT", uops)
# NOS guard on the multiply is eliminated because _BINARY_OP_EXTEND
# propagates PyFloat_Type.
self.assertNotIn("_GUARD_NOS_FLOAT", uops)

def test_unary_invert_long_type(self):
def testfunc(n):
for _ in range(n):
Expand Down
34 changes: 13 additions & 21 deletions Lib/test/test_pyrepl/test_fancycompleter.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class C(object):
self.assertEqual(compl.attr_matches('a.'), ['a.attr', 'a.mro'])
self.assertEqual(
compl.attr_matches('a._'),
['a._C__attr__attr', 'a._attr', ' '],
['a._C__attr__attr', 'a._attr'],
)
matches = compl.attr_matches('a.__')
self.assertNotIn('__class__', matches)
Expand All @@ -79,7 +79,7 @@ def test_complete_attribute_colored(self):
break
else:
self.assertFalse(True, matches)
self.assertIn(' ', matches)
self.assertNotIn(' ', matches)

def test_preserves_callable_postfix_for_single_attribute_match(self):
compl = Completer({'os': os}, use_colors=False)
Expand Down Expand Up @@ -159,22 +159,17 @@ def test_complete_global_colored(self):
self.assertEqual(compl.global_matches('foo'), ['fooba'])
matches = compl.global_matches('fooba')

# these are the fake escape sequences which are needed so that
# readline displays the matches in the proper order
N0 = f"\x1b[000;00m"
N1 = f"\x1b[000;01m"
int_color = theme.fancycompleter.int
self.assertEqual(set(matches), {
' ',
f'{N0}{int_color}foobar{ANSIColors.RESET}',
f'{N1}{int_color}foobazzz{ANSIColors.RESET}',
})
self.assertEqual(matches, [
f'{int_color}foobar{ANSIColors.RESET}',
f'{int_color}foobazzz{ANSIColors.RESET}',
])
self.assertEqual(compl.global_matches('foobaz'), ['foobazzz'])
self.assertEqual(compl.global_matches('nothing'), [])

def test_large_color_sort_prefix_is_stripped(self):
def test_colorized_match_is_stripped(self):
compl = Completer({'a': 42}, use_colors=True)
match = compl._color_for_obj(1000, 'spam', 1)
match = compl._color_for_obj('spam', 1)
self.assertEqual(stripcolor(match), 'spam')

def test_complete_with_indexer(self):
Expand All @@ -197,27 +192,24 @@ class A:
compl = Completer({'A': A}, use_colors=False)
#
# In this case, we want to display all attributes which start with
# 'a'. Moreover, we also include a space to prevent readline to
# automatically insert the common prefix (which will the the ANSI escape
# sequence if we use colors).
# 'a'.
matches = compl.attr_matches('A.a')
self.assertEqual(
sorted(matches),
[' ', 'A.aaa', 'A.abc_1', 'A.abc_2', 'A.abc_3'],
['A.aaa', 'A.abc_1', 'A.abc_2', 'A.abc_3'],
)
#
# If there is an actual common prefix, we return just it, so that readline
# will insert it into place
matches = compl.attr_matches('A.ab')
self.assertEqual(matches, ['A.abc_'])
#
# Finally, at the next tab, we display again all the completions available
# for this common prefix. Again, we insert a spurious space to prevent the
# automatic completion of ANSI sequences.
# Finally, at the next tab, we display again all the completions
# available for this common prefix.
matches = compl.attr_matches('A.abc_')
self.assertEqual(
sorted(matches),
[' ', 'A.abc_1', 'A.abc_2', 'A.abc_3'],
['A.abc_1', 'A.abc_2', 'A.abc_3'],
)

def test_complete_exception(self):
Expand Down
22 changes: 22 additions & 0 deletions Lib/test/test_pyrepl/test_pyrepl.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
code_to_events,
)
from _pyrepl.console import Event
from _pyrepl.completing_reader import stripcolor
from _pyrepl._module_completer import (
ImportParser,
ModuleCompleter,
Expand Down Expand Up @@ -999,6 +1000,27 @@ class Obj:
self.assertNotIn("banana", menu)
self.assertNotIn("mro", menu)

def test_get_completions_sorts_colored_matches_by_visible_text(self):
console = FakeConsole(iter(()))
config = ReadlineConfig()
config.readline_completer = FancyCompleter(
{
"foo_str": "value",
"foo_int": 1,
"foo_none": None,
},
use_colors=True,
).complete
reader = ReadlineAlikeReader(console=console, config=config)

matches, action = reader.get_completions("foo_")

self.assertIsNone(action)
self.assertEqual(
[stripcolor(match) for match in matches],
["foo_int", "foo_none", "foo_str"],
)


class TestPyReplReadlineSetup(TestCase):
def test_setup_ignores_basic_completer_env_when_env_is_disabled(self):
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_type_comments.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,9 @@ def test_non_utf8_type_comment_with_ignore_cookie(self):
with self.assertRaises(UnicodeDecodeError):
_testcapi.Py_CompileStringExFlags(
b"a=1 # type: \x80", "<test>", 256, flags)
with self.assertRaises(UnicodeDecodeError):
_testcapi.Py_CompileStringExFlags(
b"def a(f=8, #type: \x80\n\x80", "<test>", 256, flags)

def test_func_type_input(self):

Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_webbrowser.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ def test_default_open(self):
url = "https://python.org"
self.browser.open(url)
self.assertTrue(self.popen_pipe._closed)
self.assertEqual(self.popen_pipe.cmd, "osascript")
self.assertEqual(self.popen_pipe.cmd, "/usr/bin/osascript")
script = self.popen_pipe.pipe.getvalue()
self.assertEqual(script.strip(), f'open location "{url}"')

Expand Down
2 changes: 1 addition & 1 deletion Lib/turtledemo/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def __init__(self, filename=None):
# so that our menu bar appears.
subprocess.run(
[
'osascript',
'/usr/bin/osascript',
'-e', 'tell application "System Events"',
'-e', 'set frontmost of the first process whose '
'unix id is {} to true'.format(os.getpid()),
Expand Down
2 changes: 1 addition & 1 deletion Lib/webbrowser.py
Original file line number Diff line number Diff line change
Expand Up @@ -656,7 +656,7 @@ def open(self, url, new=0, autoraise=True):
end
'''

osapipe = os.popen("osascript", "w")
osapipe = os.popen("/usr/bin/osascript", "w")
if osapipe is None:
return False

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Cache ``pickle.dumps`` and ``pickle.loads`` per interpreter in the XIData
framework, avoiding repeated module lookups on every cross-interpreter data
transfer. This speeds up :class:`~concurrent.futures.InterpreterPoolExecutor`
for mutable types (``list``, ``dict``) by 1.7x--3.3x.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Propagate result type and uniqueness information through
``_BINARY_OP_EXTEND`` in the tier 2 optimizer, enabling elimination of
downstream type guards and selection of inplace float operations.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix an unlikely crash when parsing an invalid type comments for function
parameters. Found by OSS Fuzz in :oss-fuzz:`492782951`.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Invoke :program:`osascript` with absolute path in :mod:`webbrowser` and :mod:`!turtledemo`.
6 changes: 5 additions & 1 deletion Objects/structseq.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ static Py_ssize_t
get_type_attr_as_size(PyTypeObject *tp, PyObject *name)
{
PyObject *v = PyDict_GetItemWithError(_PyType_GetDict(tp), name);
if (v == NULL && !PyErr_Occurred()) {

if (v == NULL) {
if (PyErr_Occurred()) {
return -1;
}
PyErr_Format(PyExc_TypeError,
"Missed attribute '%U' of type %s",
name, tp->tp_name);
Expand Down
3 changes: 3 additions & 0 deletions Parser/action_helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,9 @@ _PyPegen_name_default_pair(Parser *p, arg_ty arg, expr_ty value, Token *tc)
return NULL;
}
a->arg = _PyPegen_add_type_comment_to_arg(p, arg, tc);
if (!a->arg) {
return NULL;
}
a->value = value;
return a;
}
Expand Down
Loading
Loading