439 lines
12 KiB
C
439 lines
12 KiB
C
// Dict primitive operations
|
|
//
|
|
// These are registered in mypyc.primitives.dict_ops.
|
|
|
|
#include <Python.h>
|
|
#include "CPy.h"
|
|
|
|
// Dict subclasses like defaultdict override things in interesting
|
|
// ways, so we don't want to just directly use the dict methods. Not
|
|
// sure if it is actually worth doing all this stuff, but it saves
|
|
// some indirections.
|
|
PyObject *CPyDict_GetItem(PyObject *dict, PyObject *key) {
|
|
if (PyDict_CheckExact(dict)) {
|
|
PyObject *res = PyDict_GetItemWithError(dict, key);
|
|
if (!res) {
|
|
if (!PyErr_Occurred()) {
|
|
PyErr_SetObject(PyExc_KeyError, key);
|
|
}
|
|
} else {
|
|
Py_INCREF(res);
|
|
}
|
|
return res;
|
|
} else {
|
|
return PyObject_GetItem(dict, key);
|
|
}
|
|
}
|
|
|
|
PyObject *CPyDict_Build(Py_ssize_t size, ...) {
|
|
Py_ssize_t i;
|
|
|
|
PyObject *res = _PyDict_NewPresized(size);
|
|
if (res == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
va_list args;
|
|
va_start(args, size);
|
|
|
|
for (i = 0; i < size; i++) {
|
|
PyObject *key = va_arg(args, PyObject *);
|
|
PyObject *value = va_arg(args, PyObject *);
|
|
if (PyDict_SetItem(res, key, value)) {
|
|
Py_DECREF(res);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
va_end(args);
|
|
return res;
|
|
}
|
|
|
|
PyObject *CPyDict_Get(PyObject *dict, PyObject *key, PyObject *fallback) {
|
|
// We are dodgily assuming that get on a subclass doesn't have
|
|
// different behavior.
|
|
PyObject *res = PyDict_GetItemWithError(dict, key);
|
|
if (!res) {
|
|
if (PyErr_Occurred()) {
|
|
return NULL;
|
|
}
|
|
res = fallback;
|
|
}
|
|
Py_INCREF(res);
|
|
return res;
|
|
}
|
|
|
|
PyObject *CPyDict_GetWithNone(PyObject *dict, PyObject *key) {
|
|
return CPyDict_Get(dict, key, Py_None);
|
|
}
|
|
|
|
PyObject *CPyDict_SetDefault(PyObject *dict, PyObject *key, PyObject *value) {
|
|
if (PyDict_CheckExact(dict)) {
|
|
PyObject* ret = PyDict_SetDefault(dict, key, value);
|
|
Py_XINCREF(ret);
|
|
return ret;
|
|
}
|
|
_Py_IDENTIFIER(setdefault);
|
|
return _PyObject_CallMethodIdObjArgs(dict, &PyId_setdefault, key, value, NULL);
|
|
}
|
|
|
|
PyObject *CPyDict_SetDefaultWithNone(PyObject *dict, PyObject *key) {
|
|
return CPyDict_SetDefault(dict, key, Py_None);
|
|
}
|
|
|
|
PyObject *CPyDict_SetDefaultWithEmptyDatatype(PyObject *dict, PyObject *key,
|
|
int data_type) {
|
|
PyObject *res = CPyDict_GetItem(dict, key);
|
|
if (!res) {
|
|
// CPyDict_GetItem() would generates an PyExc_KeyError
|
|
// when key is not found.
|
|
PyErr_Clear();
|
|
|
|
PyObject *new_obj;
|
|
if (data_type == 1) {
|
|
new_obj = PyList_New(0);
|
|
} else if (data_type == 2) {
|
|
new_obj = PyDict_New();
|
|
} else if (data_type == 3) {
|
|
new_obj = PySet_New(NULL);
|
|
} else {
|
|
return NULL;
|
|
}
|
|
|
|
if (CPyDict_SetItem(dict, key, new_obj) == -1) {
|
|
return NULL;
|
|
} else {
|
|
return new_obj;
|
|
}
|
|
} else {
|
|
return res;
|
|
}
|
|
}
|
|
|
|
int CPyDict_SetItem(PyObject *dict, PyObject *key, PyObject *value) {
|
|
if (PyDict_CheckExact(dict)) {
|
|
return PyDict_SetItem(dict, key, value);
|
|
} else {
|
|
return PyObject_SetItem(dict, key, value);
|
|
}
|
|
}
|
|
|
|
static inline int CPy_ObjectToStatus(PyObject *obj) {
|
|
if (obj) {
|
|
Py_DECREF(obj);
|
|
return 0;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static int CPyDict_UpdateGeneral(PyObject *dict, PyObject *stuff) {
|
|
_Py_IDENTIFIER(update);
|
|
PyObject *res = _PyObject_CallMethodIdOneArg(dict, &PyId_update, stuff);
|
|
return CPy_ObjectToStatus(res);
|
|
}
|
|
|
|
int CPyDict_UpdateInDisplay(PyObject *dict, PyObject *stuff) {
|
|
// from https://github.com/python/cpython/blob/55d035113dfb1bd90495c8571758f504ae8d4802/Python/ceval.c#L2710
|
|
int ret = PyDict_Update(dict, stuff);
|
|
if (ret < 0) {
|
|
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
|
|
PyErr_Format(PyExc_TypeError,
|
|
"'%.200s' object is not a mapping",
|
|
Py_TYPE(stuff)->tp_name);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int CPyDict_Update(PyObject *dict, PyObject *stuff) {
|
|
if (PyDict_CheckExact(dict)) {
|
|
return PyDict_Update(dict, stuff);
|
|
} else {
|
|
return CPyDict_UpdateGeneral(dict, stuff);
|
|
}
|
|
}
|
|
|
|
int CPyDict_UpdateFromAny(PyObject *dict, PyObject *stuff) {
|
|
if (PyDict_CheckExact(dict)) {
|
|
// Argh this sucks
|
|
_Py_IDENTIFIER(keys);
|
|
if (PyDict_Check(stuff) || _CPyObject_HasAttrId(stuff, &PyId_keys)) {
|
|
return PyDict_Update(dict, stuff);
|
|
} else {
|
|
return PyDict_MergeFromSeq2(dict, stuff, 1);
|
|
}
|
|
} else {
|
|
return CPyDict_UpdateGeneral(dict, stuff);
|
|
}
|
|
}
|
|
|
|
PyObject *CPyDict_FromAny(PyObject *obj) {
|
|
if (PyDict_Check(obj)) {
|
|
return PyDict_Copy(obj);
|
|
} else {
|
|
int res;
|
|
PyObject *dict = PyDict_New();
|
|
if (!dict) {
|
|
return NULL;
|
|
}
|
|
_Py_IDENTIFIER(keys);
|
|
if (_CPyObject_HasAttrId(obj, &PyId_keys)) {
|
|
res = PyDict_Update(dict, obj);
|
|
} else {
|
|
res = PyDict_MergeFromSeq2(dict, obj, 1);
|
|
}
|
|
if (res < 0) {
|
|
Py_DECREF(dict);
|
|
return NULL;
|
|
}
|
|
return dict;
|
|
}
|
|
}
|
|
|
|
PyObject *CPyDict_KeysView(PyObject *dict) {
|
|
if (PyDict_CheckExact(dict)){
|
|
return _CPyDictView_New(dict, &PyDictKeys_Type);
|
|
}
|
|
_Py_IDENTIFIER(keys);
|
|
return _PyObject_CallMethodIdNoArgs(dict, &PyId_keys);
|
|
}
|
|
|
|
PyObject *CPyDict_ValuesView(PyObject *dict) {
|
|
if (PyDict_CheckExact(dict)){
|
|
return _CPyDictView_New(dict, &PyDictValues_Type);
|
|
}
|
|
_Py_IDENTIFIER(values);
|
|
return _PyObject_CallMethodIdNoArgs(dict, &PyId_values);
|
|
}
|
|
|
|
PyObject *CPyDict_ItemsView(PyObject *dict) {
|
|
if (PyDict_CheckExact(dict)){
|
|
return _CPyDictView_New(dict, &PyDictItems_Type);
|
|
}
|
|
_Py_IDENTIFIER(items);
|
|
return _PyObject_CallMethodIdNoArgs(dict, &PyId_items);
|
|
}
|
|
|
|
PyObject *CPyDict_Keys(PyObject *dict) {
|
|
if (PyDict_CheckExact(dict)) {
|
|
return PyDict_Keys(dict);
|
|
}
|
|
// Inline generic fallback logic to also return a list.
|
|
PyObject *list = PyList_New(0);
|
|
_Py_IDENTIFIER(keys);
|
|
PyObject *view = _PyObject_CallMethodIdNoArgs(dict, &PyId_keys);
|
|
if (view == NULL) {
|
|
return NULL;
|
|
}
|
|
PyObject *res = _PyList_Extend((PyListObject *)list, view);
|
|
Py_DECREF(view);
|
|
if (res == NULL) {
|
|
return NULL;
|
|
}
|
|
Py_DECREF(res);
|
|
return list;
|
|
}
|
|
|
|
PyObject *CPyDict_Values(PyObject *dict) {
|
|
if (PyDict_CheckExact(dict)) {
|
|
return PyDict_Values(dict);
|
|
}
|
|
// Inline generic fallback logic to also return a list.
|
|
PyObject *list = PyList_New(0);
|
|
_Py_IDENTIFIER(values);
|
|
PyObject *view = _PyObject_CallMethodIdNoArgs(dict, &PyId_values);
|
|
if (view == NULL) {
|
|
return NULL;
|
|
}
|
|
PyObject *res = _PyList_Extend((PyListObject *)list, view);
|
|
Py_DECREF(view);
|
|
if (res == NULL) {
|
|
return NULL;
|
|
}
|
|
Py_DECREF(res);
|
|
return list;
|
|
}
|
|
|
|
PyObject *CPyDict_Items(PyObject *dict) {
|
|
if (PyDict_CheckExact(dict)) {
|
|
return PyDict_Items(dict);
|
|
}
|
|
// Inline generic fallback logic to also return a list.
|
|
PyObject *list = PyList_New(0);
|
|
_Py_IDENTIFIER(items);
|
|
PyObject *view = _PyObject_CallMethodIdNoArgs(dict, &PyId_items);
|
|
if (view == NULL) {
|
|
return NULL;
|
|
}
|
|
PyObject *res = _PyList_Extend((PyListObject *)list, view);
|
|
Py_DECREF(view);
|
|
if (res == NULL) {
|
|
return NULL;
|
|
}
|
|
Py_DECREF(res);
|
|
return list;
|
|
}
|
|
|
|
char CPyDict_Clear(PyObject *dict) {
|
|
if (PyDict_CheckExact(dict)) {
|
|
PyDict_Clear(dict);
|
|
} else {
|
|
_Py_IDENTIFIER(clear);
|
|
PyObject *res = _PyObject_CallMethodIdNoArgs(dict, &PyId_clear);
|
|
if (res == NULL) {
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
PyObject *CPyDict_Copy(PyObject *dict) {
|
|
if (PyDict_CheckExact(dict)) {
|
|
return PyDict_Copy(dict);
|
|
}
|
|
_Py_IDENTIFIER(copy);
|
|
return _PyObject_CallMethodIdNoArgs(dict, &PyId_copy);
|
|
}
|
|
|
|
PyObject *CPyDict_GetKeysIter(PyObject *dict) {
|
|
if (PyDict_CheckExact(dict)) {
|
|
// Return dict itself to indicate we can use fast path instead.
|
|
Py_INCREF(dict);
|
|
return dict;
|
|
}
|
|
return PyObject_GetIter(dict);
|
|
}
|
|
|
|
PyObject *CPyDict_GetItemsIter(PyObject *dict) {
|
|
if (PyDict_CheckExact(dict)) {
|
|
// Return dict itself to indicate we can use fast path instead.
|
|
Py_INCREF(dict);
|
|
return dict;
|
|
}
|
|
_Py_IDENTIFIER(items);
|
|
PyObject *view = _PyObject_CallMethodIdNoArgs(dict, &PyId_items);
|
|
if (view == NULL) {
|
|
return NULL;
|
|
}
|
|
PyObject *iter = PyObject_GetIter(view);
|
|
Py_DECREF(view);
|
|
return iter;
|
|
}
|
|
|
|
PyObject *CPyDict_GetValuesIter(PyObject *dict) {
|
|
if (PyDict_CheckExact(dict)) {
|
|
// Return dict itself to indicate we can use fast path instead.
|
|
Py_INCREF(dict);
|
|
return dict;
|
|
}
|
|
_Py_IDENTIFIER(values);
|
|
PyObject *view = _PyObject_CallMethodIdNoArgs(dict, &PyId_values);
|
|
if (view == NULL) {
|
|
return NULL;
|
|
}
|
|
PyObject *iter = PyObject_GetIter(view);
|
|
Py_DECREF(view);
|
|
return iter;
|
|
}
|
|
|
|
static void _CPyDict_FromNext(tuple_T3CIO *ret, PyObject *dict_iter) {
|
|
// Get next item from iterator and set "should continue" flag.
|
|
ret->f2 = PyIter_Next(dict_iter);
|
|
if (ret->f2 == NULL) {
|
|
ret->f0 = 0;
|
|
Py_INCREF(Py_None);
|
|
ret->f2 = Py_None;
|
|
} else {
|
|
ret->f0 = 1;
|
|
}
|
|
}
|
|
|
|
// Helpers for fast dictionary iteration, return a single tuple
|
|
// instead of writing to multiple registers, for exact dicts use
|
|
// the fast path, and fall back to generic iterator logic for subclasses.
|
|
tuple_T3CIO CPyDict_NextKey(PyObject *dict_or_iter, CPyTagged offset) {
|
|
tuple_T3CIO ret;
|
|
Py_ssize_t py_offset = CPyTagged_AsSsize_t(offset);
|
|
PyObject *dummy;
|
|
|
|
if (PyDict_CheckExact(dict_or_iter)) {
|
|
ret.f0 = PyDict_Next(dict_or_iter, &py_offset, &ret.f2, &dummy);
|
|
if (ret.f0) {
|
|
ret.f1 = CPyTagged_FromSsize_t(py_offset);
|
|
} else {
|
|
// Set key to None, so mypyc can manage refcounts.
|
|
ret.f1 = 0;
|
|
ret.f2 = Py_None;
|
|
}
|
|
// PyDict_Next() returns borrowed references.
|
|
Py_INCREF(ret.f2);
|
|
} else {
|
|
// offset is dummy in this case, just use the old value.
|
|
ret.f1 = offset;
|
|
_CPyDict_FromNext(&ret, dict_or_iter);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
tuple_T3CIO CPyDict_NextValue(PyObject *dict_or_iter, CPyTagged offset) {
|
|
tuple_T3CIO ret;
|
|
Py_ssize_t py_offset = CPyTagged_AsSsize_t(offset);
|
|
PyObject *dummy;
|
|
|
|
if (PyDict_CheckExact(dict_or_iter)) {
|
|
ret.f0 = PyDict_Next(dict_or_iter, &py_offset, &dummy, &ret.f2);
|
|
if (ret.f0) {
|
|
ret.f1 = CPyTagged_FromSsize_t(py_offset);
|
|
} else {
|
|
// Set value to None, so mypyc can manage refcounts.
|
|
ret.f1 = 0;
|
|
ret.f2 = Py_None;
|
|
}
|
|
// PyDict_Next() returns borrowed references.
|
|
Py_INCREF(ret.f2);
|
|
} else {
|
|
// offset is dummy in this case, just use the old value.
|
|
ret.f1 = offset;
|
|
_CPyDict_FromNext(&ret, dict_or_iter);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
tuple_T4CIOO CPyDict_NextItem(PyObject *dict_or_iter, CPyTagged offset) {
|
|
tuple_T4CIOO ret;
|
|
Py_ssize_t py_offset = CPyTagged_AsSsize_t(offset);
|
|
|
|
if (PyDict_CheckExact(dict_or_iter)) {
|
|
ret.f0 = PyDict_Next(dict_or_iter, &py_offset, &ret.f2, &ret.f3);
|
|
if (ret.f0) {
|
|
ret.f1 = CPyTagged_FromSsize_t(py_offset);
|
|
} else {
|
|
// Set key and value to None, so mypyc can manage refcounts.
|
|
ret.f1 = 0;
|
|
ret.f2 = Py_None;
|
|
ret.f3 = Py_None;
|
|
}
|
|
} else {
|
|
ret.f1 = offset;
|
|
PyObject *item = PyIter_Next(dict_or_iter);
|
|
if (item == NULL || !PyTuple_Check(item) || PyTuple_GET_SIZE(item) != 2) {
|
|
if (item != NULL) {
|
|
PyErr_SetString(PyExc_TypeError, "a tuple of length 2 expected");
|
|
}
|
|
ret.f0 = 0;
|
|
ret.f2 = Py_None;
|
|
ret.f3 = Py_None;
|
|
} else {
|
|
ret.f0 = 1;
|
|
ret.f2 = PyTuple_GET_ITEM(item, 0);
|
|
ret.f3 = PyTuple_GET_ITEM(item, 1);
|
|
Py_DECREF(item);
|
|
}
|
|
}
|
|
// PyDict_Next() returns borrowed references.
|
|
Py_INCREF(ret.f2);
|
|
Py_INCREF(ret.f3);
|
|
return ret;
|
|
}
|