summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Include/dictobject.h6
-rw-r--r--Lib/test/test_extcall.py25
-rw-r--r--Misc/NEWS3
-rw-r--r--Objects/dictobject.c45
-rw-r--r--Python/ceval.c98
5 files changed, 124 insertions, 53 deletions
diff --git a/Include/dictobject.h b/Include/dictobject.h
index cf0745853e..f06f2409c8 100644
--- a/Include/dictobject.h
+++ b/Include/dictobject.h
@@ -132,6 +132,12 @@ PyAPI_FUNC(int) PyDict_Merge(PyObject *mp,
int override);
#ifndef Py_LIMITED_API
+/* Like PyDict_Merge, but override can be 0, 1 or 2. If override is 0,
+ the first occurrence of a key wins, if override is 1, the last occurrence
+ of a key wins, if override is 2, a KeyError with conflicting key as
+ argument is raised.
+*/
+PyAPI_FUNC(int) _PyDict_MergeEx(PyObject *mp, PyObject *other, int override);
PyAPI_FUNC(PyObject *) _PyDictView_Intersect(PyObject* self, PyObject *other);
#endif
diff --git a/Lib/test/test_extcall.py b/Lib/test/test_extcall.py
index 96f3ede9a3..043df01311 100644
--- a/Lib/test/test_extcall.py
+++ b/Lib/test/test_extcall.py
@@ -259,6 +259,31 @@ not function
...
TypeError: h() argument after ** must be a mapping, not function
+ >>> h(**[])
+ Traceback (most recent call last):
+ ...
+ TypeError: h() argument after ** must be a mapping, not list
+
+ >>> h(a=1, **h)
+ Traceback (most recent call last):
+ ...
+ TypeError: h() argument after ** must be a mapping, not function
+
+ >>> h(a=1, **[])
+ Traceback (most recent call last):
+ ...
+ TypeError: h() argument after ** must be a mapping, not list
+
+ >>> h(**{'a': 1}, **h)
+ Traceback (most recent call last):
+ ...
+ TypeError: h() argument after ** must be a mapping, not function
+
+ >>> h(**{'a': 1}, **[])
+ Traceback (most recent call last):
+ ...
+ TypeError: h() argument after ** must be a mapping, not list
+
>>> dir(**h)
Traceback (most recent call last):
...
diff --git a/Misc/NEWS b/Misc/NEWS
index 919674b769..b547e4a039 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -46,6 +46,9 @@ Core and Builtins
Library
-------
+- Issue #27358: Optimized merging var-keyword arguments and improved error
+ message when pass a non-mapping as a var-keyword argument.
+
- Issue #28257: Improved error message when pass a non-iterable as
a var-positional argument. Added opcode BUILD_TUPLE_UNPACK_WITH_CALL.
diff --git a/Objects/dictobject.c b/Objects/dictobject.c
index fe19445a02..da061aa2c4 100644
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -2380,18 +2380,14 @@ Return:
}
int
-PyDict_Update(PyObject *a, PyObject *b)
-{
- return PyDict_Merge(a, b, 1);
-}
-
-int
-PyDict_Merge(PyObject *a, PyObject *b, int override)
+dict_merge(PyObject *a, PyObject *b, int override)
{
PyDictObject *mp, *other;
Py_ssize_t i, n;
PyDictKeyEntry *entry, *ep0;
+ assert(0 <= override && override <= 2);
+
/* We accept for the argument either a concrete dictionary object,
* or an abstract "mapping" object. For the former, we can do
* things quite efficiently. For the latter, we only require that
@@ -2436,8 +2432,14 @@ PyDict_Merge(PyObject *a, PyObject *b, int override)
int err = 0;
Py_INCREF(key);
Py_INCREF(value);
- if (override || PyDict_GetItem(a, key) == NULL)
+ if (override == 1 || _PyDict_GetItem_KnownHash(a, key, hash) == NULL)
err = insertdict(mp, key, hash, value);
+ else if (override != 0) {
+ _PyErr_SetKeyError(key);
+ Py_DECREF(value);
+ Py_DECREF(key);
+ return -1;
+ }
Py_DECREF(value);
Py_DECREF(key);
if (err != 0)
@@ -2472,7 +2474,13 @@ PyDict_Merge(PyObject *a, PyObject *b, int override)
return -1;
for (key = PyIter_Next(iter); key; key = PyIter_Next(iter)) {
- if (!override && PyDict_GetItem(a, key) != NULL) {
+ if (override != 1 && PyDict_GetItem(a, key) != NULL) {
+ if (override != 0) {
+ _PyErr_SetKeyError(key);
+ Py_DECREF(key);
+ Py_DECREF(iter);
+ return -1;
+ }
Py_DECREF(key);
continue;
}
@@ -2499,6 +2507,25 @@ PyDict_Merge(PyObject *a, PyObject *b, int override)
return 0;
}
+int
+PyDict_Update(PyObject *a, PyObject *b)
+{
+ return dict_merge(a, b, 1);
+}
+
+int
+PyDict_Merge(PyObject *a, PyObject *b, int override)
+{
+ /* XXX Deprecate override not in (0, 1). */
+ return dict_merge(a, b, override != 0);
+}
+
+int
+_PyDict_MergeEx(PyObject *a, PyObject *b, int override)
+{
+ return dict_merge(a, b, override);
+}
+
static PyObject *
dict_copy(PyDictObject *mp)
{
diff --git a/Python/ceval.c b/Python/ceval.c
index 717ac33891..c443305211 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -2710,9 +2710,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
DISPATCH();
}
- TARGET(BUILD_MAP_UNPACK_WITH_CALL)
TARGET(BUILD_MAP_UNPACK) {
- int with_call = opcode == BUILD_MAP_UNPACK_WITH_CALL;
Py_ssize_t i;
PyObject *sum = PyDict_New();
if (sum == NULL)
@@ -2720,55 +2718,67 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
for (i = oparg; i > 0; i--) {
PyObject *arg = PEEK(i);
- if (with_call && PyDict_Size(sum)) {
- PyObject *intersection = _PyDictView_Intersect(sum, arg);
-
- if (intersection == NULL) {
- if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
- PyObject *func = PEEK(2 + oparg);
- PyErr_Format(PyExc_TypeError,
- "%.200s%.200s argument after ** "
- "must be a mapping, not %.200s",
- PyEval_GetFuncName(func),
- PyEval_GetFuncDesc(func),
- arg->ob_type->tp_name);
- }
- Py_DECREF(sum);
- goto error;
- }
-
- if (PySet_GET_SIZE(intersection)) {
- Py_ssize_t idx = 0;
- PyObject *key;
- PyObject *func = PEEK(2 + oparg);
- Py_hash_t hash;
- _PySet_NextEntry(intersection, &idx, &key, &hash);
- if (!PyUnicode_Check(key)) {
- PyErr_Format(PyExc_TypeError,
- "%.200s%.200s keywords must be strings",
- PyEval_GetFuncName(func),
- PyEval_GetFuncDesc(func));
- } else {
- PyErr_Format(PyExc_TypeError,
- "%.200s%.200s got multiple "
- "values for keyword argument '%U'",
- PyEval_GetFuncName(func),
- PyEval_GetFuncDesc(func),
- key);
- }
- Py_DECREF(intersection);
- Py_DECREF(sum);
- goto error;
+ if (PyDict_Update(sum, arg) < 0) {
+ if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
+ PyErr_Format(PyExc_TypeError,
+ "'%.200s' object is not a mapping1",
+ arg->ob_type->tp_name);
}
- Py_DECREF(intersection);
+ Py_DECREF(sum);
+ goto error;
}
+ }
- if (PyDict_Update(sum, arg) < 0) {
+ while (oparg--)
+ Py_DECREF(POP());
+ PUSH(sum);
+ DISPATCH();
+ }
+
+ TARGET(BUILD_MAP_UNPACK_WITH_CALL) {
+ Py_ssize_t i;
+ PyObject *sum = PyDict_New();
+ if (sum == NULL)
+ goto error;
+
+ for (i = oparg; i > 0; i--) {
+ PyObject *arg = PEEK(i);
+ if (_PyDict_MergeEx(sum, arg, 2) < 0) {
+ PyObject *func = PEEK(2 + oparg);
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Format(PyExc_TypeError,
- "'%.200s' object is not a mapping",
+ "%.200s%.200s argument after ** "
+ "must be a mapping, not %.200s",
+ PyEval_GetFuncName(func),
+ PyEval_GetFuncDesc(func),
arg->ob_type->tp_name);
}
+ else if (PyErr_ExceptionMatches(PyExc_KeyError)) {
+ PyObject *exc, *val, *tb;
+ PyErr_Fetch(&exc, &val, &tb);
+ if (val && PyTuple_Check(val) && PyTuple_GET_SIZE(val) == 1) {
+ PyObject *key = PyTuple_GET_ITEM(val, 0);
+ if (!PyUnicode_Check(key)) {
+ PyErr_Format(PyExc_TypeError,
+ "%.200s%.200s keywords must be strings",
+ PyEval_GetFuncName(func),
+ PyEval_GetFuncDesc(func));
+ } else {
+ PyErr_Format(PyExc_TypeError,
+ "%.200s%.200s got multiple "
+ "values for keyword argument '%U'",
+ PyEval_GetFuncName(func),
+ PyEval_GetFuncDesc(func),
+ key);
+ }
+ Py_XDECREF(exc);
+ Py_XDECREF(val);
+ Py_XDECREF(tb);
+ }
+ else {
+ PyErr_Restore(exc, val, tb);
+ }
+ }
Py_DECREF(sum);
goto error;
}