From 9076455d1b8132928470c11df874ca7c8580b012 Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Fri, 16 Dec 2022 10:18:31 -0800 Subject: [PATCH 1/5] GH-90043: Handle NaNs in COMPARE_OP_FLOAT_JUMP (GH-100278) --- ...2-12-15-00-50-25.gh-issue-90043.gyoKdx.rst | 2 ++ Python/bytecodes.c | 12 +++++------ Python/generated_cases.c.h | 12 +++++------ Python/specialize.c | 21 ++++++++++--------- 4 files changed, 23 insertions(+), 24 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-12-15-00-50-25.gh-issue-90043.gyoKdx.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-12-15-00-50-25.gh-issue-90043.gyoKdx.rst b/Misc/NEWS.d/next/Core and Builtins/2022-12-15-00-50-25.gh-issue-90043.gyoKdx.rst new file mode 100644 index 00000000000000..9a4f896e2cf51f --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-12-15-00-50-25.gh-issue-90043.gyoKdx.rst @@ -0,0 +1,2 @@ +Handle NaNs when specializing :opcode:`COMPARE_OP` for :class:`float` +values. diff --git a/Python/bytecodes.c b/Python/bytecodes.c index c56f1d3ef9f498..8e95b737030252 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2024,13 +2024,11 @@ dummy_func( // Combined: COMPARE_OP (float ? float) + POP_JUMP_IF_(true/false) DEOPT_IF(!PyFloat_CheckExact(left), COMPARE_OP); DEOPT_IF(!PyFloat_CheckExact(right), COMPARE_OP); + STAT_INC(COMPARE_OP, hit); double dleft = PyFloat_AS_DOUBLE(left); double dright = PyFloat_AS_DOUBLE(right); - // 1 if <, 2 if ==, 4 if >; this matches when _to_jump_mask - int sign_ish = 2*(dleft > dright) + 2 - (dleft < dright); - DEOPT_IF(isnan(dleft), COMPARE_OP); - DEOPT_IF(isnan(dright), COMPARE_OP); - STAT_INC(COMPARE_OP, hit); + // 1 if NaN, 2 if <, 4 if >, 8 if ==; this matches when_to_jump_mask + int sign_ish = 1 << (2 * (dleft >= dright) + (dleft <= dright)); _Py_DECREF_SPECIALIZED(left, _PyFloat_ExactDealloc); _Py_DECREF_SPECIALIZED(right, _PyFloat_ExactDealloc); jump = sign_ish & when_to_jump_mask; @@ -2057,8 +2055,8 @@ dummy_func( assert(Py_ABS(Py_SIZE(left)) <= 1 && Py_ABS(Py_SIZE(right)) <= 1); Py_ssize_t ileft = Py_SIZE(left) * ((PyLongObject *)left)->ob_digit[0]; Py_ssize_t iright = Py_SIZE(right) * ((PyLongObject *)right)->ob_digit[0]; - // 1 if <, 2 if ==, 4 if >; this matches when _to_jump_mask - int sign_ish = 2*(ileft > iright) + 2 - (ileft < iright); + // 2 if <, 4 if >, 8 if ==; this matches when_to_jump_mask + int sign_ish = 1 << (2 * (ileft >= iright) + (ileft <= iright)); _Py_DECREF_SPECIALIZED(left, (destructor)PyObject_Free); _Py_DECREF_SPECIALIZED(right, (destructor)PyObject_Free); jump = sign_ish & when_to_jump_mask; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 63635fbfc2f4cb..6d84a643b45756 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -2214,13 +2214,11 @@ // Combined: COMPARE_OP (float ? float) + POP_JUMP_IF_(true/false) DEOPT_IF(!PyFloat_CheckExact(left), COMPARE_OP); DEOPT_IF(!PyFloat_CheckExact(right), COMPARE_OP); + STAT_INC(COMPARE_OP, hit); double dleft = PyFloat_AS_DOUBLE(left); double dright = PyFloat_AS_DOUBLE(right); - // 1 if <, 2 if ==, 4 if >; this matches when _to_jump_mask - int sign_ish = 2*(dleft > dright) + 2 - (dleft < dright); - DEOPT_IF(isnan(dleft), COMPARE_OP); - DEOPT_IF(isnan(dright), COMPARE_OP); - STAT_INC(COMPARE_OP, hit); + // 1 if NaN, 2 if <, 4 if >, 8 if ==; this matches when_to_jump_mask + int sign_ish = 1 << (2 * (dleft >= dright) + (dleft <= dright)); _Py_DECREF_SPECIALIZED(left, _PyFloat_ExactDealloc); _Py_DECREF_SPECIALIZED(right, _PyFloat_ExactDealloc); jump = sign_ish & when_to_jump_mask; @@ -2258,8 +2256,8 @@ assert(Py_ABS(Py_SIZE(left)) <= 1 && Py_ABS(Py_SIZE(right)) <= 1); Py_ssize_t ileft = Py_SIZE(left) * ((PyLongObject *)left)->ob_digit[0]; Py_ssize_t iright = Py_SIZE(right) * ((PyLongObject *)right)->ob_digit[0]; - // 1 if <, 2 if ==, 4 if >; this matches when _to_jump_mask - int sign_ish = 2*(ileft > iright) + 2 - (ileft < iright); + // 2 if <, 4 if >, 8 if ==; this matches when_to_jump_mask + int sign_ish = 1 << (2 * (ileft >= iright) + (ileft <= iright)); _Py_DECREF_SPECIALIZED(left, (destructor)PyObject_Free); _Py_DECREF_SPECIALIZED(right, (destructor)PyObject_Free); jump = sign_ish & when_to_jump_mask; diff --git a/Python/specialize.c b/Python/specialize.c index 020127e9bf3c99..a1666ccc91594b 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -1951,15 +1951,16 @@ compare_op_fail_kind(PyObject *lhs, PyObject *rhs) static int compare_masks[] = { - // 1-bit: jump if less than - // 2-bit: jump if equal + // 1-bit: jump if unordered + // 2-bit: jump if less // 4-bit: jump if greater - [Py_LT] = 1 | 0 | 0, - [Py_LE] = 1 | 2 | 0, - [Py_EQ] = 0 | 2 | 0, - [Py_NE] = 1 | 0 | 4, - [Py_GT] = 0 | 0 | 4, - [Py_GE] = 0 | 2 | 4, + // 8-bit: jump if equal + [Py_LT] = 0 | 2 | 0 | 0, + [Py_LE] = 0 | 2 | 0 | 8, + [Py_EQ] = 0 | 0 | 0 | 8, + [Py_NE] = 1 | 2 | 4 | 0, + [Py_GT] = 0 | 0 | 4 | 0, + [Py_GE] = 0 | 0 | 4 | 8, }; void @@ -1980,7 +1981,7 @@ _Py_Specialize_CompareOp(PyObject *lhs, PyObject *rhs, _Py_CODEUNIT *instr, assert(oparg <= Py_GE); int when_to_jump_mask = compare_masks[oparg]; if (next_opcode == POP_JUMP_IF_FALSE) { - when_to_jump_mask = (1 | 2 | 4) & ~when_to_jump_mask; + when_to_jump_mask = (1 | 2 | 4 | 8) & ~when_to_jump_mask; } if (Py_TYPE(lhs) != Py_TYPE(rhs)) { SPECIALIZATION_FAIL(COMPARE_OP, compare_op_fail_kind(lhs, rhs)); @@ -2009,7 +2010,7 @@ _Py_Specialize_CompareOp(PyObject *lhs, PyObject *rhs, _Py_CODEUNIT *instr, } else { _py_set_opcode(instr, COMPARE_OP_STR_JUMP); - cache->mask = (when_to_jump_mask & 2) == 0; + cache->mask = (when_to_jump_mask & 8) == 0; goto success; } } From f23236a92d8796ae91772adaf27c3485fda963e8 Mon Sep 17 00:00:00 2001 From: Frank Dana Date: Fri, 16 Dec 2022 13:43:43 -0500 Subject: [PATCH 2/5] "Compound statement" docs: Fix with-statement step indexing (#100286) Back in commit 226e6e7d4326cf91ef37e13528eb1f62de1bb832 an item was added to the list, renumbering all the rest of the items, but the forward-reference wasn't updated to match. --- Doc/reference/compound_stmts.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst index 9e09515f50d12f..fe9dda933c8071 100644 --- a/Doc/reference/compound_stmts.rst +++ b/Doc/reference/compound_stmts.rst @@ -506,7 +506,7 @@ The execution of the :keyword:`with` statement with one "item" proceeds as follo method returns without an error, then :meth:`__exit__` will always be called. Thus, if an error occurs during the assignment to the target list, it will be treated the same as an error occurring within the suite would - be. See step 6 below. + be. See step 7 below. #. The suite is executed. From 5234e1cbea686e38392f113707db322ad8405048 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Fri, 16 Dec 2022 14:04:55 -0500 Subject: [PATCH 3/5] gh-99830: asyncio: Document returns of remove_{reader,writer} (#100302) --- Doc/library/asyncio-eventloop.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst index fd47b0c24d8a16..470d1aa130e40f 100644 --- a/Doc/library/asyncio-eventloop.rst +++ b/Doc/library/asyncio-eventloop.rst @@ -932,7 +932,8 @@ Watching file descriptors .. method:: loop.remove_reader(fd) - Stop monitoring the *fd* file descriptor for read availability. + Stop monitoring the *fd* file descriptor for read availability. Returns + ``True`` if *fd* was previously being monitored for reads. .. method:: loop.add_writer(fd, callback, *args) @@ -945,7 +946,8 @@ Watching file descriptors .. method:: loop.remove_writer(fd) - Stop monitoring the *fd* file descriptor for write availability. + Stop monitoring the *fd* file descriptor for write availability. Returns + ``True`` if *fd* was previously being monitored for writes. See also :ref:`Platform Support ` section for some limitations of these methods. From a5a7cea202d34ca699d9594d428ba527ef7ff2ce Mon Sep 17 00:00:00 2001 From: Ethan Furman Date: Fri, 16 Dec 2022 12:30:47 -0800 Subject: [PATCH 4/5] gh-100039: enhance __signature__ to work with str and callables (GH-100168) Callables should be either class- or static-methods. Enum now uses the classmethod version to greatly improve the help given for enums and flags. --- Lib/enum.py | 9 +++++- Lib/inspect.py | 10 +++++- Lib/test/test_enum.py | 25 +++++++++++++-- Lib/test/test_inspect.py | 32 +++++++++++++++++++ ...-12-10-20-52-28.gh-issue-100039.zDqjT4.rst | 1 + 5 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-12-10-20-52-28.gh-issue-100039.zDqjT4.rst diff --git a/Lib/enum.py b/Lib/enum.py index a0cad066dc23f7..21f63881d78d4d 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -685,7 +685,7 @@ def __new__(metacls, cls, bases, classdict, *, boundary=None, _simple=False, **k 'member order does not match _order_:\n %r\n %r' % (enum_class._member_names_, _order_) ) - + # return enum_class def __bool__(cls): @@ -1083,6 +1083,13 @@ class Enum(metaclass=EnumType): attributes -- see the documentation for details. """ + @classmethod + def __signature__(cls): + if cls._member_names_: + return '(*values)' + else: + return '(new_class_name, /, names, *, module=None, qualname=None, type=None, start=1, boundary=None)' + def __new__(cls, value): # all enum instances are actually created during class construction # without calling this method; this method is called by the metaclass' diff --git a/Lib/inspect.py b/Lib/inspect.py index e165937e448a95..e92c355220fd8d 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -2443,10 +2443,18 @@ def _signature_from_callable(obj, *, pass else: if sig is not None: + # since __text_signature__ is not writable on classes, __signature__ + # may contain text (or be a callable that returns text); + # if so, convert it + o_sig = sig + if not isinstance(sig, (Signature, str)) and callable(sig): + sig = sig() + if isinstance(sig, str): + sig = _signature_fromstr(sigcls, obj, sig) if not isinstance(sig, Signature): raise TypeError( 'unexpected object {!r} in __signature__ ' - 'attribute'.format(sig)) + 'attribute'.format(o_sig)) return sig try: diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index d876c8e5fb7798..1e653e94f6b57a 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -4259,7 +4259,7 @@ class TestEnumTypeSubclassing(unittest.TestCase): Help on class Color in module %s: class Color(enum.Enum) - | Color(value, names=None, *values, module=None, qualname=None, type=None, start=1, boundary=None) + | Color(*values) | | Method resolution order: | Color @@ -4315,7 +4315,7 @@ class Color(enum.Enum) Help on class Color in module %s: class Color(enum.Enum) - | Color(value, names=None, *values, module=None, qualname=None, type=None, start=1) + | Color(*values) | | Method resolution order: | Color @@ -4462,6 +4462,27 @@ def test_inspect_classify_class_attrs(self): if failed: self.fail("result does not equal expected, see print above") + def test_inspect_signatures(self): + from inspect import signature, Signature, Parameter + self.assertEqual( + signature(Enum), + Signature([ + Parameter('new_class_name', Parameter.POSITIONAL_ONLY), + Parameter('names', Parameter.POSITIONAL_OR_KEYWORD), + Parameter('module', Parameter.KEYWORD_ONLY, default=None), + Parameter('qualname', Parameter.KEYWORD_ONLY, default=None), + Parameter('type', Parameter.KEYWORD_ONLY, default=None), + Parameter('start', Parameter.KEYWORD_ONLY, default=1), + Parameter('boundary', Parameter.KEYWORD_ONLY, default=None), + ]), + ) + self.assertEqual( + signature(enum.FlagBoundary), + Signature([ + Parameter('values', Parameter.VAR_POSITIONAL), + ]), + ) + def test_test_simple_enum(self): @_simple_enum(Enum) class SimpleColor: diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index 2b7977b1648f70..0f8217ba3b7550 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -3614,6 +3614,38 @@ def foo(): pass self.assertEqual(signature_func(foo), inspect.Signature()) self.assertEqual(inspect.get_annotations(foo), {}) + def test_signature_as_str(self): + self.maxDiff = None + class S: + __signature__ = '(a, b=2)' + + self.assertEqual(self.signature(S), + ((('a', ..., ..., 'positional_or_keyword'), + ('b', 2, ..., 'positional_or_keyword')), + ...)) + + def test_signature_as_callable(self): + # __signature__ should be either a staticmethod or a bound classmethod + class S: + @classmethod + def __signature__(cls): + return '(a, b=2)' + + self.assertEqual(self.signature(S), + ((('a', ..., ..., 'positional_or_keyword'), + ('b', 2, ..., 'positional_or_keyword')), + ...)) + + class S: + @staticmethod + def __signature__(): + return '(a, b=2)' + + self.assertEqual(self.signature(S), + ((('a', ..., ..., 'positional_or_keyword'), + ('b', 2, ..., 'positional_or_keyword')), + ...)) + class TestParameterObject(unittest.TestCase): def test_signature_parameter_kinds(self): diff --git a/Misc/NEWS.d/next/Library/2022-12-10-20-52-28.gh-issue-100039.zDqjT4.rst b/Misc/NEWS.d/next/Library/2022-12-10-20-52-28.gh-issue-100039.zDqjT4.rst new file mode 100644 index 00000000000000..d56643f7e87192 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-12-10-20-52-28.gh-issue-100039.zDqjT4.rst @@ -0,0 +1 @@ +Improve signatures for enums and flags. From 432117cd1f59c76d97da2eaff55a7d758301dbc7 Mon Sep 17 00:00:00 2001 From: yonillasky Date: Fri, 16 Dec 2022 22:36:13 +0200 Subject: [PATCH 5/5] gh-99540: Constant hash for _PyNone_Type to aid reproducibility (GH-99541) Needed for ASLR builds of Python. --- .../2022-12-10-20-00-13.gh-issue-99540.ZZZHeP.rst | 1 + Objects/object.c | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-12-10-20-00-13.gh-issue-99540.ZZZHeP.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-12-10-20-00-13.gh-issue-99540.ZZZHeP.rst b/Misc/NEWS.d/next/Core and Builtins/2022-12-10-20-00-13.gh-issue-99540.ZZZHeP.rst new file mode 100644 index 00000000000000..ae043f3aba55e8 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-12-10-20-00-13.gh-issue-99540.ZZZHeP.rst @@ -0,0 +1 @@ +``None`` now hashes to a constant value. This is not a requirements change. diff --git a/Objects/object.c b/Objects/object.c index 687bd36d2b4af1..028b0edc911155 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1641,6 +1641,11 @@ none_bool(PyObject *v) return 0; } +static Py_hash_t none_hash(PyObject *v) +{ + return 0xFCA86420; +} + static PyNumberMethods none_as_number = { 0, /* nb_add */ 0, /* nb_subtract */ @@ -1692,7 +1697,7 @@ PyTypeObject _PyNone_Type = { &none_as_number, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ - 0, /*tp_hash */ + (hashfunc)none_hash,/*tp_hash */ 0, /*tp_call */ 0, /*tp_str */ 0, /*tp_getattro */