diff options
author | Stefan Behnel <stefan_ml@behnel.de> | 2017-08-25 09:40:50 +0200 |
---|---|---|
committer | Stefan Behnel <stefan_ml@behnel.de> | 2017-08-25 09:40:50 +0200 |
commit | d343cc133089f36cb3a43934021a8999d0a3d3f2 (patch) | |
tree | 6f32a7431b1dd908070b0e25770fd1ed1b5a0991 | |
parent | 570f187bfbd24842df32b719d77567968d131679 (diff) | |
parent | 2cad794f5bfcdda802840c02a21e722fa3283e5f (diff) | |
download | cython-d343cc133089f36cb3a43934021a8999d0a3d3f2.tar.gz |
Merge branch 'master' of git+ssh://github.com/cython/cython
-rw-r--r-- | CHANGES.rst | 6 | ||||
-rw-r--r-- | Cython/Compiler/Builtin.py | 21 | ||||
-rw-r--r-- | Cython/Compiler/Code.py | 9 | ||||
-rw-r--r-- | Cython/Compiler/ExprNodes.py | 10 | ||||
-rw-r--r-- | Cython/Compiler/ModuleNode.py | 53 | ||||
-rw-r--r-- | Cython/Compiler/Symtab.py | 22 | ||||
-rw-r--r-- | Cython/Compiler/TypeInference.py | 8 | ||||
-rw-r--r-- | Cython/Utility/Builtins.c | 4 | ||||
-rw-r--r-- | Cython/Utility/ModuleSetupCode.c | 3 | ||||
-rw-r--r-- | tests/run/builtin_abs.pyx | 52 | ||||
-rw-r--r-- | tests/run/cpp_classes_def.pyx | 37 | ||||
-rw-r--r-- | tests/run/type_inference.pyx | 5 |
12 files changed, 181 insertions, 49 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index 089882f17..448c04a11 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -28,6 +28,9 @@ Features added * ``len(memoryview)`` can be used in nogil sections to get the size of the first dimension of a memory view (``shape[0]``). (Github issue #1733) +* C++ classes can now contain (properly refcounted) Python objects. + + Bugs fixed ---------- @@ -39,6 +42,9 @@ Bugs fixed * Compile time ``DEF`` assignments were evaluated even when they occur inside of falsy ``IF`` blocks. (Github issue #1796) +* abs(signed int) now returns a signed rather than unsigned int. + (Github issue #1837) + 0.26.1 (2017-??-??) diff --git a/Cython/Compiler/Builtin.py b/Cython/Compiler/Builtin.py index 05df48819..d175bd312 100644 --- a/Cython/Compiler/Builtin.py +++ b/Cython/Compiler/Builtin.py @@ -95,16 +95,25 @@ builtin_function_table = [ is_strict_signature = True), BuiltinFunction('abs', "f", "f", "fabsf", is_strict_signature = True), + BuiltinFunction('abs', "i", "i", "abs", + is_strict_signature = True), + BuiltinFunction('abs', "l", "l", "labs", + is_strict_signature = True), + BuiltinFunction('abs', None, None, "__Pyx_abs_longlong", + utility_code = UtilityCode.load("abs_longlong", "Builtins.c"), + func_type = PyrexTypes.CFuncType( + PyrexTypes.c_longlong_type, [ + PyrexTypes.CFuncTypeArg("arg", PyrexTypes.c_longlong_type, None) + ], + is_strict_signature = True, nogil=True)), ] + list( # uses getattr to get PyrexTypes.c_uint_type etc to allow easy iteration over a list - BuiltinFunction('abs', None, None, "__Pyx_abs_{0}".format(t), - utility_code = UtilityCode.load("abs_{0}".format(t), "Builtins.c"), + BuiltinFunction('abs', None, None, "/*abs_{0}*/".format(t.specialization_name()), func_type = PyrexTypes.CFuncType( - getattr(PyrexTypes,"c_u{0}_type".format(t)), [ - PyrexTypes.CFuncTypeArg("arg", getattr(PyrexTypes,"c_{0}_type".format(t)), None) - ], + t, + [PyrexTypes.CFuncTypeArg("arg", t, None)], is_strict_signature = True, nogil=True)) - for t in ("int", "long", "longlong") + for t in (PyrexTypes.c_uint_type, PyrexTypes.c_ulong_type, PyrexTypes.c_ulonglong_type) ) + list( BuiltinFunction('abs', None, None, "__Pyx_c_abs{0}".format(t.funcsuffix), func_type = PyrexTypes.CFuncType( diff --git a/Cython/Compiler/Code.py b/Cython/Compiler/Code.py index 1b4c63cf2..44a65adf9 100644 --- a/Cython/Compiler/Code.py +++ b/Cython/Compiler/Code.py @@ -274,7 +274,7 @@ class UtilityCodeBase(object): elif not values: values = None elif len(values) == 1: - values = values[0] + values = list(values)[0] kwargs[name] = values if proto is not None: @@ -1967,9 +1967,12 @@ class CCodeWriter(object): if entry.type.is_pyobject: self.putln("__Pyx_XDECREF(%s);" % self.entry_as_pyobject(entry)) - def put_var_xdecref(self, entry): + def put_var_xdecref(self, entry, nanny=True): if entry.type.is_pyobject: - self.putln("__Pyx_XDECREF(%s);" % self.entry_as_pyobject(entry)) + if nanny: + self.putln("__Pyx_XDECREF(%s);" % self.entry_as_pyobject(entry)) + else: + self.putln("Py_XDECREF(%s);" % self.entry_as_pyobject(entry)) def put_var_decref_clear(self, entry): self._put_var_decref_clear(entry, null_check=False) diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index 6334d42ce..95dff68f5 100644 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -5201,6 +5201,7 @@ class SimpleCallNode(CallNode): has_optional_args = False nogil = False analysed = False + overflowcheck = False def compile_time_value(self, denv): function = self.function.compile_time_value(denv) @@ -5510,6 +5511,8 @@ class SimpleCallNode(CallNode): if func_type.exception_value is None: env.use_utility_code(UtilityCode.load_cached("CppExceptionConversion", "CppSupport.cpp")) + self.overflowcheck = env.directives['overflowcheck'] + def calculate_result_code(self): return self.c_call_code() @@ -5620,7 +5623,12 @@ class SimpleCallNode(CallNode): translate_cpp_exception(code, self.pos, '%s%s;' % (lhs, rhs), func_type.exception_value, self.nogil) else: - if exc_checks: + 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 (%s < 0) { PyErr_SetString(PyExc_OverflowError, "value too large"); %s; }' % (self.result(), code.error_goto(self.pos)) + elif exc_checks: goto_error = code.error_goto_if(" && ".join(exc_checks), self.pos) else: goto_error = "" diff --git a/Cython/Compiler/ModuleNode.py b/Cython/Compiler/ModuleNode.py index 13047e3b9..e00b3247c 100644 --- a/Cython/Compiler/ModuleNode.py +++ b/Cython/Compiler/ModuleNode.py @@ -897,19 +897,60 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode): [base_class.empty_declaration_code() for base_class in type.base_classes]) code.put(" : public %s" % base_class_decl) code.putln(" {") + py_attrs = [e for e in scope.entries.values() + if e.type.is_pyobject and not e.is_inherited] has_virtual_methods = False - has_destructor = False + constructor = None + destructor = None for attr in scope.var_entries: if attr.type.is_cfunction and attr.type.is_static_method: code.put("static ") - elif attr.type.is_cfunction and attr.name != "<init>": + elif attr.name == "<init>": + constructor = attr + elif attr.name == "<del>": + destructor = attr + elif attr.type.is_cfunction: code.put("virtual ") has_virtual_methods = True - if attr.cname[0] == '~': - has_destructor = True code.putln("%s;" % attr.type.declaration_code(attr.cname)) - if has_virtual_methods and not has_destructor: - code.putln("virtual ~%s() { }" % type.cname) + if constructor or py_attrs: + if constructor: + arg_decls = [] + arg_names = [] + for arg in constructor.type.args[:len(constructor.type.args)-constructor.type.optional_arg_count]: + arg_decls.append(arg.declaration_code()) + arg_names.append(arg.cname) + if constructor.type.optional_arg_count: + arg_decls.append(constructor.type.op_arg_struct.declaration_code(Naming.optional_args_cname)) + arg_names.append(Naming.optional_args_cname) + if not arg_decls: + arg_decls = ["void"] + else: + arg_decls = ["void"] + arg_names = [] + code.putln("%s(%s) {" % (type.cname, ", ".join(arg_decls))) + if py_attrs: + code.put_ensure_gil() + for attr in py_attrs: + code.put_init_var_to_py_none(attr, nanny=False); + if constructor: + code.putln("%s(%s);" % (constructor.cname, ", ".join(arg_names))) + if py_attrs: + code.put_release_ensured_gil() + code.putln("}") + if destructor or py_attrs or has_virtual_methods: + if has_virtual_methods: + code.put("virtual ") + code.putln("~%s() {" % type.cname) + if py_attrs: + code.put_ensure_gil() + if destructor: + code.putln("%s();" % destructor.cname) + if py_attrs: + for attr in py_attrs: + code.put_var_xdecref(attr, nanny=False); + code.put_release_ensured_gil() + code.putln("}") code.putln("};") def generate_enum_definition(self, entry, code): diff --git a/Cython/Compiler/Symtab.py b/Cython/Compiler/Symtab.py index d92cfb44f..d38cbf42e 100644 --- a/Cython/Compiler/Symtab.py +++ b/Cython/Compiler/Symtab.py @@ -2255,8 +2255,7 @@ class CppClassScope(Scope): def declare_var(self, name, type, pos, cname = None, visibility = 'extern', - api = 0, in_pxd = 0, is_cdef = 0, - allow_pyobject = 0, defining = 0): + api = 0, in_pxd = 0, is_cdef = 0, defining = 0): # Add an entry for an attribute. if not cname: cname = name @@ -2275,22 +2274,20 @@ class CppClassScope(Scope): entry.func_cname = "%s::%s" % (self.type.empty_declaration_code(), cname) if name != "this" and (defining or name != "<init>"): self.var_entries.append(entry) - if type.is_pyobject and not allow_pyobject: - error(pos, - "C++ class member cannot be a Python object") return entry def declare_cfunction(self, name, type, pos, cname=None, visibility='extern', api=0, in_pxd=0, defining=0, modifiers=(), utility_code=None, overridable=False): - if name in (self.name.split('::')[-1], '__init__') and cname is None: - cname = self.type.cname + class_name = self.name.split('::')[-1] + if name in (class_name, '__init__') and cname is None: + cname = "%s__init__%s" % (Naming.func_prefix, class_name) name = '<init>' - type.return_type = PyrexTypes.InvisibleVoidType() + type.return_type = PyrexTypes.CVoidType() elif name == '__dealloc__' and cname is None: - cname = "~%s" % self.type.cname + cname = "%s__dealloc__%s" % (Naming.func_prefix, class_name) name = '<del>' - type.return_type = PyrexTypes.InvisibleVoidType() + type.return_type = PyrexTypes.CVoidType() prev_entry = self.lookup_here(name) entry = self.declare_var(name, type, pos, defining=defining, @@ -2315,8 +2312,8 @@ class CppClassScope(Scope): # to work with this type. for base_entry in \ base_scope.inherited_var_entries + base_scope.var_entries: - #contructor is not inherited - if base_entry.name == "<init>": + #contructor/destructor is not inherited + if base_entry.name in ("<init>", "<del>"): continue #print base_entry.name, self.entries if base_entry.name in self.entries: @@ -2324,6 +2321,7 @@ class CppClassScope(Scope): entry = self.declare(base_entry.name, base_entry.cname, base_entry.type, None, 'extern') entry.is_variable = 1 + entry.is_inherited = 1 self.inherited_var_entries.append(entry) for base_entry in base_scope.cfunc_entries: entry = self.declare_cfunction(base_entry.name, base_entry.type, diff --git a/Cython/Compiler/TypeInference.py b/Cython/Compiler/TypeInference.py index 1ef15e3dd..5137f2513 100644 --- a/Cython/Compiler/TypeInference.py +++ b/Cython/Compiler/TypeInference.py @@ -306,6 +306,14 @@ class MarkOverflowingArithmetic(CythonTransform): else: return self.visit_dangerous_node(node) + def visit_SimpleCallNode(self, node): + if (isinstance(node.function, ExprNodes.NameNode) + and node.function.name == 'abs'): + # Overflows for minimum value of fixed size ints. + return self.visit_dangerous_node(node) + else: + return self.visit_neutral_node(node) + visit_UnopNode = visit_neutral_node visit_UnaryMinusNode = visit_dangerous_node diff --git a/Cython/Utility/Builtins.c b/Cython/Utility/Builtins.c index b8a1877ac..1bd333c1d 100644 --- a/Cython/Utility/Builtins.c +++ b/Cython/Utility/Builtins.c @@ -245,9 +245,7 @@ static CYTHON_INLINE unsigned long __Pyx_abs_long(long x) { //////////////////// abs_longlong.proto //////////////////// -static CYTHON_INLINE unsigned PY_LONG_LONG __Pyx_abs_longlong(PY_LONG_LONG x) { - if (unlikely(x == -PY_LLONG_MAX-1)) - return ((unsigned PY_LONG_LONG)PY_LLONG_MAX) + 1U; +static CYTHON_INLINE PY_LONG_LONG __Pyx_abs_longlong(PY_LONG_LONG x) { #if defined (__cplusplus) && __cplusplus >= 201103L return (unsigned PY_LONG_LONG) std::abs(x); #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L diff --git a/Cython/Utility/ModuleSetupCode.c b/Cython/Utility/ModuleSetupCode.c index 1ab144372..8e251672a 100644 --- a/Cython/Utility/ModuleSetupCode.c +++ b/Cython/Utility/ModuleSetupCode.c @@ -690,6 +690,7 @@ typedef struct {PyObject **p; const char *s; const Py_ssize_t n; const char* enc const char is_unicode; const char is_str; const char intern; } __Pyx_StringTabEntry; /*proto*/ /////////////// ForceInitThreads.proto /////////////// +//@proto_block: utility_code_proto_before_types #ifndef __PYX_FORCE_INIT_THREADS #define __PYX_FORCE_INIT_THREADS 0 @@ -1081,6 +1082,7 @@ __Pyx_FastGilFuncInit(); #endif /////////////// NoFastGil.proto /////////////// +//@proto_block: utility_code_proto_before_types #define __Pyx_PyGILState_Ensure PyGILState_Ensure #define __Pyx_PyGILState_Release PyGILState_Release @@ -1089,6 +1091,7 @@ __Pyx_FastGilFuncInit(); #define __Pyx_FastGilFuncInit() /////////////// FastGil.proto /////////////// +//@proto_block: utility_code_proto_before_types struct __Pyx_FastGilVtab { PyGILState_STATE (*Fast_PyGILState_Ensure)(void); diff --git a/tests/run/builtin_abs.pyx b/tests/run/builtin_abs.pyx index bc1bd4466..bd12ca15a 100644 --- a/tests/run/builtin_abs.pyx +++ b/tests/run/builtin_abs.pyx @@ -31,25 +31,37 @@ def py_abs(a): return abs(a) @cython.test_assert_path_exists("//ReturnStatNode//NameNode[@entry.name = 'abs']", - "//ReturnStatNode//NameNode[@entry.cname = '__Pyx_abs_int']") + "//ReturnStatNode//NameNode[@entry.cname = 'abs']") +def sub_abs(int a): + """ + >>> sub_abs(5) + (-5, 95) + >>> sub_abs(105) + (-105, -5) + """ + return -abs(a), 100 - abs(a) + +@cython.overflowcheck(True) +@cython.test_assert_path_exists("//ReturnStatNode//NameNode[@entry.name = 'abs']", + "//ReturnStatNode//NameNode[@entry.cname = 'abs']") def int_abs(int a): """ >>> int_abs(-5) == 5 True >>> int_abs(-5.1) == 5 True - >>> int_abs(-max_int-1) > 0 - True - >>> int_abs(-max_int-1) == abs(-max_int-1) or (max_int, int_abs(-max_int-1), abs(-max_int-1)) - True + >>> int_abs(-max_int-1) #doctest: +ELLIPSIS + Traceback (most recent call last): + ... + OverflowError: ... >>> int_abs(max_int) == abs(max_int) or (max_int, int_abs(max_int), abs(max_int)) True """ return abs(a) @cython.test_assert_path_exists("//ReturnStatNode//NameNode[@entry.name = 'abs']") -@cython.test_fail_if_path_exists("//ReturnStatNode//NameNode[@entry.cname = '__Pyx_abs_int']", - "//ReturnStatNode//NameNode[@entry.cname = '__Pyx_abs_long']") +@cython.test_fail_if_path_exists("//ReturnStatNode//NameNode[@entry.cname = 'abs']", + "//ReturnStatNode//NameNode[@entry.cname = 'labs']") def uint_abs(unsigned int a): """ >>> uint_abs(max_int) == abs(max_int) or (max_int, uint_abs(max_int), abs(max_int)) @@ -57,43 +69,47 @@ def uint_abs(unsigned int a): """ return abs(a) +@cython.overflowcheck(True) @cython.test_assert_path_exists("//ReturnStatNode//NameNode[@entry.name = 'abs']", - "//ReturnStatNode//NameNode[@entry.cname = '__Pyx_abs_long']") + "//ReturnStatNode//NameNode[@entry.cname = 'labs']") def long_abs(long a): """ >>> long_abs(-5) == 5 True >>> long_abs(-5.1) == 5 True - >>> long_abs(-max_long-1) > 0 - True - >>> long_abs(-max_long-1) == abs(-max_long-1) or (max_long, long_abs(-max_long-1), abs(-max_long-1)) - True + >>> long_abs(-max_long-1) #doctest: +ELLIPSIS + Traceback (most recent call last): + ... + OverflowError: ... >>> long_abs(max_long) == abs(max_long) or (max_long, long_abs(max_long), abs(max_long)) True """ return abs(a) @cython.test_assert_path_exists("//ReturnStatNode//NameNode[@entry.name = 'abs']") -@cython.test_fail_if_path_exists("//ReturnStatNode//NameNode[@entry.cname = '__Pyx_abs_int']", - "//ReturnStatNode//NameNode[@entry.cname = '__Pyx_abs_long']") +@cython.test_fail_if_path_exists("//ReturnStatNode//NameNode[@entry.cname = 'abs']", + "//ReturnStatNode//NameNode[@entry.cname = 'labs']") def ulong_abs(unsigned long a): """ >>> ulong_abs(max_long) == abs(max_long) or (max_int, ulong_abs(max_long), abs(max_long)) True + >>> ulong_abs(max_long + 5) == abs(max_long + 5) or (max_long + 5, ulong_abs(max_long + 5), abs(max_long + 5)) + True """ return abs(a) +@cython.overflowcheck(True) @cython.test_assert_path_exists("//ReturnStatNode//NameNode[@entry.name = 'abs']", "//ReturnStatNode//NameNode[@entry.cname = '__Pyx_abs_longlong']") def long_long_abs(long long a): """ >>> long_long_abs(-(2**33)) == 2**33 True - >>> long_long_abs(-max_long_long-1) > 0 - True - >>> long_long_abs(-max_long_long-1) == abs(-max_long_long-1) or (max_long_long, long_long_abs(-max_long_long-1), abs(-max_long_long-1)) - True + >>> long_long_abs(-max_long_long-1) #doctest: +ELLIPSIS + Traceback (most recent call last): + ... + OverflowError: ... >>> long_long_abs(max_long_long) == abs(max_long_long) or (max_long_long, long_long_abs(max_long_long), abs(max_long_long)) True """ diff --git a/tests/run/cpp_classes_def.pyx b/tests/run/cpp_classes_def.pyx index 2441b3378..f8dd6f7ac 100644 --- a/tests/run/cpp_classes_def.pyx +++ b/tests/run/cpp_classes_def.pyx @@ -151,3 +151,40 @@ def test_default_init_no_gil(): with nogil: s = new Simple() del s + + +cdef class NoisyAlloc(object): + cdef public name + def __init__(self, name): + print "NoisyAlloc.__init__", name + self.name = name + def __dealloc__(self): + try: + print "NoisyAlloc.__dealloc__", self.name + except: + pass # Suppress unraisable exception warning. + +cdef cppclass CppClassWithObjectMember: + NoisyAlloc o + __init__(name): + try: + print "CppClassWithObjectMember.__init__", name + this.o = NoisyAlloc(name) + except: + pass # Suppress unraisable exception warning. + __dealloc__(): + try: + print "CppClassWithObjectMember.__dealloc__", this.o.name + except: + pass # Suppress unraisable exception warning. + +def test_CppClassWithObjectMember(name): + """ + >>> test_CppClassWithObjectMember("gertrude") + CppClassWithObjectMember.__init__ gertrude + NoisyAlloc.__init__ gertrude + CppClassWithObjectMember.__dealloc__ gertrude + NoisyAlloc.__dealloc__ gertrude + """ + x = new CppClassWithObjectMember(name) + del x diff --git a/tests/run/type_inference.pyx b/tests/run/type_inference.pyx index cf10eedb2..4e9d7a384 100644 --- a/tests/run/type_inference.pyx +++ b/tests/run/type_inference.pyx @@ -486,6 +486,11 @@ def safe_only(): for j in range(10): res = -j assert typeof(j) == "Python object", typeof(j) + h = 1 + res = abs(h) + assert typeof(h) == "Python object", typeof(h) + cdef int c_int = 1 + assert typeof(abs(c_int)) == "int", typeof(abs(c_int)) @infer_types(None) def safe_c_functions(): |