| #ifndef CPY_CPY_H |
| #define CPY_CPY_H |
| |
| #include <stdbool.h> |
| #include <Python.h> |
| #include <frameobject.h> |
| #include <structmember.h> |
| #include <assert.h> |
| #include "pythonsupport.h" |
| #include "mypyc_util.h" |
| |
| #ifdef __cplusplus |
| extern "C" { |
| #endif |
| #if 0 |
| } // why isn't emacs smart enough to not indent this |
| #endif |
| |
| /* We use intentionally non-inlined decrefs since it pretty |
| * substantially speeds up compile time while only causing a ~1% |
| * performance degradation. We have our own copies both to avoid the |
| * null check in Py_DecRef and to avoid making an indirect PIC |
| * call. */ |
| CPy_NOINLINE |
| static void CPy_DecRef(PyObject *p) { |
| CPy_DECREF(p); |
| } |
| |
| CPy_NOINLINE |
| static void CPy_XDecRef(PyObject *p) { |
| CPy_XDECREF(p); |
| } |
| |
| // Naming conventions: |
| // |
| // Tagged: tagged int |
| // Long: tagged long int (pointer) |
| // Short: tagged short int (unboxed) |
| // Ssize_t: A Py_ssize_t, which ought to be the same width as pointers |
| // Object: CPython object (PyObject *) |
| |
| static void CPyDebug_Print(const char *msg) { |
| printf("%s\n", msg); |
| fflush(stdout); |
| } |
| |
| // Search backwards through the trait part of a vtable (which sits *before* |
| // the start of the vtable proper) looking for the subvtable describing a trait |
| // implementation. We don't do any bounds checking so we'd better be pretty sure |
| // we know that it is there. |
| static inline CPyVTableItem *CPy_FindTraitVtable(PyTypeObject *trait, CPyVTableItem *vtable) { |
| int i; |
| for (i = -2; ; i -= 2) { |
| if ((PyTypeObject *)vtable[i] == trait) { |
| return (CPyVTableItem *)vtable[i + 1]; |
| } |
| } |
| } |
| |
| static bool _CPy_IsSafeMetaClass(PyTypeObject *metaclass) { |
| // mypyc classes can't work with metaclasses in |
| // general. Through some various nasty hacks we *do* |
| // manage to work with TypingMeta and its friends. |
| if (metaclass == &PyType_Type) |
| return true; |
| PyObject *module = PyObject_GetAttrString((PyObject *)metaclass, "__module__"); |
| if (!module) { |
| PyErr_Clear(); |
| return false; |
| } |
| |
| bool matches = false; |
| if (PyUnicode_CompareWithASCIIString(module, "typing") == 0 && |
| (strcmp(metaclass->tp_name, "TypingMeta") == 0 |
| || strcmp(metaclass->tp_name, "GenericMeta") == 0)) { |
| matches = true; |
| } else if (PyUnicode_CompareWithASCIIString(module, "abc") == 0 && |
| strcmp(metaclass->tp_name, "ABCMeta") == 0) { |
| matches = true; |
| } |
| Py_DECREF(module); |
| return matches; |
| } |
| |
| // Create a heap type based on a template non-heap type. |
| // This is super hacky and maybe we should suck it up and use PyType_FromSpec instead. |
| // We allow bases to be NULL to represent just inheriting from object. |
| // We don't support NULL bases and a non-type metaclass. |
| static PyObject *CPyType_FromTemplate(PyTypeObject *template_, |
| PyObject *orig_bases, |
| PyObject *modname) { |
| PyHeapTypeObject *t = NULL; |
| PyTypeObject *dummy_class = NULL; |
| PyObject *name = NULL; |
| PyObject *bases = NULL; |
| PyObject *slots; |
| |
| // If the type of the class (the metaclass) is NULL, we default it |
| // to being type. (This allows us to avoid needing to initialize |
| // it explicitly on windows.) |
| if (!Py_TYPE(template_)) { |
| Py_TYPE(template_) = &PyType_Type; |
| } |
| PyTypeObject *metaclass = Py_TYPE(template_); |
| |
| if (orig_bases) { |
| bases = update_bases(orig_bases); |
| // update_bases doesn't increment the refcount if nothing changes, |
| // so we do it to make sure we have distinct "references" to both |
| if (bases == orig_bases) |
| Py_INCREF(bases); |
| |
| // Find the appropriate metaclass from our base classes. We |
| // care about this because Generic uses a metaclass prior to |
| // Python 3.7. |
| metaclass = _PyType_CalculateMetaclass(metaclass, bases); |
| if (!metaclass) |
| goto error; |
| |
| if (!_CPy_IsSafeMetaClass(metaclass)) { |
| PyErr_SetString(PyExc_TypeError, "mypyc classes can't have a metaclass"); |
| goto error; |
| } |
| } |
| |
| name = PyUnicode_FromString(template_->tp_name); |
| if (!name) |
| goto error; |
| |
| // If there is a metaclass other than type, we would like to call |
| // its __new__ function. Unfortunately there doesn't seem to be a |
| // good way to mix a C extension class and creating it via a |
| // metaclass. We need to do it anyways, though, in order to |
| // support subclassing Generic[T] prior to Python 3.7. |
| // |
| // We solve this with a kind of atrocious hack: create a parallel |
| // class using the metaclass, determine the bases of the real |
| // class by pulling them out of the parallel class, creating the |
| // real class, and then merging its dict back into the original |
| // class. There are lots of cases where this won't really work, |
| // but for the case of GenericMeta setting a bunch of properties |
| // on the class we should be fine. |
| if (metaclass != &PyType_Type) { |
| assert(bases && "non-type metaclasses require non-NULL bases"); |
| |
| PyObject *ns = PyDict_New(); |
| if (!ns) |
| goto error; |
| |
| dummy_class = (PyTypeObject *)PyObject_CallFunctionObjArgs( |
| (PyObject *)metaclass, name, bases, ns, NULL); |
| Py_DECREF(ns); |
| if (!dummy_class) |
| goto error; |
| |
| Py_DECREF(bases); |
| bases = dummy_class->tp_bases; |
| Py_INCREF(bases); |
| } |
| |
| // Allocate the type and then copy the main stuff in. |
| t = (PyHeapTypeObject*)PyType_GenericAlloc(&PyType_Type, 0); |
| if (!t) |
| goto error; |
| memcpy((char *)t + sizeof(PyVarObject), |
| (char *)template_ + sizeof(PyVarObject), |
| sizeof(PyTypeObject) - sizeof(PyVarObject)); |
| |
| if (bases != orig_bases) { |
| if (PyObject_SetAttrString((PyObject *)t, "__orig_bases__", orig_bases) < 0) |
| goto error; |
| } |
| |
| // Having tp_base set is I think required for stuff to get |
| // inherited in PyType_Ready, which we needed for subclassing |
| // BaseException. XXX: Taking the first element is wrong I think though. |
| if (bases) { |
| t->ht_type.tp_base = (PyTypeObject *)PyTuple_GET_ITEM(bases, 0); |
| Py_INCREF((PyObject *)t->ht_type.tp_base); |
| } |
| |
| t->ht_name = name; |
| Py_INCREF(name); |
| t->ht_qualname = name; |
| t->ht_type.tp_bases = bases; |
| // references stolen so NULL these out |
| bases = name = NULL; |
| |
| if (PyType_Ready((PyTypeObject *)t) < 0) |
| goto error; |
| |
| assert(t->ht_type.tp_base != NULL); |
| |
| // XXX: This is a terrible hack to work around a cpython check on |
| // the mro. It was needed for mypy.stats. I need to investigate |
| // what is actually going on here. |
| Py_INCREF(metaclass); |
| Py_TYPE(t) = metaclass; |
| |
| if (dummy_class) { |
| if (PyDict_Merge(t->ht_type.tp_dict, dummy_class->tp_dict, 0) != 0) |
| goto error; |
| // This is the *really* tasteless bit. GenericMeta's __new__ |
| // in certain versions of typing sets _gorg to point back to |
| // the class. We need to override it to keep it from pointing |
| // to the proxy. |
| if (PyDict_SetItemString(t->ht_type.tp_dict, "_gorg", (PyObject *)t) < 0) |
| goto error; |
| } |
| |
| // Reject anything that would give us a nontrivial __slots__, |
| // because the layout will conflict |
| slots = PyObject_GetAttrString((PyObject *)t, "__slots__"); |
| if (slots) { |
| // don't fail on an empty __slots__ |
| int is_true = PyObject_IsTrue(slots); |
| Py_DECREF(slots); |
| if (is_true > 0) |
| PyErr_SetString(PyExc_TypeError, "mypyc classes can't have __slots__"); |
| if (is_true != 0) |
| goto error; |
| } else { |
| PyErr_Clear(); |
| } |
| |
| if (PyObject_SetAttrString((PyObject *)t, "__module__", modname) < 0) |
| goto error; |
| |
| if (init_subclass((PyTypeObject *)t, NULL)) |
| goto error; |
| |
| Py_XDECREF(dummy_class); |
| |
| return (PyObject *)t; |
| |
| error: |
| Py_XDECREF(t); |
| Py_XDECREF(bases); |
| Py_XDECREF(dummy_class); |
| Py_XDECREF(name); |
| return NULL; |
| } |
| |
| // Get attribute value using vtable (may return an undefined value) |
| #define CPY_GET_ATTR(obj, type, vtable_index, object_type, attr_type) \ |
| ((attr_type (*)(object_type *))((object_type *)obj)->vtable[vtable_index])((object_type *)obj) |
| |
| #define CPY_GET_ATTR_TRAIT(obj, trait, vtable_index, object_type, attr_type) \ |
| ((attr_type (*)(object_type *))(CPy_FindTraitVtable(trait, ((object_type *)obj)->vtable))[vtable_index])((object_type *)obj) |
| |
| // Set attribute value using vtable |
| #define CPY_SET_ATTR(obj, type, vtable_index, value, object_type, attr_type) \ |
| ((bool (*)(object_type *, attr_type))((object_type *)obj)->vtable[vtable_index])( \ |
| (object_type *)obj, value) |
| |
| #define CPY_SET_ATTR_TRAIT(obj, trait, vtable_index, value, object_type, attr_type) \ |
| ((bool (*)(object_type *, attr_type))(CPy_FindTraitVtable(trait, ((object_type *)obj)->vtable))[vtable_index])( \ |
| (object_type *)obj, value) |
| |
| #define CPY_GET_METHOD(obj, type, vtable_index, object_type, method_type) \ |
| ((method_type)(((object_type *)obj)->vtable[vtable_index])) |
| |
| #define CPY_GET_METHOD_TRAIT(obj, trait, vtable_index, object_type, method_type) \ |
| ((method_type)(CPy_FindTraitVtable(trait, ((object_type *)obj)->vtable)[vtable_index])) |
| |
| |
| static void CPyError_OutOfMemory(void) { |
| fprintf(stderr, "fatal: out of memory\n"); |
| fflush(stderr); |
| abort(); |
| } |
| |
| static inline int CPyTagged_CheckLong(CPyTagged x) { |
| return x & CPY_INT_TAG; |
| } |
| |
| static inline int CPyTagged_CheckShort(CPyTagged x) { |
| return !CPyTagged_CheckLong(x); |
| } |
| |
| static inline Py_ssize_t CPyTagged_ShortAsSsize_t(CPyTagged x) { |
| // NOTE: Assume that we sign extend. |
| return (Py_ssize_t)x >> 1; |
| } |
| |
| static inline PyObject *CPyTagged_LongAsObject(CPyTagged x) { |
| // NOTE: Assume target is not a short int. |
| return (PyObject *)(x & ~CPY_INT_TAG); |
| } |
| |
| static inline bool CPyTagged_TooBig(Py_ssize_t value) { |
| // Micro-optimized for the common case where it fits. |
| return (size_t)value > CPY_TAGGED_MAX |
| && (value >= 0 || value < CPY_TAGGED_MIN); |
| } |
| |
| static CPyTagged CPyTagged_FromSsize_t(Py_ssize_t value) { |
| // We use a Python object if the value shifted left by 1 is too |
| // large for Py_ssize_t |
| if (CPyTagged_TooBig(value)) { |
| PyObject *object = PyLong_FromSsize_t(value); |
| return ((CPyTagged)object) | CPY_INT_TAG; |
| } else { |
| return value << 1; |
| } |
| } |
| |
| static CPyTagged CPyTagged_FromObject(PyObject *object) { |
| int overflow; |
| // The overflow check knows about CPyTagged's width |
| Py_ssize_t value = CPyLong_AsSsize_tAndOverflow(object, &overflow); |
| if (overflow != 0) { |
| Py_INCREF(object); |
| return ((CPyTagged)object) | CPY_INT_TAG; |
| } else { |
| return value << 1; |
| } |
| } |
| |
| static CPyTagged CPyTagged_StealFromObject(PyObject *object) { |
| int overflow; |
| // The overflow check knows about CPyTagged's width |
| Py_ssize_t value = CPyLong_AsSsize_tAndOverflow(object, &overflow); |
| if (overflow != 0) { |
| return ((CPyTagged)object) | CPY_INT_TAG; |
| } else { |
| Py_DECREF(object); |
| return value << 1; |
| } |
| } |
| |
| static CPyTagged CPyTagged_BorrowFromObject(PyObject *object) { |
| int overflow; |
| // The overflow check knows about CPyTagged's width |
| Py_ssize_t value = CPyLong_AsSsize_tAndOverflow(object, &overflow); |
| if (overflow != 0) { |
| return ((CPyTagged)object) | CPY_INT_TAG; |
| } else { |
| return value << 1; |
| } |
| } |
| |
| static PyObject *CPyTagged_AsObject(CPyTagged x) { |
| PyObject *value; |
| if (CPyTagged_CheckLong(x)) { |
| value = CPyTagged_LongAsObject(x); |
| Py_INCREF(value); |
| } else { |
| value = PyLong_FromSsize_t(CPyTagged_ShortAsSsize_t(x)); |
| if (value == NULL) { |
| CPyError_OutOfMemory(); |
| } |
| } |
| return value; |
| } |
| |
| static PyObject *CPyTagged_StealAsObject(CPyTagged x) { |
| PyObject *value; |
| if (CPyTagged_CheckLong(x)) { |
| value = CPyTagged_LongAsObject(x); |
| } else { |
| value = PyLong_FromSsize_t(CPyTagged_ShortAsSsize_t(x)); |
| if (value == NULL) { |
| CPyError_OutOfMemory(); |
| } |
| } |
| return value; |
| } |
| |
| static Py_ssize_t CPyTagged_AsSsize_t(CPyTagged x) { |
| if (CPyTagged_CheckShort(x)) { |
| return CPyTagged_ShortAsSsize_t(x); |
| } else { |
| return PyLong_AsSsize_t(CPyTagged_LongAsObject(x)); |
| } |
| } |
| |
| CPy_NOINLINE |
| static void CPyTagged_IncRef(CPyTagged x) { |
| if (CPyTagged_CheckLong(x)) { |
| Py_INCREF(CPyTagged_LongAsObject(x)); |
| } |
| } |
| |
| CPy_NOINLINE |
| static void CPyTagged_DecRef(CPyTagged x) { |
| if (CPyTagged_CheckLong(x)) { |
| Py_DECREF(CPyTagged_LongAsObject(x)); |
| } |
| } |
| |
| CPy_NOINLINE |
| static void CPyTagged_XDecRef(CPyTagged x) { |
| if (CPyTagged_CheckLong(x)) { |
| Py_XDECREF(CPyTagged_LongAsObject(x)); |
| } |
| } |
| |
| static inline bool CPyTagged_IsAddOverflow(CPyTagged sum, CPyTagged left, CPyTagged right) { |
| // This check was copied from some of my old code I believe that it works :-) |
| return (Py_ssize_t)(sum ^ left) < 0 && (Py_ssize_t)(sum ^ right) < 0; |
| } |
| |
| static CPyTagged CPyTagged_Negate(CPyTagged num) { |
| if (CPyTagged_CheckShort(num) |
| && num != (CPyTagged) ((Py_ssize_t)1 << (CPY_INT_BITS - 1))) { |
| // The only possibility of an overflow error happening when negating a short is if we |
| // attempt to negate the most negative number. |
| return -num; |
| } |
| PyObject *num_obj = CPyTagged_AsObject(num); |
| PyObject *result = PyNumber_Negative(num_obj); |
| if (result == NULL) { |
| CPyError_OutOfMemory(); |
| } |
| Py_DECREF(num_obj); |
| return CPyTagged_StealFromObject(result); |
| } |
| |
| static CPyTagged CPyTagged_Add(CPyTagged left, CPyTagged right) { |
| // TODO: Use clang/gcc extension __builtin_saddll_overflow instead. |
| if (CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right)) { |
| CPyTagged sum = left + right; |
| if (!CPyTagged_IsAddOverflow(sum, left, right)) { |
| return sum; |
| } |
| } |
| PyObject *left_obj = CPyTagged_AsObject(left); |
| PyObject *right_obj = CPyTagged_AsObject(right); |
| PyObject *result = PyNumber_Add(left_obj, right_obj); |
| if (result == NULL) { |
| CPyError_OutOfMemory(); |
| } |
| Py_DECREF(left_obj); |
| Py_DECREF(right_obj); |
| return CPyTagged_StealFromObject(result); |
| } |
| |
| static inline bool CPyTagged_IsSubtractOverflow(CPyTagged diff, CPyTagged left, CPyTagged right) { |
| // This check was copied from some of my old code I believe that it works :-) |
| return (Py_ssize_t)(diff ^ left) < 0 && (Py_ssize_t)(diff ^ right) >= 0; |
| } |
| |
| static CPyTagged CPyTagged_Subtract(CPyTagged left, CPyTagged right) { |
| // TODO: Use clang/gcc extension __builtin_saddll_overflow instead. |
| if (CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right)) { |
| CPyTagged diff = left - right; |
| if (!CPyTagged_IsSubtractOverflow(diff, left, right)) { |
| return diff; |
| } |
| } |
| PyObject *left_obj = CPyTagged_AsObject(left); |
| PyObject *right_obj = CPyTagged_AsObject(right); |
| PyObject *result = PyNumber_Subtract(left_obj, right_obj); |
| if (result == NULL) { |
| CPyError_OutOfMemory(); |
| } |
| Py_DECREF(left_obj); |
| Py_DECREF(right_obj); |
| return CPyTagged_StealFromObject(result); |
| } |
| |
| static inline bool CPyTagged_IsMultiplyOverflow(CPyTagged left, CPyTagged right) { |
| // This is conservative -- return false only in a small number of all non-overflow cases |
| return left >= (1U << (CPY_INT_BITS/2 - 1)) || right >= (1U << (CPY_INT_BITS/2 - 1)); |
| } |
| |
| static CPyTagged CPyTagged_Multiply(CPyTagged left, CPyTagged right) { |
| // TODO: Consider using some clang/gcc extension |
| if (CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right)) { |
| if (!CPyTagged_IsMultiplyOverflow(left, right)) { |
| return left * CPyTagged_ShortAsSsize_t(right); |
| } |
| } |
| PyObject *left_obj = CPyTagged_AsObject(left); |
| PyObject *right_obj = CPyTagged_AsObject(right); |
| PyObject *result = PyNumber_Multiply(left_obj, right_obj); |
| if (result == NULL) { |
| CPyError_OutOfMemory(); |
| } |
| Py_DECREF(left_obj); |
| Py_DECREF(right_obj); |
| return CPyTagged_StealFromObject(result); |
| } |
| |
| static inline bool CPyTagged_MaybeFloorDivideFault(CPyTagged left, CPyTagged right) { |
| return right == 0 || left == -((size_t)1 << (CPY_INT_BITS-1)); |
| } |
| |
| static CPyTagged CPyTagged_FloorDivide(CPyTagged left, CPyTagged right) { |
| if (CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right) |
| && !CPyTagged_MaybeFloorDivideFault(left, right)) { |
| Py_ssize_t result = ((Py_ssize_t)left / CPyTagged_ShortAsSsize_t(right)) & ~1; |
| if (((Py_ssize_t)left < 0) != (((Py_ssize_t)right) < 0)) { |
| if (result / 2 * right != left) { |
| // Round down |
| result -= 2; |
| } |
| } |
| return result; |
| } |
| PyObject *left_obj = CPyTagged_AsObject(left); |
| PyObject *right_obj = CPyTagged_AsObject(right); |
| PyObject *result = PyNumber_FloorDivide(left_obj, right_obj); |
| Py_DECREF(left_obj); |
| Py_DECREF(right_obj); |
| // Handle exceptions honestly because it could be ZeroDivisionError |
| if (result == NULL) { |
| return CPY_INT_TAG; |
| } else { |
| return CPyTagged_StealFromObject(result); |
| } |
| } |
| |
| static inline bool CPyTagged_MaybeRemainderFault(CPyTagged left, CPyTagged right) { |
| // Division/modulus can fault when dividing INT_MIN by -1, but we |
| // do our mods on still-tagged integers with the low-bit clear, so |
| // -1 is actually represented as -2 and can't overflow. |
| // Mod by 0 can still fault though. |
| return right == 0; |
| } |
| |
| static CPyTagged CPyTagged_Remainder(CPyTagged left, CPyTagged right) { |
| if (CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right) |
| && !CPyTagged_MaybeRemainderFault(left, right)) { |
| Py_ssize_t result = (Py_ssize_t)left % (Py_ssize_t)right; |
| if (((Py_ssize_t)right < 0) != ((Py_ssize_t)left < 0) && result != 0) { |
| result += right; |
| } |
| return result; |
| } |
| PyObject *left_obj = CPyTagged_AsObject(left); |
| PyObject *right_obj = CPyTagged_AsObject(right); |
| PyObject *result = PyNumber_Remainder(left_obj, right_obj); |
| Py_DECREF(left_obj); |
| Py_DECREF(right_obj); |
| // Handle exceptions honestly because it could be ZeroDivisionError |
| if (result == NULL) { |
| return CPY_INT_TAG; |
| } else { |
| return CPyTagged_StealFromObject(result); |
| } |
| } |
| |
| static bool CPyTagged_IsEq_(CPyTagged left, CPyTagged right) { |
| if (CPyTagged_CheckShort(right)) { |
| return false; |
| } else { |
| int result = PyObject_RichCompareBool(CPyTagged_LongAsObject(left), |
| CPyTagged_LongAsObject(right), Py_EQ); |
| if (result == -1) { |
| CPyError_OutOfMemory(); |
| } |
| return result; |
| } |
| } |
| |
| static inline bool CPyTagged_IsEq(CPyTagged left, CPyTagged right) { |
| if (CPyTagged_CheckShort(left)) { |
| return left == right; |
| } else { |
| return CPyTagged_IsEq_(left, right); |
| } |
| } |
| |
| static inline bool CPyTagged_IsNe(CPyTagged left, CPyTagged right) { |
| if (CPyTagged_CheckShort(left)) { |
| return left != right; |
| } else { |
| return !CPyTagged_IsEq_(left, right); |
| } |
| } |
| |
| static bool CPyTagged_IsLt_(CPyTagged left, CPyTagged right) { |
| PyObject *left_obj = CPyTagged_AsObject(left); |
| PyObject *right_obj = CPyTagged_AsObject(right); |
| int result = PyObject_RichCompareBool(left_obj, right_obj, Py_LT); |
| Py_DECREF(left_obj); |
| Py_DECREF(right_obj); |
| if (result == -1) { |
| CPyError_OutOfMemory(); |
| } |
| return result; |
| } |
| |
| static inline bool CPyTagged_IsLt(CPyTagged left, CPyTagged right) { |
| if (CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right)) { |
| return (Py_ssize_t)left < (Py_ssize_t)right; |
| } else { |
| return CPyTagged_IsLt_(left, right); |
| } |
| } |
| |
| static inline bool CPyTagged_IsGe(CPyTagged left, CPyTagged right) { |
| if (CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right)) { |
| return (Py_ssize_t)left >= (Py_ssize_t)right; |
| } else { |
| return !CPyTagged_IsLt_(left, right); |
| } |
| } |
| |
| static inline bool CPyTagged_IsGt(CPyTagged left, CPyTagged right) { |
| if (CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right)) { |
| return (Py_ssize_t)left > (Py_ssize_t)right; |
| } else { |
| return CPyTagged_IsLt_(right, left); |
| } |
| } |
| |
| static inline bool CPyTagged_IsLe(CPyTagged left, CPyTagged right) { |
| if (CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right)) { |
| return (Py_ssize_t)left <= (Py_ssize_t)right; |
| } else { |
| return !CPyTagged_IsLt_(right, left); |
| } |
| } |
| |
| static CPyTagged CPyTagged_Id(PyObject *o) { |
| return CPyTagged_FromSsize_t((Py_ssize_t)o); |
| } |
| |
| static PyObject *CPyList_GetItemUnsafe(PyObject *list, CPyTagged index) { |
| Py_ssize_t n = CPyTagged_ShortAsSsize_t(index); |
| PyObject *result = PyList_GET_ITEM(list, n); |
| Py_INCREF(result); |
| return result; |
| } |
| |
| static PyObject *CPyList_GetItemShort(PyObject *list, CPyTagged index) { |
| Py_ssize_t n = CPyTagged_ShortAsSsize_t(index); |
| Py_ssize_t size = PyList_GET_SIZE(list); |
| if (n >= 0) { |
| if (n >= size) { |
| PyErr_SetString(PyExc_IndexError, "list index out of range"); |
| return NULL; |
| } |
| } else { |
| n += size; |
| if (n < 0) { |
| PyErr_SetString(PyExc_IndexError, "list index out of range"); |
| return NULL; |
| } |
| } |
| PyObject *result = PyList_GET_ITEM(list, n); |
| Py_INCREF(result); |
| return result; |
| } |
| |
| static PyObject *CPyList_GetItem(PyObject *list, CPyTagged index) { |
| if (CPyTagged_CheckShort(index)) { |
| Py_ssize_t n = CPyTagged_ShortAsSsize_t(index); |
| Py_ssize_t size = PyList_GET_SIZE(list); |
| if (n >= 0) { |
| if (n >= size) { |
| PyErr_SetString(PyExc_IndexError, "list index out of range"); |
| return NULL; |
| } |
| } else { |
| n += size; |
| if (n < 0) { |
| PyErr_SetString(PyExc_IndexError, "list index out of range"); |
| return NULL; |
| } |
| } |
| PyObject *result = PyList_GET_ITEM(list, n); |
| Py_INCREF(result); |
| return result; |
| } else { |
| PyErr_SetString(PyExc_IndexError, "list index out of range"); |
| return NULL; |
| } |
| } |
| |
| static bool CPyList_SetItem(PyObject *list, CPyTagged index, PyObject *value) { |
| if (CPyTagged_CheckShort(index)) { |
| Py_ssize_t n = CPyTagged_ShortAsSsize_t(index); |
| Py_ssize_t size = PyList_GET_SIZE(list); |
| if (n >= 0) { |
| if (n >= size) { |
| PyErr_SetString(PyExc_IndexError, "list assignment index out of range"); |
| return false; |
| } |
| } else { |
| n += size; |
| if (n < 0) { |
| PyErr_SetString(PyExc_IndexError, "list assignment index out of range"); |
| return false; |
| } |
| } |
| // PyList_SET_ITEM doesn't decref the old element, so we do |
| Py_DECREF(PyList_GET_ITEM(list, n)); |
| // N.B: Steals reference |
| PyList_SET_ITEM(list, n, value); |
| return true; |
| } else { |
| PyErr_SetString(PyExc_IndexError, "list assignment index out of range"); |
| return false; |
| } |
| } |
| |
| static PyObject *CPyList_PopLast(PyObject *obj) |
| { |
| // I tried a specalized version of pop_impl for just removing the |
| // last element and it wasn't any faster in microbenchmarks than |
| // the generic one so I ditched it. |
| return list_pop_impl((PyListObject *)obj, -1); |
| } |
| |
| static PyObject *CPyList_Pop(PyObject *obj, CPyTagged index) |
| { |
| if (CPyTagged_CheckShort(index)) { |
| Py_ssize_t n = CPyTagged_ShortAsSsize_t(index); |
| return list_pop_impl((PyListObject *)obj, n); |
| } else { |
| PyErr_SetString(PyExc_IndexError, "pop index out of range"); |
| return NULL; |
| } |
| } |
| |
| static CPyTagged CPyList_Count(PyObject *obj, PyObject *value) |
| { |
| return list_count((PyListObject *)obj, value); |
| } |
| |
| static bool CPySet_Remove(PyObject *set, PyObject *key) { |
| int success = PySet_Discard(set, key); |
| if (success == 1) { |
| return true; |
| } |
| if (success == 0) { |
| _PyErr_SetKeyError(key); |
| } |
| return false; |
| } |
| |
| static PyObject *CPySequenceTuple_GetItem(PyObject *tuple, CPyTagged index) { |
| if (CPyTagged_CheckShort(index)) { |
| Py_ssize_t n = CPyTagged_ShortAsSsize_t(index); |
| Py_ssize_t size = PyTuple_GET_SIZE(tuple); |
| if (n >= 0) { |
| if (n >= size) { |
| PyErr_SetString(PyExc_IndexError, "tuple index out of range"); |
| return NULL; |
| } |
| } else { |
| n += size; |
| if (n < 0) { |
| PyErr_SetString(PyExc_IndexError, "tuple index out of range"); |
| return NULL; |
| } |
| } |
| PyObject *result = PyTuple_GET_ITEM(tuple, n); |
| Py_INCREF(result); |
| return result; |
| } else { |
| PyErr_SetString(PyExc_IndexError, "tuple index out of range"); |
| return NULL; |
| } |
| } |
| |
| static CPyTagged CPyObject_Hash(PyObject *o) { |
| Py_hash_t h = PyObject_Hash(o); |
| if (h == -1) { |
| return CPY_INT_TAG; |
| } else { |
| // This is tragically annoying. The range of hash values in |
| // 64-bit python covers 64-bits, and our short integers only |
| // cover 63. This means that half the time we are boxing the |
| // result for basically no good reason. To add insult to |
| // injury it is probably about to be immediately unboxed by a |
| // tp_hash wrapper. |
| return CPyTagged_FromSsize_t(h); |
| } |
| } |
| |
| static inline CPyTagged CPyObject_Size(PyObject *obj) { |
| Py_ssize_t s = PyObject_Size(obj); |
| if (s < 0) { |
| return CPY_INT_TAG; |
| } else { |
| // Technically __len__ could return a really big number, so we |
| // should allow this to produce a boxed int. In practice it |
| // shouldn't ever if the data structure actually contains all |
| // the elements, but... |
| return CPyTagged_FromSsize_t(s); |
| } |
| } |
| |
| static inline int CPy_ObjectToStatus(PyObject *obj) { |
| if (obj) { |
| Py_DECREF(obj); |
| return 0; |
| } else { |
| return -1; |
| } |
| } |
| |
| // 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. |
| static 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); |
| } |
| } |
| |
| static 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; |
| } |
| |
| static 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; |
| } |
| |
| static 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 int CPyDict_UpdateGeneral(PyObject *dict, PyObject *stuff) { |
| _Py_IDENTIFIER(update); |
| PyObject *res = _PyObject_CallMethodIdObjArgs(dict, &PyId_update, stuff, NULL); |
| return CPy_ObjectToStatus(res); |
| } |
| |
| static 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", |
| stuff->ob_type->tp_name); |
| } |
| } |
| return ret; |
| } |
| |
| static int CPyDict_Update(PyObject *dict, PyObject *stuff) { |
| if (PyDict_CheckExact(dict)) { |
| return PyDict_Update(dict, stuff); |
| } else { |
| return CPyDict_UpdateGeneral(dict, stuff); |
| } |
| } |
| |
| static int CPyDict_UpdateFromAny(PyObject *dict, PyObject *stuff) { |
| if (PyDict_CheckExact(dict)) { |
| // Argh this sucks |
| _Py_IDENTIFIER(keys); |
| if (PyDict_Check(stuff) || _PyObject_HasAttrId(stuff, &PyId_keys)) { |
| return PyDict_Update(dict, stuff); |
| } else { |
| return PyDict_MergeFromSeq2(dict, stuff, 1); |
| } |
| } else { |
| return CPyDict_UpdateGeneral(dict, stuff); |
| } |
| } |
| |
| static 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 (_PyObject_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; |
| } |
| } |
| |
| static PyObject *CPyIter_Next(PyObject *iter) |
| { |
| return (*iter->ob_type->tp_iternext)(iter); |
| } |
| |
| static PyObject *CPy_FetchStopIterationValue(void) |
| { |
| PyObject *val = NULL; |
| _PyGen_FetchStopIterationValue(&val); |
| return val; |
| } |
| |
| static PyObject *CPyIter_Send(PyObject *iter, PyObject *val) |
| { |
| // Do a send, or a next if second arg is None. |
| // (This behavior is to match the PEP 380 spec for yield from.) |
| _Py_IDENTIFIER(send); |
| if (val == Py_None) { |
| return CPyIter_Next(iter); |
| } else { |
| return _PyObject_CallMethodIdObjArgs(iter, &PyId_send, val, NULL); |
| } |
| } |
| |
| static PyObject *CPy_GetCoro(PyObject *obj) |
| { |
| // If the type has an __await__ method, call it, |
| // otherwise, fallback to calling __iter__. |
| PyAsyncMethods* async_struct = obj->ob_type->tp_as_async; |
| if (async_struct != NULL && async_struct->am_await != NULL) { |
| return (async_struct->am_await)(obj); |
| } else { |
| // TODO: We should check that the type is a generator decorated with |
| // asyncio.coroutine |
| return PyObject_GetIter(obj); |
| } |
| } |
| |
| static PyObject *CPyObject_GetAttr3(PyObject *v, PyObject *name, PyObject *defl) |
| { |
| PyObject *result = PyObject_GetAttr(v, name); |
| if (!result && PyErr_ExceptionMatches(PyExc_AttributeError)) { |
| PyErr_Clear(); |
| Py_INCREF(defl); |
| result = defl; |
| } |
| return result; |
| } |
| |
| // mypy lets ints silently coerce to floats, so a mypyc runtime float |
| // might be an int also |
| static inline bool CPyFloat_Check(PyObject *o) { |
| return PyFloat_Check(o) || PyLong_Check(o); |
| } |
| |
| static PyObject *CPyLong_FromFloat(PyObject *o) { |
| if (PyLong_Check(o)) { |
| CPy_INCREF(o); |
| return o; |
| } else { |
| return PyLong_FromDouble(PyFloat_AS_DOUBLE(o)); |
| } |
| } |
| |
| // Construct a nicely formatted type name based on __module__ and __name__. |
| static PyObject *CPy_GetTypeName(PyObject *type) { |
| PyObject *module = NULL, *name = NULL; |
| PyObject *full = NULL; |
| |
| module = PyObject_GetAttrString(type, "__module__"); |
| if (!module || !PyUnicode_Check(module)) { |
| goto out; |
| } |
| name = PyObject_GetAttrString(type, "__qualname__"); |
| if (!name || !PyUnicode_Check(name)) { |
| goto out; |
| } |
| |
| if (PyUnicode_CompareWithASCIIString(module, "builtins") == 0) { |
| Py_INCREF(name); |
| full = name; |
| } else { |
| full = PyUnicode_FromFormat("%U.%U", module, name); |
| } |
| |
| out: |
| Py_XDECREF(module); |
| Py_XDECREF(name); |
| return full; |
| } |
| |
| |
| // Get the type of a value as a string, expanding tuples to include |
| // all the element types. |
| static PyObject *CPy_FormatTypeName(PyObject *value) { |
| if (value == Py_None) { |
| return PyUnicode_FromString("None"); |
| } |
| |
| if (!PyTuple_CheckExact(value)) { |
| return CPy_GetTypeName((PyObject *)Py_TYPE(value)); |
| } |
| |
| if (PyTuple_GET_SIZE(value) > 10) { |
| return PyUnicode_FromFormat("tuple[<%d items>]", PyTuple_GET_SIZE(value)); |
| } |
| |
| // Most of the logic is all for tuples, which is the only interesting case |
| PyObject *output = PyUnicode_FromString("tuple["); |
| if (!output) { |
| return NULL; |
| } |
| /* This is quadratic but if that ever matters something is really weird. */ |
| int i; |
| for (i = 0; i < PyTuple_GET_SIZE(value); i++) { |
| PyObject *s = CPy_FormatTypeName(PyTuple_GET_ITEM(value, i)); |
| if (!s) { |
| Py_DECREF(output); |
| return NULL; |
| } |
| PyObject *next = PyUnicode_FromFormat("%U%U%s", output, s, |
| i + 1 == PyTuple_GET_SIZE(value) ? "]" : ", "); |
| Py_DECREF(output); |
| Py_DECREF(s); |
| if (!next) { |
| return NULL; |
| } |
| output = next; |
| } |
| return output; |
| } |
| |
| static void CPy_TypeError(const char *expected, PyObject *value) { |
| PyObject *out = CPy_FormatTypeName(value); |
| if (out) { |
| PyErr_Format(PyExc_TypeError, "%s object expected; got %U", expected, out); |
| Py_DECREF(out); |
| } else { |
| PyErr_Format(PyExc_TypeError, "%s object expected; and errored formatting real type!", |
| expected); |
| } |
| } |
| |
| // These functions are basically exactly PyCode_NewEmpty and |
| // _PyTraceback_Add which are available in all the versions we support. |
| // We're continuing to use them because we'll probably optimize them later. |
| static PyCodeObject *CPy_CreateCodeObject(const char *filename, const char *funcname, int line) { |
| PyObject *filename_obj = PyUnicode_FromString(filename); |
| PyObject *funcname_obj = PyUnicode_FromString(funcname); |
| PyObject *empty_bytes = PyBytes_FromStringAndSize("", 0); |
| PyObject *empty_tuple = PyTuple_New(0); |
| PyCodeObject *code_obj = NULL; |
| if (filename_obj == NULL || funcname_obj == NULL || empty_bytes == NULL |
| || empty_tuple == NULL) { |
| goto Error; |
| } |
| code_obj = PyCode_New(0, 0, 0, 0, 0, |
| empty_bytes, |
| empty_tuple, |
| empty_tuple, |
| empty_tuple, |
| empty_tuple, |
| empty_tuple, |
| filename_obj, |
| funcname_obj, |
| line, |
| empty_bytes); |
| Error: |
| Py_XDECREF(empty_bytes); |
| Py_XDECREF(empty_tuple); |
| Py_XDECREF(filename_obj); |
| Py_XDECREF(funcname_obj); |
| return code_obj; |
| } |
| |
| static void CPy_AddTraceback(const char *filename, const char *funcname, int line, |
| PyObject *globals) { |
| |
| PyObject *exc, *val, *tb; |
| PyThreadState *thread_state = PyThreadState_GET(); |
| PyFrameObject *frame_obj; |
| |
| // We need to save off the exception state because in 3.8, |
| // PyFrame_New fails if there is an error set and it fails to look |
| // up builtins in the globals. (_PyTraceback_Add documents that it |
| // needs to do it because it decodes the filename according to the |
| // FS encoding, which could have a decoder in Python. We don't do |
| // that so *that* doesn't apply to us.) |
| PyErr_Fetch(&exc, &val, &tb); |
| PyCodeObject *code_obj = CPy_CreateCodeObject(filename, funcname, line); |
| if (code_obj == NULL) { |
| goto error; |
| } |
| |
| frame_obj = PyFrame_New(thread_state, code_obj, globals, 0); |
| if (frame_obj == NULL) { |
| Py_DECREF(code_obj); |
| goto error; |
| } |
| frame_obj->f_lineno = line; |
| PyErr_Restore(exc, val, tb); |
| PyTraceBack_Here(frame_obj); |
| Py_DECREF(code_obj); |
| Py_DECREF(frame_obj); |
| |
| return; |
| |
| error: |
| _PyErr_ChainExceptions(exc, val, tb); |
| } |
| |
| // mypyc is not very good at dealing with refcount management of |
| // pointers that might be NULL. As a workaround for this, the |
| // exception APIs that might want to return NULL pointers instead |
| // return properly refcounted pointers to this dummy object. |
| struct ExcDummyStruct { PyObject_HEAD }; |
| extern struct ExcDummyStruct _CPy_ExcDummyStruct; |
| extern PyObject *_CPy_ExcDummy; |
| |
| static inline void _CPy_ToDummy(PyObject **p) { |
| if (*p == NULL) { |
| Py_INCREF(_CPy_ExcDummy); |
| *p = _CPy_ExcDummy; |
| } |
| } |
| |
| static inline PyObject *_CPy_FromDummy(PyObject *p) { |
| if (p == _CPy_ExcDummy) return NULL; |
| Py_INCREF(p); |
| return p; |
| } |
| |
| static void CPy_CatchError(PyObject **p_type, PyObject **p_value, PyObject **p_traceback) { |
| // We need to return the existing sys.exc_info() information, so |
| // that it can be restored when we finish handling the error we |
| // are catching now. Grab that triple and convert NULL values to |
| // the ExcDummy object in order to simplify refcount handling in |
| // generated code. |
| PyErr_GetExcInfo(p_type, p_value, p_traceback); |
| _CPy_ToDummy(p_type); |
| _CPy_ToDummy(p_value); |
| _CPy_ToDummy(p_traceback); |
| |
| if (!PyErr_Occurred()) { |
| PyErr_SetString(PyExc_RuntimeError, "CPy_CatchError called with no error!"); |
| } |
| |
| // Retrieve the error info and normalize it so that it looks like |
| // what python code needs it to be. |
| PyObject *type, *value, *traceback; |
| PyErr_Fetch(&type, &value, &traceback); |
| // Could we avoid always normalizing? |
| PyErr_NormalizeException(&type, &value, &traceback); |
| if (traceback != NULL) { |
| PyException_SetTraceback(value, traceback); |
| } |
| // Indicate that we are now handling this exception by stashing it |
| // in sys.exc_info(). mypyc routines that need access to the |
| // exception will read it out of there. |
| PyErr_SetExcInfo(type, value, traceback); |
| // Clear the error indicator, since the exception isn't |
| // propagating anymore. |
| PyErr_Clear(); |
| } |
| |
| static void CPy_RestoreExcInfo(PyObject *type, PyObject *value, PyObject *traceback) { |
| // PyErr_SetExcInfo steals the references to the values passed to it. |
| PyErr_SetExcInfo(_CPy_FromDummy(type), _CPy_FromDummy(value), _CPy_FromDummy(traceback)); |
| } |
| |
| static void CPy_Raise(PyObject *exc) { |
| if (PyObject_IsInstance(exc, (PyObject *)&PyType_Type)) { |
| PyObject *obj = PyObject_CallFunctionObjArgs(exc, NULL); |
| if (!obj) |
| return; |
| PyErr_SetObject(exc, obj); |
| Py_DECREF(obj); |
| } else { |
| PyErr_SetObject((PyObject *)Py_TYPE(exc), exc); |
| } |
| } |
| |
| static void CPy_Reraise(void) { |
| PyObject *p_type, *p_value, *p_traceback; |
| PyErr_GetExcInfo(&p_type, &p_value, &p_traceback); |
| PyErr_Restore(p_type, p_value, p_traceback); |
| } |
| |
| static void CPyErr_SetObjectAndTraceback(PyObject *type, PyObject *value, PyObject *traceback) { |
| // Set the value and traceback of an error. Because calling |
| // PyErr_Restore takes away a reference to each object passed in |
| // as an argument, we manually increase the reference count of |
| // each argument before calling it. |
| Py_INCREF(type); |
| Py_INCREF(value); |
| Py_INCREF(traceback); |
| PyErr_Restore(type, value, traceback); |
| } |
| |
| // We want to avoid the public PyErr_GetExcInfo API for these because |
| // it requires a bunch of spurious refcount traffic on the parts of |
| // the triple we don't care about. Unfortunately the layout of the |
| // data structure changed in 3.7 so we need to handle that. |
| #if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 7 |
| #define CPy_ExcState() PyThreadState_GET()->exc_info |
| #else |
| #define CPy_ExcState() PyThreadState_GET() |
| #endif |
| |
| static bool CPy_ExceptionMatches(PyObject *type) { |
| return PyErr_GivenExceptionMatches(CPy_ExcState()->exc_type, type); |
| } |
| |
| static PyObject *CPy_GetExcValue(void) { |
| PyObject *exc = CPy_ExcState()->exc_value; |
| Py_INCREF(exc); |
| return exc; |
| } |
| |
| static inline void _CPy_ToNone(PyObject **p) { |
| if (*p == NULL) { |
| Py_INCREF(Py_None); |
| *p = Py_None; |
| } |
| } |
| |
| static void CPy_GetExcInfo(PyObject **p_type, PyObject **p_value, PyObject **p_traceback) { |
| PyErr_GetExcInfo(p_type, p_value, p_traceback); |
| _CPy_ToNone(p_type); |
| _CPy_ToNone(p_value); |
| _CPy_ToNone(p_traceback); |
| } |
| |
| void CPy_Init(void); |
| |
| |
| // A somewhat hairy implementation of specifically most of the error handling |
| // in `yield from` error handling. The point here is to reduce code size. |
| // |
| // This implements most of the bodies of the `except` blocks in the |
| // pseudocode in PEP 380. |
| // |
| // Returns true (1) if a StopIteration was received and we should return. |
| // Returns false (0) if a value should be yielded. |
| // In both cases the value is stored in outp. |
| // Signals an error (2) if the an exception should be propagated. |
| static int CPy_YieldFromErrorHandle(PyObject *iter, PyObject **outp) |
| { |
| _Py_IDENTIFIER(close); |
| _Py_IDENTIFIER(throw); |
| PyObject *exc_type = CPy_ExcState()->exc_type; |
| PyObject *type, *value, *traceback; |
| PyObject *_m; |
| PyObject *res; |
| *outp = NULL; |
| |
| if (PyErr_GivenExceptionMatches(exc_type, PyExc_GeneratorExit)) { |
| _m = _PyObject_GetAttrId(iter, &PyId_close); |
| if (_m) { |
| res = PyObject_CallFunctionObjArgs(_m, NULL); |
| Py_DECREF(_m); |
| if (!res) |
| return 2; |
| Py_DECREF(res); |
| } else if (PyErr_ExceptionMatches(PyExc_AttributeError)) { |
| PyErr_Clear(); |
| } else { |
| return 2; |
| } |
| } else { |
| _m = _PyObject_GetAttrId(iter, &PyId_throw); |
| if (_m) { |
| CPy_GetExcInfo(&type, &value, &traceback); |
| res = PyObject_CallFunctionObjArgs(_m, type, value, traceback, NULL); |
| Py_DECREF(type); |
| Py_DECREF(value); |
| Py_DECREF(traceback); |
| Py_DECREF(_m); |
| if (res) { |
| *outp = res; |
| return 0; |
| } else { |
| res = CPy_FetchStopIterationValue(); |
| if (res) { |
| *outp = res; |
| return 1; |
| } |
| } |
| } else if (PyErr_ExceptionMatches(PyExc_AttributeError)) { |
| PyErr_Clear(); |
| } else { |
| return 2; |
| } |
| } |
| |
| CPy_Reraise(); |
| return 2; |
| } |
| |
| static int _CPy_UpdateObjFromDict(PyObject *obj, PyObject *dict) |
| { |
| Py_ssize_t pos = 0; |
| PyObject *key, *value; |
| while (PyDict_Next(dict, &pos, &key, &value)) { |
| if (PyObject_SetAttr(obj, key, value) != 0) { |
| return -1; |
| } |
| } |
| return 0; |
| } |
| |
| // Support for pickling; reusable getstate and setstate functions |
| static PyObject * |
| CPyPickle_SetState(PyObject *obj, PyObject *state) |
| { |
| if (_CPy_UpdateObjFromDict(obj, state) != 0) { |
| return NULL; |
| } |
| Py_RETURN_NONE; |
| } |
| |
| static PyObject * |
| CPyPickle_GetState(PyObject *obj) |
| { |
| PyObject *attrs = NULL, *state = NULL; |
| |
| attrs = PyObject_GetAttrString((PyObject *)Py_TYPE(obj), "__mypyc_attrs__"); |
| if (!attrs) { |
| goto fail; |
| } |
| if (!PyTuple_Check(attrs)) { |
| PyErr_SetString(PyExc_TypeError, "__mypyc_attrs__ is not a tuple"); |
| goto fail; |
| } |
| state = PyDict_New(); |
| if (!state) { |
| goto fail; |
| } |
| |
| // Collect all the values of attributes in __mypyc_attrs__ |
| // Attributes that are missing we just ignore |
| int i; |
| for (i = 0; i < PyTuple_GET_SIZE(attrs); i++) { |
| PyObject *key = PyTuple_GET_ITEM(attrs, i); |
| PyObject *value = PyObject_GetAttr(obj, key); |
| if (!value) { |
| if (PyErr_ExceptionMatches(PyExc_AttributeError)) { |
| PyErr_Clear(); |
| continue; |
| } |
| goto fail; |
| } |
| int result = PyDict_SetItem(state, key, value); |
| Py_DECREF(value); |
| if (result != 0) { |
| goto fail; |
| } |
| } |
| |
| Py_DECREF(attrs); |
| |
| return state; |
| fail: |
| Py_XDECREF(attrs); |
| Py_XDECREF(state); |
| return NULL; |
| } |
| |
| /* Support for our partial built-in support for dataclasses. |
| * |
| * Take a class we want to make a dataclass, remove any descriptors |
| * for annotated attributes, swap in the actual values of the class |
| * variables invoke dataclass, and then restore all of the |
| * descriptors. |
| * |
| * The purpose of all this is that dataclasses uses the values of |
| * class variables to drive which attributes are required and what the |
| * default values/factories are for optional attributes. This means |
| * that the class dict needs to contain those values instead of getset |
| * descriptors for the attributes when we invoke dataclass. |
| * |
| * We need to remove descriptors for attributes even when there is no |
| * default value for them, or else dataclass will think the descriptor |
| * is the default value. We remove only the attributes, since we don't |
| * want dataclasses to try generating functions when they are already |
| * implemented. |
| * |
| * Args: |
| * dataclass_dec: The decorator to apply |
| * tp: The class we are making a dataclass |
| * dict: The dictionary containing values that dataclasses needs |
| * annotations: The type annotation dictionary |
| */ |
| static int |
| CPyDataclass_SleightOfHand(PyObject *dataclass_dec, PyObject *tp, |
| PyObject *dict, PyObject *annotations) { |
| PyTypeObject *ttp = (PyTypeObject *)tp; |
| Py_ssize_t pos; |
| PyObject *res; |
| |
| /* Make a copy of the original class __dict__ */ |
| PyObject *orig_dict = PyDict_Copy(ttp->tp_dict); |
| if (!orig_dict) { |
| goto fail; |
| } |
| |
| /* Delete anything that had an annotation */ |
| pos = 0; |
| PyObject *key; |
| while (PyDict_Next(annotations, &pos, &key, NULL)) { |
| if (PyObject_DelAttr(tp, key) != 0) { |
| goto fail; |
| } |
| } |
| |
| /* Copy in all the attributes that we want dataclass to see */ |
| if (_CPy_UpdateObjFromDict(tp, dict) != 0) { |
| goto fail; |
| } |
| |
| /* Run the @dataclass descriptor */ |
| res = PyObject_CallFunctionObjArgs(dataclass_dec, tp, NULL); |
| if (!res) { |
| goto fail; |
| } |
| Py_DECREF(res); |
| |
| /* Copy back the original contents of the dict */ |
| if (_CPy_UpdateObjFromDict(tp, orig_dict) != 0) { |
| goto fail; |
| } |
| |
| Py_DECREF(orig_dict); |
| return 1; |
| |
| fail: |
| Py_XDECREF(orig_dict); |
| return 0; |
| } |
| |
| |
| int CPyArg_ParseTupleAndKeywords(PyObject *, PyObject *, |
| const char *, char **, ...); |
| |
| #ifdef __cplusplus |
| } |
| #endif |
| |
| #endif // CPY_CPY_H |