From dc9041a8460ad9aa6bac56a14e544bbde3574921 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Wed, 3 Nov 2010 00:48:55 -0700 Subject: [PATCH 01/77] Cython.__version__ --- Cython/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cython/__init__.py b/Cython/__init__.py index f9f635d1f92..424bd952d76 100644 --- a/Cython/__init__.py +++ b/Cython/__init__.py @@ -1,2 +1,4 @@ +from Compiler.Version import version as __version__ + # Void cython.* directives (for case insensitive operating systems). from Cython.Shadow import * From 00b0bb863805c84d8ef0b559824acd5744816f2c Mon Sep 17 00:00:00 2001 From: Haoyu Bai Date: Wed, 26 May 2010 01:26:37 +0800 Subject: [PATCH 02/77] implemented T487 'with' with multiple managers --- Cython/Compiler/Parsing.py | 74 +++++++++++++---------- tests/run/withstat.pyx | 118 +++++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+), 31 deletions(-) diff --git a/Cython/Compiler/Parsing.py b/Cython/Compiler/Parsing.py index 72471e8d0ce..124129421a3 100644 --- a/Cython/Compiler/Parsing.py +++ b/Cython/Compiler/Parsing.py @@ -1503,50 +1503,62 @@ def p_include_statement(s, ctx): return Nodes.PassStatNode(pos) def p_with_statement(s): - pos = s.position() s.next() # 'with' -# if s.sy == 'IDENT' and s.systring in ('gil', 'nogil'): + if s.systring == 'template': + node = p_with_template(s) + else: + node = p_with_items(s) + return node + +def p_with_items(s): + pos = s.position() if s.sy == 'IDENT' and s.systring == 'nogil': state = s.systring s.next() - body = p_suite(s) - return Nodes.GILStatNode(pos, state = state, body = body) - elif s.systring == 'template': - templates = [] - s.next() - s.expect('[') - #s.next() - templates.append(s.systring) - s.next() - while s.systring == ',': - s.next() - templates.append(s.systring) - s.next() - s.expect(']') - if s.sy == ':': + if s.sy == ',': s.next() - s.expect_newline("Syntax error in template function declaration") - s.expect_indent() - body_ctx = Ctx() - body_ctx.templates = templates - func_or_var = p_c_func_or_var_declaration(s, pos, body_ctx) - s.expect_dedent() - return func_or_var + body = p_with_items(s) else: - error(pos, "Syntax error in template function declaration") + body = p_suite(s) + return Nodes.GILStatNode(pos, state = state, body = body) else: manager = p_test(s) target = None if s.sy == 'IDENT' and s.systring == 'as': s.next() - allow_multi = (s.sy == '(') - target = p_target(s, ':') - if not allow_multi and isinstance(target, ExprNodes.TupleNode): - s.error("Multiple with statement target values not allowed without paranthesis") - body = p_suite(s) + target = p_starred_expr(s) + if s.sy == ',': + s.next() + body = p_with_items(s) + else: + body = p_suite(s) return Nodes.WithStatNode(pos, manager = manager, target = target, body = body) - + +def p_with_template(s): + pos = s.position() + templates = [] + s.next() + s.expect('[') + templates.append(s.systring) + s.next() + while s.systring == ',': + s.next() + templates.append(s.systring) + s.next() + s.expect(']') + if s.sy == ':': + s.next() + s.expect_newline("Syntax error in template function declaration") + s.expect_indent() + body_ctx = Ctx() + body_ctx.templates = templates + func_or_var = p_c_func_or_var_declaration(s, pos, body_ctx) + s.expect_dedent() + return func_or_var + else: + error(pos, "Syntax error in template function declaration") + def p_simple_statement(s, first_statement = 0): #print "p_simple_statement:", s.sy, s.systring ### if s.sy == 'global': diff --git a/tests/run/withstat.pyx b/tests/run/withstat.pyx index bea6437e376..fad2eaff1a8 100644 --- a/tests/run/withstat.pyx +++ b/tests/run/withstat.pyx @@ -118,3 +118,121 @@ def typed(): with c as i: i += 11 print i + +def multimanager(): + """ + >>> multimanager() + enter + enter + enter + enter + enter + enter + 2 + value + 1 2 3 4 5 + nested + exit + exit + exit + exit + exit + exit + """ + with ContextManager(1), ContextManager(2) as x, ContextManager(u'value') as y,\ + ContextManager(3), ContextManager((1, 2, (3, (4, 5)))) as (a, b, (c, (d, e))): + with ContextManager(u'nested') as nested: + print x + print y + print a, b, c, d, e + print nested + +# Tests borrowed from pyregr test_with.py, +# modified to follow the constraints of Cython. +import unittest + +class Dummy(object): + def __init__(self, value=None, gobble=False): + if value is None: + value = self + self.value = value + self.gobble = gobble + self.enter_called = False + self.exit_called = False + + def __enter__(self): + self.enter_called = True + return self.value + + def __exit__(self, *exc_info): + self.exit_called = True + self.exc_info = exc_info + if self.gobble: + return True + +class InitRaises(object): + def __init__(self): raise RuntimeError() + +class EnterRaises(object): + def __enter__(self): raise RuntimeError() + def __exit__(self, *exc_info): pass + +class ExitRaises(object): + def __enter__(self): pass + def __exit__(self, *exc_info): raise RuntimeError() + +class NestedWith(unittest.TestCase): + """ + >>> NestedWith().runTest() + """ + + def runTest(self): + self.testNoExceptions() + self.testExceptionInExprList() + self.testExceptionInEnter() + self.testExceptionInExit() + self.testEnterReturnsTuple() + + def testNoExceptions(self): + with Dummy() as a, Dummy() as b: + self.assertTrue(a.enter_called) + self.assertTrue(b.enter_called) + self.assertTrue(a.exit_called) + self.assertTrue(b.exit_called) + + def testExceptionInExprList(self): + try: + with Dummy() as a, InitRaises(): + pass + except: + pass + self.assertTrue(a.enter_called) + self.assertTrue(a.exit_called) + + def testExceptionInEnter(self): + try: + with Dummy() as a, EnterRaises(): + self.fail('body of bad with executed') + except RuntimeError: + pass + else: + self.fail('RuntimeError not reraised') + self.assertTrue(a.enter_called) + self.assertTrue(a.exit_called) + + def testExceptionInExit(self): + body_executed = False + with Dummy(gobble=True) as a, ExitRaises(): + body_executed = True + self.assertTrue(a.enter_called) + self.assertTrue(a.exit_called) + self.assertTrue(body_executed) + self.assertNotEqual(a.exc_info[0], None) + + def testEnterReturnsTuple(self): + with Dummy(value=(1,2)) as (a1, a2), \ + Dummy(value=(10, 20)) as (b1, b2): + self.assertEquals(1, a1) + self.assertEquals(2, a2) + self.assertEquals(10, b1) + self.assertEquals(20, b2) From 552b69942342828b964e8765478821a9e289537d Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Tue, 14 Sep 2010 21:44:29 -0700 Subject: [PATCH 03/77] cleanup GetAttrGetItemRedirect test. --- tests/run/special_methods_T561.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/run/special_methods_T561.pyx b/tests/run/special_methods_T561.pyx index 27cce9cfecc..1d37d20833c 100644 --- a/tests/run/special_methods_T561.pyx +++ b/tests/run/special_methods_T561.pyx @@ -544,10 +544,10 @@ cdef class GetAttrGetItemRedirect: def __getattr__(self, name): if name == 'item': - return self.__getitem__(name) + return self[name] return ('attr', self.obj) def __getitem__(self, key): if key == 'attr': - return self.__getattr__(key) + return getattr(self, key) return ('item', self.obj) From 348974c84401eccb35020c5ecec61d4863070004 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Wed, 22 Sep 2010 01:40:45 -0700 Subject: [PATCH 04/77] Fix pow(float, -) for MSVC. --- Cython/Compiler/ExprNodes.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index fd30a8ddaeb..073d7ddc57d 100755 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -5658,7 +5658,7 @@ def analyse_c_operation(self, env): error(self.pos, "complex powers not yet supported") self.pow_func = "" elif self.type.is_float: - self.pow_func = "pow" + self.pow_func = "pow" + self.type.math_h_modifier else: self.pow_func = "__Pyx_pow_%s" % self.type.declaration_code('').replace(' ', '_') env.use_utility_code( @@ -5666,10 +5666,16 @@ def analyse_c_operation(self, env): type=self.type.declaration_code(''))) def calculate_result_code(self): + # Work around MSVC overloading ambiguity. + def typecast(operand): + if self.type == operand.type: + return operand.result() + else: + return self.type.cast_code(operand.result()) return "%s(%s, %s)" % ( self.pow_func, - self.operand1.result(), - self.operand2.result()) + typecast(self.operand1), + typecast(self.operand2)) # Note: This class is temporarily "shut down" into an ineffective temp From fc96c622a2cd33e07e55277a5a77c9b4038ccf37 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Thu, 23 Sep 2010 01:34:58 -0700 Subject: [PATCH 05/77] Complex powers. --- Cython/Compiler/ExprNodes.py | 9 +++- Cython/Compiler/PyrexTypes.py | 84 +++++++++++++++++++++++++----- tests/run/complex_numbers_T305.pyx | 33 ++++++++++++ 3 files changed, 110 insertions(+), 16 deletions(-) diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index 073d7ddc57d..8946ec0c725 100755 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -5655,8 +5655,13 @@ class PowNode(NumBinopNode): def analyse_c_operation(self, env): NumBinopNode.analyse_c_operation(self, env) if self.type.is_complex: - error(self.pos, "complex powers not yet supported") - self.pow_func = "" + if self.type.real_type.is_float: + self.operand1 = self.operand1.coerce_to(self.type, env) + self.operand2 = self.operand2.coerce_to(self.type, env) + self.pow_func = "__Pyx_c_pow" + self.type.real_type.math_h_modifier + else: + error(self.pos, "complex int powers not supported") + self.pow_func = "" elif self.type.is_float: self.pow_func = "pow" + self.type.math_h_modifier else: diff --git a/Cython/Compiler/PyrexTypes.py b/Cython/Compiler/PyrexTypes.py index 993d784af3f..3509bd85b94 100755 --- a/Cython/Compiler/PyrexTypes.py +++ b/Cython/Compiler/PyrexTypes.py @@ -1095,7 +1095,8 @@ def create_declaration_utility_code(self, env): utility_code.specialize( self, real_type = self.real_type.declaration_code(''), - m = self.funcsuffix)) + m = self.funcsuffix, + is_float = self.real_type.is_float)) return True def create_to_py_utility_code(self, env): @@ -1112,7 +1113,8 @@ def create_from_py_utility_code(self, env): utility_code.specialize( self, real_type = self.real_type.declaration_code(''), - m = self.funcsuffix)) + m = self.funcsuffix, + is_float = self.real_type.is_float)) self.from_py_function = "__Pyx_PyComplex_As_" + self.specialization_name() return True @@ -1271,11 +1273,17 @@ def binary_op(self, op): #ifdef __cplusplus #define __Pyx_c_is_zero%(m)s(z) ((z)==(%(real_type)s)0) #define __Pyx_c_conj%(m)s(z) (::std::conj(z)) - /*#define __Pyx_c_abs%(m)s(z) (::std::abs(z))*/ + #if %(is_float)s + #define __Pyx_c_abs%(m)s(z) (::std::abs(z)) + #define __Pyx_c_pow%(m)s(a, b) (::std::pow(a, b)) + #endif #else #define __Pyx_c_is_zero%(m)s(z) ((z)==0) #define __Pyx_c_conj%(m)s(z) (conj%(m)s(z)) - /*#define __Pyx_c_abs%(m)s(z) (cabs%(m)s(z))*/ + #if %(is_float)s + #define __Pyx_c_abs%(m)s(z) (cabs%(m)s(z)) + #define __Pyx_c_pow%(m)s(a, b) (cpow%(m)s(a, b)) + #endif #endif #else static CYTHON_INLINE int __Pyx_c_eq%(m)s(%(type)s, %(type)s); @@ -1286,7 +1294,10 @@ def binary_op(self, op): static CYTHON_INLINE %(type)s __Pyx_c_neg%(m)s(%(type)s); static CYTHON_INLINE int __Pyx_c_is_zero%(m)s(%(type)s); static CYTHON_INLINE %(type)s __Pyx_c_conj%(m)s(%(type)s); - /*static CYTHON_INLINE %(real_type)s __Pyx_c_abs%(m)s(%(type)s);*/ + #if %(is_float)s + static CYTHON_INLINE %(real_type)s __Pyx_c_abs%(m)s(%(type)s); + static CYTHON_INLINE %(type)s __Pyx_c_pow%(m)s(%(type)s, %(type)s); + #endif #endif """, impl=""" @@ -1335,15 +1346,60 @@ def binary_op(self, op): z.imag = -a.imag; return z; } -/* - static CYTHON_INLINE %(real_type)s __Pyx_c_abs%(m)s(%(type)s z) { -#if HAVE_HYPOT - return hypot%(m)s(z.real, z.imag); -#else - return sqrt%(m)s(z.real*z.real + z.imag*z.imag); -#endif - } -*/ + #if %(is_float)s + static CYTHON_INLINE %(real_type)s __Pyx_c_abs%(m)s(%(type)s z) { + #if HAVE_HYPOT + return hypot%(m)s(z.real, z.imag); + #else + return sqrt%(m)s(z.real*z.real + z.imag*z.imag); + #endif + } + static CYTHON_INLINE %(type)s __Pyx_c_pow%(m)s(%(type)s a, %(type)s b) { + %(type)s z; + %(real_type)s r, lnr, theta, z_r, z_theta; + if (b.imag == 0 && b.real == (int)b.real) { + if (b.real < 0) { + %(real_type)s denom = a.real * a.real + a.imag * a.imag; + a.real = a.real / denom; + a.imag = -a.imag / denom; + b.real = -b.real; + } + switch ((int)b.real) { + case 0: + z.real = 1; + z.imag = 0; + return z; + case 1: + return a; + case 2: + z = __Pyx_c_prod%(m)s(a, a); + return __Pyx_c_prod%(m)s(a, a); + case 3: + z = __Pyx_c_prod%(m)s(a, a); + return __Pyx_c_prod%(m)s(z, a); + case 4: + z = __Pyx_c_prod%(m)s(a, a); + return __Pyx_c_prod%(m)s(z, z); + } + } + if (a.imag == 0) { + if (a.real == 0) { + return a; + } + r = a.real; + theta = 0; + } else { + r = __Pyx_c_abs%(m)s(a); + theta = atan2%(m)s(a.imag, a.real); + } + lnr = log%(m)s(r); + z_r = exp%(m)s(lnr * b.real - theta * b.imag); + z_theta = theta * b.real + lnr * b.imag; + z.real = z_r * cos%(m)s(z_theta); + z.imag = z_r * sin%(m)s(z_theta); + return z; + } + #endif #endif """) diff --git a/tests/run/complex_numbers_T305.pyx b/tests/run/complex_numbers_T305.pyx index 0df26f42788..6343ceb88e5 100644 --- a/tests/run/complex_numbers_T305.pyx +++ b/tests/run/complex_numbers_T305.pyx @@ -23,6 +23,39 @@ def test_arithmetic(double complex z, double complex w): """ return +z, -z+0, z+w, z-w, z*w, z/w +def test_pow(double complex z, double complex w, tol=None): + """ + Various implementations produce slightly different results... + + >>> a = complex(3, 1) + >>> test_pow(a, 1) + (3+1j) + >>> test_pow(a, 2, 1e-15) + True + >>> test_pow(a, a, 1e-15) + True + >>> test_pow(complex(0.5, -.25), complex(3, 4), 1e-15) + True + """ + if tol is None: + return z**w + else: + return abs(z**w / z ** w - 1) < tol + +def test_int_pow(double complex z, int n, tol=None): + """ + >>> [test_int_pow(complex(0, 1), k, 1e-15) for k in range(-4, 5)] + [True, True, True, True, True, True, True, True, True] + >>> [test_int_pow(complex(0, 2), k, 1e-15) for k in range(-4, 5)] + [True, True, True, True, True, True, True, True, True] + >>> [test_int_pow(complex(2, 0.5), k, 1e-15) for k in range(0, 10)] + [True, True, True, True, True, True, True, True, True, True] + """ + if tol is None: + return z**n + 0 # add zero to normalize zero sign + else: + return abs(z**n / z ** n - 1) < tol + @cython.cdivision(False) def test_div_by_zero(double complex z): """ From 2a8c7772d0dbb0d7f0ccd2e4c9c09b326d6d50e4 Mon Sep 17 00:00:00 2001 From: Lisandro Dalcin Date: Mon, 27 Sep 2010 12:58:49 -0300 Subject: [PATCH 06/77] Fix C++ exception handling for nogil functions (with Stephane Drouard) --- Cython/Compiler/ExprNodes.py | 3 + tests/run/cpp_exceptions_nogil.pyx | 307 ++++++++++++++++++++++++ tests/run/cpp_exceptions_nogil_helper.h | 6 + 3 files changed, 316 insertions(+) create mode 100644 tests/run/cpp_exceptions_nogil.pyx create mode 100644 tests/run/cpp_exceptions_nogil_helper.h diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index 8946ec0c725..a21254a8aa5 100755 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -2863,6 +2863,7 @@ def analyse_c_function_call(self, env): or func_type.exception_check: self.is_temp = 1 # C++ exception handler + self.nogil = env.nogil if func_type.exception_check == '+': if func_type.exception_value is None: env.use_utility_code(cpp_exception_utility_code) @@ -2957,6 +2958,8 @@ def generate_result_code(self, code): func_type.exception_value.entry.cname) else: raise_py_exception = '%s(); if (!PyErr_Occurred()) PyErr_SetString(PyExc_RuntimeError , "Error converting c++ exception.")' % func_type.exception_value.entry.cname + if self.nogil: + raise_py_exception = 'Py_BLOCK_THREADS; %s; Py_UNBLOCK_THREADS' % raise_py_exception code.putln( "try {%s%s;} catch(...) {%s; %s}" % ( lhs, diff --git a/tests/run/cpp_exceptions_nogil.pyx b/tests/run/cpp_exceptions_nogil.pyx new file mode 100644 index 00000000000..3fe982225a5 --- /dev/null +++ b/tests/run/cpp_exceptions_nogil.pyx @@ -0,0 +1,307 @@ +cdef int raise_TypeError() except *: + raise TypeError("custom") + +cdef extern from "cpp_exceptions_nogil_helper.h" nogil: + cdef void foo "foo"(int i) except + + cdef void bar "foo"(int i) except +ValueError + cdef void spam"foo"(int i) except +raise_TypeError + +def test_foo(): + """ + >>> test_foo() + """ + # + foo(0) + foo(0) + with nogil: + foo(0) + foo(0) + # + try: + with nogil: + foo(0) + finally: + pass + # + try: + with nogil: + foo(0) + with nogil: + foo(0) + finally: + pass + # + try: + with nogil: + foo(0) + with nogil: + foo(1) + except: + with nogil: + foo(0) + finally: + with nogil: + foo(0) + pass + # + try: + with nogil: + foo(0) + foo(0) + finally: + pass + # + try: + with nogil: + foo(0) + foo(1) + except: + with nogil: + foo(0) + finally: + with nogil: + foo(0) + pass + # + try: + with nogil: + foo(0) + try: + with nogil: + foo(1) + except: + with nogil: + foo(1) + finally: + with nogil: + foo(0) + pass + except: + with nogil: + foo(0) + finally: + with nogil: + foo(0) + pass + # + try: + with nogil: + foo(0) + try: + with nogil: + foo(1) + except: + with nogil: + foo(1) + finally: + with nogil: + foo(1) + pass + except: + with nogil: + foo(0) + finally: + with nogil: + foo(0) + pass + # + +def test_bar(): + """ + >>> test_bar() + """ + # + bar(0) + bar(0) + with nogil: + bar(0) + bar(0) + # + try: + with nogil: + bar(0) + finally: + pass + # + try: + with nogil: + bar(0) + with nogil: + bar(0) + finally: + pass + # + try: + with nogil: + bar(0) + with nogil: + bar(1) + except ValueError: + with nogil: + bar(0) + finally: + with nogil: + bar(0) + pass + # + try: + with nogil: + bar(0) + bar(0) + finally: + pass + # + try: + with nogil: + bar(0) + bar(1) + except ValueError: + with nogil: + bar(0) + finally: + with nogil: + bar(0) + pass + # + try: + with nogil: + bar(0) + try: + with nogil: + bar(1) + except ValueError: + with nogil: + bar(1) + finally: + with nogil: + bar(0) + pass + except ValueError: + with nogil: + bar(0) + finally: + with nogil: + bar(0) + pass + # + try: + with nogil: + bar(0) + try: + with nogil: + bar(1) + except ValueError: + with nogil: + bar(1) + finally: + with nogil: + bar(1) + pass + except ValueError: + with nogil: + bar(0) + finally: + with nogil: + bar(0) + pass + # + +def test_spam(): + """ + >>> test_spam() + """ + # + spam(0) + spam(0) + with nogil: + spam(0) + spam(0) + # + try: + with nogil: + spam(0) + finally: + pass + # + try: + with nogil: + spam(0) + with nogil: + spam(0) + finally: + pass + # + try: + with nogil: + spam(0) + with nogil: + spam(1) + except TypeError: + with nogil: + spam(0) + finally: + with nogil: + spam(0) + pass + # + try: + with nogil: + spam(0) + spam(0) + finally: + pass + # + try: + with nogil: + spam(0) + spam(1) + except TypeError: + with nogil: + spam(0) + finally: + with nogil: + spam(0) + pass + # + try: + with nogil: + spam(0) + try: + with nogil: + spam(1) + except TypeError: + with nogil: + spam(1) + finally: + with nogil: + spam(0) + pass + except TypeError: + with nogil: + spam(0) + finally: + with nogil: + spam(0) + pass + # + try: + with nogil: + spam(0) + try: + with nogil: + spam(1) + except TypeError: + with nogil: + spam(1) + finally: + with nogil: + spam(1) + pass + except TypeError: + with nogil: + spam(0) + finally: + with nogil: + spam(0) + pass + # diff --git a/tests/run/cpp_exceptions_nogil_helper.h b/tests/run/cpp_exceptions_nogil_helper.h new file mode 100644 index 00000000000..63f159f1d25 --- /dev/null +++ b/tests/run/cpp_exceptions_nogil_helper.h @@ -0,0 +1,6 @@ +void foo(int i) { + if (i==0) + return; + else + throw i; +} From 5bc3a71e82e74c6b336a9439fadd18b22000c405 Mon Sep 17 00:00:00 2001 From: Lisandro Dalcin Date: Fri, 1 Oct 2010 22:36:30 -0300 Subject: [PATCH 07/77] protect PyGILState_Ensure|Release for Python configured without threads --- Cython/Compiler/Nodes.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py index ecd4b8f7b9b..5864b472d09 100644 --- a/Cython/Compiler/Nodes.py +++ b/Cython/Compiler/Nodes.py @@ -1284,7 +1284,9 @@ def generate_function_definitions(self, env, code): acquire_gil = self.acquire_gil if acquire_gil: env.use_utility_code(force_init_threads_utility_code) + code.putln("#ifdef WITH_THREAD") code.putln("PyGILState_STATE _save = PyGILState_Ensure();") + code.putln("#endif") # ----- set up refnanny if not lenv.nogil: code.put_setup_refcount_context(self.entry.name) @@ -1469,7 +1471,9 @@ def generate_function_definitions(self, env, code): code.put_finish_refcount_context() if acquire_gil: + code.putln("#ifdef WITH_THREAD") code.putln("PyGILState_Release(_save);") + code.putln("#endif") if not self.return_type.is_void: code.putln("return %s;" % Naming.retval_cname) @@ -5085,7 +5089,9 @@ def analyse_expressions(self, env): def generate_execution_code(self, code): code.mark_pos(self.pos) if self.state == 'gil': + code.putln("#ifdef WITH_THREAD") code.putln("{ PyGILState_STATE _save = PyGILState_Ensure();") + code.putln("#endif") else: code.putln("{ PyThreadState *_save;") code.putln("Py_UNBLOCK_THREADS") @@ -5105,7 +5111,9 @@ def analyse_expressions(self, env): def generate_execution_code(self, code): if self.state == 'gil': - code.putln("PyGILState_Release();") + code.putln("#ifdef WITH_THREAD") + code.putln("PyGILState_Release(_save); }") + code.putln("#endif") else: code.putln("Py_BLOCK_THREADS") From 2b8bff70e1a631806fac71b211e9b35996e09897 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Sat, 2 Oct 2010 23:40:57 -0700 Subject: [PATCH 08/77] #579 - compiler crash on invalid buffer type --- Cython/Compiler/Interpreter.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Cython/Compiler/Interpreter.py b/Cython/Compiler/Interpreter.py index 0acbd923174..298041d012a 100644 --- a/Cython/Compiler/Interpreter.py +++ b/Cython/Compiler/Interpreter.py @@ -37,7 +37,10 @@ def interpret_compiletime_options(optlist, optdict, type_env=None, type_args=()) def interpret(node, ix): if ix in type_args: if type_env: - return (node.analyse_as_type(type_env), node.pos) + type = node.analyse_as_type(type_env) + if not type: + raise CompileError(node.pos, "Invalid type.") + return (type, node.pos) else: raise CompileError(node.pos, "Type not allowed here.") else: From 4d676ccfe3287c4378dc8ba7b990c6178894bb10 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Sat, 2 Oct 2010 23:49:58 -0700 Subject: [PATCH 09/77] Test for bad buffer types. --- tests/errors/e_bufaccess.pyx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/errors/e_bufaccess.pyx b/tests/errors/e_bufaccess.pyx index 0281b67b675..e6f9e39c68f 100644 --- a/tests/errors/e_bufaccess.pyx +++ b/tests/errors/e_bufaccess.pyx @@ -10,6 +10,7 @@ def f(): cdef object[int,2,3,4,5,6] buf4 cdef object[int, 2, 'foo'] buf5 cdef object[int, 2, well] buf6 + cdef object[x, 1] buf0 _ERRORS = u""" 1:17: Buffer types only allowed as function local variables @@ -23,5 +24,6 @@ _ERRORS = u""" #10:15: Too many buffer options #11:24: Only allowed buffer modes are "full" or "strided" (as a compile-time string) #12:28: Only allowed buffer modes are "full" or "strided" (as a compile-time string) +#13:17: Invalid type. #""" From 33abc02789244ed98fbdabe545c053204dc600eb Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Mon, 4 Oct 2010 11:24:57 +0200 Subject: [PATCH 10/77] fix PYTHONPATH setting for end-to-end tests --- runtests.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/runtests.py b/runtests.py index 89d22793e8d..7ee5650c5bb 100644 --- a/runtests.py +++ b/runtests.py @@ -662,11 +662,12 @@ def runTest(self): commands = (self.commands .replace("CYTHON", "PYTHON %s" % os.path.join(self.cython_root, 'cython.py')) .replace("PYTHON", sys.executable)) - commands = """ - PYTHONPATH="%s%s$PYTHONPATH" - %s - """ % (self.cython_root, os.pathsep, commands) - self.assertEqual(0, os.system(commands)) + old_path = os.environ.get('PYTHONPATH') + try: + os.environ['PYTHONPATH'] = self.cython_root + os.pathsep + (old_path or '') + self.assertEqual(0, os.system(commands)) + finally: + os.environ['PYTHONPATH'] = old_path # TODO: Support cython_freeze needed here as well. From f5a99b8660ae8039b79e2bd5b4195cba98d45cdf Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Mon, 4 Oct 2010 11:40:23 +0200 Subject: [PATCH 11/77] fix PYTHONPATH setup if not previously set --- runtests.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/runtests.py b/runtests.py index 7ee5650c5bb..8d3f74e0211 100644 --- a/runtests.py +++ b/runtests.py @@ -667,7 +667,10 @@ def runTest(self): os.environ['PYTHONPATH'] = self.cython_root + os.pathsep + (old_path or '') self.assertEqual(0, os.system(commands)) finally: - os.environ['PYTHONPATH'] = old_path + if old_path: + os.environ['PYTHONPATH'] = old_path + else: + del os.environ['PYTHONPATH'] # TODO: Support cython_freeze needed here as well. From 464923673475879fedc103ef2ee0260ba88d1493 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Mon, 4 Oct 2010 20:53:08 -0700 Subject: [PATCH 12/77] #581 - bad reference type coercion --- Cython/Compiler/ExprNodes.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index a21254a8aa5..950c0df3269 100755 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -559,6 +559,9 @@ def coerce_to(self, dst_type, env): if self.check_for_coercion_error(dst_type): return self + if dst_type.is_reference: + dst_type = dst_type.ref_base_type + if dst_type.is_pyobject: if not src.type.is_pyobject: if dst_type is bytes_type and src.type.is_int: From e4a3fd74f2fdd99f64a05faef84741c3a708eda2 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Mon, 4 Oct 2010 21:02:38 -0700 Subject: [PATCH 13/77] Make sure libcpp test runs as well as compiles. --- tests/{compile => run}/libcpp_all.pyx | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{compile => run}/libcpp_all.pyx (100%) diff --git a/tests/compile/libcpp_all.pyx b/tests/run/libcpp_all.pyx similarity index 100% rename from tests/compile/libcpp_all.pyx rename to tests/run/libcpp_all.pyx From 3cf1d712b6468c4175f2b6b5347f77cf114ffdf7 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Mon, 4 Oct 2010 21:02:55 -0700 Subject: [PATCH 14/77] Test for broken reference coercion - #581. --- tests/run/libcpp_all.pyx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/run/libcpp_all.pyx b/tests/run/libcpp_all.pyx index 8ab5192d3b1..5eaf009e29d 100644 --- a/tests/run/libcpp_all.pyx +++ b/tests/run/libcpp_all.pyx @@ -53,3 +53,15 @@ cdef vector[int].iterator iv1 = v1.begin() cdef vector[int].iterator iv2 = v1.end() cdef vector[int].reverse_iterator riv1 = v1.rbegin() cdef vector[int].reverse_iterator riv2 = v1.rend() + +def test_vector_coercion(*args): + """ + >>> test_vector_coercion(1.75) + [1.75] + >>> test_vector_coercion(1, 10, 100) + [1.0, 10.0, 100.0] + """ + v = new vector[double]() + for a in args: + v.push_back(a) + return [v[0][i] for i in range(v.size())] From e0814f76a508d1f1da11eb70468a9bcc53633f6d Mon Sep 17 00:00:00 2001 From: Lisandro Dalcin Date: Tue, 5 Oct 2010 14:35:12 -0300 Subject: [PATCH 15/77] fixes, additions and tests for libcpp.vector --- Cython/Includes/libcpp/vector.pxd | 12 ++++++-- tests/run/cpp_stl_vector.pyx | 48 +++++++++++++++++++++++++++---- 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/Cython/Includes/libcpp/vector.pxd b/Cython/Includes/libcpp/vector.pxd index 0a6ba1f078f..61c6aefbc8a 100644 --- a/Cython/Includes/libcpp/vector.pxd +++ b/Cython/Includes/libcpp/vector.pxd @@ -6,12 +6,20 @@ cdef extern from "" namespace "std": iterator operator--() bint operator==(iterator) bint operator!=(iterator) + bint operator< (iterator) + bint operator> (iterator) + bint operator<=(iterator) + bint operator>=(iterator) cppclass reverse_iterator: T& operator*() iterator operator++() iterator operator--() - bint operator==(iterator) - bint operator!=(iterator) + bint operator==(reverse_iterator) + bint operator!=(reverse_iterator) + bint operator< (reverse_iterator) + bint operator> (reverse_iterator) + bint operator<=(reverse_iterator) + bint operator>=(reverse_iterator) #cppclass const_iterator(iterator): # pass #cppclass const_reverse_iterator(reverse_iterator): diff --git a/tests/run/cpp_stl_vector.pyx b/tests/run/cpp_stl_vector.pyx index eee6ef893b2..4e03706bf21 100644 --- a/tests/run/cpp_stl_vector.pyx +++ b/tests/run/cpp_stl_vector.pyx @@ -1,11 +1,7 @@ from cython.operator cimport dereference as d +from cython.operator cimport preincrement as incr -cdef extern from "" namespace "std": - - cdef cppclass vector[T]: - void push_back(T) - size_t size() - T& operator[](size_t) +from libcpp.vector cimport vector def simple_test(double x): """ @@ -71,3 +67,43 @@ def index_set_test(L): return d(v)[0], d(v)[v.size()-1] finally: del v + +def iteration_test(L): + """ + >>> iteration_test([1,2,4,8]) + 1 + 2 + 4 + 8 + """ + try: + v = new vector[int]() + for a in L: + v.push_back(a) + it = v.begin() + while it != v.end(): + a = d(it) + incr(it) + print(a) + finally: + del v + +def reverse_iteration_test(L): + """ + >>> reverse_iteration_test([1,2,4,8]) + 8 + 4 + 2 + 1 + """ + try: + v = new vector[int]() + for a in L: + v.push_back(a) + it = v.rbegin() + while it != v.rend(): + a = d(it) + incr(it) + print(a) + finally: + del v From 17111fa709ca24ac5aa639b3c85e86ac6b5d9c1a Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Wed, 6 Oct 2010 02:52:01 -0700 Subject: [PATCH 16/77] Get rid of unused __get__/__set__ warnings for cimported classes. --- Cython/Compiler/Nodes.py | 6 +----- Cython/Compiler/ParseTreeTransforms.py | 27 ++++++++++++++------------ 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py index 5864b472d09..2288185c274 100644 --- a/Cython/Compiler/Nodes.py +++ b/Cython/Compiler/Nodes.py @@ -899,13 +899,11 @@ class CVarDefNode(StatNode): # declarators [CDeclaratorNode] # in_pxd boolean # api boolean - # properties [entry] # decorators [cython.locals(...)] or None # directive_locals { string : NameNode } locals defined by cython.locals(...) child_attrs = ["base_type", "declarators"] - properties = () decorators = None directive_locals = {} @@ -921,7 +919,6 @@ def analyse_declarations(self, env, dest_scope = None): # a property; made in AnalyseDeclarationsTransform). if (dest_scope.is_c_class_scope and self.visibility in ('public', 'readonly')): - self.properties = [] need_property = True else: need_property = False @@ -955,8 +952,7 @@ def analyse_declarations(self, env, dest_scope = None): "Only 'extern' C variable declaration allowed in .pxd file") entry = dest_scope.declare_var(name, type, declarator.pos, cname = cname, visibility = visibility, is_cdef = 1) - if need_property: - self.properties.append(entry) + entry.needs_property = need_property class CStructOrUnionDefNode(StatNode): diff --git a/Cython/Compiler/ParseTreeTransforms.py b/Cython/Compiler/ParseTreeTransforms.py index fccba177545..17bfb3f66ce 100644 --- a/Cython/Compiler/ParseTreeTransforms.py +++ b/Cython/Compiler/ParseTreeTransforms.py @@ -1018,6 +1018,20 @@ def visit_ClassDefNode(self, node): self.visitchildren(node) self.env_stack.pop() return node + + def visit_CClassDefNode(self, node): + node = self.visit_ClassDefNode(node) + if node.scope and node.scope.implemented: + stats = [] + for entry in node.scope.var_entries: + if entry.needs_property: + property = self.create_Property(entry) + property.analyse_declarations(node.scope) + self.visit(property) + stats.append(property) + if stats: + node.body.stats += stats + return node def visit_FuncDefNode(self, node): self.seen_vars_stack.append(set()) @@ -1093,20 +1107,9 @@ def visit_CNameDeclaratorNode(self, node): return node def visit_CVarDefNode(self, node): - # to ensure all CNameDeclaratorNodes are visited. self.visitchildren(node) - - if node.properties: - stats = [] - for entry in node.properties: - property = self.create_Property(entry) - property.analyse_declarations(node.dest_scope) - self.visit(property) - stats.append(property) - return StatListNode(pos=node.pos, stats=stats) - else: - return None + return None def create_Property(self, entry): if entry.visibility == 'public': From f3e947442cefc6df968922ea8824e0ce9d1f9936 Mon Sep 17 00:00:00 2001 From: Lisandro Dalcin Date: Wed, 6 Oct 2010 11:42:05 -0300 Subject: [PATCH 17/77] fix Python exception checking within nogil blocks --- Cython/Compiler/ExprNodes.py | 32 +++- Cython/Compiler/Nodes.py | 9 +- tests/run/exceptions_nogil.pyx | 310 +++++++++++++++++++++++++++++++++ 3 files changed, 346 insertions(+), 5 deletions(-) create mode 100644 tests/run/exceptions_nogil.pyx diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index 950c0df3269..d12d6cfc775 100755 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -2692,6 +2692,7 @@ class SimpleCallNode(CallNode): # coerced_self ExprNode or None used internally # wrapper_call bool used internally # has_optional_args bool used internally + # nogil bool used internally subexprs = ['self', 'coerced_self', 'function', 'args', 'arg_tuple'] @@ -2865,8 +2866,13 @@ def analyse_c_function_call(self, env): elif func_type.exception_value is not None \ or func_type.exception_check: self.is_temp = 1 - # C++ exception handler + # Called in 'nogil' context? self.nogil = env.nogil + if (self.nogil and + func_type.exception_check and + func_type.exception_check != '+'): + env.use_utility_code(pyerr_occurred_withgil_utility_code) + # C++ exception handler if func_type.exception_check == '+': if func_type.exception_value is None: env.use_utility_code(cpp_exception_utility_code) @@ -2940,7 +2946,10 @@ def generate_result_code(self, code): if exc_val is not None: exc_checks.append("%s == %s" % (self.result(), exc_val)) if exc_check: - exc_checks.append("PyErr_Occurred()") + if self.nogil: + exc_checks.append("__Pyx_ErrOccurredWithGIL()") + else: + exc_checks.append("PyErr_Occurred()") if self.is_temp or exc_checks: rhs = self.c_call_code() if self.result(): @@ -7144,6 +7153,25 @@ def generate_result_code(self, code): impl = "" ) +pyerr_occurred_withgil_utility_code= UtilityCode( +proto = """ +static CYTHON_INLINE int __Pyx_ErrOccurredWithGIL(void); /* proto */ +""", +impl = """ +static CYTHON_INLINE int __Pyx_ErrOccurredWithGIL(void) { + int err; + #ifdef WITH_THREAD + PyGILState_STATE _save = PyGILState_Ensure(); + #endif + err = !!PyErr_Occurred(); + #ifdef WITH_THREAD + PyGILState_Release(_save); + #endif + return err; +} +""" +) + #------------------------------------------------------------------------------------ raise_noneattr_error_utility_code = UtilityCode( diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py index 2288185c274..a7c9592683a 100644 --- a/Cython/Compiler/Nodes.py +++ b/Cython/Compiler/Nodes.py @@ -5084,12 +5084,15 @@ def analyse_expressions(self, env): def generate_execution_code(self, code): code.mark_pos(self.pos) + code.putln("{") if self.state == 'gil': code.putln("#ifdef WITH_THREAD") - code.putln("{ PyGILState_STATE _save = PyGILState_Ensure();") + code.putln("PyGILState_STATE _save = PyGILState_Ensure();") code.putln("#endif") else: - code.putln("{ PyThreadState *_save;") + code.putln("#ifdef WITH_THREAD") + code.putln("PyThreadState *_save;") + code.putln("#endif") code.putln("Py_UNBLOCK_THREADS") TryFinallyStatNode.generate_execution_code(self, code) code.putln("}") @@ -5108,7 +5111,7 @@ def analyse_expressions(self, env): def generate_execution_code(self, code): if self.state == 'gil': code.putln("#ifdef WITH_THREAD") - code.putln("PyGILState_Release(_save); }") + code.putln("PyGILState_Release(_save);") code.putln("#endif") else: code.putln("Py_BLOCK_THREADS") diff --git a/tests/run/exceptions_nogil.pyx b/tests/run/exceptions_nogil.pyx new file mode 100644 index 00000000000..c8032cfea48 --- /dev/null +++ b/tests/run/exceptions_nogil.pyx @@ -0,0 +1,310 @@ +cdef void foo(int i) except * with gil: + if i != 0: raise ValueError + +cdef int bar(int i) except? -1 with gil: + if i != 0: raise ValueError + return 0 + +cdef int spam(int i) except? -1 with gil: + if i != 0: raise TypeError + return -1 + +def test_foo(): + """ + >>> test_foo() + """ + # + foo(0) + foo(0) + with nogil: + foo(0) + foo(0) + # + try: + with nogil: + foo(0) + finally: + pass + # + try: + with nogil: + foo(0) + with nogil: + foo(0) + finally: + pass + # + try: + with nogil: + foo(0) + with nogil: + foo(1) + except: + with nogil: + foo(0) + finally: + with nogil: + foo(0) + pass + # + try: + with nogil: + foo(0) + foo(0) + finally: + pass + # + try: + with nogil: + foo(0) + foo(1) + except: + with nogil: + foo(0) + finally: + with nogil: + foo(0) + pass + # + try: + with nogil: + foo(0) + try: + with nogil: + foo(1) + except: + with nogil: + foo(1) + finally: + with nogil: + foo(0) + pass + except: + with nogil: + foo(0) + finally: + with nogil: + foo(0) + pass + # + try: + with nogil: + foo(0) + try: + with nogil: + foo(1) + except: + with nogil: + foo(1) + finally: + with nogil: + foo(1) + pass + except: + with nogil: + foo(0) + finally: + with nogil: + foo(0) + pass + # + +def test_bar(): + """ + >>> test_bar() + """ + # + bar(0) + bar(0) + with nogil: + bar(0) + bar(0) + # + try: + with nogil: + bar(0) + finally: + pass + # + try: + with nogil: + bar(0) + with nogil: + bar(0) + finally: + pass + # + try: + with nogil: + bar(0) + with nogil: + bar(1) + except ValueError: + with nogil: + bar(0) + finally: + with nogil: + bar(0) + pass + # + try: + with nogil: + bar(0) + bar(0) + finally: + pass + # + try: + with nogil: + bar(0) + bar(1) + except ValueError: + with nogil: + bar(0) + finally: + with nogil: + bar(0) + pass + # + try: + with nogil: + bar(0) + try: + with nogil: + bar(1) + except ValueError: + with nogil: + bar(1) + finally: + with nogil: + bar(0) + pass + except ValueError: + with nogil: + bar(0) + finally: + with nogil: + bar(0) + pass + # + try: + with nogil: + bar(0) + try: + with nogil: + bar(1) + except ValueError: + with nogil: + bar(1) + finally: + with nogil: + bar(1) + pass + except ValueError: + with nogil: + bar(0) + finally: + with nogil: + bar(0) + pass + # + +def test_spam(): + """ + >>> test_spam() + """ + # + spam(0) + spam(0) + with nogil: + spam(0) + spam(0) + # + try: + with nogil: + spam(0) + finally: + pass + # + try: + with nogil: + spam(0) + with nogil: + spam(0) + finally: + pass + # + try: + with nogil: + spam(0) + with nogil: + spam(1) + except TypeError: + with nogil: + spam(0) + finally: + with nogil: + spam(0) + pass + # + try: + with nogil: + spam(0) + spam(0) + finally: + pass + # + try: + with nogil: + spam(0) + spam(1) + except TypeError: + with nogil: + spam(0) + finally: + with nogil: + spam(0) + pass + # + try: + with nogil: + spam(0) + try: + with nogil: + spam(1) + except TypeError: + with nogil: + spam(1) + finally: + with nogil: + spam(0) + pass + except TypeError: + with nogil: + spam(0) + finally: + with nogil: + spam(0) + pass + # + try: + with nogil: + spam(0) + try: + with nogil: + spam(1) + except TypeError: + with nogil: + spam(1) + finally: + with nogil: + spam(1) + pass + except TypeError: + with nogil: + spam(0) + finally: + with nogil: + spam(0) + pass + # From 6cdd24b05aa5047a8ae5270c391ca91597f67070 Mon Sep 17 00:00:00 2001 From: Lisandro Dalcin Date: Wed, 6 Oct 2010 19:46:12 -0300 Subject: [PATCH 18/77] fixes and enhancements in libcpp --- Cython/Includes/libcpp/deque.pxd | 4 ++-- Cython/Includes/libcpp/list.pxd | 5 +++-- Cython/Includes/libcpp/map.pxd | 8 ++++---- Cython/Includes/libcpp/pair.pxd | 8 +------- Cython/Includes/libcpp/set.pxd | 8 ++++---- Cython/Includes/libcpp/utility.pxd | 13 +++++++++++++ Cython/Includes/libcpp/vector.pxd | 8 ++++---- 7 files changed, 31 insertions(+), 23 deletions(-) create mode 100644 Cython/Includes/libcpp/utility.pxd diff --git a/Cython/Includes/libcpp/deque.pxd b/Cython/Includes/libcpp/deque.pxd index fc0a4913b0e..62965ae5a4d 100644 --- a/Cython/Includes/libcpp/deque.pxd +++ b/Cython/Includes/libcpp/deque.pxd @@ -12,8 +12,8 @@ cdef extern from "" namespace "std": T& operator*() iterator operator++() iterator operator--() - bint operator==(iterator) - bint operator!=(iterator) + bint operator==(reverse_iterator) + bint operator!=(reverse_iterator) #cppclass const_iterator(iterator): # pass #cppclass const_reverse_iterator(reverse_iterator): diff --git a/Cython/Includes/libcpp/list.pxd b/Cython/Includes/libcpp/list.pxd index b395f7d636f..0b6eb1dcb2b 100644 --- a/Cython/Includes/libcpp/list.pxd +++ b/Cython/Includes/libcpp/list.pxd @@ -10,8 +10,8 @@ cdef extern from "" namespace "std": T& operator*() iterator operator++() iterator operator--() - bint operator==(iterator) - bint operator!=(iterator) + bint operator==(reverse_iterator) + bint operator!=(reverse_iterator) #cppclass const_iterator(iterator): # pass #cppclass const_reverse_iterator(reverse_iterator): @@ -30,6 +30,7 @@ cdef extern from "" namespace "std": T& back() iterator begin() #const_iterator begin() + void clear() bint empty() iterator end() #const_iterator end() diff --git a/Cython/Includes/libcpp/map.pxd b/Cython/Includes/libcpp/map.pxd index 78c8cd9925f..4768a54d484 100644 --- a/Cython/Includes/libcpp/map.pxd +++ b/Cython/Includes/libcpp/map.pxd @@ -1,4 +1,4 @@ -from pair cimport pair +from utility cimport pair cdef extern from "" namespace "std": cdef cppclass map[T, U]: @@ -9,11 +9,11 @@ cdef extern from "" namespace "std": bint operator==(iterator) bint operator!=(iterator) cppclass reverse_iterator: - pair[T,U] operator*() + pair[T,U]& operator*() iterator operator++() iterator operator--() - bint operator==(iterator) - bint operator!=(iterator) + bint operator==(reverse_iterator) + bint operator!=(reverse_iterator) #cppclass const_iterator(iterator): # pass #cppclass const_reverse_iterator(reverse_iterator): diff --git a/Cython/Includes/libcpp/pair.pxd b/Cython/Includes/libcpp/pair.pxd index da0eb7731e3..0915eee3c13 100644 --- a/Cython/Includes/libcpp/pair.pxd +++ b/Cython/Includes/libcpp/pair.pxd @@ -1,7 +1 @@ -cdef extern from "" namespace "std": - cdef cppclass pair[T, U]: - T first - U second - pair() - pair(pair&) - pair(T&, U&) +from utility cimport pair diff --git a/Cython/Includes/libcpp/set.pxd b/Cython/Includes/libcpp/set.pxd index 6d6607ee4ea..89a6f557e6b 100644 --- a/Cython/Includes/libcpp/set.pxd +++ b/Cython/Includes/libcpp/set.pxd @@ -3,17 +3,17 @@ from pair cimport pair cdef extern from "" namespace "std": cdef cppclass set[T]: cppclass iterator: - T operator*() + T& operator*() iterator operator++() iterator operator--() bint operator==(iterator) bint operator!=(iterator) cppclass reverse_iterator: - T operator*() + T& operator*() iterator operator++() iterator operator--() - bint operator==(iterator) - bint operator!=(iterator) + bint operator==(reverse_iterator) + bint operator!=(reverse_iterator) #cppclass const_iterator(iterator): # pass #cppclass const_reverse_iterator(reverse_iterator): diff --git a/Cython/Includes/libcpp/utility.pxd b/Cython/Includes/libcpp/utility.pxd new file mode 100644 index 00000000000..1d8ead78e5e --- /dev/null +++ b/Cython/Includes/libcpp/utility.pxd @@ -0,0 +1,13 @@ +cdef extern from "" namespace "std": + cdef cppclass pair[T, U]: + T first + U second + pair() + pair(pair&) + pair(T&, U&) + bint operator==(pair&, pair&) + bint operator!=(pair&, pair&) + bint operator<(pair&, pair&) + bint operator>(pair&, pair&) + bint operator<=(pair&, pair&) + bint operator>=(pair&, pair&) diff --git a/Cython/Includes/libcpp/vector.pxd b/Cython/Includes/libcpp/vector.pxd index 61c6aefbc8a..0ec55702729 100644 --- a/Cython/Includes/libcpp/vector.pxd +++ b/Cython/Includes/libcpp/vector.pxd @@ -6,8 +6,8 @@ cdef extern from "" namespace "std": iterator operator--() bint operator==(iterator) bint operator!=(iterator) - bint operator< (iterator) - bint operator> (iterator) + bint operator<(iterator) + bint operator>(iterator) bint operator<=(iterator) bint operator>=(iterator) cppclass reverse_iterator: @@ -16,8 +16,8 @@ cdef extern from "" namespace "std": iterator operator--() bint operator==(reverse_iterator) bint operator!=(reverse_iterator) - bint operator< (reverse_iterator) - bint operator> (reverse_iterator) + bint operator<(reverse_iterator) + bint operator>(reverse_iterator) bint operator<=(reverse_iterator) bint operator>=(reverse_iterator) #cppclass const_iterator(iterator): From 75ae247deb973c3c43289163cbace41077ac16c8 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Fri, 8 Oct 2010 10:18:22 +0200 Subject: [PATCH 19/77] fix 'nogil' flag on PythonCapiCallNode after the last exception handling change --- Cython/Compiler/ExprNodes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index d12d6cfc775..2eabed1b75c 100755 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -2701,6 +2701,7 @@ class SimpleCallNode(CallNode): arg_tuple = None wrapper_call = False has_optional_args = False + nogil = False def compile_time_value(self, denv): function = self.function.compile_time_value(denv) From f856a43481db9b7cf87d6e51f7110ea54fee7751 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Fri, 15 Oct 2010 08:24:32 -0700 Subject: [PATCH 20/77] Pyrex -> Cython in error message. --- Cython/Compiler/Errors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cython/Compiler/Errors.py b/Cython/Compiler/Errors.py index a492e5bb2cf..0f3031b8e56 100644 --- a/Cython/Compiler/Errors.py +++ b/Cython/Compiler/Errors.py @@ -39,7 +39,7 @@ def format_error(message, position): if position: pos_str = format_position(position) cont = context(position) - message = u'\nError converting Pyrex file to C:\n%s\n%s%s' % (cont, pos_str, message or u'') + message = u'\nError compiling Cython file:\n%s\n%s%s' % (cont, pos_str, message or u'') return message class CompileError(PyrexError): From c1fc77d52db352729209df74607e7909e7942bfa Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Fri, 22 Oct 2010 14:04:27 +0200 Subject: [PATCH 21/77] code cleanup --- Cython/Compiler/Dependencies.py | 18 +++++++++--------- Cython/Distutils/build_ext.py | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cython/Compiler/Dependencies.py b/Cython/Compiler/Dependencies.py index 8b6a55f54e5..b9747c3ac89 100644 --- a/Cython/Compiler/Dependencies.py +++ b/Cython/Compiler/Dependencies.py @@ -274,7 +274,7 @@ def fully_qualifeid_name(self, filename): def find_pxd(self, module, filename=None): if module[0] == '.': - raise NotImplementedError, "New relative imports." + raise NotImplementedError("New relative imports.") if filename is not None: relative = '.'.join(self.package(filename) + tuple(module.split('.'))) pxd = self.context.find_pxd_file(relative, None) @@ -291,9 +291,9 @@ def cimported_files(self, filename): a = self.cimports(filename) b = filter(None, [self.find_pxd(m, filename) for m in self.cimports(filename)]) if len(a) != len(b): - print (filename) - print ("\n\t".join(a)) - print ("\n\t".join(b)) + print(filename) + print("\n\t".join(a)) + print("\n\t".join(b)) return tuple(self_pxd + filter(None, [self.find_pxd(m, filename) for m in self.cimports(filename)])) cimported_files = cached_method(cimported_files) @@ -392,7 +392,7 @@ def create_extension_list(patterns, ctx=None, aliases=None): base = DistutilsInfo(template) exn_type = type(template) else: - raise TypeError, pattern + raise TypeError(pattern) for file in glob(filepattern): pkg = deps.package(file) if name == '*': @@ -428,7 +428,7 @@ def cythonize(module_list, ctx=None, nthreads=0, aliases=None): dep_timestamp, dep = deps.newest_dependency(source) priority = 2 - (dep in deps.immediate_dependencies(source)) if c_timestamp < dep_timestamp: - print "Compiling", source, "because it depends on", dep + print("Compiling %s because it depends on %s" % (source, dep)) to_compile.append((priority, source, c_file)) new_sources.append(c_file) else: @@ -441,7 +441,7 @@ def cythonize(module_list, ctx=None, nthreads=0, aliases=None): try: import multiprocessing except ImportError: - print "multiprocessing required for parallel cythonization" + print("multiprocessing required for parallel cythonization") nthreads = 0 pool = multiprocessing.Pool(nthreads) pool.map(cythonoize_one_helper, to_compile) @@ -454,9 +454,9 @@ def cythonize(module_list, ctx=None, nthreads=0, aliases=None): def cythonoize_one(pyx_file, c_file): cmd = "%s %s %s -o %s" % (sys.executable, cython_py, pyx_file, c_file) - print cmd + print(cmd) if os.system(cmd) != 0: - raise CompilerError, pyx_file + raise CompilerError(pyx_file) def cythonoize_one_helper(m): return cythonoize_one(*m[1:]) diff --git a/Cython/Distutils/build_ext.py b/Cython/Distutils/build_ext.py index 397f0972f08..b7895a02a51 100644 --- a/Cython/Distutils/build_ext.py +++ b/Cython/Distutils/build_ext.py @@ -3,7 +3,7 @@ Implements a version of the Distutils 'build_ext' command, for building Cython extension modules.""" -# This module should be kept compatible with Python 2.1. +# This module should be kept compatible with Python 2.3. __revision__ = "$Id:$" From 463643e2a4df11a2e5b0eac1561b712176adb6b0 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Fri, 22 Oct 2010 14:23:38 +0200 Subject: [PATCH 22/77] fix end-to-end test setup in Py3 --- runtests.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/runtests.py b/runtests.py index 8d3f74e0211..150b5f7d26d 100644 --- a/runtests.py +++ b/runtests.py @@ -640,6 +640,14 @@ def __init__(self, treefile, workdir, cleanup_workdir=True): self.treefile = treefile self.workdir = os.path.join(workdir, os.path.splitext(treefile)[0]) self.cleanup_workdir = cleanup_workdir + cython_syspath = self.cython_root + for path in sys.path[::-1]: + if path.startswith(self.cython_root): + # Py3 installation and refnanny build prepend their + # fixed paths to sys.path => prefer that over the + # generic one + cython_syspath = path + os.pathsep + cython_syspath + self.cython_syspath = cython_syspath unittest.TestCase.__init__(self) def shortDescription(self): @@ -664,7 +672,8 @@ def runTest(self): .replace("PYTHON", sys.executable)) old_path = os.environ.get('PYTHONPATH') try: - os.environ['PYTHONPATH'] = self.cython_root + os.pathsep + (old_path or '') + os.environ['PYTHONPATH'] = self.cython_syspath + os.pathsep + (old_path or '') + print(os.environ['PYTHONPATH']) self.assertEqual(0, os.system(commands)) finally: if old_path: From 14f8fc4e23bbc8e24feac68be1b9bdc1205bbc22 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Fri, 22 Oct 2010 14:25:48 +0200 Subject: [PATCH 23/77] typos --- Cython/Compiler/Dependencies.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cython/Compiler/Dependencies.py b/Cython/Compiler/Dependencies.py index b9747c3ac89..a7e0b26dec2 100644 --- a/Cython/Compiler/Dependencies.py +++ b/Cython/Compiler/Dependencies.py @@ -444,19 +444,19 @@ def cythonize(module_list, ctx=None, nthreads=0, aliases=None): print("multiprocessing required for parallel cythonization") nthreads = 0 pool = multiprocessing.Pool(nthreads) - pool.map(cythonoize_one_helper, to_compile) + pool.map(cythonize_one_helper, to_compile) if not nthreads: for priority, pyx_file, c_file in to_compile: - cythonoize_one(pyx_file, c_file) + cythonize_one(pyx_file, c_file) return module_list cython_py = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../cython.py')) -def cythonoize_one(pyx_file, c_file): +def cythonize_one(pyx_file, c_file): cmd = "%s %s %s -o %s" % (sys.executable, cython_py, pyx_file, c_file) print(cmd) if os.system(cmd) != 0: raise CompilerError(pyx_file) -def cythonoize_one_helper(m): - return cythonoize_one(*m[1:]) +def cythonize_one_helper(m): + return cythonize_one(*m[1:]) From e63ba22ce09c1e816ac4244cc65a89d678977d83 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Fri, 22 Oct 2010 14:41:36 +0200 Subject: [PATCH 24/77] fix new build for an installed Cython and in Py3 test setting --- Cython/Compiler/Dependencies.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/Cython/Compiler/Dependencies.py b/Cython/Compiler/Dependencies.py index a7e0b26dec2..b6c5efa3f3c 100644 --- a/Cython/Compiler/Dependencies.py +++ b/Cython/Compiler/Dependencies.py @@ -450,13 +450,24 @@ def cythonize(module_list, ctx=None, nthreads=0, aliases=None): cythonize_one(pyx_file, c_file) return module_list -cython_py = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../cython.py')) +def cythonize_one(pyx_file, c_file, options=None): + from Cython.Compiler.Main import compile, CompilationOptions, default_options + from Cython.Compiler.Errors import CompileError, PyrexError -def cythonize_one(pyx_file, c_file): - cmd = "%s %s %s -o %s" % (sys.executable, cython_py, pyx_file, c_file) - print(cmd) - if os.system(cmd) != 0: - raise CompilerError(pyx_file) + if options is None: + options = CompilationOptions(default_options) + options.output_file = c_file + + any_failures = 0 + try: + result = compile([pyx_file], options) + if result.num_errors > 0: + any_failures = 1 + except (EnvironmentError, PyrexError), e: + sys.stderr.write(str(e) + '\n') + any_failures = 1 + if any_failures: + raise CompileError(None, pyx_file) def cythonize_one_helper(m): return cythonize_one(*m[1:]) From 5c273b271726200567c811665e9c1fff1b048494 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Fri, 22 Oct 2010 14:59:33 +0200 Subject: [PATCH 25/77] clean up some imports --- Cython/Distutils/build_ext.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Cython/Distutils/build_ext.py b/Cython/Distutils/build_ext.py index b7895a02a51..2e9cd77fa82 100644 --- a/Cython/Distutils/build_ext.py +++ b/Cython/Distutils/build_ext.py @@ -7,10 +7,11 @@ __revision__ = "$Id:$" -import sys, os, re -from types import * +import sys +import os +import re from distutils.core import Command -from distutils.errors import * +from distutils.errors import DistutilsPlatformError from distutils.sysconfig import customize_compiler, get_python_version from distutils.dep_util import newer, newer_group from distutils import log @@ -66,7 +67,7 @@ def finalize_options (self): _build_ext.build_ext.finalize_options(self) if self.pyrex_include_dirs is None: self.pyrex_include_dirs = [] - elif type(self.pyrex_include_dirs) is StringType: + elif isinstance(self.pyrex_include_dirs, basestring): self.pyrex_include_dirs = \ self.pyrex_include_dirs.split(os.pathsep) if self.pyrex_directives is None: From e0494ef1b0f8217e9e6fa5cbf8ebac7e78c97370 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Fri, 22 Oct 2010 16:55:39 +0200 Subject: [PATCH 26/77] comment --- runtests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/runtests.py b/runtests.py index 150b5f7d26d..8c28d719ee3 100644 --- a/runtests.py +++ b/runtests.py @@ -869,6 +869,8 @@ def refactor_for_py3(distdir, cy3_dir): # try if Cython is installed in a Py3 version import Cython.Compiler.Main except Exception: + # back out anything the import process loaded, then + # 2to3 the Cython sources to make them re-importable cy_modules = [ name for name in sys.modules if name == 'Cython' or name.startswith('Cython.') ] for name in cy_modules: From e74ad055a31e39d01924e45c408971b37f399b06 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Fri, 22 Oct 2010 17:01:45 +0200 Subject: [PATCH 27/77] Py3 install fix: keep original Py2 source modules of Cython alive during installation when replacing them with 2to3-ed versions - we still need the build_ext stuff --- setup.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/setup.py b/setup.py index 401a60fd825..81d2eb65985 100644 --- a/setup.py +++ b/setup.py @@ -106,12 +106,19 @@ def compile_cython_modules(profile=False): ) class build_ext(build_ext_orig): + # we must keep the original modules alive to make sure + # their code keeps working when we remove them from + # sys.modules + dead_modules = [] + def build_extensions(self): # add path where 2to3 installed the transformed sources # and make sure Python (re-)imports them from there already_imported = [ module for module in sys.modules if module == 'Cython' or module.startswith('Cython.') ] + keep_alive = self.dead_modules.append for module in already_imported: + keep_alive(sys.modules[module]) del sys.modules[module] sys.path.insert(0, os.path.join(source_root, self.build_lib)) From 0f9ba0d22a4337823b3d1576d579651c2e91e7f3 Mon Sep 17 00:00:00 2001 From: Haoyu Bai Date: Sun, 10 Oct 2010 18:06:04 +0800 Subject: [PATCH 28/77] Fix T422 by making module name as a StringConst --- Cython/Compiler/ExprNodes.py | 50 +++++++++++++++------------ Cython/Compiler/Symtab.py | 5 +-- tests/run/method_module_name_T422.pyx | 26 ++++++++++++++ 3 files changed, 57 insertions(+), 24 deletions(-) create mode 100644 tests/run/method_module_name_T422.pyx diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index 2eabed1b75c..62de863ceb0 100755 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -4417,8 +4417,15 @@ def free_temps(self, code): def __iter__(self): return iter([self.key, self.value]) +class ModuleNameMixin(object): + def set_mod_name(self, env): + self.module_name = env.global_scope().qualified_name + + def get_py_mod_name(self, code): + return code.get_py_string_const( + self.module_name, identifier=True) -class ClassNode(ExprNode): +class ClassNode(ExprNode, ModuleNameMixin): # Helper class used in the implementation of Python # class definitions. Constructs a class object given # a name, tuple of bases and class dictionary. @@ -4427,7 +4434,7 @@ class ClassNode(ExprNode): # bases ExprNode Base class tuple # dict ExprNode Class dict (not owned by this node) # doc ExprNode or None Doc string - # module_name string Name of defining module + # module_name EncodedString Name of defining module subexprs = ['bases', 'doc'] @@ -4436,10 +4443,11 @@ def analyse_types(self, env): if self.doc: self.doc.analyse_types(env) self.doc = self.doc.coerce_to_pyobject(env) - self.module_name = env.global_scope().qualified_name self.type = py_object_type self.is_temp = 1 env.use_utility_code(create_class_utility_code); + #TODO(craig,haoyu) This should be moved to a better place + self.set_mod_name(env) def may_be_none(self): return False @@ -4453,13 +4461,14 @@ def generate_result_code(self, code): 'PyDict_SetItemString(%s, "__doc__", %s)' % ( self.dict.py_result(), self.doc.py_result())) + py_mod_name = self.get_py_mod_name(code) code.putln( - '%s = __Pyx_CreateClass(%s, %s, %s, "%s"); %s' % ( + '%s = __Pyx_CreateClass(%s, %s, %s, %s); %s' % ( self.result(), self.bases.py_result(), self.dict.py_result(), cname, - self.module_name, + py_mod_name, code.error_goto_if_null(self.result(), self.pos))) code.put_gotref(self.py_result()) @@ -4521,14 +4530,15 @@ def generate_result_code(self, code): code.put_gotref(self.py_result()) -class PyCFunctionNode(ExprNode): +class PyCFunctionNode(ExprNode, ModuleNameMixin): # Helper class used in the implementation of Python # class definitions. Constructs a PyCFunction object # from a PyMethodDef struct. # - # pymethdef_cname string PyMethodDef structure + # pymethdef_cname string PyMethodDef structure # self_object ExprNode or None # binding bool + # module_name EncodedString Name of defining module subexprs = [] self_object = None @@ -4541,6 +4551,9 @@ def analyse_types(self, env): if self.binding: env.use_utility_code(binding_cfunc_utility_code) + #TODO(craig,haoyu) This should be moved to a better place + self.set_mod_name(env) + def may_be_none(self): return False @@ -4555,15 +4568,17 @@ def self_result_code(self): def generate_result_code(self, code): if self.binding: - constructor = "%s_New" % Naming.binding_cfunc + constructor = "%s_NewEx" % Naming.binding_cfunc else: - constructor = "PyCFunction_New" + constructor = "PyCFunction_NewEx" + py_mod_name = self.get_py_mod_name(code) code.putln( - "%s = %s(&%s, %s); %s" % ( + '%s = %s(&%s, %s, %s); %s' % ( self.result(), constructor, self.pymethdef_cname, self.self_result_code(), + py_mod_name, code.error_goto_if_null(self.result(), self.pos))) code.put_gotref(self.py_result()) @@ -7093,23 +7108,15 @@ def generate_result_code(self, code): create_class_utility_code = UtilityCode( proto = """ -static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *name, const char *modname); /*proto*/ +static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *name, PyObject *modname); /*proto*/ """, impl = """ static PyObject *__Pyx_CreateClass( - PyObject *bases, PyObject *dict, PyObject *name, const char *modname) + PyObject *bases, PyObject *dict, PyObject *name, PyObject *modname) { - PyObject *py_modname; PyObject *result = 0; - #if PY_MAJOR_VERSION < 3 - py_modname = PyString_FromString(modname); - #else - py_modname = PyUnicode_FromString(modname); - #endif - if (!py_modname) - goto bad; - if (PyDict_SetItemString(dict, "__module__", py_modname) < 0) + if (PyDict_SetItemString(dict, "__module__", modname) < 0) goto bad; #if PY_MAJOR_VERSION < 3 result = PyClass_New(bases, dict, name); @@ -7117,7 +7124,6 @@ def generate_result_code(self, code): result = PyObject_CallFunctionObjArgs((PyObject *)&PyType_Type, name, bases, dict, NULL); #endif bad: - Py_XDECREF(py_modname); return result; } """) diff --git a/Cython/Compiler/Symtab.py b/Cython/Compiler/Symtab.py index 0c0fccade09..9e0068a41b5 100644 --- a/Cython/Compiler/Symtab.py +++ b/Cython/Compiler/Symtab.py @@ -246,7 +246,7 @@ def __init__(self, name, outer_scope, parent_scope): self.qualified_name = qual_scope.qualify_name(name) self.scope_prefix = qual_scope.scope_prefix + mangled_name else: - self.qualified_name = name + self.qualified_name = EncodedString(name) self.scope_prefix = mangled_name self.entries = {} self.const_entries = [] @@ -348,7 +348,7 @@ def declare(self, name, cname, type, pos, visibility): return entry def qualify_name(self, name): - return "%s.%s" % (self.qualified_name, name) + return EncodedString("%s.%s" % (self.qualified_name, name)) def declare_const(self, name, type, value, pos, cname = None, visibility = 'private'): # Add an entry for a named constant. @@ -813,6 +813,7 @@ def __init__(self, name, parent_module, context): # Treat Spam/__init__.pyx specially, so that when Python loads # Spam/__init__.so, initSpam() is defined. self.module_name = parent_module.module_name + self.module_name = EncodedString(self.module_name) self.context = context self.module_cname = Naming.module_cname self.module_dict_cname = Naming.moddict_cname diff --git a/tests/run/method_module_name_T422.pyx b/tests/run/method_module_name_T422.pyx new file mode 100644 index 00000000000..f7334c266d0 --- /dev/null +++ b/tests/run/method_module_name_T422.pyx @@ -0,0 +1,26 @@ +""" +>>> Foo.incr.__module__ is not None +True +>>> Foo.incr.__module__ == Foo.__module__ == bar.__module__ +True +>>> Simpleton.incr.__module__ == Simpleton.__module__ == bar.__module__ +True + +""" +class Foo(object): + def incr(self,x): + return x+1 + +def bar(): + pass + + +class Simpleton: + def __str__(self): + return "A simpleton" + + def incr(self,x): + """Increment x by one. + """ + return x+1 + From 305e7ba8b5fb93c80f31937652b620c726d869d8 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Fri, 29 Oct 2010 17:17:39 +0200 Subject: [PATCH 29/77] Index: Cython/Compiler/Parsing.py --- Cython/Compiler/Parsing.py | 27 +++++++++++++-------------- tests/compile/ellipsis_T488.pyx | 6 ++++++ tests/run/ellipsis_T488.pyx | 20 ++++++++++++++++++++ 3 files changed, 39 insertions(+), 14 deletions(-) create mode 100644 tests/compile/ellipsis_T488.pyx create mode 100644 tests/run/ellipsis_T488.pyx diff --git a/Cython/Compiler/Parsing.py b/Cython/Compiler/Parsing.py index 8c331b76612..4903bd6ba76 100644 --- a/Cython/Compiler/Parsing.py +++ b/Cython/Compiler/Parsing.py @@ -497,20 +497,16 @@ def p_subscript(s): # 1, 2 or 3 ExprNodes, depending on how # many slice elements were encountered. pos = s.position() - if s.sy == '.': - expect_ellipsis(s) - return [ExprNodes.EllipsisNode(pos)] - else: - start = p_slice_element(s, (':',)) - if s.sy != ':': - return [start] - s.next() - stop = p_slice_element(s, (':', ',', ']')) - if s.sy != ':': - return [start, stop] - s.next() - step = p_slice_element(s, (':', ',', ']')) - return [start, stop, step] + start = p_slice_element(s, (':',)) + if s.sy != ':': + return [start] + s.next() + stop = p_slice_element(s, (':', ',', ']')) + if s.sy != ':': + return [start, stop] + s.next() + step = p_slice_element(s, (':', ',', ']')) + return [start, stop, step] def p_slice_element(s, follow_set): # Simple expression which may be missing iff @@ -569,6 +565,9 @@ def p_atom(s): return p_dict_or_set_maker(s) elif sy == '`': return p_backquote_expr(s) + elif sy == '.': + expect_ellipsis(s) + return ExprNodes.EllipsisNode(pos) elif sy == 'INT': value = s.systring s.next() diff --git a/tests/compile/ellipsis_T488.pyx b/tests/compile/ellipsis_T488.pyx new file mode 100644 index 00000000000..a745112e39d --- /dev/null +++ b/tests/compile/ellipsis_T488.pyx @@ -0,0 +1,6 @@ +#from ... import foo + +print ... +def test(): + x = ... + assert x is Ellipsis diff --git a/tests/run/ellipsis_T488.pyx b/tests/run/ellipsis_T488.pyx new file mode 100644 index 00000000000..909ee608dac --- /dev/null +++ b/tests/run/ellipsis_T488.pyx @@ -0,0 +1,20 @@ +""" +>>> test() +""" +def test(): + x = ... + assert x is Ellipsis + + d = {} + d[...] = 1 + assert d[...] == 1 + del d[...] + assert ... not in d + + d[..., ...] = 1 + assert d[..., ...] == 1 + assert d[..., Ellipsis] == 1 + assert (Ellipsis, Ellipsis) in d + del d[..., ...] + assert (Ellipsis, Ellipsis) not in d + From 0722bb267e8c969a4f742377f09779d74c3657ee Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Sat, 30 Oct 2010 14:39:54 +0200 Subject: [PATCH 30/77] allow decorators on classes in the parser, just disable them on cdef classes later on --- Cython/Compiler/ParseTreeTransforms.py | 12 +++++++----- Cython/Compiler/Parsing.py | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Cython/Compiler/ParseTreeTransforms.py b/Cython/Compiler/ParseTreeTransforms.py index 17bfb3f66ce..9e16924715c 100644 --- a/Cython/Compiler/ParseTreeTransforms.py +++ b/Cython/Compiler/ParseTreeTransforms.py @@ -929,9 +929,8 @@ def visit_DefNode(self, func_node): return self._handle_decorators( func_node, func_node.name) - def _visit_CClassDefNode(self, class_node): - # This doesn't currently work, so it's disabled (also in the - # parser). + def visit_CClassDefNode(self, class_node): + # This doesn't currently work, so it's disabled. # # Problem: assignments to cdef class names do not work. They # would require an additional check anyway, as the extension @@ -941,8 +940,11 @@ def _visit_CClassDefNode(self, class_node): self.visitchildren(class_node) if not class_node.decorators: return class_node - return self._handle_decorators( - class_node, class_node.class_name) + error(class_node.pos, + "Decorators not allowed on cdef classes (used on type '%s')" % class_node.class_name) + return class_node + #return self._handle_decorators( + # class_node, class_node.class_name) def visit_ClassDefNode(self, class_node): self.visitchildren(class_node) diff --git a/Cython/Compiler/Parsing.py b/Cython/Compiler/Parsing.py index 4903bd6ba76..8df5ca102f9 100644 --- a/Cython/Compiler/Parsing.py +++ b/Cython/Compiler/Parsing.py @@ -1681,8 +1681,8 @@ def p_statement(s, ctx, first_statement = 0): s.level = ctx.level node = p_cdef_statement(s, ctx(overridable = overridable)) if decorators is not None: - if not isinstance(node, (Nodes.CFuncDefNode, Nodes.CVarDefNode)): - s.error("Decorators can only be followed by functions or Python classes") + if not isinstance(node, (Nodes.CFuncDefNode, Nodes.CVarDefNode, Nodes.CClassDefNode)): + s.error("Decorators can only be followed by functions or classes") node.decorators = decorators return node else: From 765f2b7a2e62ff352df5ec8a6c2a4cb92c4120e0 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Sat, 30 Oct 2010 15:24:04 +0200 Subject: [PATCH 31/77] clean up directive decorator parsing, support boolean directives as plain names, support directives on cdef classes --- Cython/Compiler/ParseTreeTransforms.py | 116 +++++++++++++++---------- 1 file changed, 68 insertions(+), 48 deletions(-) diff --git a/Cython/Compiler/ParseTreeTransforms.py b/Cython/Compiler/ParseTreeTransforms.py index 9e16924715c..3e6a398bf79 100644 --- a/Cython/Compiler/ParseTreeTransforms.py +++ b/Cython/Compiler/ParseTreeTransforms.py @@ -727,7 +727,18 @@ def try_to_parse_directives(self, node): return directives directives.append(self.try_to_parse_directive(optname, args, kwds, node.function.pos)) return directives - + elif isinstance(node, (AttributeNode, NameNode)): + self.visit(node) + optname = node.as_cython_attribute() + if optname: + directivetype = Options.directive_types.get(optname) + if directivetype is bool: + return [(optname, True)] + elif directivetype is None: + return [(optname, None)] + else: + raise PostParseError( + node.pos, "The '%s' directive should be used as a function call." % optname) return None def try_to_parse_directive(self, optname, args, kwds, pos): @@ -771,57 +782,66 @@ def visit_with_directives(self, body, directives): # Handle decorators def visit_FuncDefNode(self, node): - directives = [] - if node.decorators: - # Split the decorators into two lists -- real decorators and directives - realdecs = [] - for dec in node.decorators: - new_directives = self.try_to_parse_directives(dec.decorator) - if new_directives is not None: - directives.extend(new_directives) + directives = self._extract_directives(node, 'function') + if not directives: + return self.visit_Node(node) + body = StatListNode(node.pos, stats=[node]) + return self.visit_with_directives(body, directives) + + def visit_CVarDefNode(self, node): + for dec in node.decorators: + for directive in self.try_to_parse_directives(dec.decorator) or (): + if directive is not None and directive[0] == u'locals': + node.directive_locals = directive[1] else: - realdecs.append(dec) - if realdecs and isinstance(node, CFuncDefNode): - raise PostParseError(realdecs[0].pos, "Cdef functions cannot take arbitrary decorators.") + self.context.nonfatal_error(PostParseError(dec.pos, + "Cdef functions can only take cython.locals() decorator.")) + return node + + def visit_CClassDefNode(self, node): + directives = self._extract_directives(node, 'cclass') + if not directives: + return self.visit_Node(node) + body = StatListNode(node.pos, stats=[node]) + return self.visit_with_directives(body, directives) + + def _extract_directives(self, node, scope_name): + if not node.decorators: + return {} + # Split the decorators into two lists -- real decorators and directives + directives = [] + realdecs = [] + for dec in node.decorators: + new_directives = self.try_to_parse_directives(dec.decorator) + if new_directives is not None: + for directive in new_directives: + if self.check_directive_scope(node.pos, directive[0], scope_name): + directives.append(directive) else: - node.decorators = realdecs - - if directives: - optdict = {} - directives.reverse() # Decorators coming first take precedence - for directive in directives: - name, value = directive - legal_scopes = Options.directive_scopes.get(name, None) - if not self.check_directive_scope(node.pos, name, 'function'): - continue - if name in optdict: - old_value = optdict[name] - # keywords and arg lists can be merged, everything - # else overrides completely - if isinstance(old_value, dict): - old_value.update(value) - elif isinstance(old_value, list): - old_value.extend(value) - else: - optdict[name] = value + realdecs.append(dec) + if realdecs and isinstance(node, (CFuncDefNode, CClassDefNode)): + raise PostParseError(realdecs[0].pos, "Cdef functions/classes cannot take arbitrary decorators.") + else: + node.decorators = realdecs + # merge or override repeated directives + optdict = {} + directives.reverse() # Decorators coming first take precedence + for directive in directives: + name, value = directive + if name in optdict: + old_value = optdict[name] + # keywords and arg lists can be merged, everything + # else overrides completely + if isinstance(old_value, dict): + old_value.update(value) + elif isinstance(old_value, list): + old_value.extend(value) else: optdict[name] = value - body = StatListNode(node.pos, stats=[node]) - return self.visit_with_directives(body, optdict) - else: - return self.visit_Node(node) - - def visit_CVarDefNode(self, node): - if node.decorators: - for dec in node.decorators: - for directive in self.try_to_parse_directives(dec.decorator) or []: - if directive is not None and directive[0] == u'locals': - node.directive_locals = directive[1] - else: - self.context.nonfatal_error(PostParseError(dec.pos, - "Cdef functions can only take cython.locals() decorator.")) - return node - + else: + optdict[name] = value + return optdict + # Handle with statements def visit_WithStatNode(self, node): directive_dict = {} From 4fabd474d30ce2e070beb79c3b66bd1450a2aa38 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Sat, 30 Oct 2010 18:44:29 +0200 Subject: [PATCH 32/77] fix decorators in external .pxd files after last commit --- Cython/Compiler/ParseTreeTransforms.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cython/Compiler/ParseTreeTransforms.py b/Cython/Compiler/ParseTreeTransforms.py index 3e6a398bf79..8098828a230 100644 --- a/Cython/Compiler/ParseTreeTransforms.py +++ b/Cython/Compiler/ParseTreeTransforms.py @@ -789,6 +789,8 @@ def visit_FuncDefNode(self, node): return self.visit_with_directives(body, directives) def visit_CVarDefNode(self, node): + if not node.decorators: + return node for dec in node.decorators: for directive in self.try_to_parse_directives(dec.decorator) or (): if directive is not None and directive[0] == u'locals': From 9b57b1c56271b06108d92cc78f623c1c9b16a12a Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Sat, 30 Oct 2010 19:04:39 +0200 Subject: [PATCH 33/77] support for 'final' cdef types using a directive decorator --- Cython/Compiler/Options.py | 2 ++ Cython/Compiler/TypeSlots.py | 4 +++- tests/run/final_cdef_class.pyx | 43 ++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 tests/run/final_cdef_class.pyx diff --git a/Cython/Compiler/Options.py b/Cython/Compiler/Options.py index ccc5c0f9e8a..fd7666ac9c8 100644 --- a/Cython/Compiler/Options.py +++ b/Cython/Compiler/Options.py @@ -81,6 +81,7 @@ # Override types possibilities above, if needed directive_types = { + 'final' : bool, # final cdef classes and methods 'infer_types' : bool, # values can be True/None/False } @@ -90,6 +91,7 @@ directive_scopes = { # defaults to available everywhere # 'module', 'function', 'class', 'with statement' + 'final' : ('cclass',), # add 'method' in the future 'autotestdict' : ('module',), 'test_assert_path_exists' : ('function',), 'test_fail_if_path_exists' : ('function',), diff --git a/Cython/Compiler/TypeSlots.py b/Cython/Compiler/TypeSlots.py index b4dbdda99f2..6e1b1131834 100644 --- a/Cython/Compiler/TypeSlots.py +++ b/Cython/Compiler/TypeSlots.py @@ -323,7 +323,9 @@ class TypeFlagsSlot(SlotDescriptor): # Descriptor for the type flags slot. def slot_code(self, scope): - value = "Py_TPFLAGS_DEFAULT|Py_TPFLAGS_CHECKTYPES|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_NEWBUFFER" + value = "Py_TPFLAGS_DEFAULT|Py_TPFLAGS_CHECKTYPES|Py_TPFLAGS_HAVE_NEWBUFFER" + if not scope.directives.get('final', False): + value += "|Py_TPFLAGS_BASETYPE" if scope.needs_gc(): value += "|Py_TPFLAGS_HAVE_GC" return value diff --git a/tests/run/final_cdef_class.pyx b/tests/run/final_cdef_class.pyx new file mode 100644 index 00000000000..6845c71ec42 --- /dev/null +++ b/tests/run/final_cdef_class.pyx @@ -0,0 +1,43 @@ + +cimport cython + +@cython.final +cdef class FinalClass: + """ + >>> f = FinalClass() + >>> test_final_class(f) + Type tested + + >>> try: + ... class SubType(FinalClass): pass + ... except TypeError: + ... print 'PASSED!' + PASSED! + """ + +cdef class NonFinalClass: + """ + >>> class SubType(NonFinalClass): pass + >>> s = SubType() + """ + +@cython.final +cdef class FinalSubClass(NonFinalClass): + """ + >>> f = FinalSubClass() + >>> test_non_final_class(f) + Type tested + + >>> try: + ... class SubType(FinalSubClass): pass + ... except TypeError: + ... print 'PASSED!' + PASSED! + """ + + +def test_final_class(FinalClass c): + print u"Type tested" + +def test_non_final_class(NonFinalClass c): + print u"Type tested" From 3fcfc62da07f088c79413fb782b4d8073a5d1c37 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Sat, 30 Oct 2010 19:35:26 +0200 Subject: [PATCH 34/77] prevent subtyping final types in Cython (inside of the same module) --- Cython/Compiler/Nodes.py | 7 ++++++- tests/errors/subtyping_final_class.pyx | 13 +++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tests/errors/subtyping_final_class.pyx diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py index a7c9592683a..e109b5071ba 100644 --- a/Cython/Compiler/Nodes.py +++ b/Cython/Compiler/Nodes.py @@ -3104,7 +3104,12 @@ def analyse_declarations(self, env): elif not base_class_entry.type.is_extension_type: error(self.pos, "'%s' is not an extension type" % self.base_class_name) elif not base_class_entry.type.is_complete(): - error(self.pos, "Base class '%s' is incomplete" % self.base_class_name) + error(self.pos, "Base class '%s' of type '%s' is incomplete" % ( + self.base_class_name, self.class_name)) + elif base_class_entry.type.scope and base_class_entry.type.scope.directives and \ + base_class_entry.type.scope.directives.get('final', False): + error(self.pos, "Base class '%s' of type '%s' is final" % ( + self.base_class_name, self.class_name)) else: self.base_type = base_class_entry.type has_body = self.body is not None diff --git a/tests/errors/subtyping_final_class.pyx b/tests/errors/subtyping_final_class.pyx new file mode 100644 index 00000000000..477669b1dd9 --- /dev/null +++ b/tests/errors/subtyping_final_class.pyx @@ -0,0 +1,13 @@ + +cimport cython + +@cython.final +cdef class FinalClass: + pass + +cdef class SubType(FinalClass): + pass + +_ERRORS = """ +8:5: Base class 'FinalClass' of type 'SubType' is final +""" From 4ef3dcb642b07a757d5768c2732963bca8c19870 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Sat, 30 Oct 2010 19:54:35 +0200 Subject: [PATCH 35/77] fix scope of closures that was missing a 'directives' dict, explicitly mark closure classes 'final' --- Cython/Compiler/ParseTreeTransforms.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Cython/Compiler/ParseTreeTransforms.py b/Cython/Compiler/ParseTreeTransforms.py index 8098828a230..e04b28bfc18 100644 --- a/Cython/Compiler/ParseTreeTransforms.py +++ b/Cython/Compiler/ParseTreeTransforms.py @@ -1293,6 +1293,7 @@ def create_class_from_scope(self, node, target_module_scope): func_scope.scope_class = entry class_scope = entry.type.scope class_scope.is_internal = True + class_scope.directives = {'final': True} if node.entry.scope.is_closure_scope: class_scope.declare_var(pos=node.pos, name=Naming.outer_scope_cname, # this could conflict? From 46e443be5b3dfbef7c664ca6730a92941c616246 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Sat, 30 Oct 2010 20:00:47 +0200 Subject: [PATCH 36/77] more missing 'directives' dicts, 'bool' actually is final in CPython --- Cython/Compiler/PyrexTypes.py | 1 + Cython/Compiler/Symtab.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Cython/Compiler/PyrexTypes.py b/Cython/Compiler/PyrexTypes.py index 3509bd85b94..6a17c06d1ad 100755 --- a/Cython/Compiler/PyrexTypes.py +++ b/Cython/Compiler/PyrexTypes.py @@ -1072,6 +1072,7 @@ def attributes_known(self): None, visibility="extern") scope.parent_type = self + scope.directives = {} scope.declare_var("real", self.real_type, None, "real", is_cdef=True) scope.declare_var("imag", self.real_type, None, "imag", is_cdef=True) entry = scope.declare_cfunction( diff --git a/Cython/Compiler/Symtab.py b/Cython/Compiler/Symtab.py index 9e0068a41b5..a160842fd22 100644 --- a/Cython/Compiler/Symtab.py +++ b/Cython/Compiler/Symtab.py @@ -726,7 +726,11 @@ def declare_builtin_cfunction(self, name, type, cname, python_equiv = None, def declare_builtin_type(self, name, cname, utility_code = None): name = EncodedString(name) type = PyrexTypes.BuiltinObjectType(name, cname) - type.set_scope(CClassScope(name, outer_scope=None, visibility='extern')) + scope = CClassScope(name, outer_scope=None, visibility='extern') + scope.directives = {} + if name == 'bool': + scope.directives['final'] = True + type.set_scope(scope) self.type_names[name] = 1 entry = self.declare_type(name, type, None, visibility='extern') entry.utility_code = utility_code From 55ae33d77d2d7985c3545c75d5b7b019da902aa6 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Sat, 30 Oct 2010 20:59:13 +0200 Subject: [PATCH 37/77] support 'internal' cdef classes that do not show up in the module dict --- Cython/Compiler/ModuleNode.py | 16 ++++++++++------ Cython/Compiler/Nodes.py | 2 +- Cython/Compiler/Options.py | 4 ++++ Cython/Compiler/TypeSlots.py | 2 +- tests/run/internal_cdef_class.pyx | 29 +++++++++++++++++++++++++++++ 5 files changed, 45 insertions(+), 8 deletions(-) create mode 100644 tests/run/internal_cdef_class.pyx diff --git a/Cython/Compiler/ModuleNode.py b/Cython/Compiler/ModuleNode.py index 27188d3d4b4..a3650396d5e 100644 --- a/Cython/Compiler/ModuleNode.py +++ b/Cython/Compiler/ModuleNode.py @@ -2075,12 +2075,16 @@ def generate_type_ready_code(self, env, entry, code): type.vtabptr_cname, code.error_goto(entry.pos))) env.use_utility_code(Nodes.set_vtable_utility_code) - code.putln( - 'if (__Pyx_SetAttrString(%s, "%s", (PyObject *)&%s) < 0) %s' % ( - Naming.module_cname, - scope.class_name, - typeobj_cname, - code.error_goto(entry.pos))) + if not type.scope.is_internal and not type.scope.directives['internal']: + # scope.is_internal is set for types defined by + # Cython (such as closures), the 'internal' + # directive is set by users + code.putln( + 'if (__Pyx_SetAttrString(%s, "%s", (PyObject *)&%s) < 0) %s' % ( + Naming.module_cname, + scope.class_name, + typeobj_cname, + code.error_goto(entry.pos))) weakref_entry = scope.lookup_here("__weakref__") if weakref_entry: if weakref_entry.type is py_object_type: diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py index e109b5071ba..92536a7a3aa 100644 --- a/Cython/Compiler/Nodes.py +++ b/Cython/Compiler/Nodes.py @@ -3107,7 +3107,7 @@ def analyse_declarations(self, env): error(self.pos, "Base class '%s' of type '%s' is incomplete" % ( self.base_class_name, self.class_name)) elif base_class_entry.type.scope and base_class_entry.type.scope.directives and \ - base_class_entry.type.scope.directives.get('final', False): + base_class_entry.type.scope.directives['final']: error(self.pos, "Base class '%s' of type '%s' is final" % ( self.base_class_name, self.class_name)) else: diff --git a/Cython/Compiler/Options.py b/Cython/Compiler/Options.py index fd7666ac9c8..f36a33a05ce 100644 --- a/Cython/Compiler/Options.py +++ b/Cython/Compiler/Options.py @@ -62,6 +62,8 @@ 'wraparound' : True, 'ccomplex' : False, # use C99/C++ for complex types and arith 'callspec' : "", + 'final' : False, + 'internal' : False, 'profile': False, 'infer_types': None, 'infer_types.verbose': False, @@ -82,6 +84,7 @@ # Override types possibilities above, if needed directive_types = { 'final' : bool, # final cdef classes and methods + 'internal' : bool, # cdef class visibility in the module dict 'infer_types' : bool, # values can be True/None/False } @@ -92,6 +95,7 @@ directive_scopes = { # defaults to available everywhere # 'module', 'function', 'class', 'with statement' 'final' : ('cclass',), # add 'method' in the future + 'internal' : ('cclass',), 'autotestdict' : ('module',), 'test_assert_path_exists' : ('function',), 'test_fail_if_path_exists' : ('function',), diff --git a/Cython/Compiler/TypeSlots.py b/Cython/Compiler/TypeSlots.py index 6e1b1131834..a85bbae1eff 100644 --- a/Cython/Compiler/TypeSlots.py +++ b/Cython/Compiler/TypeSlots.py @@ -324,7 +324,7 @@ class TypeFlagsSlot(SlotDescriptor): def slot_code(self, scope): value = "Py_TPFLAGS_DEFAULT|Py_TPFLAGS_CHECKTYPES|Py_TPFLAGS_HAVE_NEWBUFFER" - if not scope.directives.get('final', False): + if not scope.directives['final']: value += "|Py_TPFLAGS_BASETYPE" if scope.needs_gc(): value += "|Py_TPFLAGS_HAVE_GC" diff --git a/tests/run/internal_cdef_class.pyx b/tests/run/internal_cdef_class.pyx new file mode 100644 index 00000000000..e532cc620c2 --- /dev/null +++ b/tests/run/internal_cdef_class.pyx @@ -0,0 +1,29 @@ + +cimport cython + + +@cython.internal +cdef class InternalType: + """ + NOTE: this doesn't fail because it is never tested ! + >>> i = InternalType + """ + +cdef class PublicType: + """ + >>> p = PublicType + """ + +def test(): + """ + >>> p,i = test() + + >>> p = PublicType + + >>> i = InternalType + Traceback (most recent call last): + NameError: name 'InternalType' is not defined + """ + p = PublicType + i = InternalType + return p,i From 6f1c3a892aaca81bf0f0180c38fdf880aa3f6b24 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Sat, 30 Oct 2010 22:01:55 +0200 Subject: [PATCH 38/77] new (partial) test case for ticket #582 --- tests/run/charptr_comparison_T582.pyx | 75 +++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 tests/run/charptr_comparison_T582.pyx diff --git a/tests/run/charptr_comparison_T582.pyx b/tests/run/charptr_comparison_T582.pyx new file mode 100644 index 00000000000..c30a431c694 --- /dev/null +++ b/tests/run/charptr_comparison_T582.pyx @@ -0,0 +1,75 @@ + +cimport cython + +@cython.test_assert_path_exists('//SingleAssignmentNode') +#FIXME: optimise me! +#@cython.test_fail_if_path_exists('//SingleAssignmentNode//CoerceFromPyTypeNode') +def slice_equals_literal(char* s): + """ + >>> slice_equals_literal('abc'.encode('ASCII')) + True + >>> slice_equals_literal('aabc'.encode('ASCII')) + False + >>> slice_equals_literal('abcx'.encode('ASCII')) + True + >>> slice_equals_literal('bcx'.encode('ASCII')) + False + """ + cdef bint result = (s[:3] == b"abc") + return result + +def slice_gt_literal(char* s): + """ + >>> slice_gt_literal('abc'.encode('ASCII')) + False + >>> slice_gt_literal('aabc'.encode('ASCII')) + False + >>> slice_gt_literal('abcx'.encode('ASCII')) + False + >>> slice_gt_literal('bcx'.encode('ASCII')) + True + """ + cdef bint result = (s[:3] > b"abc") + return result + +def slice_lt_literal(char* s): + """ + >>> slice_lt_literal('abc'.encode('ASCII')) + False + >>> slice_lt_literal('aabc'.encode('ASCII')) + True + >>> slice_lt_literal('abcx'.encode('ASCII')) + False + >>> slice_lt_literal('bcx'.encode('ASCII')) + False + """ + cdef bint result = (s[:3] < b"abc") + return result + +def slice_ge_literal(char* s): + """ + >>> slice_ge_literal('abc'.encode('ASCII')) + True + >>> slice_ge_literal('aabc'.encode('ASCII')) + False + >>> slice_ge_literal('abcx'.encode('ASCII')) + True + >>> slice_ge_literal('bcx'.encode('ASCII')) + True + """ + cdef bint result = (s[:3] >= b"abc") + return result + +def slice_le_literal(char* s): + """ + >>> slice_le_literal('abc'.encode('ASCII')) + True + >>> slice_le_literal('aabc'.encode('ASCII')) + True + >>> slice_le_literal('abcx'.encode('ASCII')) + True + >>> slice_le_literal('bcx'.encode('ASCII')) + False + """ + cdef bint result = (s[:3] <= b"abc") + return result From 02623216042e07b66094710a6aaf211903c4e96f Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Sat, 30 Oct 2010 22:03:28 +0200 Subject: [PATCH 39/77] Py3 test fix --- tests/run/final_cdef_class.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/run/final_cdef_class.pyx b/tests/run/final_cdef_class.pyx index 6845c71ec42..e11df209853 100644 --- a/tests/run/final_cdef_class.pyx +++ b/tests/run/final_cdef_class.pyx @@ -11,7 +11,7 @@ cdef class FinalClass: >>> try: ... class SubType(FinalClass): pass ... except TypeError: - ... print 'PASSED!' + ... print('PASSED!') PASSED! """ @@ -31,7 +31,7 @@ cdef class FinalSubClass(NonFinalClass): >>> try: ... class SubType(FinalSubClass): pass ... except TypeError: - ... print 'PASSED!' + ... print('PASSED!') PASSED! """ From ff27eac9af38bbaca3273c5a8902c3f5463991a0 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Sat, 30 Oct 2010 22:19:11 +0200 Subject: [PATCH 40/77] make bytes the common type of char* and bytes literal in comparisons (ticket #582) --- Cython/Compiler/ExprNodes.py | 6 ++ tests/run/charptr_comparison_T582.pyx | 79 +++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index 62de863ceb0..50ee5a74dc9 100755 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -6031,6 +6031,12 @@ def find_common_type(self, env, op, operand1, common_type=None): self.invalid_types_error(operand1, op, operand2) new_common_type = error_type + if new_common_type.is_string and (isinstance(operand1, BytesNode) or + isinstance(operand2, BytesNode)): + # special case when comparing char* to bytes literal: must + # compare string values! + new_common_type = bytes_type + # recursively merge types if common_type is None or new_common_type.is_error: common_type = new_common_type diff --git a/tests/run/charptr_comparison_T582.pyx b/tests/run/charptr_comparison_T582.pyx index c30a431c694..aecb5b92b1e 100644 --- a/tests/run/charptr_comparison_T582.pyx +++ b/tests/run/charptr_comparison_T582.pyx @@ -1,6 +1,85 @@ cimport cython +################################################################################ +## plain char* + +@cython.test_assert_path_exists('//SingleAssignmentNode') +#@cython.test_fail_if_path_exists('//SingleAssignmentNode//CoerceFromPyTypeNode') +def charptr_equals_literal(char* s): + """ + >>> charptr_equals_literal('abc'.encode('ASCII')) + True + >>> charptr_equals_literal('aabc'.encode('ASCII')) + False + >>> charptr_equals_literal('abcx'.encode('ASCII')) + False + >>> charptr_equals_literal('bcx'.encode('ASCII')) + False + """ + cdef bint result = (s == b"abc") + return result + +def charptr_gt_literal(char* s): + """ + >>> charptr_gt_literal('abc'.encode('ASCII')) + False + >>> charptr_gt_literal('aabc'.encode('ASCII')) + False + >>> charptr_gt_literal('abcx'.encode('ASCII')) + True + >>> charptr_gt_literal('bcx'.encode('ASCII')) + True + """ + cdef bint result = (s > b"abc") + return result + +def charptr_lt_literal(char* s): + """ + >>> charptr_lt_literal('abc'.encode('ASCII')) + False + >>> charptr_lt_literal('aabc'.encode('ASCII')) + True + >>> charptr_lt_literal('abcx'.encode('ASCII')) + False + >>> charptr_lt_literal('bcx'.encode('ASCII')) + False + """ + cdef bint result = (s < b"abc") + return result + +def charptr_ge_literal(char* s): + """ + >>> charptr_ge_literal('abc'.encode('ASCII')) + True + >>> charptr_ge_literal('aabc'.encode('ASCII')) + False + >>> charptr_ge_literal('abcx'.encode('ASCII')) + True + >>> charptr_ge_literal('bcx'.encode('ASCII')) + True + """ + cdef bint result = (s >= b"abc") + return result + +def charptr_le_literal(char* s): + """ + >>> charptr_le_literal('abc'.encode('ASCII')) + True + >>> charptr_le_literal('aabc'.encode('ASCII')) + True + >>> charptr_le_literal('abcx'.encode('ASCII')) + False + >>> charptr_le_literal('bcx'.encode('ASCII')) + False + """ + cdef bint result = (s <= b"abc") + return result + + +################################################################################ +## slices + @cython.test_assert_path_exists('//SingleAssignmentNode') #FIXME: optimise me! #@cython.test_fail_if_path_exists('//SingleAssignmentNode//CoerceFromPyTypeNode') From 799bf36be96229b9a4b5189b2108b89124c585c0 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Sat, 30 Oct 2010 21:20:56 -0700 Subject: [PATCH 41/77] Basic cython.inline --- Cython/Build/Inline.py | 74 ++++++++++++++++++++++++++++++++++++++++ Cython/Build/__init__.py | 0 Cython/Shadow.py | 12 +++++++ 3 files changed, 86 insertions(+) create mode 100644 Cython/Build/Inline.py create mode 100644 Cython/Build/__init__.py diff --git a/Cython/Build/Inline.py b/Cython/Build/Inline.py new file mode 100644 index 00000000000..a0b27f590df --- /dev/null +++ b/Cython/Build/Inline.py @@ -0,0 +1,74 @@ +import tempfile +import sys, os, re + +try: + import hashlib +except ImportError: + import md5 as hashlib + +from distutils.dist import Distribution +from distutils.core import Extension +from Cython.Distutils import build_ext + +code_cache = {} + +def get_type(arg): + py_type = type(arg) + # TODO: numpy + # TODO: extension types + if py_type in [list, tuple, dict, str]: + return py_type.__name__ + elif py_type is float: + return 'double' + elif py_type is int: + return 'long' + else: + return 'object' + +# TODO: use locals/globals for unbound variables +def cython_inline(code, types='aggressive', lib_dir=os.path.expanduser('~/.cython/inline'), **kwds): + _, pyx_file = tempfile.mkstemp('.pyx') + arg_names = kwds.keys() + arg_names.sort() + arg_sigs = tuple((get_type(kwds[arg]), arg) for arg in arg_names) + key = code, arg_sigs + module = code_cache.get(key) + if not module: + module_body, extract_func_code = extract_bodies(code) + params = ', '.join('%s %s' % a for a in arg_sigs) + module_code = """ +%(module_body)s +def __invoke(%(params)s): +%(func_body)s + """ % locals() + open(pyx_file, 'w').write(module_code) + module = "_" + hashlib.md5(code + str(arg_sigs)).hexdigest() + extension = Extension( + name = module, + sources=[pyx_file]) + build_extension = build_ext(Distribution()) + build_extension.finalize_options() + build_extension.extensions = [extension] + build_extension.build_temp = os.path.dirname(pyx_file) + if lib_dir not in sys.path: + sys.path.append(lib_dir) + build_extension.build_lib = lib_dir + build_extension.run() + code_cache[key] = module + arg_list = [kwds[arg] for arg in arg_names] + return __import__(module).__invoke(*arg_list) + +module_statement = re.compile(r'^((cdef +(extern|class))|cimport|(from .+ cimport)|(from .+ import +[*]))') +def extract_func_code(code): + module = [] + function = [] + # TODO: string literals, backslash + current = function + for line in code.split('\n'): + if not line.startswith(' '): + if module_statement.match(line): + current = module + else: + current = function + current.append(line) + return '\n'.join(module), ' ' + '\n '.join(function) diff --git a/Cython/Build/__init__.py b/Cython/Build/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Cython/Shadow.py b/Cython/Shadow.py index d7dd186d5b5..72b864bd751 100644 --- a/Cython/Shadow.py +++ b/Cython/Shadow.py @@ -1,11 +1,23 @@ +# cython.* namespace for pure mode. + compiled = False def empty_decorator(x): return x +# Function decorators + def locals(**arg_types): return empty_decorator +def inline(f, *args, **kwds): + if isinstance(f, basestring): + from Cython.Build.Inline import cython_inline + return cython_inline(f, *args, **kwds) + else: + assert len(args) == len(kwds) == 0 + return f + # Special functions def cdiv(a, b): From d0a66db8c92ebd34d8453a2cc3bfee1759a68ade Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Sat, 30 Oct 2010 21:35:25 -0700 Subject: [PATCH 42/77] Strip common indentation in inline code. --- Cython/Build/Inline.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/Cython/Build/Inline.py b/Cython/Build/Inline.py index a0b27f590df..dfadec6a1c5 100644 --- a/Cython/Build/Inline.py +++ b/Cython/Build/Inline.py @@ -34,7 +34,7 @@ def cython_inline(code, types='aggressive', lib_dir=os.path.expanduser('~/.cytho key = code, arg_sigs module = code_cache.get(key) if not module: - module_body, extract_func_code = extract_bodies(code) + module_body, func_body = extract_func_code(code) params = ', '.join('%s %s' % a for a in arg_sigs) module_code = """ %(module_body)s @@ -58,13 +58,39 @@ def __invoke(%(params)s): arg_list = [kwds[arg] for arg in arg_names] return __import__(module).__invoke(*arg_list) +non_space = re.compile('[^ ]') +def strip_common_indent(lines): + min_indent = None + for line in lines: + if not line: + continue # empty + indent = non_space.search(line).start() + if indent == len(line): + continue # blank + elif line[indent] == '#': + continue # comment + elif min_indent is None or min_indent > indent: + min_indent = indent + for line in lines: + if not line: + continue + indent = non_space.search(line).start() + if indent == len(line): + continue + elif line[indent] == '#': + yield line + else: + yield line[min_indent:] + module_statement = re.compile(r'^((cdef +(extern|class))|cimport|(from .+ cimport)|(from .+ import +[*]))') def extract_func_code(code): module = [] function = [] # TODO: string literals, backslash current = function - for line in code.split('\n'): + code = code.replace('\t', ' ') + lines = strip_common_indent(code.split('\n')) + for line in lines: if not line.startswith(' '): if module_statement.match(line): current = module From 2d2fba93da93924700a0195a5b0c6fb7bb992fc2 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Sat, 30 Oct 2010 22:21:07 -0700 Subject: [PATCH 43/77] numpy and extension types for runtime cython --- Cython/Build/Inline.py | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/Cython/Build/Inline.py b/Cython/Build/Inline.py index dfadec6a1c5..4de123e402d 100644 --- a/Cython/Build/Inline.py +++ b/Cython/Build/Inline.py @@ -7,45 +7,69 @@ import md5 as hashlib from distutils.dist import Distribution -from distutils.core import Extension +from Cython.Distutils.extension import Extension from Cython.Distutils import build_ext - + +from Cython.Compiler.Main import Context, CompilationOptions, default_options + code_cache = {} -def get_type(arg): + +def get_type(arg, context=None): py_type = type(arg) - # TODO: numpy # TODO: extension types if py_type in [list, tuple, dict, str]: return py_type.__name__ elif py_type is float: return 'double' + elif py_type is bool: + return 'bint' elif py_type is int: return 'long' + elif 'numpy' in sys.modules and isinstance(arg, sys.modules['numpy'].ndarray): + return 'numpy.ndarray[numpy.%s_t, ndim=%s]' % (arg.dtype.name, arg.ndim) else: + for base_type in py_type.mro(): + if base_type.__module__ == '__builtin__': + return 'object' + module = context.find_module(base_type.__module__, need_pxd=False) + if module: + entry = module.lookup(base_type.__name__) + if entry.is_type: + return '%s.%s' % (base_type.__module__, base_type.__name__) return 'object' # TODO: use locals/globals for unbound variables -def cython_inline(code, types='aggressive', lib_dir=os.path.expanduser('~/.cython/inline'), **kwds): +def cython_inline(code, types='aggressive', lib_dir=os.path.expanduser('~/.cython/inline'), include_dirs=['.'], **kwds): + ctx = Context(include_dirs, default_options) _, pyx_file = tempfile.mkstemp('.pyx') arg_names = kwds.keys() arg_names.sort() - arg_sigs = tuple((get_type(kwds[arg]), arg) for arg in arg_names) + arg_sigs = tuple((get_type(kwds[arg], ctx), arg) for arg in arg_names) key = code, arg_sigs module = code_cache.get(key) if not module: + cimports = '' + qualified = re.compile(r'([.\w]+)[.]') + for type, _ in arg_sigs: + m = qualified.match(type) + if m: + cimports += '\ncimport %s' % m.groups()[0] module_body, func_body = extract_func_code(code) params = ', '.join('%s %s' % a for a in arg_sigs) module_code = """ +%(cimports)s %(module_body)s def __invoke(%(params)s): %(func_body)s """ % locals() + print module_code open(pyx_file, 'w').write(module_code) module = "_" + hashlib.md5(code + str(arg_sigs)).hexdigest() extension = Extension( name = module, - sources=[pyx_file]) + sources = [pyx_file], + pyrex_include_dirs = include_dirs) build_extension = build_ext(Distribution()) build_extension.finalize_options() build_extension.extensions = [extension] From 880dee0a5701d0c9d6efc06b19297846ce9d2394 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Sat, 30 Oct 2010 23:55:13 -0700 Subject: [PATCH 44/77] Use unbound symbols from local/global scope. --- Cython/Build/Inline.py | 79 ++++++++++++++++++++++++++++----- Cython/Compiler/TreeFragment.py | 3 ++ 2 files changed, 71 insertions(+), 11 deletions(-) diff --git a/Cython/Build/Inline.py b/Cython/Build/Inline.py index 4de123e402d..cc15a3a00b8 100644 --- a/Cython/Build/Inline.py +++ b/Cython/Build/Inline.py @@ -1,5 +1,7 @@ +print "Warning: Using prototype cython.inline code..." + import tempfile -import sys, os, re +import sys, os, re, inspect try: import hashlib @@ -12,12 +14,44 @@ from Cython.Compiler.Main import Context, CompilationOptions, default_options -code_cache = {} +from Cython.Compiler.ParseTreeTransforms import CythonTransform, SkipDeclarations, AnalyseDeclarationsTransform +from Cython.Compiler.TreeFragment import parse_from_strings + +_code_cache = {} + +class AllSymbols(CythonTransform, SkipDeclarations): + def __init__(self): + CythonTransform.__init__(self, None) + self.names = set() + def visit_NameNode(self, node): + self.names.add(node.name) + +def unbound_symbols(code, context=None): + if context is None: + context = Context([], default_options) + from Cython.Compiler.ParseTreeTransforms import AnalyseDeclarationsTransform + if isinstance(code, str): + code = code.decode('ascii') + tree = parse_from_strings('(tree fragment)', code) + for phase in context.create_pipeline(pxd=False): + if phase is None: + continue + tree = phase(tree) + if isinstance(phase, AnalyseDeclarationsTransform): + break + symbol_collector = AllSymbols() + symbol_collector(tree) + unbound = [] + import __builtin__ + for name in symbol_collector.names: + if not tree.scope.lookup(name) and not hasattr(__builtin__, name): + unbound.append(name) + return unbound + def get_type(arg, context=None): py_type = type(arg) - # TODO: extension types if py_type in [list, tuple, dict, str]: return py_type.__name__ elif py_type is float: @@ -40,21 +74,43 @@ def get_type(arg, context=None): return 'object' # TODO: use locals/globals for unbound variables -def cython_inline(code, types='aggressive', lib_dir=os.path.expanduser('~/.cython/inline'), include_dirs=['.'], **kwds): +def cython_inline(code, + types='aggressive', + lib_dir=os.path.expanduser('~/.cython/inline'), + include_dirs=['.'], + locals=None, + globals=None, + **kwds): ctx = Context(include_dirs, default_options) - _, pyx_file = tempfile.mkstemp('.pyx') + if locals is None: + locals = inspect.currentframe().f_back.f_back.f_locals + if globals is None: + globals = inspect.currentframe().f_back.f_back.f_globals + try: + for symbol in unbound_symbols(code): + if symbol in kwds: + continue + elif symbol in locals: + kwds[symbol] = locals[symbol] + elif symbol in globals: + kwds[symbol] = globals[symbol] + else: + print "Couldn't find ", symbol + except AssertionError: + # Parsing from strings not fully supported (e.g. cimports). + print "Could not parse code as a string (to extract unbound symbols)." arg_names = kwds.keys() arg_names.sort() arg_sigs = tuple((get_type(kwds[arg], ctx), arg) for arg in arg_names) key = code, arg_sigs - module = code_cache.get(key) + module = _code_cache.get(key) if not module: - cimports = '' + cimports = [] qualified = re.compile(r'([.\w]+)[.]') for type, _ in arg_sigs: m = qualified.match(type) if m: - cimports += '\ncimport %s' % m.groups()[0] + cimports.append('\ncimport %s' % m.groups()[0]) module_body, func_body = extract_func_code(code) params = ', '.join('%s %s' % a for a in arg_sigs) module_code = """ @@ -62,8 +118,9 @@ def cython_inline(code, types='aggressive', lib_dir=os.path.expanduser('~/.cytho %(module_body)s def __invoke(%(params)s): %(func_body)s - """ % locals() - print module_code + """ % {'cimports': '\n'.join(cimports), 'module_body': module_body, 'params': params, 'func_body': func_body } +# print module_code + _, pyx_file = tempfile.mkstemp('.pyx') open(pyx_file, 'w').write(module_code) module = "_" + hashlib.md5(code + str(arg_sigs)).hexdigest() extension = Extension( @@ -78,7 +135,7 @@ def __invoke(%(params)s): sys.path.append(lib_dir) build_extension.build_lib = lib_dir build_extension.run() - code_cache[key] = module + _code_cache[key] = module arg_list = [kwds[arg] for arg in arg_names] return __import__(module).__invoke(*arg_list) diff --git a/Cython/Compiler/TreeFragment.py b/Cython/Compiler/TreeFragment.py index 66feaf09e61..13e0dc11154 100644 --- a/Cython/Compiler/TreeFragment.py +++ b/Cython/Compiler/TreeFragment.py @@ -60,6 +60,7 @@ def parse_from_strings(name, code, pxds={}, level=None, initial_pos=None): scope = scope, context = context, initial_pos = initial_pos) if level is None: tree = Parsing.p_module(scanner, 0, module_name) + tree.scope = scope else: tree = Parsing.p_code(scanner, level=level) return tree @@ -201,6 +202,8 @@ def fmt(x): return u"\n".join(strip_common_indent(x.split(u"\n"))) if not isinstance(t, StatListNode): t = StatListNode(pos=mod.pos, stats=[t]) for transform in pipeline: + if transform is None: + continue t = transform(t) self.root = t elif isinstance(code, Node): From 240d4885b23a79b1a244ee6767f513c38072d669 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Sun, 31 Oct 2010 00:08:13 -0700 Subject: [PATCH 45/77] Another entry in .hgignore. --- .hgignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgignore b/.hgignore index a4e97a7fed2..6fa71316b16 100644 --- a/.hgignore +++ b/.hgignore @@ -6,6 +6,7 @@ syntax: glob Cython/Compiler/Lexicon.pickle BUILD/ build/ +dist/ .coverage *~ *.orig From 780e405083d60c6c74997d4b222abc5ec6758ba2 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Sun, 31 Oct 2010 00:10:40 -0700 Subject: [PATCH 46/77] Python 2.3 fix. --- Cython/Build/Inline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cython/Build/Inline.py b/Cython/Build/Inline.py index cc15a3a00b8..0a706980bfe 100644 --- a/Cython/Build/Inline.py +++ b/Cython/Build/Inline.py @@ -101,7 +101,7 @@ def cython_inline(code, print "Could not parse code as a string (to extract unbound symbols)." arg_names = kwds.keys() arg_names.sort() - arg_sigs = tuple((get_type(kwds[arg], ctx), arg) for arg in arg_names) + arg_sigs = tuple([(get_type(kwds[arg], ctx), arg) for arg in arg_names]) key = code, arg_sigs module = _code_cache.get(key) if not module: From 2ac6a6225275600a35f38a00da53228f32cc2a9f Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Sun, 31 Oct 2010 00:20:00 -0700 Subject: [PATCH 47/77] Fix indent stripping. --- Cython/Build/Inline.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/Cython/Build/Inline.py b/Cython/Build/Inline.py index 0a706980bfe..96f3cf3fce3 100644 --- a/Cython/Build/Inline.py +++ b/Cython/Build/Inline.py @@ -81,6 +81,7 @@ def cython_inline(code, locals=None, globals=None, **kwds): + code = strip_common_indent(code) ctx = Context(include_dirs, default_options) if locals is None: locals = inspect.currentframe().f_back.f_back.f_locals @@ -140,28 +141,25 @@ def __invoke(%(params)s): return __import__(module).__invoke(*arg_list) non_space = re.compile('[^ ]') -def strip_common_indent(lines): +def strip_common_indent(code): min_indent = None + lines = code.split('\n') for line in lines: - if not line: - continue # empty - indent = non_space.search(line).start() - if indent == len(line): + match = non_space.search(line) + if not match: continue # blank - elif line[indent] == '#': + indent = match.start() + if line[indent] == '#': continue # comment elif min_indent is None or min_indent > indent: min_indent = indent - for line in lines: - if not line: - continue - indent = non_space.search(line).start() - if indent == len(line): + for ix, line in enumerate(lines): + match = non_space.search(line) + if not match or line[indent] == '#': continue - elif line[indent] == '#': - yield line else: - yield line[min_indent:] + lines[ix] = line[min_indent:] + return '\n'.join(lines) module_statement = re.compile(r'^((cdef +(extern|class))|cimport|(from .+ cimport)|(from .+ import +[*]))') def extract_func_code(code): @@ -170,7 +168,7 @@ def extract_func_code(code): # TODO: string literals, backslash current = function code = code.replace('\t', ' ') - lines = strip_common_indent(code.split('\n')) + lines = code.split('\n') for line in lines: if not line.startswith(' '): if module_statement.match(line): From 4978fea0caf2d48fbce44474fd8830824cf4c370 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Sun, 31 Oct 2010 19:26:56 +0100 Subject: [PATCH 48/77] simplify auto __test__ dict generation: store docstrings directly in the dict instead of looking them up at module init time => much faster, a lot less code, fewer redundant string constants (duplicate strings are unified anyway), and just as good, as docstrings of Cython functions/methods can't currently be changed anyway --- Cython/Compiler/AnalysedTreeTransforms.py | 127 +++++++++------------- tests/run/autotestdict.pyx | 9 +- 2 files changed, 59 insertions(+), 77 deletions(-) diff --git a/Cython/Compiler/AnalysedTreeTransforms.py b/Cython/Compiler/AnalysedTreeTransforms.py index 18ca5bd8ad2..deaf2dfe6ef 100644 --- a/Cython/Compiler/AnalysedTreeTransforms.py +++ b/Cython/Compiler/AnalysedTreeTransforms.py @@ -18,91 +18,72 @@ class AutoTestDictTransform(ScopeTrackingTransform): def visit_ModuleNode(self, node): if node.is_pxd: return node + self.scope_type = 'module' self.scope_node = node - if self.current_directives['autotestdict']: - assert isinstance(node.body, StatListNode) - # First see if __test__ is already created - if u'__test__' in node.scope.entries: - # Do nothing - return node - - pos = node.pos + if not self.current_directives['autotestdict']: + return node + + assert isinstance(node.body, StatListNode) - self.tests = [] - self.testspos = node.pos + # First see if __test__ is already created + if u'__test__' in node.scope.entries: + # Do nothing + return node + + pos = node.pos - test_dict_entry = node.scope.declare_var(EncodedString(u'__test__'), - py_object_type, - pos, - visibility='public') - create_test_dict_assignment = SingleAssignmentNode(pos, - lhs=NameNode(pos, name=EncodedString(u'__test__'), - entry=test_dict_entry), - rhs=DictNode(pos, key_value_pairs=self.tests)) - self.visitchildren(node) - node.body.stats.append(create_test_dict_assignment) + self.tests = [] + self.testspos = node.pos - + test_dict_entry = node.scope.declare_var(EncodedString(u'__test__'), + py_object_type, + pos, + visibility='public') + create_test_dict_assignment = SingleAssignmentNode(pos, + lhs=NameNode(pos, name=EncodedString(u'__test__'), + entry=test_dict_entry), + rhs=DictNode(pos, key_value_pairs=self.tests)) + self.visitchildren(node) + node.body.stats.append(create_test_dict_assignment) return node - def add_test(self, testpos, name, func_ref_node): - # func_ref_node must evaluate to the function object containing - # the docstring, BUT it should not be the function itself (which - # would lead to a new *definition* of the function) + def add_test(self, testpos, path, doctest): pos = self.testspos - keystr = u'%s (line %d)' % (name, testpos[1]) + keystr = u'%s (line %d)' % (path, testpos[1]) key = UnicodeNode(pos, value=EncodedString(keystr)) - - value = DocstringRefNode(pos, func_ref_node) + value = UnicodeNode(pos, value=EncodedString(doctest)) self.tests.append(DictItemNode(pos, key=key, value=value)) - + def visit_FuncDefNode(self, node): - if node.doc: - if isinstance(node, CFuncDefNode) and not node.py_func: - # skip non-cpdef cdef functions + if not node.doc: + return node + if isinstance(node, CFuncDefNode) and not node.py_func: + # skip non-cpdef cdef functions + return node + + pos = self.testspos + if self.scope_type == 'module': + path = node.entry.name + elif self.scope_type in ('pyclass', 'cclass'): + if isinstance(node, CFuncDefNode): + name = node.py_func.name + else: + name = node.name + if self.scope_type == 'cclass' and name in self.blacklist: return node - - pos = self.testspos - if self.scope_type == 'module': - parent = ModuleRefNode(pos) - name = node.entry.name - elif self.scope_type in ('pyclass', 'cclass'): - if isinstance(node, CFuncDefNode): - name = node.py_func.name - else: - name = node.name - if self.scope_type == 'cclass' and name in self.blacklist: - return node - mod = ModuleRefNode(pos) - if self.scope_type == 'pyclass': - clsname = self.scope_node.name - else: - clsname = self.scope_node.class_name - parent = AttributeNode(pos, obj=mod, - attribute=clsname, - type=py_object_type, - is_py_attr=True, - is_temp=True) - if isinstance(node.entry.scope, Symtab.PropertyScope): - new_node = AttributeNode(pos, obj=parent, - attribute=node.entry.scope.name, - type=py_object_type, - is_py_attr=True, - is_temp=True) - parent = new_node - name = "%s.%s.%s" % (clsname, node.entry.scope.name, - node.entry.name) - else: - name = "%s.%s" % (clsname, node.entry.name) + if self.scope_type == 'pyclass': + class_name = self.scope_node.name + else: + class_name = self.scope_node.class_name + if isinstance(node.entry.scope, Symtab.PropertyScope): + property_method_name = node.entry.scope.name + path = "%s.%s.%s" % (class_name, node.entry.scope.name, + node.entry.name) else: - assert False - getfunc = AttributeNode(pos, obj=parent, - attribute=node.entry.name, - type=py_object_type, - is_py_attr=True, - is_temp=True) - self.add_test(node.pos, name, getfunc) + path = "%s.%s" % (class_name, node.entry.name) + else: + assert False + self.add_test(node.pos, path, node.doc) return node - diff --git a/tests/run/autotestdict.pyx b/tests/run/autotestdict.pyx index 51e347eeded..0e3bc09ee28 100644 --- a/tests/run/autotestdict.pyx +++ b/tests/run/autotestdict.pyx @@ -12,8 +12,8 @@ all_tests_run() is executed which does final validation. >>> items.sort() >>> for key, value in items: ... print('%s ; %s' % (key, value)) -MyCdefClass.cpdef_method (line 78) ; >>> add_log("cpdef class method") -MyCdefClass.method (line 75) ; >>> add_log("cdef class method") +MyCdefClass.cpdef_method (line 79) ; >>> add_log("cpdef class method") +MyCdefClass.method (line 76) ; >>> add_log("cdef class method") MyClass.method (line 65) ; >>> add_log("class method") doc_without_test (line 47) ; Some docs mycpdeffunc (line 53) ; >>> add_log("cpdef") @@ -33,7 +33,7 @@ cdef cdeffunc(): def all_tests_run(): log.sort() - assert log == [u'cdef class method', u'class method', u'cpdef', u'def'], log + assert log == [u'cdef class', u'cdef class method', u'class method', u'cpdef', u'cpdef class method', u'def'], log def add_log(s): log.append(unicode(s)) @@ -68,7 +68,8 @@ class MyClass: cdef class MyCdefClass: """ Needs no hack - + + >>> add_log("cdef class") >>> True True """ From d4fefe87f91a788f88c2291b0fbee8d6b1a7e96c Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Sun, 31 Oct 2010 19:47:12 +0100 Subject: [PATCH 49/77] Py2.[34] fix --- Cython/Build/Inline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cython/Build/Inline.py b/Cython/Build/Inline.py index 96f3cf3fce3..b1ab529d6e4 100644 --- a/Cython/Build/Inline.py +++ b/Cython/Build/Inline.py @@ -113,7 +113,7 @@ def cython_inline(code, if m: cimports.append('\ncimport %s' % m.groups()[0]) module_body, func_body = extract_func_code(code) - params = ', '.join('%s %s' % a for a in arg_sigs) + params = ', '.join(['%s %s' % a for a in arg_sigs]) module_code = """ %(cimports)s %(module_body)s From fee84f22f341c18e8cdb5de07e08a99a3cbb2079 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Mon, 1 Nov 2010 19:33:18 +0100 Subject: [PATCH 50/77] fix ticket #578 by working around CPython bug 9834: crash in Py3.[0-1.2] when slicing non-sliceable objects --- Cython/Compiler/ExprNodes.py | 6 +++--- Cython/Compiler/ModuleNode.py | 19 +++++++++++++++++++ tests/run/slice2.pyx | 12 ++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index 50ee5a74dc9..72e7b5e3b58 100755 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -2459,7 +2459,7 @@ def generate_result_code(self, code): code.error_goto_if_null(self.result(), self.pos))) else: code.putln( - "%s = PySequence_GetSlice(%s, %s, %s); %s" % ( + "%s = __Pyx_PySequence_GetSlice(%s, %s, %s); %s" % ( self.result(), self.base.py_result(), self.start_code(), @@ -2471,7 +2471,7 @@ def generate_assignment_code(self, rhs, code): self.generate_subexpr_evaluation_code(code) if self.type.is_pyobject: code.put_error_if_neg(self.pos, - "PySequence_SetSlice(%s, %s, %s, %s)" % ( + "__Pyx_PySequence_SetSlice(%s, %s, %s, %s)" % ( self.base.py_result(), self.start_code(), self.stop_code(), @@ -2508,7 +2508,7 @@ def generate_deletion_code(self, code): return self.generate_subexpr_evaluation_code(code) code.put_error_if_neg(self.pos, - "PySequence_DelSlice(%s, %s, %s)" % ( + "__Pyx_PySequence_DelSlice(%s, %s, %s)" % ( self.base.py_result(), self.start_code(), self.stop_code())) diff --git a/Cython/Compiler/ModuleNode.py b/Cython/Compiler/ModuleNode.py index a3650396d5e..e00ccd07406 100644 --- a/Cython/Compiler/ModuleNode.py +++ b/Cython/Compiler/ModuleNode.py @@ -593,6 +593,25 @@ def generate_module_preamble(self, env, cimported_modules, code): code.putln("#endif") code.put(""" +#if (PY_MAJOR_VERSION < 3) || (PY_VERSION_HEX >= 0x03010300) + #define __Pyx_PySequence_GetSlice(obj, a, b) PySequence_GetSlice(obj, a, b) + #define __Pyx_PySequence_SetSlice(obj, a, b, value) PySequence_SetSlice(obj, a, b, value) + #define __Pyx_PySequence_DelSlice(obj, a, b) PySequence_DelSlice(obj, a, b) +#else + #define __Pyx_PySequence_GetSlice(obj, a, b) ((!(obj)) ? \\ + (PyErr_SetString(PyExc_SystemError, "null argument to internal routine"), (PyObject*)0) : \\ + (((obj)->ob_type->tp_as_mapping) ? (PySequence_GetSlice(obj, a, b)) : \\ + (PyErr_Format(PyExc_TypeError, "'%.200s' object is unsliceable", (obj)->ob_type->tp_name), (PyObject*)0))) + #define __Pyx_PySequence_SetSlice(obj, a, b, value) ((!(obj)) ? \\ + (PyErr_SetString(PyExc_SystemError, "null argument to internal routine"), -1) : \\ + (((obj)->ob_type->tp_as_mapping) ? (PySequence_SetSlice(obj, a, b, value)) : \\ + (PyErr_Format(PyExc_TypeError, "'%.200s' object doesn't support slice assignment", (obj)->ob_type->tp_name), -1))) + #define __Pyx_PySequence_DelSlice(obj, a, b) ((!(obj)) ? \\ + (PyErr_SetString(PyExc_SystemError, "null argument to internal routine"), -1) : \\ + (((obj)->ob_type->tp_as_mapping) ? (PySequence_DelSlice(obj, a, b)) : \\ + (PyErr_Format(PyExc_TypeError, "'%.200s' object doesn't support slice deletion", (obj)->ob_type->tp_name), -1))) +#endif + #if PY_MAJOR_VERSION >= 3 #define PyMethod_New(func, self, klass) ((self) ? PyMethod_New(func, self) : PyInstanceMethod_New(func)) #endif diff --git a/tests/run/slice2.pyx b/tests/run/slice2.pyx index 29e3f9994a8..0b510bd9cbc 100644 --- a/tests/run/slice2.pyx +++ b/tests/run/slice2.pyx @@ -7,6 +7,9 @@ def f(obj1, obj2, obj3, obj4): True >>> l is f(1, l, 2, 3) False + >>> f(1, 42, 2, 3) + Traceback (most recent call last): + TypeError: 'int' object is unsliceable """ obj1 = obj2[:] return obj1 @@ -15,6 +18,9 @@ def g(obj1, obj2, obj3, obj4): """ >>> g(1, [1,2,3,4], 2, 3) [3, 4] + >>> g(1, 42, 2, 3) + Traceback (most recent call last): + TypeError: 'int' object is unsliceable """ obj1 = obj2[obj3:] return obj1 @@ -23,6 +29,9 @@ def h(obj1, obj2, obj3, obj4): """ >>> h(1, [1,2,3,4], 2, 3) [1, 2, 3] + >>> h(1, 42, 2, 3) + Traceback (most recent call last): + TypeError: 'int' object is unsliceable """ obj1 = obj2[:obj4] return obj1 @@ -31,6 +40,9 @@ def j(obj1, obj2, obj3, obj4): """ >>> j(1, [1,2,3,4], 2, 3) [3] + >>> j(1, 42, 2, 3) + Traceback (most recent call last): + TypeError: 'int' object is unsliceable """ obj1 = obj2[obj3:obj4] return obj1 From dbf774ccbf49f5ea99644fbfd5bc36fdf6b1d6f4 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Mon, 1 Nov 2010 19:39:48 +0100 Subject: [PATCH 51/77] use branch hints in macros of slicing work-around --- Cython/Compiler/ModuleNode.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cython/Compiler/ModuleNode.py b/Cython/Compiler/ModuleNode.py index e00ccd07406..6cf3b176ac4 100644 --- a/Cython/Compiler/ModuleNode.py +++ b/Cython/Compiler/ModuleNode.py @@ -598,17 +598,17 @@ def generate_module_preamble(self, env, cimported_modules, code): #define __Pyx_PySequence_SetSlice(obj, a, b, value) PySequence_SetSlice(obj, a, b, value) #define __Pyx_PySequence_DelSlice(obj, a, b) PySequence_DelSlice(obj, a, b) #else - #define __Pyx_PySequence_GetSlice(obj, a, b) ((!(obj)) ? \\ + #define __Pyx_PySequence_GetSlice(obj, a, b) (unlikely(!(obj)) ? \\ (PyErr_SetString(PyExc_SystemError, "null argument to internal routine"), (PyObject*)0) : \\ - (((obj)->ob_type->tp_as_mapping) ? (PySequence_GetSlice(obj, a, b)) : \\ + (likely((obj)->ob_type->tp_as_mapping) ? (PySequence_GetSlice(obj, a, b)) : \\ (PyErr_Format(PyExc_TypeError, "'%.200s' object is unsliceable", (obj)->ob_type->tp_name), (PyObject*)0))) - #define __Pyx_PySequence_SetSlice(obj, a, b, value) ((!(obj)) ? \\ + #define __Pyx_PySequence_SetSlice(obj, a, b, value) (unlikely(!(obj)) ? \\ (PyErr_SetString(PyExc_SystemError, "null argument to internal routine"), -1) : \\ - (((obj)->ob_type->tp_as_mapping) ? (PySequence_SetSlice(obj, a, b, value)) : \\ + (likely((obj)->ob_type->tp_as_mapping) ? (PySequence_SetSlice(obj, a, b, value)) : \\ (PyErr_Format(PyExc_TypeError, "'%.200s' object doesn't support slice assignment", (obj)->ob_type->tp_name), -1))) - #define __Pyx_PySequence_DelSlice(obj, a, b) ((!(obj)) ? \\ + #define __Pyx_PySequence_DelSlice(obj, a, b) (unlikely(!(obj)) ? \\ (PyErr_SetString(PyExc_SystemError, "null argument to internal routine"), -1) : \\ - (((obj)->ob_type->tp_as_mapping) ? (PySequence_DelSlice(obj, a, b)) : \\ + (likely((obj)->ob_type->tp_as_mapping) ? (PySequence_DelSlice(obj, a, b)) : \\ (PyErr_Format(PyExc_TypeError, "'%.200s' object doesn't support slice deletion", (obj)->ob_type->tp_name), -1))) #endif From 4cefc93853f275a360a3f7d4c8370dd809a2967d Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Tue, 2 Nov 2010 09:44:27 +0100 Subject: [PATCH 52/77] enable doctests in cdef functions/methods, do not rewrap docstrings in __test__ dict as EncodedString() but keep them as they are --- Cython/Compiler/AnalysedTreeTransforms.py | 11 +++++----- tests/run/autotestdict.pyx | 26 +++++++++++------------ 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/Cython/Compiler/AnalysedTreeTransforms.py b/Cython/Compiler/AnalysedTreeTransforms.py index deaf2dfe6ef..bb2996fd2e0 100644 --- a/Cython/Compiler/AnalysedTreeTransforms.py +++ b/Cython/Compiler/AnalysedTreeTransforms.py @@ -53,22 +53,21 @@ def add_test(self, testpos, path, doctest): pos = self.testspos keystr = u'%s (line %d)' % (path, testpos[1]) key = UnicodeNode(pos, value=EncodedString(keystr)) - value = UnicodeNode(pos, value=EncodedString(doctest)) + value = UnicodeNode(pos, value=doctest) self.tests.append(DictItemNode(pos, key=key, value=value)) def visit_FuncDefNode(self, node): if not node.doc: return node - if isinstance(node, CFuncDefNode) and not node.py_func: - # skip non-cpdef cdef functions - return node - pos = self.testspos if self.scope_type == 'module': path = node.entry.name elif self.scope_type in ('pyclass', 'cclass'): if isinstance(node, CFuncDefNode): - name = node.py_func.name + if node.py_func is not None: + name = node.py_func.name + else: + name = node.entry.name else: name = node.name if self.scope_type == 'cclass' and name in self.blacklist: diff --git a/tests/run/autotestdict.pyx b/tests/run/autotestdict.pyx index 0e3bc09ee28..eb3744eddff 100644 --- a/tests/run/autotestdict.pyx +++ b/tests/run/autotestdict.pyx @@ -12,28 +12,25 @@ all_tests_run() is executed which does final validation. >>> items.sort() >>> for key, value in items: ... print('%s ; %s' % (key, value)) -MyCdefClass.cpdef_method (line 79) ; >>> add_log("cpdef class method") -MyCdefClass.method (line 76) ; >>> add_log("cdef class method") -MyClass.method (line 65) ; >>> add_log("class method") -doc_without_test (line 47) ; Some docs -mycpdeffunc (line 53) ; >>> add_log("cpdef") -myfunc (line 44) ; >>> add_log("def") +MyCdefClass.cdef_method (line 79) ; >>> add_log("cdef class method") +MyCdefClass.cpdef_method (line 76) ; >>> add_log("cpdef class method") +MyCdefClass.method (line 73) ; >>> add_log("cdef class method") +MyClass.method (line 62) ; >>> add_log("class method") +cdeffunc (line 28) ; >>> add_log("cdef") +doc_without_test (line 44) ; Some docs +mycpdeffunc (line 50) ; >>> add_log("cpdef") +myfunc (line 41) ; >>> add_log("def") """ log = [] cdef cdeffunc(): - """ - Please don't include me! - - >>> True - False - """ + """>>> add_log("cdef")""" def all_tests_run(): log.sort() - assert log == [u'cdef class', u'cdef class method', u'class method', u'cpdef', u'cpdef class method', u'def'], log + assert log == [u'cdef', u'cdef class', u'cdef class method', u'class method', u'cpdef', u'cpdef class method', u'def'], log def add_log(s): log.append(unicode(s)) @@ -79,6 +76,9 @@ cdef class MyCdefClass: cpdef cpdef_method(self): """>>> add_log("cpdef class method")""" + cdef cdef_method(self): + """>>> add_log("cdef class method")""" + def __cinit__(self): """ Should not be included, as it can't be looked up with getattr From 9dd53a87461af5691e9efceff38e0812c779f387 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Tue, 2 Nov 2010 12:42:31 +0100 Subject: [PATCH 53/77] reverted support for cdef functions in __test__ dict: increases module size and init code but rarely helps --- Cython/Compiler/AnalysedTreeTransforms.py | 9 ++++---- tests/run/autotestdict.pyx | 26 +++++++++++------------ 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/Cython/Compiler/AnalysedTreeTransforms.py b/Cython/Compiler/AnalysedTreeTransforms.py index bb2996fd2e0..69667abf658 100644 --- a/Cython/Compiler/AnalysedTreeTransforms.py +++ b/Cython/Compiler/AnalysedTreeTransforms.py @@ -59,15 +59,16 @@ def add_test(self, testpos, path, doctest): def visit_FuncDefNode(self, node): if not node.doc: return node + if isinstance(node, CFuncDefNode) and not node.py_func: + # skip non-cpdef cdef functions + return node + pos = self.testspos if self.scope_type == 'module': path = node.entry.name elif self.scope_type in ('pyclass', 'cclass'): if isinstance(node, CFuncDefNode): - if node.py_func is not None: - name = node.py_func.name - else: - name = node.entry.name + name = node.py_func.name else: name = node.name if self.scope_type == 'cclass' and name in self.blacklist: diff --git a/tests/run/autotestdict.pyx b/tests/run/autotestdict.pyx index eb3744eddff..0e3bc09ee28 100644 --- a/tests/run/autotestdict.pyx +++ b/tests/run/autotestdict.pyx @@ -12,25 +12,28 @@ all_tests_run() is executed which does final validation. >>> items.sort() >>> for key, value in items: ... print('%s ; %s' % (key, value)) -MyCdefClass.cdef_method (line 79) ; >>> add_log("cdef class method") -MyCdefClass.cpdef_method (line 76) ; >>> add_log("cpdef class method") -MyCdefClass.method (line 73) ; >>> add_log("cdef class method") -MyClass.method (line 62) ; >>> add_log("class method") -cdeffunc (line 28) ; >>> add_log("cdef") -doc_without_test (line 44) ; Some docs -mycpdeffunc (line 50) ; >>> add_log("cpdef") -myfunc (line 41) ; >>> add_log("def") +MyCdefClass.cpdef_method (line 79) ; >>> add_log("cpdef class method") +MyCdefClass.method (line 76) ; >>> add_log("cdef class method") +MyClass.method (line 65) ; >>> add_log("class method") +doc_without_test (line 47) ; Some docs +mycpdeffunc (line 53) ; >>> add_log("cpdef") +myfunc (line 44) ; >>> add_log("def") """ log = [] cdef cdeffunc(): - """>>> add_log("cdef")""" + """ + Please don't include me! + + >>> True + False + """ def all_tests_run(): log.sort() - assert log == [u'cdef', u'cdef class', u'cdef class method', u'class method', u'cpdef', u'cpdef class method', u'def'], log + assert log == [u'cdef class', u'cdef class method', u'class method', u'cpdef', u'cpdef class method', u'def'], log def add_log(s): log.append(unicode(s)) @@ -76,9 +79,6 @@ cdef class MyCdefClass: cpdef cpdef_method(self): """>>> add_log("cpdef class method")""" - cdef cdef_method(self): - """>>> add_log("cdef class method")""" - def __cinit__(self): """ Should not be included, as it can't be looked up with getattr From a22a763fced5d89e830d7a3becfc584c835d9316 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Tue, 2 Nov 2010 12:43:14 +0100 Subject: [PATCH 54/77] Py2.4 test fix --- tests/run/slice2.pyx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/run/slice2.pyx b/tests/run/slice2.pyx index 0b510bd9cbc..1cc322e7383 100644 --- a/tests/run/slice2.pyx +++ b/tests/run/slice2.pyx @@ -7,9 +7,9 @@ def f(obj1, obj2, obj3, obj4): True >>> l is f(1, l, 2, 3) False - >>> f(1, 42, 2, 3) + >>> f(1, 42, 2, 3) #doctest: +ELLIPSIS Traceback (most recent call last): - TypeError: 'int' object is unsliceable + TypeError: ...unsliceable... """ obj1 = obj2[:] return obj1 @@ -18,9 +18,9 @@ def g(obj1, obj2, obj3, obj4): """ >>> g(1, [1,2,3,4], 2, 3) [3, 4] - >>> g(1, 42, 2, 3) + >>> g(1, 42, 2, 3) #doctest: +ELLIPSIS Traceback (most recent call last): - TypeError: 'int' object is unsliceable + TypeError: ...unsliceable... """ obj1 = obj2[obj3:] return obj1 @@ -29,9 +29,9 @@ def h(obj1, obj2, obj3, obj4): """ >>> h(1, [1,2,3,4], 2, 3) [1, 2, 3] - >>> h(1, 42, 2, 3) + >>> h(1, 42, 2, 3) #doctest: +ELLIPSIS Traceback (most recent call last): - TypeError: 'int' object is unsliceable + TypeError: ...unsliceable... """ obj1 = obj2[:obj4] return obj1 @@ -40,9 +40,9 @@ def j(obj1, obj2, obj3, obj4): """ >>> j(1, [1,2,3,4], 2, 3) [3] - >>> j(1, 42, 2, 3) + >>> j(1, 42, 2, 3) #doctest: +ELLIPSIS Traceback (most recent call last): - TypeError: 'int' object is unsliceable + TypeError: ...unsliceable... """ obj1 = obj2[obj3:obj4] return obj1 From 915d88612fe08dbb27b47900d5529c8a660b090f Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Tue, 2 Nov 2010 14:02:16 +0100 Subject: [PATCH 55/77] test cleanup --- tests/run/lambda_T195.pyx | 78 ++++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/tests/run/lambda_T195.pyx b/tests/run/lambda_T195.pyx index b2e61ac8a46..3a298eb2379 100644 --- a/tests/run/lambda_T195.pyx +++ b/tests/run/lambda_T195.pyx @@ -2,67 +2,77 @@ __doc__ = u""" #>>> py_identity = lambda x:x #>>> py_identity(1) == cy_identity(1) #True - ->>> idcall = make_identity() ->>> idcall(1) -1 ->>> idcall(2) -2 - ->>> make_const0(1)() -1 - ->>> make_const1(1)(2) -1 - ->>> make_const1(1)(2) -1 - ->>> make_const_calc0()() -11 ->>> make_const_calc1()(2) -11 ->>> make_const_calc1_xy(8)(2) -27 - ->>> make_lambda_lambda(1)(2)(4) -7 - ->>> make_typed_lambda_lambda(1)(2)(4) -7 - ->>> partial_lambda = make_typed_lambda_lambda(1)(2) ->>> partial_lambda(4) -7 ->>> partial_lambda(5) -8 """ #cy_identity = lambda x:x + def make_identity(): + """ + >>> idcall = make_identity() + >>> idcall(1) + 1 + >>> idcall(2) + 2 + """ return lambda x:x def make_const0(x): + """ + >>> make_const0(1)() + 1 + """ return lambda :x def make_const1(x): + """ + >>> make_const1(1)(2) + 1 + >>> make_const1(1)(2) + 1 + """ return lambda _:x def make_const_calc0(): + """ + >>> make_const_calc0()() + 11 + """ return lambda : 1*2*3+5 def make_const_calc1(): + """ + >>> make_const_calc1()(2) + 11 + """ return lambda _: 1*2*3+5 def make_const_calc1_xy(x): + """ + >>> make_const_calc1_xy(8)(2) + 27 + """ return lambda y: x*y+(1*2*3+5) def make_lambda_lambda(x): + """ + >>> make_lambda_lambda(1)(2)(4) + 7 + """ return lambda y : \ lambda z:x+y+z def make_typed_lambda_lambda(int x): + """ + >>> make_typed_lambda_lambda(1)(2)(4) + 7 + + >>> partial_lambda = make_typed_lambda_lambda(1)(2) + >>> partial_lambda(4) + 7 + >>> partial_lambda(5) + 8 + """ return lambda int y : \ lambda int z:x+y+z From 8c5adff1245e21e2ceee56d110a6e8367db618dd Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Tue, 2 Nov 2010 14:27:17 +0100 Subject: [PATCH 56/77] fix *args/**kwargs in lambda function declaration: was messed up with annotation syntax --- Cython/Compiler/Parsing.py | 10 +++++----- tests/run/lambda_T195.pyx | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/Cython/Compiler/Parsing.py b/Cython/Compiler/Parsing.py index 8df5ca102f9..72471e8d0ce 100644 --- a/Cython/Compiler/Parsing.py +++ b/Cython/Compiler/Parsing.py @@ -2568,23 +2568,23 @@ def p_varargslist(s, terminator=')', annotated=1): if s.sy == '*': s.next() if s.sy == 'IDENT': - star_arg = p_py_arg_decl(s) + star_arg = p_py_arg_decl(s, annotated=annotated) if s.sy == ',': s.next() args.extend(p_c_arg_list(s, in_pyfunc = 1, - nonempty_declarators = 1, kw_only = 1)) + nonempty_declarators = 1, kw_only = 1, annotated = annotated)) elif s.sy != terminator: s.error("Syntax error in Python function argument list") if s.sy == '**': s.next() - starstar_arg = p_py_arg_decl(s) + starstar_arg = p_py_arg_decl(s, annotated=annotated) return (args, star_arg, starstar_arg) -def p_py_arg_decl(s): +def p_py_arg_decl(s, annotated = 1): pos = s.position() name = p_ident(s) annotation = None - if s.sy == ':': + if annotated and s.sy == ':': s.next() annotation = p_test(s) return Nodes.PyArgDeclNode(pos, name = name, annotation = annotation) diff --git a/tests/run/lambda_T195.pyx b/tests/run/lambda_T195.pyx index 3a298eb2379..447f3d4fa16 100644 --- a/tests/run/lambda_T195.pyx +++ b/tests/run/lambda_T195.pyx @@ -76,3 +76,35 @@ def make_typed_lambda_lambda(int x): """ return lambda int y : \ lambda int z:x+y+z + +def pass_lambda(f): + """ + >>> def f(a, lfunc): return lfunc(a,2) + >>> pass_lambda(f) + 12 + """ + return f(1, lambda a, b : a*10+b) + +def pass_lambda_with_args(f): + """ + >>> def f(a, lfunc): return lfunc(a,2,3) + >>> pass_lambda_with_args(f) + 123 + """ + return f(1, lambda a, *args : (a*10 + args[0])*10 + args[1]) + +def pass_lambda_with_args_kwargs(f): + """ + >>> def f(a, lfunc): return lfunc(a,2,3, b=4) + >>> pass_lambda_with_args_kwargs(f) + 1234 + """ + return f(1, lambda a, *args, **kwargs : ((a*10 + args[0])*10 + args[1])*10 + kwargs['b']) + +def pass_lambda_with_args_kwargs_kwonly_args(f): + """ + >>> def f(a, lfunc): return lfunc(a,2,3, b=4, c=5) + >>> pass_lambda_with_args_kwargs_kwonly_args(f) + 12345 + """ + return f(1, lambda a, *args, b, **kwargs : (((a*10 + args[0])*10 + args[1])*10 + b)*10 + kwargs['c']) From 32eda021ac3be27213206e85b3125fcc13950ff5 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Tue, 2 Nov 2010 14:42:23 +0100 Subject: [PATCH 57/77] support running tests with language level 3 (mostly run the CPython regression tests) --- runtests.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/runtests.py b/runtests.py index 8c28d719ee3..39c1746040c 100644 --- a/runtests.py +++ b/runtests.py @@ -850,6 +850,9 @@ def refactor_for_py3(distdir, cy3_dir): parser.add_option("-T", "--ticket", dest="tickets", action="append", help="a bug ticket number to run the respective test in 'tests/*'") + 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)'") parser.add_option("--xml-output", dest="xml_output_dir", metavar="DIR", help="write test results in XML to directory DIR") parser.add_option("--exit-ok", dest="exit_ok", default=False, @@ -918,13 +921,13 @@ def refactor_for_py3(distdir, cy3_dir): if not os.path.exists(WORKDIR): os.makedirs(WORKDIR) + sys.stderr.write("Python %s\n" % sys.version) + sys.stderr.write("\n") if WITH_CYTHON: from Cython.Compiler.Version import version sys.stderr.write("Running tests against Cython %s\n" % version) else: sys.stderr.write("Running tests without Cython.\n") - sys.stderr.write("Python %s\n" % sys.version) - sys.stderr.write("\n") if options.with_refnanny: from pyximport.pyxbuild import pyx_to_dll @@ -939,6 +942,13 @@ def refactor_for_py3(distdir, cy3_dir): sys.stderr.write("Disabling forked testing to support XML test output\n") options.fork = False + if WITH_CYTHON and options.language_level == 3: + sys.stderr.write("Using Cython language level 3.\n") + from Cython.Compiler import Options + Options.directive_defaults['language_level'] = 3 + + sys.stderr.write("\n") + test_bugs = False if options.tickets: for ticket_number in options.tickets: From 9b59fa8d491ea2e98a8c0d83f7778fe8fffe8627 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Tue, 2 Nov 2010 14:46:51 +0100 Subject: [PATCH 58/77] build fix --- Cython/Compiler/Parsing.pxd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cython/Compiler/Parsing.pxd b/Cython/Compiler/Parsing.pxd index ac0ad879d3d..b6418c9a6d0 100644 --- a/Cython/Compiler/Parsing.pxd +++ b/Cython/Compiler/Parsing.pxd @@ -149,7 +149,7 @@ cpdef p_ctypedef_statement(PyrexScanner s, ctx) cpdef p_decorators(PyrexScanner s) cpdef p_def_statement(PyrexScanner s, list decorators = *) cpdef p_varargslist(PyrexScanner s, terminator=*, bint annotated = *) -cpdef p_py_arg_decl(PyrexScanner s) +cpdef p_py_arg_decl(PyrexScanner s, bint annotated = *) cpdef p_class_statement(PyrexScanner s, decorators) cpdef p_c_class_definition(PyrexScanner s, pos, ctx) cpdef p_c_class_options(PyrexScanner s) From f5e3ddeee06376c248b33328e51682e4c6dd3b9b Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Tue, 2 Nov 2010 16:46:33 +0100 Subject: [PATCH 59/77] fix language level setting in test runner --- runtests.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/runtests.py b/runtests.py index 39c1746040c..1063d5fb552 100644 --- a/runtests.py +++ b/runtests.py @@ -117,7 +117,7 @@ def getall(self): class TestBuilder(object): def __init__(self, rootdir, workdir, selectors, exclude_selectors, annotate, cleanup_workdir, cleanup_sharedlibs, with_pyregr, cython_only, - languages, test_bugs, fork): + languages, test_bugs, fork, language_level): self.rootdir = rootdir self.workdir = workdir self.selectors = selectors @@ -130,6 +130,7 @@ def __init__(self, rootdir, workdir, selectors, exclude_selectors, annotate, self.languages = languages self.test_bugs = test_bugs self.fork = fork + self.language_level = language_level def build_suite(self): suite = unittest.TestSuite() @@ -220,12 +221,14 @@ def build_test(self, test_class, path, workdir, module, cleanup_workdir=self.cleanup_workdir, cleanup_sharedlibs=self.cleanup_sharedlibs, cython_only=self.cython_only, - fork=self.fork) + fork=self.fork, + language_level=self.language_level) class CythonCompileTestCase(unittest.TestCase): def __init__(self, test_directory, workdir, module, language='c', expect_errors=False, annotate=False, cleanup_workdir=True, - cleanup_sharedlibs=True, cython_only=False, fork=True): + cleanup_sharedlibs=True, cython_only=False, fork=True, + language_level=2): self.test_directory = test_directory self.workdir = workdir self.module = module @@ -236,6 +239,7 @@ def __init__(self, test_directory, workdir, module, language='c', self.cleanup_sharedlibs = cleanup_sharedlibs self.cython_only = cython_only self.fork = fork + self.language_level = language_level unittest.TestCase.__init__(self) def shortDescription(self): @@ -339,6 +343,7 @@ def run_cython(self, test_directory, module, targetdir, incdir, annotate): annotate = annotate, use_listing_file = False, cplus = self.language == 'cpp', + language_level = self.language_level, generate_pxi = False, evaluate_tree_assertions = True, ) @@ -944,8 +949,6 @@ def refactor_for_py3(distdir, cy3_dir): if WITH_CYTHON and options.language_level == 3: sys.stderr.write("Using Cython language level 3.\n") - from Cython.Compiler import Options - Options.directive_defaults['language_level'] = 3 sys.stderr.write("\n") @@ -999,7 +1002,7 @@ def refactor_for_py3(distdir, cy3_dir): options.annotate_source, options.cleanup_workdir, options.cleanup_sharedlibs, options.pyregr, options.cython_only, languages, test_bugs, - options.fork) + options.fork, options.language_level) test_suite.addTest(filetests.build_suite()) if options.system_pyregr and languages: @@ -1007,7 +1010,7 @@ def refactor_for_py3(distdir, cy3_dir): options.annotate_source, options.cleanup_workdir, options.cleanup_sharedlibs, True, options.cython_only, languages, test_bugs, - options.fork) + options.fork, options.language_level) test_suite.addTest( filetests.handle_directory( os.path.join(sys.prefix, 'lib', 'python'+sys.version[:3], 'test'), From 22111321b037fc20ebe9af6723b24080278535a9 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Tue, 2 Nov 2010 16:58:59 +0100 Subject: [PATCH 60/77] prevent 'file' from being recognised as a builtin type - it's no longer available in Py3 --- Cython/Compiler/Builtin.py | 5 +++-- Cython/Compiler/Symtab.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cython/Compiler/Builtin.py b/Cython/Compiler/Builtin.py index 24b4f971d4e..b88dba9b914 100644 --- a/Cython/Compiler/Builtin.py +++ b/Cython/Compiler/Builtin.py @@ -115,7 +115,7 @@ ("copy", "O", "O", "PyDict_Copy")]), ("slice", "PySlice_Type", []), - ("file", "PyFile_Type", []), +# ("file", "PyFile_Type", []), # not in Py3 ("set", "PySet_Type", [("clear", "O", "i", "PySet_Clear"), ("discard", "OO", "i", "PySet_Discard"), @@ -128,8 +128,9 @@ # some builtin types do not always return an instance of # themselves - these do: 'type', 'bool', 'long', 'float', 'bytes', 'unicode', 'tuple', 'list', - 'dict', 'file', 'set', 'frozenset' + 'dict', 'set', 'frozenset' # 'str', # only in Py3.x + # 'file', # only in Py2.x ) diff --git a/Cython/Compiler/Symtab.py b/Cython/Compiler/Symtab.py index a160842fd22..b229ee27ad1 100644 --- a/Cython/Compiler/Symtab.py +++ b/Cython/Compiler/Symtab.py @@ -772,7 +772,7 @@ def builtin_scope(self): "frozenset": ["((PyObject*)&PyFrozenSet_Type)", py_object_type], "slice": ["((PyObject*)&PySlice_Type)", py_object_type], - "file": ["((PyObject*)&PyFile_Type)", py_object_type], +# "file": ["((PyObject*)&PyFile_Type)", py_object_type], # not in Py3 "None": ["Py_None", py_object_type], "False": ["Py_False", py_object_type], From 3d280f63e31cb927abb0bda2292ad4cd5830b7f5 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Tue, 2 Nov 2010 18:14:29 +0100 Subject: [PATCH 61/77] fix octal literals as Python constants, convert octal/binary/hex integer literals to plain decimal integers when using them as Python integers --- Cython/Compiler/ExprNodes.py | 15 ++++++++-- tests/run/int_literals.pyx | 53 ++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index 72e7b5e3b58..4d8f3ed17af 100755 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -833,19 +833,28 @@ def coerce_to_boolean(self, env): def generate_evaluation_code(self, code): if self.type.is_pyobject: # pre-allocate a Python version of the number - self.result_code = code.get_py_num(self.value, self.longness) + plain_integer_string = self.value_as_c_integer_string(plain_digits=True) + self.result_code = code.get_py_num(plain_integer_string, self.longness) else: self.result_code = self.get_constant_c_result_code() def get_constant_c_result_code(self): + return self.value_as_c_integer_string() + self.unsigned + self.longness + + def value_as_c_integer_string(self, plain_digits=False): value = self.value if isinstance(value, basestring) and len(value) > 2: # must convert C-incompatible Py3 oct/bin notations if value[1] in 'oO': - value = value[0] + value[2:] # '0o123' => '0123' + if plain_digits: + value = int(value[2:], 8) + else: + value = value[0] + value[2:] # '0o123' => '0123' elif value[1] in 'bB': value = int(value[2:], 2) - return str(value) + self.unsigned + self.longness + elif plain_digits and value[1] in 'xX': + value = int(value[2:], 16) + return str(value) def calculate_result_code(self): return self.result_code diff --git a/tests/run/int_literals.pyx b/tests/run/int_literals.pyx index 2e38b0cf583..2ff8351bde8 100644 --- a/tests/run/int_literals.pyx +++ b/tests/run/int_literals.pyx @@ -53,3 +53,56 @@ def c_long_types(): print typeof(1U) print typeof(1UL) print typeof(1ULL) + +# different ways to write an integer in Python + +def c_oct(): + """ + >>> c_oct() + (1, 17, 63) + """ + cdef int a = 0o01 + cdef int b = 0o21 + cdef int c = 0o77 + return a,b,c + +def py_oct(): + """ + >>> py_oct() + (1, 17, 63) + """ + return 0o01, 0o21, 0o77 + +def c_hex(): + """ + >>> c_hex() + (1, 33, 255) + """ + cdef int a = 0x01 + cdef int b = 0x21 + cdef int c = 0xFF + return a,b,c + +def py_hex(): + """ + >>> py_hex() + (1, 33, 255) + """ + return 0x01, 0x21, 0xFF + +def c_bin(): + """ + >>> c_bin() + (1, 2, 15) + """ + cdef int a = 0b01 + cdef int b = 0b10 + cdef int c = 0b1111 + return a,b,c + +def py_bin(): + """ + >>> py_bin() + (1, 2, 15) + """ + return 0b01, 0b10, 0b1111 From fa91d0627d4886448d0c6bd6746dbb904cc9bd58 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Tue, 2 Nov 2010 18:16:30 +0100 Subject: [PATCH 62/77] fix test runner warning in Python 3.2 debug builds --- runtests.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/runtests.py b/runtests.py index 1063d5fb552..bb4937c4703 100644 --- a/runtests.py +++ b/runtests.py @@ -763,10 +763,14 @@ class FileListExcluder: def __init__(self, list_file): self.excludes = {} - for line in open(list_file).readlines(): - line = line.strip() - if line and line[0] != '#': - self.excludes[line.split()[0]] = True + f = open(list_file) + try: + for line in f.readlines(): + line = line.strip() + if line and line[0] != '#': + self.excludes[line.split()[0]] = True + finally: + f.close() def __call__(self, testname): return testname in self.excludes or testname.split('.')[-1] in self.excludes From 86a3c4d74163b23734f65f460ec8a11d37f479ad Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Tue, 2 Nov 2010 10:24:24 -0700 Subject: [PATCH 63/77] Unsigned PY_LONG_LONG. --- Cython/Includes/cpython/long.pxd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cython/Includes/cpython/long.pxd b/Cython/Includes/cpython/long.pxd index 145ce8c6d19..4c5700cf479 100644 --- a/Cython/Includes/cpython/long.pxd +++ b/Cython/Includes/cpython/long.pxd @@ -1,7 +1,7 @@ cdef extern from "Python.h": ctypedef long long PY_LONG_LONG - ctypedef unsigned long long uPY_LONG_LONG + ctypedef unsigned long long uPY_LONG_LONG "unsigned PY_LONG_LONG" ############################################################################ # 7.2.3 Long Integer Objects From c90cecd481cb02cb80abf9b9a779f5c5df8abc17 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Wed, 3 Nov 2010 02:11:17 -0700 Subject: [PATCH 64/77] Fix __version__ for py3 --- Cython/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cython/__init__.py b/Cython/__init__.py index 424bd952d76..e10a8f34fdd 100644 --- a/Cython/__init__.py +++ b/Cython/__init__.py @@ -1,4 +1,4 @@ -from Compiler.Version import version as __version__ +from Cython.Compiler.Version import version as __version__ # Void cython.* directives (for case insensitive operating systems). from Cython.Shadow import * From ee3e8d260f9fb7f31d4beb48548ccbd55f80e274 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Wed, 3 Nov 2010 10:20:29 +0100 Subject: [PATCH 65/77] new autotestdict.{cdef,all} directives that put cdef and non-doctest docstrings into __test__, skip non-doctest docstrings by default --HG-- rename : tests/run/autotestdict.pyx => tests/run/autotestdict_all.pyx rename : tests/run/autotestdict.pyx => tests/run/autotestdict_cdef.pyx --- Cython/Compiler/AnalysedTreeTransforms.py | 14 ++- Cython/Compiler/Options.py | 4 + tests/run/autotestdict.pyx | 30 +++-- tests/run/autotestdict_all.pyx | 144 ++++++++++++++++++++++ tests/run/autotestdict_cdef.pyx | 143 +++++++++++++++++++++ tests/run/autotestdict_skip.pyx | 4 +- 6 files changed, 317 insertions(+), 22 deletions(-) create mode 100644 tests/run/autotestdict_all.pyx create mode 100644 tests/run/autotestdict_cdef.pyx diff --git a/Cython/Compiler/AnalysedTreeTransforms.py b/Cython/Compiler/AnalysedTreeTransforms.py index 69667abf658..38712049896 100644 --- a/Cython/Compiler/AnalysedTreeTransforms.py +++ b/Cython/Compiler/AnalysedTreeTransforms.py @@ -18,12 +18,13 @@ class AutoTestDictTransform(ScopeTrackingTransform): def visit_ModuleNode(self, node): if node.is_pxd: return node - self.scope_type = 'module' self.scope_node = node if not self.current_directives['autotestdict']: return node + self.all_docstrings = self.current_directives['autotestdict.all'] + self.cdef_docstrings = self.all_docstrings or self.current_directives['autotestdict.cdef'] assert isinstance(node.body, StatListNode) @@ -59,8 +60,10 @@ def add_test(self, testpos, path, doctest): def visit_FuncDefNode(self, node): if not node.doc: return node - if isinstance(node, CFuncDefNode) and not node.py_func: - # skip non-cpdef cdef functions + if not self.cdef_docstrings: + if isinstance(node, CFuncDefNode) and not node.py_func: + return node + if not self.all_docstrings and '>>>' not in node.doc: return node pos = self.testspos @@ -68,7 +71,10 @@ def visit_FuncDefNode(self, node): path = node.entry.name elif self.scope_type in ('pyclass', 'cclass'): if isinstance(node, CFuncDefNode): - name = node.py_func.name + if node.py_func is not None: + name = node.py_func.name + else: + name = node.entry.name else: name = node.name if self.scope_type == 'cclass' and name in self.blacklist: diff --git a/Cython/Compiler/Options.py b/Cython/Compiler/Options.py index f36a33a05ce..a21fd8dfaae 100644 --- a/Cython/Compiler/Options.py +++ b/Cython/Compiler/Options.py @@ -68,6 +68,8 @@ 'infer_types': None, 'infer_types.verbose': False, 'autotestdict': True, + 'autotestdict.cdef': False, + 'autotestdict.all': False, 'language_level': 2, 'warn': None, @@ -97,6 +99,8 @@ 'final' : ('cclass',), # add 'method' in the future 'internal' : ('cclass',), 'autotestdict' : ('module',), + 'autotestdict.all' : ('module',), + 'autotestdict.cdef' : ('module',), 'test_assert_path_exists' : ('function',), 'test_fail_if_path_exists' : ('function',), } diff --git a/tests/run/autotestdict.pyx b/tests/run/autotestdict.pyx index 0e3bc09ee28..1e740c083da 100644 --- a/tests/run/autotestdict.pyx +++ b/tests/run/autotestdict.pyx @@ -1,23 +1,20 @@ # Directive defaults to True """ -Tests doctesthack compiler directive. +Tests autotestdict compiler directive. -The doctests are actually run as part of this test; -which makes the test flow a bit untraditional. Both -module test and individual tests are run; finally, +Both module test and individual tests are run; finally, all_tests_run() is executed which does final validation. >>> items = list(__test__.items()) >>> items.sort() >>> for key, value in items: ... print('%s ; %s' % (key, value)) -MyCdefClass.cpdef_method (line 79) ; >>> add_log("cpdef class method") -MyCdefClass.method (line 76) ; >>> add_log("cdef class method") -MyClass.method (line 65) ; >>> add_log("class method") -doc_without_test (line 47) ; Some docs -mycpdeffunc (line 53) ; >>> add_log("cpdef") -myfunc (line 44) ; >>> add_log("def") +MyCdefClass.cpdef_method (line 76) ; >>> add_log("cpdef class method") +MyCdefClass.method (line 73) ; >>> add_log("cdef class method") +MyClass.method (line 62) ; >>> add_log("class method") +mycpdeffunc (line 49) ; >>> add_log("cpdef") +myfunc (line 40) ; >>> add_log("def") """ @@ -25,19 +22,18 @@ log = [] cdef cdeffunc(): """ - Please don't include me! - >>> True False """ +cdeffunc() # make sure it's being used def all_tests_run(): log.sort() - assert log == [u'cdef class', u'cdef class method', u'class method', u'cpdef', u'cpdef class method', u'def'], log + assert log == [u'cdef class', u'cdef class method', u'class', u'class method', u'cpdef', u'cpdef class method', u'def'], log def add_log(s): log.append(unicode(s)) - if len(log) == len(__test__): + if len(log) == len(__test__) + 2: # Final per-function doctest executed all_tests_run() @@ -58,6 +54,7 @@ class MyClass: """ Needs no hack + >>> add_log("class") >>> True True """ @@ -79,6 +76,9 @@ cdef class MyCdefClass: cpdef cpdef_method(self): """>>> add_log("cpdef class method")""" + cdef cdef_method(self): + """>>> add_log("cdef class method")""" + def __cinit__(self): """ Should not be included, as it can't be looked up with getattr @@ -142,5 +142,3 @@ cdef class MyOtherCdefClass: >>> True False """ - -cdeffunc() diff --git a/tests/run/autotestdict_all.pyx b/tests/run/autotestdict_all.pyx new file mode 100644 index 00000000000..71deb439fef --- /dev/null +++ b/tests/run/autotestdict_all.pyx @@ -0,0 +1,144 @@ +# cython: autotestdict.all=True + +""" +Tests autotestdict compiler directive. + +Both module test and individual tests are run; finally, +all_tests_run() is executed which does final validation. + +>>> items = list(__test__.items()) +>>> items.sort() +>>> for key, value in items: +... print('%s ; %s' % (key, value)) +MyCdefClass.cdef_method (line 79) ; >>> add_log("cdef class method") +MyCdefClass.cpdef_method (line 76) ; >>> add_log("cpdef class method") +MyCdefClass.method (line 73) ; >>> add_log("cdef class method") +MyClass.method (line 62) ; >>> add_log("class method") +cdeffunc (line 26) ; >>> add_log("cdef") +doc_without_test (line 43) ; Some docs +mycpdeffunc (line 49) ; >>> add_log("cpdef") +myfunc (line 40) ; >>> add_log("def") + +""" + +log = [] + +cdef cdeffunc(): + """>>> add_log("cdef")""" +cdeffunc() # make sure it's being used + +def all_tests_run(): + log.sort() + assert log == [u'cdef', u'cdef class', u'cdef class method', u'class', u'class method', u'cpdef', u'cpdef class method', u'def'], log + +def add_log(s): + log.append(unicode(s)) + if len(log) == len(__test__): + # Final per-function doctest executed + all_tests_run() + +def myfunc(): + """>>> add_log("def")""" + +def doc_without_test(): + """Some docs""" + +def nodocstring(): + pass + +cpdef mycpdeffunc(): + """>>> add_log("cpdef")""" + + +class MyClass: + """ + Needs no hack + + >>> add_log("class") + >>> True + True + """ + + def method(self): + """>>> add_log("class method")""" + +cdef class MyCdefClass: + """ + Needs no hack + + >>> add_log("cdef class") + >>> True + True + """ + def method(self): + """>>> add_log("cdef class method")""" + + cpdef cpdef_method(self): + """>>> add_log("cpdef class method")""" + + cdef cdef_method(self): + """>>> add_log("cdef class method")""" + + def __cinit__(self): + """ + Should not be included, as it can't be looked up with getattr + + >>> True + False + """ + + def __dealloc__(self): + """ + Should not be included, as it can't be looked up with getattr + + >>> True + False + """ + + def __richcmp__(self, other, int op): + """ + Should not be included, as it can't be looked up with getattr in Py 2 + + >>> True + False + """ + + def __nonzero__(self): + """ + Should not be included, as it can't be looked up with getattr in Py 3.1 + + >>> True + False + """ + + def __len__(self): + """ + Should not be included, as it can't be looked up with getattr in Py 3.1 + + >>> True + False + """ + + def __contains__(self, value): + """ + Should not be included, as it can't be looked up with getattr in Py 3.1 + + >>> True + False + """ + +cdef class MyOtherCdefClass: + """ + Needs no hack + + >>> True + True + """ + + def __bool__(self): + """ + Should not be included, as it can't be looked up with getattr in Py 2 + + >>> True + False + """ diff --git a/tests/run/autotestdict_cdef.pyx b/tests/run/autotestdict_cdef.pyx new file mode 100644 index 00000000000..9ebd1c5397a --- /dev/null +++ b/tests/run/autotestdict_cdef.pyx @@ -0,0 +1,143 @@ +# cython: autotestdict.cdef=True + +""" +Tests autotestdict compiler directive. + +Both module test and individual tests are run; finally, +all_tests_run() is executed which does final validation. + +>>> items = list(__test__.items()) +>>> items.sort() +>>> for key, value in items: +... print('%s ; %s' % (key, value)) +MyCdefClass.cdef_method (line 78) ; >>> add_log("cdef class method") +MyCdefClass.cpdef_method (line 75) ; >>> add_log("cpdef class method") +MyCdefClass.method (line 72) ; >>> add_log("cdef class method") +MyClass.method (line 61) ; >>> add_log("class method") +cdeffunc (line 25) ; >>> add_log("cdef") +mycpdeffunc (line 48) ; >>> add_log("cpdef") +myfunc (line 39) ; >>> add_log("def") + +""" + +log = [] + +cdef cdeffunc(): + """>>> add_log("cdef")""" +cdeffunc() # make sure it's being used + +def all_tests_run(): + log.sort() + assert log == [u'cdef', u'cdef class', u'cdef class method', u'class', u'class method', u'cpdef', u'cpdef class method', u'def'], log + +def add_log(s): + log.append(unicode(s)) + if len(log) == len(__test__) + 1: + # Final per-function doctest executed + all_tests_run() + +def myfunc(): + """>>> add_log("def")""" + +def doc_without_test(): + """Some docs""" + +def nodocstring(): + pass + +cpdef mycpdeffunc(): + """>>> add_log("cpdef")""" + + +class MyClass: + """ + Needs no hack + + >>> add_log("class") + >>> True + True + """ + + def method(self): + """>>> add_log("class method")""" + +cdef class MyCdefClass: + """ + Needs no hack + + >>> add_log("cdef class") + >>> True + True + """ + def method(self): + """>>> add_log("cdef class method")""" + + cpdef cpdef_method(self): + """>>> add_log("cpdef class method")""" + + cdef cdef_method(self): + """>>> add_log("cdef class method")""" + + def __cinit__(self): + """ + Should not be included, as it can't be looked up with getattr + + >>> True + False + """ + + def __dealloc__(self): + """ + Should not be included, as it can't be looked up with getattr + + >>> True + False + """ + + def __richcmp__(self, other, int op): + """ + Should not be included, as it can't be looked up with getattr in Py 2 + + >>> True + False + """ + + def __nonzero__(self): + """ + Should not be included, as it can't be looked up with getattr in Py 3.1 + + >>> True + False + """ + + def __len__(self): + """ + Should not be included, as it can't be looked up with getattr in Py 3.1 + + >>> True + False + """ + + def __contains__(self, value): + """ + Should not be included, as it can't be looked up with getattr in Py 3.1 + + >>> True + False + """ + +cdef class MyOtherCdefClass: + """ + Needs no hack + + >>> True + True + """ + + def __bool__(self): + """ + Should not be included, as it can't be looked up with getattr in Py 2 + + >>> True + False + """ diff --git a/tests/run/autotestdict_skip.pyx b/tests/run/autotestdict_skip.pyx index 8770b6667ba..9042606ddc7 100644 --- a/tests/run/autotestdict_skip.pyx +++ b/tests/run/autotestdict_skip.pyx @@ -1,6 +1,6 @@ -#cython: doctesthack=True +#cython: autotestdict=True """ -Tests that doctesthack doesn't come into effect when +Tests that autotestdict doesn't come into effect when a __test__ is defined manually. If this doesn't work, then the function doctest should fail. From 6c734f76133f659ff4018f6121f0afc61ff37903 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Wed, 3 Nov 2010 10:34:30 +0100 Subject: [PATCH 66/77] move 'official' version number to Cython/__init__.py to avoid importing the complete compiler package for a version check --- Cython/Compiler/Version.py | 4 +++- Cython/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Cython/Compiler/Version.py b/Cython/Compiler/Version.py index e8dfdaf0a9c..1969eb78af3 100644 --- a/Cython/Compiler/Version.py +++ b/Cython/Compiler/Version.py @@ -1 +1,3 @@ -version = '0.13' +# for backwards compatibility + +from Cython import __version__ as version diff --git a/Cython/__init__.py b/Cython/__init__.py index 424bd952d76..782a106fc13 100644 --- a/Cython/__init__.py +++ b/Cython/__init__.py @@ -1,4 +1,4 @@ -from Compiler.Version import version as __version__ +__version__ = '0.13' # Void cython.* directives (for case insensitive operating systems). from Cython.Shadow import * diff --git a/setup.py b/setup.py index 81d2eb65985..58e55c86c47 100644 --- a/setup.py +++ b/setup.py @@ -197,7 +197,7 @@ def build_extension(self, ext, *args, **kargs): setup_args.update(setuptools_extra_args) -from Cython.Compiler.Version import version +from Cython import __version__ as version setup( name = 'Cython', From ee13950340f9b5143ed198ae7a1db630ff65bade Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Wed, 3 Nov 2010 02:35:05 -0700 Subject: [PATCH 67/77] Py < 2.6 fix. --- Cython/Utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cython/Utils.py b/Cython/Utils.py index d319ac063e1..06c2295dc03 100644 --- a/Cython/Utils.py +++ b/Cython/Utils.py @@ -115,7 +115,7 @@ def __init__(self, stream): self.close = stream.close self.encoding = getattr(stream, 'encoding', 'UTF-8') - def read(self, count): + def read(self, count=-1): data = self._read(count) if u'\r' not in data: return data From d7d6fd3df2e85549d4b84ad6031835d5ac625945 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Wed, 3 Nov 2010 10:45:48 +0100 Subject: [PATCH 68/77] disable 'with template' syntax in Python files --- Cython/Compiler/Parsing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cython/Compiler/Parsing.py b/Cython/Compiler/Parsing.py index 124129421a3..10cc16cddc2 100644 --- a/Cython/Compiler/Parsing.py +++ b/Cython/Compiler/Parsing.py @@ -1504,7 +1504,7 @@ def p_include_statement(s, ctx): def p_with_statement(s): s.next() # 'with' - if s.systring == 'template': + if s.systring == 'template' and not s.in_python_file: node = p_with_template(s) else: node = p_with_items(s) From cfb1d64a147a867032d9e488a14cf629a1543a76 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Wed, 3 Nov 2010 14:06:10 +0100 Subject: [PATCH 69/77] in pure mode: rename 'with nogil' to 'with cython.nogil', test 'real' pure mode by compiling .py file instead of .pyx file --HG-- rename : tests/run/pure.pyx => tests/run/pure_py.py --- Cython/Compiler/ParseTreeTransforms.py | 4 + Cython/Compiler/Parsing.py | 2 +- Cython/Shadow.py | 11 ++ tests/run/pure_py.py | 193 +++++++++++++++++++++++++ 4 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 tests/run/pure_py.py diff --git a/Cython/Compiler/ParseTreeTransforms.py b/Cython/Compiler/ParseTreeTransforms.py index e04b28bfc18..753dfb4244d 100644 --- a/Cython/Compiler/ParseTreeTransforms.py +++ b/Cython/Compiler/ParseTreeTransforms.py @@ -854,6 +854,10 @@ def visit_WithStatNode(self, node): PostParseError(node.pos, "Compiler directive with statements cannot contain 'as'")) else: name, value = directive + if name == 'nogil': + # special case: in pure mode, "with nogil" spells "with cython.nogil" + node = GILStatNode(node.pos, state = "nogil", body = node.body) + return self.visit_Node(node) if self.check_directive_scope(node.pos, name, 'with statement'): directive_dict[name] = value if directive_dict: diff --git a/Cython/Compiler/Parsing.py b/Cython/Compiler/Parsing.py index 10cc16cddc2..c79465d5716 100644 --- a/Cython/Compiler/Parsing.py +++ b/Cython/Compiler/Parsing.py @@ -1512,7 +1512,7 @@ def p_with_statement(s): def p_with_items(s): pos = s.position() - if s.sy == 'IDENT' and s.systring == 'nogil': + if not s.in_python_file and s.sy == 'IDENT' and s.systring == 'nogil': state = s.systring s.next() if s.sy == ',': diff --git a/Cython/Shadow.py b/Cython/Shadow.py index 72b864bd751..48278c0382d 100644 --- a/Cython/Shadow.py +++ b/Cython/Shadow.py @@ -58,6 +58,17 @@ def declare(type=None, value=None, **kwds): else: return value +class _nogil(object): + """Support for 'with nogil' statement + """ + def __enter__(self): + pass + def __exit__(self, exc_class, exc, tb): + return exc_class is None + +nogil = _nogil() +del _nogil + # Emulated types class CythonType(object): diff --git a/tests/run/pure_py.py b/tests/run/pure_py.py new file mode 100644 index 00000000000..26fb31070c8 --- /dev/null +++ b/tests/run/pure_py.py @@ -0,0 +1,193 @@ +import cython + +def test_sizeof(): + """ + >>> test_sizeof() + True + True + True + True + True + """ + x = cython.declare(cython.bint) + print sizeof(x) == sizeof(cython.bint) + print sizeof(cython.char) <= sizeof(cython.short) <= sizeof(cython.int) <= sizeof(cython.long) <= sizeof(cython.longlong) + print sizeof(cython.uint) == sizeof(cython.int) + print sizeof(cython.p_int) == sizeof(cython.p_double) + if cython.compiled: + print sizeof(cython.char) < sizeof(cython.longlong) + else: + print sizeof(cython.char) == 1 + +def test_declare(n): + """ + >>> test_declare(100) + (100, 100) + >>> test_declare(100.5) + (100, 100) + >>> test_declare(None) + Traceback (most recent call last): + ... + TypeError: an integer is required + """ + x = cython.declare(cython.int) + y = cython.declare(cython.int, n) + if cython.compiled: + cython.declare(xx=cython.int, yy=cython.long) + i = sizeof(xx) + ptr = cython.declare(cython.p_int, cython.address(y)) + return y, ptr[0] + +@cython.locals(x=cython.double, n=cython.int) +def test_cast(x): + """ + >>> test_cast(1.5) + 1 + >>> test_cast(None) + Traceback (most recent call last): + ... + TypeError: a float is required + """ + n = cython.cast(cython.int, x) + return n + +@cython.locals(x=cython.int, y=cython.p_int) +def test_address(x): + """ + >>> test_address(39) + 39 + """ + y = cython.address(x) + return y[0] + +@cython.locals(x=cython.int) +@cython.locals(y=cython.bint) +def test_locals(x): + """ + >>> test_locals(5) + True + """ + y = x + return y + +@cython.test_assert_path_exists("//TryFinallyStatNode", + "//TryFinallyStatNode//GILStatNode") +@cython.test_fail_if_path_exists("//TryFinallyStatNode//TryFinallyStatNode") +def test_with_nogil(nogil): + """ + >>> raised = [] + >>> class nogil(object): + ... def __enter__(self): + ... pass + ... def __exit__(self, exc_class, exc, tb): + ... raised.append(exc) + ... return exc_class is None + + >>> test_with_nogil(nogil()) + WORKS + True + >>> raised + [None] + """ + result = False + with nogil: + print "WORKS" + with cython.nogil: + result = True + return result + +@cython.test_assert_path_exists("//TryFinallyStatNode", + "//TryFinallyStatNode//GILStatNode") +@cython.test_fail_if_path_exists("//TryFinallyStatNode//TryFinallyStatNode") +def test_with_nogil_multiple(nogil): + """ + >>> raised = [] + >>> class nogil(object): + ... def __enter__(self): + ... pass + ... def __exit__(self, exc_class, exc, tb): + ... raised.append(exc) + ... return exc_class is None + + >>> test_with_nogil_multiple(nogil()) + True + >>> raised + [None] + """ + result = False + with nogil, cython.nogil: + result = True + return result + +MyUnion = cython.union(n=cython.int, x=cython.double) +MyStruct = cython.struct(is_integral=cython.bint, data=MyUnion) +MyStruct2 = cython.typedef(MyStruct[2]) + +def test_struct(n, x): + """ + >>> test_struct(389, 1.64493) + (389, 1.64493) + """ + a = cython.declare(MyStruct2) + a[0] = MyStruct(True, data=MyUnion(n=n)) + a[1] = MyStruct(is_integral=False, data={'x': x}) + return a[0].data.n, a[1].data.x + +import cython as cy +from cython import declare, cast, locals, address, typedef, p_void, compiled +from cython import declare as my_declare, locals as my_locals, p_void as my_void_star, typedef as my_typedef, compiled as my_compiled + +@my_locals(a=cython.p_void) +def test_imports(): + """ + >>> test_imports() + True + """ + a = cython.NULL + b = declare(p_void, cython.NULL) + c = my_declare(my_void_star, cython.NULL) + d = cy.declare(cy.p_void, cython.NULL) + return a == d and compiled and my_compiled + +MyStruct3 = typedef(MyStruct[3]) +MyStruct4 = my_typedef(MyStruct[4]) +MyStruct5 = cy.typedef(MyStruct[5]) + +def test_declare_c_types(n): + """ + >>> test_declare_c_types(0) + >>> test_declare_c_types(1) + >>> test_declare_c_types(2) + """ + # + b00 = cython.declare(cython.bint, 0) + b01 = cython.declare(cython.bint, 1) + b02 = cython.declare(cython.bint, 2) + # + i00 = cython.declare(cython.uchar, n) + i01 = cython.declare(cython.char, n) + i02 = cython.declare(cython.schar, n) + i03 = cython.declare(cython.ushort, n) + i04 = cython.declare(cython.short, n) + i05 = cython.declare(cython.sshort, n) + i06 = cython.declare(cython.uint, n) + i07 = cython.declare(cython.int, n) + i08 = cython.declare(cython.sint, n) + i09 = cython.declare(cython.slong, n) + i10 = cython.declare(cython.long, n) + i11 = cython.declare(cython.ulong, n) + i12 = cython.declare(cython.slonglong, n) + i13 = cython.declare(cython.longlong, n) + i14 = cython.declare(cython.ulonglong, n) + + i20 = cython.declare(cython.Py_ssize_t, n) + i21 = cython.declare(cython.size_t, n) + # + f00 = cython.declare(cython.float, n) + f01 = cython.declare(cython.double, n) + f02 = cython.declare(cython.longdouble, n) + # + #z00 = cython.declare(cython.complex, n+1j) + #z01 = cython.declare(cython.floatcomplex, n+1j) + #z02 = cython.declare(cython.doublecomplex, n+1j) + #z03 = cython.declare(cython.longdoublecomplex, n+1j) From c2da6d1a8f17b315300c7596f2ae1032a2483486 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Wed, 3 Nov 2010 15:09:36 +0100 Subject: [PATCH 70/77] enable CPython doctesting of .py files in tests/run/ --- runtests.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/runtests.py b/runtests.py index bb4937c4703..6945ddbf7c4 100644 --- a/runtests.py +++ b/runtests.py @@ -191,6 +191,9 @@ def handle_directory(self, path, context): for test in self.build_tests(test_class, path, workdir, module, expect_errors): suite.addTest(test) + if filename.endswith('.py'): + # additionally test file in real Python + suite.addTest(PureDoctestTestCase(module, os.path.join(path, filename))) return suite def build_tests(self, test_class, path, workdir, module, expect_errors): @@ -499,6 +502,38 @@ def run_doctests(self, module_name, result): try: os.unlink(result_file) except: pass +class PureDoctestTestCase(unittest.TestCase): + def __init__(self, module_name, module_path): + self.module_name = module_name + self.module_path = module_path + unittest.TestCase.__init__(self, 'run') + + def shortDescription(self): + return "running pure doctests in %s" % self.module_name + + def run(self, result=None): + if result is None: + result = self.defaultTestResult() + loaded_module_name = 'pure_doctest__' + self.module_name + result.startTest(self) + try: + self.setUp() + + import imp + m = imp.load_source(loaded_module_name, self.module_path) + try: + doctest.DocTestSuite(m).run(result) + finally: + del m + if loaded_module_name in sys.modules: + del sys.modules[loaded_module_name] + except Exception: + result.addError(self, sys.exc_info()) + result.stopTest(self) + try: + self.tearDown() + except Exception: + pass is_private_field = re.compile('^_[^_]').match From 86b46c1b83e50dbcdde136a6fc5dba8dd387a8c7 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Wed, 3 Nov 2010 15:10:18 +0100 Subject: [PATCH 71/77] drop tests from pure_py.py test file that do not work in pure mode --- tests/run/pure.pyx | 50 +++++++++++++++- tests/run/pure_py.py | 140 +++++++++++++++++++------------------------ 2 files changed, 110 insertions(+), 80 deletions(-) diff --git a/tests/run/pure.pyx b/tests/run/pure.pyx index c1a61b79256..26fb31070c8 100644 --- a/tests/run/pure.pyx +++ b/tests/run/pure.pyx @@ -69,7 +69,55 @@ def test_locals(x): """ y = x return y - + +@cython.test_assert_path_exists("//TryFinallyStatNode", + "//TryFinallyStatNode//GILStatNode") +@cython.test_fail_if_path_exists("//TryFinallyStatNode//TryFinallyStatNode") +def test_with_nogil(nogil): + """ + >>> raised = [] + >>> class nogil(object): + ... def __enter__(self): + ... pass + ... def __exit__(self, exc_class, exc, tb): + ... raised.append(exc) + ... return exc_class is None + + >>> test_with_nogil(nogil()) + WORKS + True + >>> raised + [None] + """ + result = False + with nogil: + print "WORKS" + with cython.nogil: + result = True + return result + +@cython.test_assert_path_exists("//TryFinallyStatNode", + "//TryFinallyStatNode//GILStatNode") +@cython.test_fail_if_path_exists("//TryFinallyStatNode//TryFinallyStatNode") +def test_with_nogil_multiple(nogil): + """ + >>> raised = [] + >>> class nogil(object): + ... def __enter__(self): + ... pass + ... def __exit__(self, exc_class, exc, tb): + ... raised.append(exc) + ... return exc_class is None + + >>> test_with_nogil_multiple(nogil()) + True + >>> raised + [None] + """ + result = False + with nogil, cython.nogil: + result = True + return result MyUnion = cython.union(n=cython.int, x=cython.double) MyStruct = cython.struct(is_integral=cython.bint, data=MyUnion) diff --git a/tests/run/pure_py.py b/tests/run/pure_py.py index 26fb31070c8..b733e4a240a 100644 --- a/tests/run/pure_py.py +++ b/tests/run/pure_py.py @@ -10,43 +10,41 @@ def test_sizeof(): True """ x = cython.declare(cython.bint) - print sizeof(x) == sizeof(cython.bint) - print sizeof(cython.char) <= sizeof(cython.short) <= sizeof(cython.int) <= sizeof(cython.long) <= sizeof(cython.longlong) - print sizeof(cython.uint) == sizeof(cython.int) - print sizeof(cython.p_int) == sizeof(cython.p_double) + print cython.sizeof(x) == cython.sizeof(cython.bint) + print cython.sizeof(cython.char) <= cython.sizeof(cython.short) <= cython.sizeof(cython.int) <= cython.sizeof(cython.long) <= cython.sizeof(cython.longlong) + print cython.sizeof(cython.uint) == cython.sizeof(cython.int) + print cython.sizeof(cython.p_int) == cython.sizeof(cython.p_double) if cython.compiled: - print sizeof(cython.char) < sizeof(cython.longlong) + print cython.sizeof(cython.char) < cython.sizeof(cython.longlong) else: - print sizeof(cython.char) == 1 + print cython.sizeof(cython.char) == 1 -def test_declare(n): - """ - >>> test_declare(100) - (100, 100) - >>> test_declare(100.5) - (100, 100) - >>> test_declare(None) - Traceback (most recent call last): - ... - TypeError: an integer is required - """ - x = cython.declare(cython.int) - y = cython.declare(cython.int, n) - if cython.compiled: - cython.declare(xx=cython.int, yy=cython.long) - i = sizeof(xx) - ptr = cython.declare(cython.p_int, cython.address(y)) - return y, ptr[0] +## CURRENTLY BROKEN - FIXME!! + +## def test_declare(n): +## """ +## >>> test_declare(100) +## (100, 100) +## >>> test_declare(100.5) +## (100, 100) +## >>> test_declare(None) +## Traceback (most recent call last): +## ... +## TypeError: an integer is required +## """ +## x = cython.declare(cython.int) +## y = cython.declare(cython.int, n) +## if cython.compiled: +## cython.declare(xx=cython.int, yy=cython.long) +## i = sizeof(xx) +## ptr = cython.declare(cython.p_int, cython.address(y)) +## return y, ptr[0] @cython.locals(x=cython.double, n=cython.int) def test_cast(x): """ >>> test_cast(1.5) 1 - >>> test_cast(None) - Traceback (most recent call last): - ... - TypeError: a float is required """ n = cython.cast(cython.int, x) return n @@ -60,19 +58,18 @@ def test_address(x): y = cython.address(x) return y[0] -@cython.locals(x=cython.int) -@cython.locals(y=cython.bint) -def test_locals(x): - """ - >>> test_locals(5) - True - """ - y = x - return y +## CURRENTLY BROKEN - FIXME!! + +## @cython.locals(x=cython.int) +## @cython.locals(y=cython.bint) +## def test_locals(x): +## """ +## >>> test_locals(5) +## True +## """ +## y = x +## return y -@cython.test_assert_path_exists("//TryFinallyStatNode", - "//TryFinallyStatNode//GILStatNode") -@cython.test_fail_if_path_exists("//TryFinallyStatNode//TryFinallyStatNode") def test_with_nogil(nogil): """ >>> raised = [] @@ -96,42 +93,21 @@ def test_with_nogil(nogil): result = True return result -@cython.test_assert_path_exists("//TryFinallyStatNode", - "//TryFinallyStatNode//GILStatNode") -@cython.test_fail_if_path_exists("//TryFinallyStatNode//TryFinallyStatNode") -def test_with_nogil_multiple(nogil): - """ - >>> raised = [] - >>> class nogil(object): - ... def __enter__(self): - ... pass - ... def __exit__(self, exc_class, exc, tb): - ... raised.append(exc) - ... return exc_class is None - - >>> test_with_nogil_multiple(nogil()) - True - >>> raised - [None] - """ - result = False - with nogil, cython.nogil: - result = True - return result +## CURRENTLY BROKEN - FIXME!! -MyUnion = cython.union(n=cython.int, x=cython.double) -MyStruct = cython.struct(is_integral=cython.bint, data=MyUnion) -MyStruct2 = cython.typedef(MyStruct[2]) +## MyUnion = cython.union(n=cython.int, x=cython.double) +## MyStruct = cython.struct(is_integral=cython.bint, data=MyUnion) +## MyStruct2 = cython.typedef(MyStruct[2]) -def test_struct(n, x): - """ - >>> test_struct(389, 1.64493) - (389, 1.64493) - """ - a = cython.declare(MyStruct2) - a[0] = MyStruct(True, data=MyUnion(n=n)) - a[1] = MyStruct(is_integral=False, data={'x': x}) - return a[0].data.n, a[1].data.x +## def test_struct(n, x): +## """ +## >>> test_struct(389, 1.64493) +## (389, 1.64493) +## """ +## a = cython.declare(MyStruct2) +## a[0] = MyStruct(True, data=MyUnion(n=n)) +## a[1] = MyStruct(is_integral=False, data={'x': x}) +## return a[0].data.n, a[1].data.x import cython as cy from cython import declare, cast, locals, address, typedef, p_void, compiled @@ -140,18 +116,24 @@ def test_struct(n, x): @my_locals(a=cython.p_void) def test_imports(): """ - >>> test_imports() + >>> test_imports() # (True, True) True """ a = cython.NULL b = declare(p_void, cython.NULL) c = my_declare(my_void_star, cython.NULL) d = cy.declare(cy.p_void, cython.NULL) - return a == d and compiled and my_compiled -MyStruct3 = typedef(MyStruct[3]) -MyStruct4 = my_typedef(MyStruct[4]) -MyStruct5 = cy.typedef(MyStruct[5]) + ## CURRENTLY BROKEN - FIXME!! + #return a == d, compiled == my_compiled + + return compiled == my_compiled + +## CURRENTLY BROKEN - FIXME!! + +## MyStruct3 = typedef(MyStruct[3]) +## MyStruct4 = my_typedef(MyStruct[4]) +## MyStruct5 = cy.typedef(MyStruct[5]) def test_declare_c_types(n): """ From 5986efd2064d97ddceda50b75cba3b0ef5f0270c Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Wed, 3 Nov 2010 15:14:29 +0100 Subject: [PATCH 72/77] fix test after changing autotestdict default setup --- tests/run/extpropertyref.pyx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/run/extpropertyref.pyx b/tests/run/extpropertyref.pyx index 0feb9631147..2efdffac36e 100644 --- a/tests/run/extpropertyref.pyx +++ b/tests/run/extpropertyref.pyx @@ -5,6 +5,9 @@ cdef class Spam: def __get__(self): """ This is the docstring for Spam.eggs.__get__ + + >>> True + True """ return 42 @@ -16,9 +19,9 @@ def tomato(): >>> lines = __test__.keys() >>> len(lines) 3 - >>> 'Spam.eggs.__get__ (line 5)' in lines + >>> 'Spam.eggs.__get__ (line 5)' in lines or lines True - >>> 'tomato (line 11)' in lines + >>> 'tomato (line 14)' in lines or lines True """ cdef Spam spam From 7115c1ba16aceabe2ca646da0d7bfef2feb7d384 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Wed, 3 Nov 2010 15:51:56 +0100 Subject: [PATCH 73/77] only run pure doctests in runnable tests directories --- runtests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtests.py b/runtests.py index 6945ddbf7c4..d07d13a4f91 100644 --- a/runtests.py +++ b/runtests.py @@ -191,7 +191,7 @@ def handle_directory(self, path, context): for test in self.build_tests(test_class, path, workdir, module, expect_errors): suite.addTest(test) - if filename.endswith('.py'): + if filename.endswith('.py') and context in TEST_RUN_DIRS: # additionally test file in real Python suite.addTest(PureDoctestTestCase(module, os.path.join(path, filename))) return suite From 8a79be0fb85667de65d2ca1d180bb5b375113922 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Wed, 3 Nov 2010 16:02:12 +0100 Subject: [PATCH 74/77] removed test from pure.pyx that only works in real pure mode --- tests/run/pure.pyx | 49 ---------------------------------------------- 1 file changed, 49 deletions(-) diff --git a/tests/run/pure.pyx b/tests/run/pure.pyx index 26fb31070c8..4ed263e87d3 100644 --- a/tests/run/pure.pyx +++ b/tests/run/pure.pyx @@ -70,55 +70,6 @@ def test_locals(x): y = x return y -@cython.test_assert_path_exists("//TryFinallyStatNode", - "//TryFinallyStatNode//GILStatNode") -@cython.test_fail_if_path_exists("//TryFinallyStatNode//TryFinallyStatNode") -def test_with_nogil(nogil): - """ - >>> raised = [] - >>> class nogil(object): - ... def __enter__(self): - ... pass - ... def __exit__(self, exc_class, exc, tb): - ... raised.append(exc) - ... return exc_class is None - - >>> test_with_nogil(nogil()) - WORKS - True - >>> raised - [None] - """ - result = False - with nogil: - print "WORKS" - with cython.nogil: - result = True - return result - -@cython.test_assert_path_exists("//TryFinallyStatNode", - "//TryFinallyStatNode//GILStatNode") -@cython.test_fail_if_path_exists("//TryFinallyStatNode//TryFinallyStatNode") -def test_with_nogil_multiple(nogil): - """ - >>> raised = [] - >>> class nogil(object): - ... def __enter__(self): - ... pass - ... def __exit__(self, exc_class, exc, tb): - ... raised.append(exc) - ... return exc_class is None - - >>> test_with_nogil_multiple(nogil()) - True - >>> raised - [None] - """ - result = False - with nogil, cython.nogil: - result = True - return result - MyUnion = cython.union(n=cython.int, x=cython.double) MyStruct = cython.struct(is_integral=cython.bint, data=MyUnion) MyStruct2 = cython.typedef(MyStruct[2]) From 30314265ffc83667b646b36bc0ce034b3053d6b9 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Wed, 3 Nov 2010 16:04:37 +0100 Subject: [PATCH 75/77] test syntax fixes --- tests/run/pure_py.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/run/pure_py.py b/tests/run/pure_py.py index b733e4a240a..3c91b098721 100644 --- a/tests/run/pure_py.py +++ b/tests/run/pure_py.py @@ -10,14 +10,14 @@ def test_sizeof(): True """ x = cython.declare(cython.bint) - print cython.sizeof(x) == cython.sizeof(cython.bint) - print cython.sizeof(cython.char) <= cython.sizeof(cython.short) <= cython.sizeof(cython.int) <= cython.sizeof(cython.long) <= cython.sizeof(cython.longlong) - print cython.sizeof(cython.uint) == cython.sizeof(cython.int) - print cython.sizeof(cython.p_int) == cython.sizeof(cython.p_double) + print(cython.sizeof(x) == cython.sizeof(cython.bint)) + print(cython.sizeof(cython.char) <= cython.sizeof(cython.short) <= cython.sizeof(cython.int) <= cython.sizeof(cython.long) <= cython.sizeof(cython.longlong)) + print(cython.sizeof(cython.uint) == cython.sizeof(cython.int)) + print(cython.sizeof(cython.p_int) == cython.sizeof(cython.p_double)) if cython.compiled: - print cython.sizeof(cython.char) < cython.sizeof(cython.longlong) + print(cython.sizeof(cython.char) < cython.sizeof(cython.longlong)) else: - print cython.sizeof(cython.char) == 1 + print(cython.sizeof(cython.char) == 1) ## CURRENTLY BROKEN - FIXME!! @@ -88,7 +88,7 @@ def test_with_nogil(nogil): """ result = False with nogil: - print "WORKS" + print("WORKS") with cython.nogil: result = True return result From 581671b5b48d56094f52d38edd1cb0e8e089abde Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Wed, 3 Nov 2010 16:09:26 +0100 Subject: [PATCH 76/77] exclude Python regression tests from Python doctest runs --- runtests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtests.py b/runtests.py index d07d13a4f91..8de9b204fd9 100644 --- a/runtests.py +++ b/runtests.py @@ -191,7 +191,7 @@ def handle_directory(self, path, context): for test in self.build_tests(test_class, path, workdir, module, expect_errors): suite.addTest(test) - if filename.endswith('.py') and context in TEST_RUN_DIRS: + if context == 'run' and filename.endswith('.py'): # additionally test file in real Python suite.addTest(PureDoctestTestCase(module, os.path.join(path, filename))) return suite From 54031cb0ce74d5929aa2e9f3bbb4fa04a6827319 Mon Sep 17 00:00:00 2001 From: Vitja Makarov Date: Wed, 3 Nov 2010 01:32:09 +0300 Subject: [PATCH 77/77] __metaclass__ support Fill class attributes dict before class creation. Use bindings for class methods. And use PyCFunction for staticmethods and __new__. Add simple testcase for __metaclass__. And test for staticmethod as decorator --- Cython/Compiler/ExprNodes.py | 73 +++++++++++++++++++++++++++++++----- Cython/Compiler/Nodes.py | 14 +++++-- Cython/Compiler/Symtab.py | 10 ++++- tests/run/classmethod.pyx | 12 ++++++ tests/run/metaclass.pyx | 12 ++++++ tests/run/staticmethod.pyx | 7 ++++ 6 files changed, 113 insertions(+), 15 deletions(-) create mode 100644 tests/run/metaclass.pyx diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index 4d8f3ed17af..af17c28f415 100755 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -1450,6 +1450,22 @@ def generate_result_code(self, code): return # There was an error earlier if entry.is_builtin and Options.cache_builtins: return # Lookup already cached + elif entry.is_real_dict: + assert entry.type.is_pyobject, "Python global or builtin not a Python object" + interned_cname = code.intern_identifier(self.entry.name) + if entry.is_builtin: + namespace = Naming.builtins_cname + else: # entry.is_pyglobal + namespace = entry.scope.namespace_cname + code.globalstate.use_utility_code(getitem_dict_utility_code) + code.putln( + '%s = __Pyx_PyDict_GetItem(%s, %s); %s' % ( + self.result(), + namespace, + interned_cname, + code.error_goto_if_null(self.result(), self.pos))) + code.put_gotref(self.py_result()) + elif entry.is_pyglobal or entry.is_builtin: assert entry.type.is_pyobject, "Python global or builtin not a Python object" interned_cname = code.intern_identifier(self.entry.name) @@ -1504,8 +1520,16 @@ def generate_assignment_code(self, rhs, code): rhs.free_temps(code) # in Py2.6+, we need to invalidate the method cache code.putln("PyType_Modified(%s);" % - entry.scope.parent_type.typeptr_cname) - else: + entry.scope.parent_type.typeptr_cname) + elif entry.is_real_dict: + code.put_error_if_neg(self.pos, + 'PyDict_SetItem(%s, %s, %s)' % ( + namespace, + interned_cname, + rhs.py_result())) + rhs.generate_disposal_code(code) + rhs.free_temps(code) + else: code.put_error_if_neg(self.pos, 'PyObject_SetAttr(%s, %s, %s)' % ( namespace, @@ -1584,10 +1608,17 @@ def generate_deletion_code(self, code): if not self.entry.is_pyglobal: error(self.pos, "Deletion of local or C global name not supported") return - code.put_error_if_neg(self.pos, - '__Pyx_DelAttrString(%s, "%s")' % ( - Naming.module_cname, - self.entry.name)) + if self.entry.is_real_dict: + namespace = self.entry.scope.namespace_cname + code.put_error_if_neg(self.pos, + 'PyDict_DelItemString(%s, "%s")' % ( + namespace, + self.entry.name)) + else: + code.put_error_if_neg(self.pos, + '__Pyx_DelAttrString(%s, "%s")' % ( + Naming.module_cname, + self.entry.name)) def annotate(self, code): if hasattr(self, 'is_called') and self.is_called: @@ -7127,16 +7158,38 @@ def generate_result_code(self, code): """, impl = """ static PyObject *__Pyx_CreateClass( - PyObject *bases, PyObject *dict, PyObject *name, PyObject *modname) + PyObject *bases, PyObject *methods, PyObject *name, PyObject *modname) { PyObject *result = 0; +#if PY_MAJOR_VERSION < 3 + PyObject *metaclass = 0, *base; +#endif - if (PyDict_SetItemString(dict, "__module__", modname) < 0) + if (PyDict_SetItemString(methods, "__module__", modname) < 0) goto bad; #if PY_MAJOR_VERSION < 3 - result = PyClass_New(bases, dict, name); + metaclass = PyDict_GetItemString(methods, "__metaclass__"); + + if (metaclass != NULL) + Py_INCREF(metaclass); + else if (PyTuple_Check(bases) && PyTuple_GET_SIZE(bases) > 0) { + base = PyTuple_GET_ITEM(bases, 0); + metaclass = PyObject_GetAttrString(base, "__class__"); + if (metaclass == NULL) { + PyErr_Clear(); + metaclass = (PyObject *)base->ob_type; + Py_INCREF(metaclass); + } + } + else { + metaclass = (PyObject *) &PyClass_Type; + Py_INCREF(metaclass); + } + + result = PyObject_CallFunctionObjArgs(metaclass, name, bases, methods, NULL); #else - result = PyObject_CallFunctionObjArgs((PyObject *)&PyType_Type, name, bases, dict, NULL); + /* it seems that python3+ handle __metaclass__ itself */ + result = PyObject_CallFunctionObjArgs((PyObject *)&PyType_Type, name, bases, methods, NULL); #endif bad: return result; diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py index 92536a7a3aa..be840f923e8 100644 --- a/Cython/Compiler/Nodes.py +++ b/Cython/Compiler/Nodes.py @@ -1959,6 +1959,9 @@ def analyse_declarations(self, env): # staticmethod() was overridden - not much we can do here ... self.is_staticmethod = False + if self.name == '__new__': + self.is_staticmethod = 1 + self.analyse_argument_types(env) if self.name == '': self.declare_lambda_function(env) @@ -2203,9 +2206,11 @@ def needs_assignment_synthesis(self, env, code=None): def synthesize_assignment_node(self, env): import ExprNodes if env.is_py_class_scope: - rhs = ExprNodes.UnboundMethodNode(self.pos, - function = ExprNodes.PyCFunctionNode(self.pos, - pymethdef_cname = self.entry.pymethdef_cname)) + rhs = ExprNodes.PyCFunctionNode(self.pos, + pymethdef_cname = self.entry.pymethdef_cname) + if not self.is_staticmethod and not self.is_classmethod: + rhs.binding = True + elif env.is_closure_scope: rhs = ExprNodes.InnerFunctionNode( self.pos, pymethdef_cname = self.entry.pymethdef_cname) @@ -3016,9 +3021,10 @@ def generate_execution_code(self, code): code.pyclass_stack.append(self) cenv = self.scope self.dict.generate_evaluation_code(code) + cenv.namespace_cname = cenv.class_obj_cname = self.dict.result() + self.body.generate_execution_code(code) self.classobj.generate_evaluation_code(code) cenv.namespace_cname = cenv.class_obj_cname = self.classobj.result() - self.body.generate_execution_code(code) self.target.generate_assignment_code(self.classobj, code) self.dict.generate_disposal_code(code) self.dict.free_temps(code) diff --git a/Cython/Compiler/Symtab.py b/Cython/Compiler/Symtab.py index b229ee27ad1..03d74167464 100644 --- a/Cython/Compiler/Symtab.py +++ b/Cython/Compiler/Symtab.py @@ -70,6 +70,7 @@ class Entry(object): # or class attribute during # class construction # is_member boolean Is an assigned class member + # is_real_dict boolean Is a real dict, PyClass attributes dict # is_variable boolean Is a variable # is_cfunction boolean Is a C function # is_cmethod boolean Is a C method of an extension type @@ -131,6 +132,7 @@ class Entry(object): is_cglobal = 0 is_pyglobal = 0 is_member = 0 + is_real_dict = 0 is_variable = 0 is_cfunction = 0 is_cmethod = 0 @@ -1405,6 +1407,7 @@ def declare_var(self, name, type, pos, entry = Scope.declare_var(self, name, type, pos, cname, visibility, is_cdef) entry.is_pyglobal = 1 + entry.is_real_dict = 1 return entry def add_default_value(self, type): @@ -1768,8 +1771,13 @@ def declare_pyfunction(self, name, pos): else if (PyCFunction_Check(method)) { return PyClassMethod_New(method); } +#ifdef __pyx_binding_PyCFunctionType_USED + else if (PyObject_TypeCheck(method, __pyx_binding_PyCFunctionType)) { /* binded CFunction */ + return PyClassMethod_New(method); + } +#endif PyErr_Format(PyExc_TypeError, - "Class-level classmethod() can only be called on" + "Class-level classmethod() can only be called on " "a method_descriptor or instance method."); return NULL; } diff --git a/tests/run/classmethod.pyx b/tests/run/classmethod.pyx index 863d18b8f45..e001d23b48e 100644 --- a/tests/run/classmethod.pyx +++ b/tests/run/classmethod.pyx @@ -11,6 +11,10 @@ class2 class3 >>> class3.plus(1) 8 +>>> class4.view() +class4 +>>> class5.view() +class5 """ def f_plus(cls, a): @@ -36,3 +40,11 @@ cdef class class3: def view(cls): print cls.__name__ view = classmethod(view) + +class class4: + @classmethod + def view(cls): + print cls.__name__ + +class class5(class4): + pass diff --git a/tests/run/metaclass.pyx b/tests/run/metaclass.pyx new file mode 100644 index 00000000000..78b3e40bf6a --- /dev/null +++ b/tests/run/metaclass.pyx @@ -0,0 +1,12 @@ +""" +>>> obj = Foo() +>>> obj.metaclass_was_here +True +""" +class Base(type): + def __new__(cls, name, bases, attrs): + attrs['metaclass_was_here'] = True + return type.__new__(cls, name, bases, attrs) + +class Foo(object): + __metaclass__ = Base diff --git a/tests/run/staticmethod.pyx b/tests/run/staticmethod.pyx index e8004b99a81..0bc9c05a69c 100644 --- a/tests/run/staticmethod.pyx +++ b/tests/run/staticmethod.pyx @@ -5,6 +5,8 @@ __doc__ = u""" 2 >>> class3.plus1(1) 2 +>>> class4.plus1(1) +2 """ def f_plus(a): @@ -18,3 +20,8 @@ class class2(object): cdef class class3: plus1 = f_plus + +class class4: + @staticmethod + def plus1(a): + return a + 1