Cython Changelog
================
+0.29.20 (2020-06-10)
+====================
+
+Bugs fixed
+----------
+
+* Nested try-except statements with multiple ``return`` statements could crash
+ due to incorrect deletion of the ``except as`` target variable.
+ (Github issue #3666)
+
+* The ``@classmethod`` decorator no longer rejects unknown input from other decorators.
+ Patch by David Woods. (Github issue #3660)
+
+* Fused types could leak into unrelated usages.
+ Patch by David Woods. (Github issue #3642)
+
+* Now uses ``Py_SET_SIZE()`` and ``Py_SET_REFCNT()`` in Py3.9+ to avoid low-level
+ write access to these object fields.
+ Patch by Victor Stinner. (Github issue #3639)
+
+* The built-in ``abs()`` function could lead to undefined behaviour when used on
+ the negative-most value of a signed C integer type.
+ Patch by Serge Guelton. (Github issue #1911)
+
+* Usages of ``sizeof()`` and ``typeid()`` on uninitialised variables no longer
+ produce a warning.
+ Patch by Celelibi. (Github issue #3575)
+
+* The C++ ``typeid()`` function was allowed in C mode.
+ Patch by Celelibi. (Github issue #3637)
+
+* The error position reported for errors found in f-strings was misleading.
+ (Github issue #3674)
+
+* The new ``c_api_binop_methods`` directive was added for forward compatibility, but can
+ only be set to True (the current default value). It can be disabled in Cython 3.0.
+
+
0.29.19 (2020-05-20)
====================
impl = re.sub(r'PY(IDENT|UNICODE)\("([^"]+)"\)', externalise, impl)
assert 'PYIDENT(' not in impl and 'PYUNICODE(' not in impl
- return bool(replacements), impl
+ return True, impl
def inject_unbound_methods(self, impl, output):
"""Replace 'UNBOUND_METHOD(type, "name")' by a constant Python identifier cname.
if 'CALL_UNBOUND_METHOD(' not in impl:
return False, impl
- utility_code = set()
def externalise(matchobj):
type_cname, method_name, obj_cname, args = matchobj.groups()
args = [arg.strip() for arg in args[1:].split(',')] if args else []
r'\)', externalise, impl)
assert 'CALL_UNBOUND_METHOD(' not in impl
- for helper in sorted(utility_code):
- output.use_utility_code(UtilityCode.load_cached(helper, "ObjectHandling.c"))
- return bool(utility_code), impl
+ return True, impl
def wrap_c_strings(self, impl):
"""Replace CSTRING('''xyz''') by a C compatible string
func_type = self.function_type()
if func_type.is_pyobject:
self.gil_error()
- elif not getattr(func_type, 'nogil', False):
+ elif not func_type.is_error and not getattr(func_type, 'nogil', False):
self.gil_error()
gil_message = "Calling gil-requiring function"
if function.is_name or function.is_attribute:
code.globalstate.use_entry_utility_code(function.entry)
+ abs_function_cnames = ('abs', 'labs', '__Pyx_abs_longlong')
+ is_signed_int = self.type.is_int and self.type.signed
+ if self.overflowcheck and is_signed_int and function.result() in abs_function_cnames:
+ code.globalstate.use_utility_code(UtilityCode.load_cached("Common", "Overflow.c"))
+ code.putln('if (unlikely(%s == __PYX_MIN(%s))) {\
+ PyErr_SetString(PyExc_OverflowError,\
+ "Trying to take the absolute value of the most negative integer is not defined."); %s; }' % (
+ self.args[0].result(),
+ self.args[0].type.empty_declaration_code(),
+ code.error_goto(self.pos)))
+
if not function.type.is_pyobject or len(self.arg_tuple.args) > 1 or (
self.arg_tuple.args and self.arg_tuple.is_literal):
super(SimpleCallNode, self).generate_evaluation_code(code)
self.result() if self.type.is_pyobject else None,
func_type.exception_value, self.nogil)
else:
- if (self.overflowcheck
- and self.type.is_int
- and self.type.signed
- and self.function.result() in ('abs', 'labs', '__Pyx_abs_longlong')):
- goto_error = 'if (unlikely(%s < 0)) { PyErr_SetString(PyExc_OverflowError, "value too large"); %s; }' % (
- self.result(), code.error_goto(self.pos))
- elif exc_checks:
+ if exc_checks:
goto_error = code.error_goto_if(" && ".join(exc_checks), self.pos)
else:
goto_error = ""
typeinfo_entry = typeinfo_module.lookup('type_info')
return PyrexTypes.CFakeReferenceType(PyrexTypes.c_const_type(typeinfo_entry.type))
+ cpp_message = 'typeid operator'
+
def analyse_types(self, env):
+ self.cpp_check(env)
type_info = self.get_type_info_type(env)
if not type_info:
self.error("The 'libcpp.typeinfo' module must be cimported to use the typeid() operator")
self.mark_position(node)
return node
+ def visit_SizeofVarNode(self, node):
+ return node
+
+ def visit_TypeidNode(self, node):
+ return node
+
def visit_IfStatNode(self, node):
next_block = self.flow.newblock()
parent = self.flow.block
if self.flow.loops:
self.flow.loops[-1].exceptions.append(descr)
self.flow.block = body_block
- ## XXX: Is it still required
- body_block.add_child(entry_point)
self.flow.nextblock()
self._visit(node.body)
self.flow.exceptions.pop()
self.mark_position(node)
self.visitchildren(node)
- for exception in self.flow.exceptions[::-1]:
- if exception.finally_enter:
- self.flow.block.add_child(exception.finally_enter)
- if exception.finally_exit:
- exception.finally_exit.add_child(self.flow.exit_point)
+ outer_exception_handlers = iter(self.flow.exceptions[::-1])
+ for handler in outer_exception_handlers:
+ if handler.finally_enter:
+ self.flow.block.add_child(handler.finally_enter)
+ if handler.finally_exit:
+ # 'return' goes to function exit, or to the next outer 'finally' clause
+ exit_point = self.flow.exit_point
+ for next_handler in outer_exception_handlers:
+ if next_handler.finally_enter:
+ exit_point = next_handler.finally_enter
+ break
+ handler.finally_exit.add_child(exit_point)
break
else:
if self.flow.block:
from .Errors import error, warning
from .PyrexTypes import py_object_type
from ..Utils import open_new_file, replace_suffix, decode_filename, build_hex_version
-from .Code import UtilityCode, IncludeCode
+from .Code import UtilityCode, IncludeCode, TempitaUtilityCode
from .StringEncoding import EncodedString
from .Pythran import has_np_pythran
self.generate_dict_getter_function(scope, code)
if scope.defines_any_special(TypeSlots.richcmp_special_methods):
self.generate_richcmp_function(scope, code)
+ for slot in TypeSlots.PyNumberMethods:
+ if slot.is_binop and scope.defines_any_special(slot.user_methods):
+ self.generate_binop_function(scope, slot, code)
self.generate_property_accessors(scope, code)
self.generate_method_table(scope, code)
self.generate_getset_table(scope, code)
code.putln("{")
code.putln("PyObject *etype, *eval, *etb;")
code.putln("PyErr_Fetch(&etype, &eval, &etb);")
- code.putln("++Py_REFCNT(o);")
+ # increase the refcount while we are calling into user code
+ # to prevent recursive deallocation
+ code.putln("__Pyx_SET_REFCNT(o, Py_REFCNT(o) + 1);")
code.putln("%s(o);" % entry.func_cname)
- code.putln("--Py_REFCNT(o);")
+ code.putln("__Pyx_SET_REFCNT(o, Py_REFCNT(o) - 1);")
code.putln("PyErr_Restore(etype, eval, etb);")
code.putln("}")
code.putln("}") # switch
code.putln("}")
+ def generate_binop_function(self, scope, slot, code):
+ func_name = scope.mangle_internal(slot.slot_name)
+ if scope.directives['c_api_binop_methods']:
+ code.putln('#define %s %s' % (func_name, slot.left_slot.slot_code(scope)))
+ return
+ else:
+ error(self.pos,
+ "The 'c_api_binop_methods' directive is only supported for forward compatibility"
+ " and must be True.")
+
+ code.putln()
+ preprocessor_guard = slot.preprocessor_guard_code()
+ if preprocessor_guard:
+ code.putln(preprocessor_guard)
+
+ if slot.left_slot.signature == TypeSlots.binaryfunc:
+ slot_type = 'binaryfunc'
+ extra_arg = extra_arg_decl = ''
+ elif slot.left_slot.signature == TypeSlots.ternaryfunc:
+ slot_type = 'ternaryfunc'
+ extra_arg = ', extra_arg'
+ extra_arg_decl = ', PyObject* extra_arg'
+ else:
+ error(entry.pos, "Unexpected type lost signature: %s" % slot)
+
+ def has_slot_method(method_name):
+ entry = scope.lookup(method_name)
+ return bool(entry and entry.is_special and entry.func_cname)
+ def call_slot_method(method_name, reverse):
+ entry = scope.lookup(method_name)
+ if entry and entry.is_special and entry.func_cname:
+ return "%s(%s%s)" % (
+ entry.func_cname,
+ "right, left" if reverse else "left, right",
+ extra_arg)
+ else:
+ return '%s_maybe_call_slot(%s, left, right %s)' % (
+ func_name,
+ 'Py_TYPE(right)->tp_base' if reverse else 'Py_TYPE(left)->tp_base',
+ extra_arg)
+
+ code.putln(
+ TempitaUtilityCode.load_as_string(
+ "BinopSlot", "ExtensionTypes.c",
+ context={
+ "func_name": func_name,
+ "slot_name": slot.slot_name,
+ "overloads_left": int(has_slot_method(slot.left_slot.method_name)),
+ "call_left": call_slot_method(slot.left_slot.method_name, reverse=False),
+ "call_right": call_slot_method(slot.right_slot.method_name, reverse=True),
+ "type_cname": scope.parent_type.typeptr_cname,
+ "slot_type": slot_type,
+ "extra_arg": extra_arg,
+ "extra_arg_decl": extra_arg_decl,
+ })[1])
+ if preprocessor_guard:
+ code.putln("#endif")
+
def generate_getattro_function(self, scope, code):
# First try to get the attribute using __getattribute__, if defined, or
# PyObject_GenericGetAttr.
if scope is None:
# Maybe it's a cimport.
scope = env.find_imported_module(self.module_path, self.pos)
- if scope:
- scope.fused_to_specific = env.fused_to_specific
else:
scope = env
'auto_pickle': None,
'cdivision': False, # was True before 0.12
'cdivision_warnings': False,
+ 'c_api_binop_methods': True, # Change for 3.0
'overflowcheck': False,
'overflowcheck.fold': True,
'always_allow_keywords': False,
@cython.locals(systr=unicode, is_python3_source=bint, is_raw=bint)
cdef p_string_literal(PyrexScanner s, kind_override=*)
cdef _append_escape_sequence(kind, builder, unicode escape_sequence, PyrexScanner s)
-@cython.locals(i=Py_ssize_t, size=Py_ssize_t, c=Py_UCS4)
+cdef tuple _f_string_error_pos(pos, string, Py_ssize_t i)
+@cython.locals(i=Py_ssize_t, size=Py_ssize_t, c=Py_UCS4, next_start=Py_ssize_t)
cdef list p_f_string(PyrexScanner s, unicode_value, pos, bint is_raw)
@cython.locals(i=Py_ssize_t, size=Py_ssize_t, c=Py_UCS4, quote_char=Py_UCS4, NO_CHAR=Py_UCS4)
cdef tuple p_f_string_expr(PyrexScanner s, unicode_value, pos, Py_ssize_t starting_index, bint is_raw)
pos = s.position()
is_python3_source = s.context.language_level >= 3
has_non_ascii_literal_characters = False
+ string_start_pos = (pos[0], pos[1], pos[2] + len(s.systring))
kind_string = s.systring.rstrip('"\'').lower()
if len(kind_string) > 1:
if len(set(kind_string)) != len(kind_string):
s.error("bytes can only contain ASCII literal characters.", pos=pos)
bytes_value = None
if kind == 'f':
- unicode_value = p_f_string(s, unicode_value, pos, is_raw='r' in kind_string)
+ unicode_value = p_f_string(s, unicode_value, string_start_pos, is_raw='r' in kind_string)
s.next()
return (kind, bytes_value, unicode_value)
for is_raw in (True, False)]
+def _f_string_error_pos(pos, string, i):
+ return (pos[0], pos[1], pos[2] + i + 1) # FIXME: handle newlines in string
+
+
def p_f_string(s, unicode_value, pos, is_raw):
# Parses a PEP 498 f-string literal into a list of nodes. Nodes are either UnicodeNodes
# or FormattedValueNodes.
next_start = 0
size = len(unicode_value)
builder = StringEncoding.UnicodeLiteralBuilder()
- error_pos = list(pos) # [src, line, column]
_parse_seq = _parse_escape_sequences_raw if is_raw else _parse_escape_sequences
while next_start < size:
end = next_start
- error_pos[2] = pos[2] + end # FIXME: handle newlines in string
match = _parse_seq(unicode_value, next_start)
if match is None:
- error(tuple(error_pos), "Invalid escape sequence")
+ error(_f_string_error_pos(pos, unicode_value, next_start), "Invalid escape sequence")
next_start = match.end()
part = match.group()
if part == '}}':
builder.append('}')
else:
- s.error("f-string: single '}' is not allowed", pos=tuple(error_pos))
+ error(_f_string_error_pos(pos, unicode_value, end),
+ "f-string: single '}' is not allowed")
else:
builder.append(part)
nested_depth = 0
quote_char = NO_CHAR
in_triple_quotes = False
+ backslash_reported = False
while True:
if i >= size:
- s.error("missing '}' in format string expression")
+ break # error will be reported below
c = unicode_value[i]
if quote_char != NO_CHAR:
if c == '\\':
- error_pos = (pos[0], pos[1] + i, pos[2]) # FIXME: handle newlines in string
- error(error_pos, "backslashes not allowed in f-strings")
+ # avoid redundant error reports along '\' sequences
+ if not backslash_reported:
+ error(_f_string_error_pos(pos, unicode_value, i),
+ "backslashes not allowed in f-strings")
+ backslash_reported = True
elif c == quote_char:
if in_triple_quotes:
if i + 2 < size and unicode_value[i + 1] == c and unicode_value[i + 2] == c:
elif nested_depth != 0 and c in '}])':
nested_depth -= 1
elif c == '#':
- s.error("format string cannot include #")
+ error(_f_string_error_pos(pos, unicode_value, i),
+ "format string cannot include #")
elif nested_depth == 0 and c in '!:}':
# allow != as a special case
if c == '!' and i + 1 < size and unicode_value[i + 1] == '=':
expr_pos = (pos[0], pos[1], pos[2] + starting_index + 2) # TODO: find exact code position (concat, multi-line, ...)
if not expr_str.strip():
- error(expr_pos, "empty expression not allowed in f-string")
+ error(_f_string_error_pos(pos, unicode_value, starting_index),
+ "empty expression not allowed in f-string")
if terminal_char == '!':
i += 1
if i + 2 > size:
- error(expr_pos, "invalid conversion char at end of string")
+ pass # error will be reported below
else:
conversion_char = unicode_value[i]
i += 1
start_format_spec = i + 1
while True:
if i >= size:
- s.error("missing '}' in format specifier", pos=expr_pos)
+ break # error will be reported below
c = unicode_value[i]
if not in_triple_quotes and not in_string:
if c == '{':
format_spec_str = unicode_value[start_format_spec:i]
if terminal_char != '}':
- s.error("missing '}' in format string expression', found '%s'" % terminal_char)
+ error(_f_string_error_pos(pos, unicode_value, i),
+ "missing '}' in format string expression" + (
+ ", found '%s'" % terminal_char if terminal_char else ""))
# parse the expression as if it was surrounded by parentheses
buf = StringIO('(%s)' % expr_str)
# validate the conversion char
if conversion_char is not None and not ExprNodes.FormattedValueNode.find_conversion_func(conversion_char):
- error(pos, "invalid conversion character '%s'" % conversion_char)
+ error(expr_pos, "invalid conversion character '%s'" % conversion_char)
# the format spec is itself treated like an f-string
if format_spec_str:
# ifdef Full #ifdef string that slot is wrapped in. Using this causes py3, py2 and flags to be ignored.)
def __init__(self, slot_name, dynamic=False, inherited=False,
- py3=True, py2=True, ifdef=None):
+ py3=True, py2=True, ifdef=None, is_binop=False):
self.slot_name = slot_name
self.is_initialised_dynamically = dynamic
self.is_inherited = inherited
self.ifdef = ifdef
self.py3 = py3
self.py2 = py2
+ self.is_binop = is_binop
def preprocessor_guard_code(self):
ifdef = self.ifdef
return self.default_value
+class BinopSlot(SyntheticSlot):
+ def __init__(self, signature, slot_name, left_method, **kargs):
+ assert left_method.startswith('__')
+ right_method = '__r' + left_method[2:]
+ SyntheticSlot.__init__(
+ self, slot_name, [left_method, right_method], "0", is_binop=True, **kargs)
+ # MethodSlot causes special method registration.
+ self.left_slot = MethodSlot(signature, "", left_method)
+ self.right_slot = MethodSlot(signature, "", right_method)
+
+
class RichcmpSlot(MethodSlot):
def slot_code(self, scope):
entry = scope.lookup_here(self.method_name)
PyNumberMethods_Py3_GUARD = "PY_MAJOR_VERSION < 3 || (CYTHON_COMPILING_IN_PYPY && PY_VERSION_HEX < 0x03050000)"
PyNumberMethods = (
- MethodSlot(binaryfunc, "nb_add", "__add__"),
- MethodSlot(binaryfunc, "nb_subtract", "__sub__"),
- MethodSlot(binaryfunc, "nb_multiply", "__mul__"),
- MethodSlot(binaryfunc, "nb_divide", "__div__", ifdef = PyNumberMethods_Py3_GUARD),
- MethodSlot(binaryfunc, "nb_remainder", "__mod__"),
- MethodSlot(binaryfunc, "nb_divmod", "__divmod__"),
- MethodSlot(ternaryfunc, "nb_power", "__pow__"),
+ BinopSlot(binaryfunc, "nb_add", "__add__"),
+ BinopSlot(binaryfunc, "nb_subtract", "__sub__"),
+ BinopSlot(binaryfunc, "nb_multiply", "__mul__"),
+ BinopSlot(binaryfunc, "nb_divide", "__div__", ifdef = PyNumberMethods_Py3_GUARD),
+ BinopSlot(binaryfunc, "nb_remainder", "__mod__"),
+ BinopSlot(binaryfunc, "nb_divmod", "__divmod__"),
+ BinopSlot(ternaryfunc, "nb_power", "__pow__"),
MethodSlot(unaryfunc, "nb_negative", "__neg__"),
MethodSlot(unaryfunc, "nb_positive", "__pos__"),
MethodSlot(unaryfunc, "nb_absolute", "__abs__"),
MethodSlot(inquiry, "nb_nonzero", "__nonzero__", py3 = ("nb_bool", "__bool__")),
MethodSlot(unaryfunc, "nb_invert", "__invert__"),
- MethodSlot(binaryfunc, "nb_lshift", "__lshift__"),
- MethodSlot(binaryfunc, "nb_rshift", "__rshift__"),
- MethodSlot(binaryfunc, "nb_and", "__and__"),
- MethodSlot(binaryfunc, "nb_xor", "__xor__"),
- MethodSlot(binaryfunc, "nb_or", "__or__"),
+ BinopSlot(binaryfunc, "nb_lshift", "__lshift__"),
+ BinopSlot(binaryfunc, "nb_rshift", "__rshift__"),
+ BinopSlot(binaryfunc, "nb_and", "__and__"),
+ BinopSlot(binaryfunc, "nb_xor", "__xor__"),
+ BinopSlot(binaryfunc, "nb_or", "__or__"),
EmptySlot("nb_coerce", ifdef = PyNumberMethods_Py3_GUARD),
MethodSlot(unaryfunc, "nb_int", "__int__", fallback="__long__"),
MethodSlot(unaryfunc, "nb_long", "__long__", fallback="__int__", py3 = "<RESERVED>"),
# Added in release 2.2
# The following require the Py_TPFLAGS_HAVE_CLASS flag
- MethodSlot(binaryfunc, "nb_floor_divide", "__floordiv__"),
- MethodSlot(binaryfunc, "nb_true_divide", "__truediv__"),
+ BinopSlot(binaryfunc, "nb_floor_divide", "__floordiv__"),
+ BinopSlot(binaryfunc, "nb_true_divide", "__truediv__"),
MethodSlot(ibinaryfunc, "nb_inplace_floor_divide", "__ifloordiv__"),
MethodSlot(ibinaryfunc, "nb_inplace_true_divide", "__itruediv__"),
MethodSlot(unaryfunc, "nb_index", "__index__"),
# Added in release 3.5
- MethodSlot(binaryfunc, "nb_matrix_multiply", "__matmul__", ifdef="PY_VERSION_HEX >= 0x03050000"),
+ BinopSlot(binaryfunc, "nb_matrix_multiply", "__matmul__", ifdef="PY_VERSION_HEX >= 0x03050000"),
MethodSlot(ibinaryfunc, "nb_inplace_matrix_multiply", "__imatmul__", ifdef="PY_VERSION_HEX >= 0x03050000"),
)
# cython.* namespace for pure mode.
from __future__ import absolute_import
-__version__ = "0.29.19"
+__version__ = "0.29.20"
try:
from __builtin__ import basestring
{
PyObject *copy = _PyLong_Copy((PyLongObject*)n);
if (likely(copy)) {
- Py_SIZE(copy) = -(Py_SIZE(copy));
+ // negate the size to swap the sign
+ __Pyx_SET_SIZE(copy, -Py_SIZE(copy));
}
return copy;
}
// special C-API function only in Pyston and PyPy >= 5.9
if (PyMethodDescr_Check(method))
#else
- // It appears that PyMethodDescr_Type is not exposed anywhere in the CPython C-API
+ #if PY_MAJOR_VERSION == 2
+ // PyMethodDescr_Type is not exposed in the CPython C-API in Py2.
static PyTypeObject *methoddescr_type = NULL;
if (methoddescr_type == NULL) {
PyObject *meth = PyObject_GetAttrString((PyObject*)&PyList_Type, "append");
methoddescr_type = Py_TYPE(meth);
Py_DECREF(meth);
}
+ #else
+ PyTypeObject *methoddescr_type = &PyMethodDescr_Type;
+ #endif
if (__Pyx_TypeCheck(method, methoddescr_type))
#endif
{
// python classes
return PyClassMethod_New(PyMethod_GET_FUNCTION(method));
}
- else if (PyCFunction_Check(method)) {
- return PyClassMethod_New(method);
- }
-#ifdef __Pyx_CyFunction_USED
- else if (__Pyx_CyFunction_Check(method)) {
+ else {
return PyClassMethod_New(method);
}
-#endif
- PyErr_SetString(PyExc_TypeError,
- "Class-level classmethod() can only be called on "
- "a method_descriptor or instance method.");
- return NULL;
}
Py_XDECREF(setstate_cython);
return ret;
}
+
+/////////////// BinopSlot ///////////////
+
+static CYTHON_INLINE PyObject *{{func_name}}_maybe_call_slot(PyTypeObject* type, PyObject *left, PyObject *right {{extra_arg_decl}}) {
+ {{slot_type}} slot;
+#if CYTHON_USE_TYPE_SLOTS
+ slot = type->tp_as_number ? type->tp_as_number->{{slot_name}} : NULL;
+#else
+ slot = ({{slot_type}}) PyType_GetSlot(type, Py_{{slot_name}});
+#endif
+ return slot ? slot(left, right {{extra_arg}}) : __Pyx_NewRef(Py_NotImplemented);
+}
+
+static PyObject *{{func_name}}(PyObject *left, PyObject *right {{extra_arg_decl}}) {
+ PyObject *res;
+ int maybe_self_is_left, maybe_self_is_right = 0;
+ maybe_self_is_left = Py_TYPE(left) == Py_TYPE(right)
+#if CYTHON_USE_TYPE_SLOTS
+ || (Py_TYPE(left)->tp_as_number && Py_TYPE(left)->tp_as_number->{{slot_name}} == &{{func_name}})
+#endif
+ || __Pyx_TypeCheck(left, {{type_cname}});
+ // Optimize for the common case where the left operation is defined (and successful).
+ if (!{{overloads_left}}) {
+ maybe_self_is_right = Py_TYPE(left) == Py_TYPE(right)
+#if CYTHON_USE_TYPE_SLOTS
+ || (Py_TYPE(right)->tp_as_number && Py_TYPE(right)->tp_as_number->{{slot_name}} == &{{func_name}})
+#endif
+ || __Pyx_TypeCheck(right, {{type_cname}});
+ }
+ if (maybe_self_is_left) {
+ if (maybe_self_is_right && !{{overloads_left}}) {
+ res = {{call_right}};
+ if (res != Py_NotImplemented) return res;
+ Py_DECREF(res);
+ // Don't bother calling it again.
+ maybe_self_is_right = 0;
+ }
+ res = {{call_left}};
+ if (res != Py_NotImplemented) return res;
+ Py_DECREF(res);
+ }
+ if ({{overloads_left}}) {
+ maybe_self_is_right = Py_TYPE(left) == Py_TYPE(right)
+#if CYTHON_USE_TYPE_SLOTS
+ || (Py_TYPE(right)->tp_as_number && Py_TYPE(right)->tp_as_number->{{slot_name}} == &{{func_name}})
+#endif
+ || PyType_IsSubtype(Py_TYPE(right), {{type_cname}});
+ }
+ if (maybe_self_is_right) {
+ return {{call_right}};
+ }
+ return __Pyx_NewRef(Py_NotImplemented);
+}
#define PySet_CheckExact(obj) (Py_TYPE(obj) == &PySet_Type)
#endif
+
+#if PY_VERSION_HEX >= 0x030900A4
+ #define __Pyx_SET_REFCNT(obj, refcnt) Py_SET_REFCNT(obj, refcnt)
+ #define __Pyx_SET_SIZE(obj, size) Py_SET_SIZE(obj, size)
+#else
+ #define __Pyx_SET_REFCNT(obj, refcnt) Py_REFCNT(obj) = (refcnt)
+ #define __Pyx_SET_SIZE(obj, size) Py_SIZE(obj) = (size)
+#endif
+
#if CYTHON_ASSUME_SAFE_MACROS
#define __Pyx_PySequence_SIZE(seq) Py_SIZE(seq)
#else
if (likely(L->allocated > len) & likely(len > (L->allocated >> 1))) {
Py_INCREF(x);
PyList_SET_ITEM(list, len, x);
- Py_SIZE(list) = len+1;
+ __Pyx_SET_SIZE(list, len + 1);
return 0;
}
return PyList_Append(list, x);
if (likely(L->allocated > len)) {
Py_INCREF(x);
PyList_SET_ITEM(list, len, x);
- Py_SIZE(list) = len+1;
+ __Pyx_SET_SIZE(list, len + 1);
return 0;
}
return PyList_Append(list, x);
static CYTHON_INLINE PyObject* __Pyx_PyList_Pop(PyObject* L) {
/* Check that both the size is positive and no reallocation shrinking needs to be done. */
if (likely(PyList_GET_SIZE(L) > (((PyListObject*)L)->allocated >> 1))) {
- Py_SIZE(L) -= 1;
+ __Pyx_SET_SIZE(L, Py_SIZE(L) - 1);
return PyList_GET_ITEM(L, PyList_GET_SIZE(L));
}
return CALL_UNBOUND_METHOD(PyList_Type, "pop", L);
}
if (likely(__Pyx_is_valid_index(cix, size))) {
PyObject* v = PyList_GET_ITEM(L, cix);
- Py_SIZE(L) -= 1;
+ __Pyx_SET_SIZE(L, Py_SIZE(L) - 1);
size -= 1;
memmove(&PyList_GET_ITEM(L, cix), &PyList_GET_ITEM(L, cix+1), (size_t)(size-cix)*sizeof(PyObject*));
return v;
self.cleanup_failures = options.cleanup_failures
self.with_pyregr = with_pyregr
self.cython_only = options.cython_only
+ self.doctest_selector = re.compile(options.only_pattern).search if options.only_pattern else None
self.languages = languages
self.test_bugs = test_bugs
self.fork = options.fork
else:
languages = self.languages
- if skip_c(tags) and 'c' in languages:
+ if 'c' in languages and skip_c(tags):
languages = list(languages)
languages.remove('c')
- elif 'no-cpp' in tags['tag'] and 'cpp' in self.languages:
+ if 'cpp' in languages and 'no-cpp' in tags['tag']:
languages = list(languages)
languages.remove('cpp')
+ if not languages:
+ return []
pythran_dir = self.pythran_dir
if 'pythran' in tags['tag'] and not pythran_dir and 'cpp' in languages:
cleanup_sharedlibs=self.cleanup_sharedlibs,
cleanup_failures=self.cleanup_failures,
cython_only=self.cython_only,
+ doctest_selector=self.doctest_selector,
fork=self.fork,
language_level=self.language_level,
warning_errors=warning_errors,
class CythonCompileTestCase(unittest.TestCase):
def __init__(self, test_directory, workdir, module, tags, language='c', preparse='id',
expect_errors=False, expect_warnings=False, annotate=False, cleanup_workdir=True,
- cleanup_sharedlibs=True, cleanup_failures=True, cython_only=False,
+ cleanup_sharedlibs=True, cleanup_failures=True, cython_only=False, doctest_selector=None,
fork=True, language_level=2, warning_errors=False,
test_determinism=False,
common_utility_dir=None, pythran_dir=None, stats=None):
self.cleanup_sharedlibs = cleanup_sharedlibs
self.cleanup_failures = cleanup_failures
self.cython_only = cython_only
+ self.doctest_selector = doctest_selector
self.fork = fork
self.language_level = language_level
self.warning_errors = warning_errors
else:
module = module_or_name
tests = doctest.DocTestSuite(module)
+ if self.doctest_selector is not None:
+ tests._tests[:] = [test for test in tests._tests if self.doctest_selector(test.id())]
with self.stats.time(self.name, self.language, 'run'):
tests.run(result)
run_forked_test(result, run_test, self.shortDescription(), self.fork)
parser.add_option("-T", "--ticket", dest="tickets",
action="append",
help="a bug ticket number to run the respective test in 'tests/*'")
+ parser.add_option("-k", dest="only_pattern",
+ help="a regex pattern for selecting doctests and test functions in the test modules")
parser.add_option("-3", dest="language_level",
action="store_const", const=3, default=2,
help="set language level to Python 3 (useful for running the CPython regression tests)'")
--- /dev/null
+# mode: error
+# tag: no-cpp, werror
+
+from cython.operator import typeid
+
+def use_typeid():
+ cdef int i = 0
+ print typeid(i) == typeid(i)
+
+cdef cppclass A:
+ pass
+
+def use_new():
+ cdef A* x = new A()
+
+def use_del():
+ cdef A a = A()
+ cdef A *p = &a
+ del p
+
+_ERRORS = """
+8:10: typeid operator only allowed in c++
+8:23: typeid operator only allowed in c++
+14:20: Operation only allowed in c++
+19:4: Operation only allowed in c++
+"""
--- /dev/null
+# mode: error
+# tag: fstring
+
+def incorrect_fstrings(x):
+ return [
+ f"{x}{'\\'}'{x+1}",
+ f"""{}""",
+ f"{}",
+ f"{x!}",
+ f"{",
+ f"{{}}}",
+ ]
+
+
+_ERRORS = """
+6:16: backslashes not allowed in f-strings
+7:14: empty expression not allowed in f-string
+8:12: empty expression not allowed in f-string
+9:14: missing '}' in format string expression, found '!'
+10:12: empty expression not allowed in f-string
+10:12: missing '}' in format string expression
+11:15: f-string: single '}' is not allowed
+"""
args = []
kwargs = {}
+def uninitialized_in_sizeof():
+ cdef int i
+ print sizeof(i)
+
_ERRORS = """
6:10: local variable 'a' referenced before assignment
12:11: local variable 'a' might be referenced before assignment
--- /dev/null
+# cython: warn.maybe_uninitialized=True
+# mode: error
+# tag: cpp, werror
+
+from cython.operator import typeid
+
+def uninitialized_in_typeid():
+ cdef int i
+ print typeid(i) == typeid(i)
+
+_ERRORS = """
+"""
# mode: run
# ticket: 698
+# distutils: extra_compile_args=-fwrapv
cdef extern from *:
int INT_MAX
class1
>>> class1().bview()
class1
+>>> class1().cview()
+class1
+>>> class1().cview("XX")
+class1XX
>>> class2.view()
class2
def f_plus(cls, a):
return cls.a + a
+def second_decorator(f):
+ # note - a class, not a function (didn't pass Cython's test in __Pyx_Method_ClassMethod)
+ class C:
+ def __call__(self, *args):
+ return f(*args)
+ return C()
class class1:
a = 5
def bview(cls):
print cls.__name__
+ @classmethod
+ @second_decorator
+ def cview(cls, s=""):
+ print cls.__name__+s
+
class class2(object):
a = 6
yield 6
+def nested_except_gh3666(a=False, b=False):
+ """
+ >>> print(nested_except_gh3666())
+ A
+ >>> print(nested_except_gh3666(a=True))
+ B-V
+ >>> print(nested_except_gh3666(a=True, b=True))
+ B-V-T
+ """
+ try:
+ if a:
+ raise ValueError
+ return "A"
+ except TypeError as exc:
+ return "A-T"
+ except ValueError as exc:
+ try:
+ if b:
+ raise TypeError
+ return "B-V"
+ except ValueError as exc:
+ return "B-V-V"
+ except TypeError as exc:
+ return "B-V-T"
+
+
### Py3 feature tests
def print_function(*args):
return x
else:
return 2 * x
+
+
+### see GH3642 - presence of cdef inside "unrelated" caused a type to be incorrectly inferred
+cdef unrelated(cython.floating x):
+ cdef cython.floating t = 1
+ return t
+
+cdef handle_float(float* x): return 'float'
+
+cdef handle_double(double* x): return 'double'
+
+def convert_to_ptr(cython.floating x):
+ """
+ >>> convert_to_ptr(1.0)
+ 'double'
+ >>> convert_to_ptr['double'](1.0)
+ 'double'
+ >>> convert_to_ptr['float'](1.0)
+ 'float'
+ """
+ if cython.floating is float:
+ return handle_float(&x)
+ elif cython.floating is double:
+ return handle_double(&x)
cdef extern from "Python.h":
"""
- #define Py_SET_SIZE(obj, size) Py_SIZE((obj)) = (size)
+ #define my_SET_SIZE(obj, size) __Pyx_SET_SIZE(obj, size)
"""
- void Py_SET_SIZE(object, Py_ssize_t)
+ void my_SET_SIZE(object, Py_ssize_t)
def test_square(x):
def test_set_size(x, size):
# This function manipulates Python objects in a bad way, so we
# do not call it. The real test is that it compiles.
- Py_SET_SIZE(x, size)
+ my_SET_SIZE(x, size)