]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-155051: Reject None keyword arguments in decimal.localcontext() (GH-155054)
authorSerhiy Storchaka <storchaka@gmail.com>
Sun, 2 Aug 2026 07:08:59 +0000 (10:08 +0300)
committerGitHub <noreply@github.com>
Sun, 2 Aug 2026 07:08:59 +0000 (07:08 +0000)
None is not a valid value for any of the context attributes.  The C
implementation silently ignored it, because it shared the code with the
Context constructor, where None means "not specified".  The pure Python
implementation always rejected it.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Lib/test/test_decimal.py
Misc/NEWS.d/next/Library/2026-08-01-21-00-00.gh-issue-155051.Lc1Non.rst [new file with mode: 0644]
Modules/_decimal/_decimal.c
Modules/_decimal/clinic/_decimal.c.h

index 493732c22e8231ab675dc0797ae82bba84f318c7..b8c09c7f43e3e3bc661b5e4d7c60d52121d696ce 100644 (file)
@@ -3798,6 +3798,13 @@ class ContextWithStatement:
         self.assertRaises(TypeError, self.decimal.localcontext, Emin="")
         self.assertRaises(TypeError, self.decimal.localcontext, Emax="")
 
+        # None is not a valid value for any of these attributes.
+        for name in ('prec', 'rounding', 'Emin', 'Emax', 'capitals', 'clamp',
+                     'flags', 'traps'):
+            with self.subTest(name=name):
+                self.assertRaises(TypeError, self.decimal.localcontext,
+                                  **{name: None})
+
     def test_local_context_kwargs_does_not_overwrite_existing_argument(self):
         ctx = self.decimal.getcontext()
         orig_prec = ctx.prec
diff --git a/Misc/NEWS.d/next/Library/2026-08-01-21-00-00.gh-issue-155051.Lc1Non.rst b/Misc/NEWS.d/next/Library/2026-08-01-21-00-00.gh-issue-155051.Lc1Non.rst
new file mode 100644 (file)
index 0000000..d2be40f
--- /dev/null
@@ -0,0 +1,3 @@
+:func:`decimal.localcontext` now raises :exc:`TypeError` if a keyword argument
+is ``None``, as the pure Python implementation already did.  Previously the C
+implementation silently ignored it.
index 38944e6a9d9017c013c0c61bd4874b03aeb36833..1244505db7c612dbc732b31e5000b508819e2e84 100644 (file)
@@ -1333,7 +1333,7 @@ context_setattr(PyObject *self, PyObject *name, PyObject *value)
     return PyObject_GenericSetAttr(self, name, value);
 }
 
-/* In the constructor and in localcontext() None means "not specified". */
+/* In the constructor None means "not specified". */
 #define NONE_TO_NULL(x) ((x) == Py_None ? NULL : (x))
 
 /* Set the given attributes.  An attribute is left unchanged if the
@@ -2099,14 +2099,14 @@ _decimal.localcontext
 
     ctx as local: object = None
     *
-    prec: object = None
-    rounding: object = None
-    Emin: object = None
-    Emax: object = None
-    capitals: object = None
-    clamp: object = None
-    flags: object = None
-    traps: object = None
+    prec: object = NULL
+    rounding: object = NULL
+    Emin: object = NULL
+    Emax: object = NULL
+    capitals: object = NULL
+    clamp: object = NULL
+    flags: object = NULL
+    traps: object = NULL
 
 Return a context manager for a copy of the supplied context.
 
@@ -2121,7 +2121,7 @@ _decimal_localcontext_impl(PyObject *module, PyObject *local, PyObject *prec,
                            PyObject *rounding, PyObject *Emin,
                            PyObject *Emax, PyObject *capitals,
                            PyObject *clamp, PyObject *flags, PyObject *traps)
-/*[clinic end generated code: output=9bf4e47742a809b0 input=490307b9689c3856]*/
+/*[clinic end generated code: output=9bf4e47742a809b0 input=616abb6ee1654373]*/
 {
     PyObject *global;
 
@@ -2142,9 +2142,9 @@ _decimal_localcontext_impl(PyObject *module, PyObject *local, PyObject *prec,
     }
 
     int ret = context_setattrs(
-        local_copy, NONE_TO_NULL(prec), NONE_TO_NULL(rounding),
-        NONE_TO_NULL(Emin), NONE_TO_NULL(Emax), NONE_TO_NULL(capitals),
-        NONE_TO_NULL(clamp), NONE_TO_NULL(flags), NONE_TO_NULL(traps)
+        local_copy, prec, rounding,
+        Emin, Emax, capitals,
+        clamp, flags, traps
     );
     if (ret < 0) {
         Py_DECREF(local_copy);
index 7c45bc66569bb53406d223e4429ae33b4ed41e5f..965e5f8d0da9955bc2486342c80e9916e2554002 100644 (file)
@@ -628,14 +628,14 @@ _decimal_localcontext(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
     PyObject *argsbuf[9];
     Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
     PyObject *local = Py_None;
-    PyObject *prec = Py_None;
-    PyObject *rounding = Py_None;
-    PyObject *Emin = Py_None;
-    PyObject *Emax = Py_None;
-    PyObject *capitals = Py_None;
-    PyObject *clamp = Py_None;
-    PyObject *flags = Py_None;
-    PyObject *traps = Py_None;
+    PyObject *prec = NULL;
+    PyObject *rounding = NULL;
+    PyObject *Emin = NULL;
+    PyObject *Emax = NULL;
+    PyObject *capitals = NULL;
+    PyObject *clamp = NULL;
+    PyObject *flags = NULL;
+    PyObject *traps = NULL;
 
     args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
             /*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
@@ -7100,4 +7100,4 @@ exit:
 #ifndef _DECIMAL_CONTEXT_APPLY_METHODDEF
     #define _DECIMAL_CONTEXT_APPLY_METHODDEF
 #endif /* !defined(_DECIMAL_CONTEXT_APPLY_METHODDEF) */
-/*[clinic end generated code: output=75ab23467fa0eee3 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=07ec694c7fd6b048 input=a9049054013a1b77]*/