From e1a60bbe772ab6e83efe83c0c70e9938401e854d Mon Sep 17 00:00:00 2001 From: da-woods Date: Mon, 19 Jul 2021 17:22:32 +0100 Subject: Resolve some issues with "cpp_locals" (GH-4265) * Fix class attributes access where cpp_locals=False. * Add "no-cpp-locals" tag to mark a test as not suitable for running with "cpp_locals=True". * Add a list of "extra_directives" to runtests as additional test mode. Resolves some issues in https://github.com/cython/cython/issues/4266 --- Cython/Compiler/Code.py | 4 +- Cython/Compiler/ExprNodes.py | 36 +++- Cython/Compiler/ParseTreeTransforms.py | 4 +- Cython/Compiler/PyrexTypes.py | 1 + Cython/Compiler/Symtab.py | 3 + runtests.py | 47 ++++- tests/compile/cpp_temp_assignment.pyx | 5 +- tests/errors/w_numpy_arr_as_cppvec_ref.pyx | 2 +- tests/errors/w_uninitialized_cpp.pyx | 3 +- tests/run/cpp_bool.pyx | 2 +- tests/run/cpp_class_redef.pyx | 2 +- tests/run/cpp_classes.pyx | 2 +- tests/run/cpp_classes_def.pyx | 2 +- tests/run/cpp_const_method.pyx | 4 +- tests/run/cpp_enums.pyx | 2 +- tests/run/cpp_exceptions_utility_code.pyx | 2 +- tests/run/cpp_forwarding_ref.pyx | 2 +- tests/run/cpp_iterators.pyx | 2 +- tests/run/cpp_locals_directive.pyx | 235 ++++++++++++++++++++++ tests/run/cpp_locals_directive_unused.pyx | 14 ++ tests/run/cpp_move.pyx | 2 +- tests/run/cpp_nested_classes.pyx | 2 +- tests/run/cpp_nonstdint.pyx | 2 +- tests/run/cpp_operator_exc_handling.pyx | 2 +- tests/run/cpp_operators.pyx | 2 + tests/run/cpp_optional_temps.pyx | 215 -------------------- tests/run/cpp_optional_temps_unused.pyx | 13 -- tests/run/cpp_smart_ptr.pyx | 2 +- tests/run/cpp_static_method_overload.pyx | 2 +- tests/run/cpp_stl_algo_execpolicies.pyx | 2 +- tests/run/cpp_stl_algo_modifying_sequence_ops.pyx | 2 +- tests/run/cpp_stl_algo_partitioning_ops.pyx | 2 +- tests/run/cpp_stl_algo_sorting_ops.pyx | 2 +- tests/run/cpp_stl_atomic.pyx | 2 +- tests/run/cpp_stl_cpp11.pyx | 2 +- tests/run/cpp_stl_forward_list.pyx | 2 +- tests/run/cpp_stl_list.pyx | 2 +- tests/run/cpp_stl_vector.pyx | 2 +- tests/run/cpp_template_functions.pyx | 2 +- tests/run/cpp_template_ref_args.pyx | 2 +- tests/run/cpp_template_subclasses.pyx | 2 +- tests/run/fused_cpp.pyx | 3 +- tests/run/libcpp_all.pyx | 2 +- tests/run/lvalue_refs.pyx | 2 +- 44 files changed, 362 insertions(+), 285 deletions(-) create mode 100644 tests/run/cpp_locals_directive.pyx create mode 100644 tests/run/cpp_locals_directive_unused.pyx delete mode 100644 tests/run/cpp_optional_temps.pyx delete mode 100644 tests/run/cpp_optional_temps_unused.pyx diff --git a/Cython/Compiler/Code.py b/Cython/Compiler/Code.py index acf1272e9..f02bbae27 100644 --- a/Cython/Compiler/Code.py +++ b/Cython/Compiler/Code.py @@ -849,7 +849,7 @@ class FunctionState(object): elif type.is_cfunction: from . import PyrexTypes type = PyrexTypes.c_ptr_type(type) # A function itself isn't an l-value - elif type.is_cpp_class and self.scope.directives['cpp_locals']: + elif type.is_cpp_class and not type.is_fake_reference and self.scope.directives['cpp_locals']: self.scope.use_utility_code(UtilityCode.load_cached("OptionalLocals", "CppSupport.cpp")) if not type.is_pyobject and not type.is_memoryviewslice: # Make manage_ref canonical, so that manage_ref will always mean @@ -2087,7 +2087,7 @@ class CCodeWriter(object): def put_temp_declarations(self, func_context): for name, type, manage_ref, static in func_context.temps_allocated: - if type.is_cpp_class and func_context.scope.directives['cpp_locals']: + if type.is_cpp_class and not type.is_fake_reference and func_context.scope.directives['cpp_locals']: decl = type.cpp_optional_declaration_code(name) else: decl = type.declaration_code(name) diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index 56e490490..98a41f9b0 100644 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -2288,6 +2288,8 @@ class NameNode(AtomicExprNode): entry = self.entry if not entry: return "" # There was an error earlier + if self.entry.is_cpp_optional and not self.is_target: + return "(*%s)" % entry.cname return entry.cname def generate_result_code(self, code): @@ -2897,6 +2899,7 @@ class CppIteratorNode(ExprNode): # Created at the analyse_types stage by IteratorNode cpp_sequence_cname = None cpp_attribute_op = "." + extra_dereference = "" is_temp = True subexprs = ['sequence'] @@ -2921,6 +2924,8 @@ class CppIteratorNode(ExprNode): return self iter_type = begin.type.return_type if iter_type.is_cpp_class: + if env.directives['cpp_locals']: + self.extra_dereference = "*" if env.lookup_operator_for_types( self.pos, "!=", @@ -2965,7 +2970,7 @@ class CppIteratorNode(ExprNode): # make the temp a pointer so we are not sensitive to users reassigning # the pointer than it came from temp_type = PyrexTypes.CPtrType(sequence_type.ref_base_type) - if temp_type.is_ptr: + if temp_type.is_ptr or code.globalstate.directives['cpp_locals']: self.cpp_attribute_op = "->" # 3) (otherwise) sequence comes from a function call or similar, so we must # create a temp to store it in @@ -2979,14 +2984,16 @@ class CppIteratorNode(ExprNode): def generate_iter_next_result_code(self, result_name, code): # end call isn't cached to support containers that allow adding while iterating # (much as this is usually a bad idea) - code.putln("if (!(%s != %s%send())) break;" % ( + code.putln("if (!(%s%s != %s%send())) break;" % ( + self.extra_dereference, self.result(), self.cpp_sequence_cname or self.sequence.result(), self.cpp_attribute_op)) - code.putln("%s = *%s;" % ( + code.putln("%s = *%s%s;" % ( result_name, + self.extra_dereference, self.result())) - code.putln("++%s;" % self.result()) + code.putln("++%s%s;" % (self.extra_dereference, self.result())) def free_temps(self, code): if self.cpp_sequence_cname: @@ -7162,9 +7169,6 @@ class AttributeNode(ExprNode): self.op = "->" elif obj_type.is_reference and obj_type.is_fake_reference: self.op = "->" - elif (obj_type.is_cpp_class and (self.obj.is_name or self.obj.is_attribute) and - self.obj.entry and self.obj.entry.is_cpp_optional): - self.op = "->" else: self.op = "." if obj_type.has_attributes: @@ -7297,9 +7301,14 @@ class AttributeNode(ExprNode): return NameNode.is_ephemeral(self) def calculate_result_code(self): - #print "AttributeNode.calculate_result_code:", self.member ### - #print "...obj node =", self.obj, "code", self.obj.result() ### - #print "...obj type", self.obj.type, "ctype", self.obj.ctype() ### + result = self.calculate_access_code() + if self.entry and self.entry.is_cpp_optional and not self.is_target: + result = "(*%s)" % result + return result + + def calculate_access_code(self): + # Does the job of calculate_result_code but doesn't dereference cpp_optionals + # Therefore allowing access to the holder variable obj = self.obj obj_code = obj.result_as(obj.type) #print "...obj_code =", obj_code ### @@ -7374,7 +7383,12 @@ class AttributeNode(ExprNode): '%s' '}' % (self.result(), code.error_goto(self.pos))) elif self.entry.is_cpp_optional and self.initialized_check: - unbound_check_code = self.type.cpp_optional_check_for_null_code(self.result()) + if self.is_target: + undereferenced_result = self.result() + else: + assert not self.is_temp # calculate_access_code() only makes sense for non-temps + undereferenced_result = self.calculate_access_code() + unbound_check_code = self.type.cpp_optional_check_for_null_code(undereferenced_result) code.put_error_if_unbound(self.pos, self.entry, unbound_check_code=unbound_check_code) else: # result_code contains what is needed, but we may need to insert diff --git a/Cython/Compiler/ParseTreeTransforms.py b/Cython/Compiler/ParseTreeTransforms.py index 25f691833..aa6d05b36 100644 --- a/Cython/Compiler/ParseTreeTransforms.py +++ b/Cython/Compiler/ParseTreeTransforms.py @@ -3166,7 +3166,9 @@ class CoerceCppTemps(EnvTransform, SkipDeclarations): def visit_ExprNode(self, node): self.visitchildren(node) if (self.current_env().directives['cpp_locals'] and - node.is_temp and node.type.is_cpp_class): + node.is_temp and node.type.is_cpp_class and + # Fake references are not replaced with "std::optional()". + not node.type.is_fake_reference): node = ExprNodes.CppOptionalTempCoercion(node) return node diff --git a/Cython/Compiler/PyrexTypes.py b/Cython/Compiler/PyrexTypes.py index 269c66ba5..5d0ecd1ba 100644 --- a/Cython/Compiler/PyrexTypes.py +++ b/Cython/Compiler/PyrexTypes.py @@ -248,6 +248,7 @@ class PyrexType(BaseType): is_ptr = 0 is_null_ptr = 0 is_reference = 0 + is_fake_reference = 0 is_rvalue_reference = 0 is_const = 0 is_volatile = 0 diff --git a/Cython/Compiler/Symtab.py b/Cython/Compiler/Symtab.py index 1aa65da33..17d3634bc 100644 --- a/Cython/Compiler/Symtab.py +++ b/Cython/Compiler/Symtab.py @@ -300,6 +300,7 @@ class InnerEntry(Entry): self.cf_assignments = outermost_entry.cf_assignments self.cf_references = outermost_entry.cf_references self.overloaded_alternatives = outermost_entry.overloaded_alternatives + self.is_cpp_optional = outermost_entry.is_cpp_optional self.inner_entries.append(self) def __getattr__(self, name): @@ -1925,6 +1926,8 @@ class LocalScope(Scope): elif entry.in_closure: entry.original_cname = entry.cname entry.cname = "%s->%s" % (Naming.cur_scope_cname, entry.cname) + if entry.type.is_cpp_class and entry.scope.directives['cpp_locals']: + entry.make_cpp_optional() class ComprehensionScope(Scope): diff --git a/runtests.py b/runtests.py index f9d3dbb25..e23fc41be 100755 --- a/runtests.py +++ b/runtests.py @@ -297,18 +297,20 @@ def update_cpp11_extension(ext): update cpp11 extensions that will run on versions of gcc >4.8 """ gcc_version = get_gcc_version(ext.language) + already_has_std = any(ca for ca in ext.extra_compile_args if "-std" in ca) if gcc_version: compiler_version = gcc_version.group(1) - if float(compiler_version) > 4.8: + if float(compiler_version) > 4.8 and not already_has_std: ext.extra_compile_args.append("-std=c++11") return ext clang_version = get_clang_version(ext.language) if clang_version: - ext.extra_compile_args.append("-std=c++11") + if not already_has_std: + ext.extra_compile_args.append("-std=c++11") if sys.platform == "darwin": - ext.extra_compile_args.append("-stdlib=libc++") - ext.extra_compile_args.append("-mmacosx-version-min=10.7") + ext.extra_compile_args.append("-stdlib=libc++") + ext.extra_compile_args.append("-mmacosx-version-min=10.7") return ext return EXCLUDE_EXT @@ -320,6 +322,10 @@ def update_cpp17_extension(ext): gcc_version = get_gcc_version(ext.language) if gcc_version: compiler_version = gcc_version.group(1) + if sys.version_info[0] < 3: + # The Python 2.7 headers contain the 'register' modifier + # which gcc warns about in C++17 mode. + ext.extra_compile_args.append('-Wno-register') if float(compiler_version) >= 5.0: ext.extra_compile_args.append("-std=c++17") return ext @@ -678,7 +684,8 @@ class TestBuilder(object): with_pyregr, languages, test_bugs, language_level, common_utility_dir, pythran_dir=None, default_mode='run', stats=None, - add_embedded_test=False, add_cython_import=False): + add_embedded_test=False, add_cython_import=False, + add_cpp_locals_extra_tests=False): self.rootdir = rootdir self.workdir = workdir self.selectors = selectors @@ -702,6 +709,7 @@ class TestBuilder(object): self.add_embedded_test = add_embedded_test self.add_cython_import = add_cython_import self.capture = options.capture + self.add_cpp_locals_extra_tests = add_cpp_locals_extra_tests def build_suite(self): suite = unittest.TestSuite() @@ -805,6 +813,8 @@ class TestBuilder(object): warning_errors = 'werror' in tags['tag'] expect_warnings = 'warnings' in tags['tag'] + extra_directives_list = [{}] + if expect_errors: if skip_c(tags) and 'cpp' in self.languages: languages = ['cpp'] @@ -819,6 +829,9 @@ class TestBuilder(object): if 'cpp' in languages and 'no-cpp' in tags['tag']: languages = list(languages) languages.remove('cpp') + if (self.add_cpp_locals_extra_tests and 'cpp' in languages and + 'cpp' in tags['tag'] and not 'no-cpp-locals' in tags['tag']): + extra_directives_list.append({'cpp_locals': True}) if not languages: return [] @@ -840,15 +853,18 @@ class TestBuilder(object): tags, language, language_level, expect_errors, expect_warnings, warning_errors, preparse, pythran_dir if language == "cpp" else None, - add_cython_import=add_cython_import) + add_cython_import=add_cython_import, + extra_directives=extra_directives) for language in languages for preparse in preparse_list for language_level in language_levels + for extra_directives in extra_directives_list ] return tests def build_test(self, test_class, path, workdir, module, module_path, tags, language, language_level, - expect_errors, expect_warnings, warning_errors, preparse, pythran_dir, add_cython_import): + expect_errors, expect_warnings, warning_errors, preparse, pythran_dir, add_cython_import, + extra_directives): language_workdir = os.path.join(workdir, language) if not os.path.exists(language_workdir): os.makedirs(language_workdir) @@ -857,6 +873,8 @@ class TestBuilder(object): workdir += '_%s' % (preparse,) if language_level: workdir += '_cy%d' % (language_level,) + if extra_directives: + workdir += ('_directives_'+ '_'.join('%s_%s' % (k, v) for k,v in extra_directives.items())) return test_class(path, workdir, module, module_path, tags, language=language, preparse=preparse, @@ -924,7 +942,8 @@ class CythonCompileTestCase(unittest.TestCase): cleanup_sharedlibs=True, cleanup_failures=True, cython_only=False, test_selector=None, fork=True, language_level=2, warning_errors=False, test_determinism=False, - common_utility_dir=None, pythran_dir=None, stats=None, add_cython_import=False): + common_utility_dir=None, pythran_dir=None, stats=None, add_cython_import=False, + extra_directives={}): self.test_directory = test_directory self.tags = tags self.workdir = workdir @@ -949,6 +968,7 @@ class CythonCompileTestCase(unittest.TestCase): self.pythran_dir = pythran_dir self.stats = stats self.add_cython_import = add_cython_import + self.extra_directives = extra_directives unittest.TestCase.__init__(self) def shortDescription(self): @@ -978,6 +998,7 @@ class CythonCompileTestCase(unittest.TestCase): Options.warning_errors = self.warning_errors if sys.version_info >= (3, 4): Options._directive_defaults['autotestdict'] = False + Options._directive_defaults.update(self.extra_directives) if not os.path.exists(self.workdir): os.makedirs(self.workdir) @@ -1219,6 +1240,10 @@ class CythonCompileTestCase(unittest.TestCase): if self.language == 'cpp': # Set the language now as the fixer might need it extension.language = 'c++' + if self.extra_directives.get('cpp_locals'): + extension = update_cpp17_extension(extension) + if extension is EXCLUDE_EXT: + return if 'distutils' in self.tags: from Cython.Build.Dependencies import DistutilsInfo @@ -2193,6 +2218,9 @@ def main(): parser.add_option("--no-cpp", dest="use_cpp", action="store_false", default=True, help="do not test C++ compilation backend") + parser.add_option("--no-cpp-locals", dest="use_cpp_locals", + action="store_false", default=True, + help="do not rerun select C++ tests with cpp_locals directive") parser.add_option("--no-unit", dest="unittests", action="store_false", default=True, help="do not run the unit tests") @@ -2670,7 +2698,8 @@ def runtests(options, cmd_args, coverage=None): filetests = TestBuilder(ROOTDIR, WORKDIR, selectors, exclude_selectors, options, options.pyregr, languages, test_bugs, options.language_level, common_utility_dir, - options.pythran_dir, add_embedded_test=True, stats=stats) + options.pythran_dir, add_embedded_test=True, stats=stats, + add_cpp_locals_extra_tests=options.use_cpp_locals) test_suite.addTest(filetests.build_suite()) if options.examples and languages: diff --git a/tests/compile/cpp_temp_assignment.pyx b/tests/compile/cpp_temp_assignment.pyx index 296fedce9..58ae39a70 100644 --- a/tests/compile/cpp_temp_assignment.pyx +++ b/tests/compile/cpp_temp_assignment.pyx @@ -1,5 +1,8 @@ # tag: cpp,cpp11 -# mode: compile +# mode: compile +# tag: no-cpp-locals +# TODO cpp_locals works fine with the standard library that comes with gcc11 +# but not with gcc8. Therefore disable the test for now cdef extern from *: """ diff --git a/tests/errors/w_numpy_arr_as_cppvec_ref.pyx b/tests/errors/w_numpy_arr_as_cppvec_ref.pyx index b8dd1f536..d3a70dbed 100644 --- a/tests/errors/w_numpy_arr_as_cppvec_ref.pyx +++ b/tests/errors/w_numpy_arr_as_cppvec_ref.pyx @@ -1,5 +1,5 @@ # mode: error -# tag: cpp, werror, numpy +# tag: cpp, werror, numpy, no-cpp-locals import numpy as np cimport numpy as np diff --git a/tests/errors/w_uninitialized_cpp.pyx b/tests/errors/w_uninitialized_cpp.pyx index ae35978a8..a89c58da5 100644 --- a/tests/errors/w_uninitialized_cpp.pyx +++ b/tests/errors/w_uninitialized_cpp.pyx @@ -1,6 +1,7 @@ # cython: warn.maybe_uninitialized=True # mode: error -# tag: cpp, werror +# tag: cpp, werror, no-cpp-locals +# FIXME - no-cpp-locals should work from cython.operator import typeid diff --git a/tests/run/cpp_bool.pyx b/tests/run/cpp_bool.pyx index b6027cd9d..98e281a2e 100644 --- a/tests/run/cpp_bool.pyx +++ b/tests/run/cpp_bool.pyx @@ -1,5 +1,5 @@ # mode: run -# tag: cpp, werror +# tag: cpp, werror, no-cpp-locals from libcpp cimport bool diff --git a/tests/run/cpp_class_redef.pyx b/tests/run/cpp_class_redef.pyx index 8f0ae3ad5..36cd8ea04 100644 --- a/tests/run/cpp_class_redef.pyx +++ b/tests/run/cpp_class_redef.pyx @@ -1,5 +1,5 @@ # mode: run -# tag: cpp, warnings +# tag: cpp, warnings, no-cpp-locals # This gives a warning about the previous .pxd definition, but should not give an error. cdef cppclass Foo: diff --git a/tests/run/cpp_classes.pyx b/tests/run/cpp_classes.pyx index bdaf4b7de..930a292b5 100644 --- a/tests/run/cpp_classes.pyx +++ b/tests/run/cpp_classes.pyx @@ -1,5 +1,5 @@ # mode: run -# tag: cpp, werror +# tag: cpp, werror, no-cpp-locals from libcpp.vector cimport vector diff --git a/tests/run/cpp_classes_def.pyx b/tests/run/cpp_classes_def.pyx index 3bc8a1c4d..074d85a13 100644 --- a/tests/run/cpp_classes_def.pyx +++ b/tests/run/cpp_classes_def.pyx @@ -1,5 +1,5 @@ # mode: run -# tag: cpp, werror, cpp11 +# tag: cpp, werror, cpp11, no-cpp-locals # cython: experimental_cpp_class_def=True cdef double pi diff --git a/tests/run/cpp_const_method.pyx b/tests/run/cpp_const_method.pyx index ef1cada31..d959dbe09 100644 --- a/tests/run/cpp_const_method.pyx +++ b/tests/run/cpp_const_method.pyx @@ -1,5 +1,5 @@ # mode: run -# tag: cpp, werror +# tag: cpp, werror, no-cpp-locals # cython: experimental_cpp_class_def=True from libcpp.vector cimport vector @@ -82,6 +82,6 @@ def test_vector_members(py_a, py_b): cdef vector_members(vector[const Wrapper[int]*] a, const vector[wrapInt*] b): # TODO: Cython-level error. # b[0].set(100) - + # TODO: const_iterator return [x.get() for x in a], b[0].get() diff --git a/tests/run/cpp_enums.pyx b/tests/run/cpp_enums.pyx index 2c91d5187..ac5ea9fe7 100644 --- a/tests/run/cpp_enums.pyx +++ b/tests/run/cpp_enums.pyx @@ -1,5 +1,5 @@ # tag: cpp -# mode: run +# mode: run, no-cpp-locals cdef extern from *: """ diff --git a/tests/run/cpp_exceptions_utility_code.pyx b/tests/run/cpp_exceptions_utility_code.pyx index 91291f904..74f87dfb1 100644 --- a/tests/run/cpp_exceptions_utility_code.pyx +++ b/tests/run/cpp_exceptions_utility_code.pyx @@ -1,5 +1,5 @@ # mode: run -# tag: cpp, werror +# tag: cpp, werror, no-cpp-locals # ticket: 3065 # This is intentionally in a file on its own. The issue was that it failed to generate utility-code diff --git a/tests/run/cpp_forwarding_ref.pyx b/tests/run/cpp_forwarding_ref.pyx index bf3469bc1..b3b044353 100644 --- a/tests/run/cpp_forwarding_ref.pyx +++ b/tests/run/cpp_forwarding_ref.pyx @@ -1,5 +1,5 @@ # mode: run -# tag: cpp, cpp11 +# tag: cpp, cpp11, no-cpp-locals from libcpp.utility cimport move diff --git a/tests/run/cpp_iterators.pyx b/tests/run/cpp_iterators.pyx index 36782710f..716fcb021 100644 --- a/tests/run/cpp_iterators.pyx +++ b/tests/run/cpp_iterators.pyx @@ -1,5 +1,5 @@ # mode: run -# tag: cpp, werror +# tag: cpp, werror, no-cpp-locals from libcpp.deque cimport deque from libcpp.vector cimport vector diff --git a/tests/run/cpp_locals_directive.pyx b/tests/run/cpp_locals_directive.pyx new file mode 100644 index 000000000..6c9c89ba5 --- /dev/null +++ b/tests/run/cpp_locals_directive.pyx @@ -0,0 +1,235 @@ +# mode: run +# tag: cpp, cpp17, no-cpp-locals +# no-cpp-locals because the test is already run with it explicitly set + +# cython: cpp_locals=True + +cimport cython + +from libcpp cimport bool as cppbool + +cdef extern from *: + r""" + static void print_C_destructor(); + + class C { + public: + C() = delete; // look! No default constructor + C(int x, bool print_destructor=true) : x(x), print_destructor(print_destructor) {} + C(C&& rhs) : x(rhs.x), print_destructor(rhs.print_destructor) { + rhs.print_destructor = false; // moved-from instances are deleted silently + } + C& operator=(C&& rhs) { + x=rhs.x; + print_destructor=rhs.print_destructor; + rhs.print_destructor = false; // moved-from instances are deleted silently + return *this; + } + C(const C& rhs) = default; + C& operator=(const C& rhs) = default; + ~C() { + if (print_destructor) print_C_destructor(); + } + + int getX() const { return x; } + + private: + int x; + bool print_destructor; + }; + + C make_C(int x) { + return C(x); + } + """ + cdef cppclass C: + C(int) + C(int, cppbool) + int getX() const + C make_C(int) except + # needs a temp to receive + +# this function just makes sure the output from the destructor can be captured by doctest +cdef void print_C_destructor "print_C_destructor" () with gil: + print("~C()") + +def maybe_assign_infer(assign, value, do_print): + """ + >>> maybe_assign_infer(True, 5, True) + 5 + ~C() + >>> maybe_assign_infer(False, 0, True) + Traceback (most recent call last): + ... + UnboundLocalError: local variable 'x' referenced before assignment + >>> maybe_assign_infer(False, 0, False) # no destructor call here + """ + if assign: + x = C(value) + if do_print: + print(x.getX()) + +def maybe_assign_cdef(assign, value): + """ + >>> maybe_assign_cdef(True, 5) + 5 + ~C() + >>> maybe_assign_cdef(False, 0) + Traceback (most recent call last): + ... + UnboundLocalError: local variable 'x' referenced before assignment + """ + cdef C x + if assign: + x = C(value) + print(x.getX()) + +def maybe_assign_annotation(assign, value): + """ + >>> maybe_assign_annotation(True, 5) + 5 + ~C() + >>> maybe_assign_annotation(False, 0) + Traceback (most recent call last): + ... + UnboundLocalError: local variable 'x' referenced before assignment + """ + x: C + if assign: + x = C(value) + print(x.getX()) + +def maybe_assign_directive1(assign, value): + """ + >>> maybe_assign_directive1(True, 5) + 5 + ~C() + >>> maybe_assign_directive1(False, 0) + Traceback (most recent call last): + ... + UnboundLocalError: local variable 'x' referenced before assignment + """ + x = cython.declare(C) + if assign: + x = C(value) + print(x.getX()) + +@cython.locals(x=C) +def maybe_assign_directive2(assign, value): + """ + >>> maybe_assign_directive2(True, 5) + 5 + ~C() + >>> maybe_assign_directive2(False, 0) + Traceback (most recent call last): + ... + UnboundLocalError: local variable 'x' referenced before assignment + """ + if assign: + x = C(value) + print(x.getX()) + +def maybe_assign_nocheck(assign, value): + """ + >>> maybe_assign_nocheck(True, 5) + 5 + ~C() + + # unfortunately it's quite difficult to test not assigning because there's a decent chance it'll crash + """ + if assign: + x = C(value) + with cython.initializedcheck(False): + print(x.getX()) + +def uses_temp(value): + """ + needs a temp to handle the result of make_C - still doesn't use the default constructor + >>> uses_temp(10) + 10 + ~C() + """ + + x = make_C(value) + print(x.getX()) + +# c should not be optional - it isn't easy to check this, but we can at least check it compiles +cdef void has_argument(C c): + print(c.getX()) + +def call_has_argument(): + """ + >>> call_has_argument() + 50 + """ + has_argument(C(50, False)) + +cdef class HoldsC: + """ + >>> inst = HoldsC(True, False) + >>> inst.getCX() + 10 + >>> access_from_function_with_different_directive(inst) + 10 + 10 + >>> inst.getCX() # it was changed in access_from_function_with_different_directive + 20 + >>> inst = HoldsC(False, False) + >>> inst.getCX() + Traceback (most recent call last): + ... + AttributeError: C++ attribute 'value' is not initialized + >>> access_from_function_with_different_directive(inst) + Traceback (most recent call last): + ... + AttributeError: C++ attribute 'value' is not initialized + """ + cdef C value + def __cinit__(self, initialize, print_destructor): + if initialize: + self.value = C(10, print_destructor) + + def getCX(self): + return self.value.getX() + +cdef acceptC(C& c): + return c.getX() + +@cython.cpp_locals(False) +def access_from_function_with_different_directive(HoldsC c): + # doctest is in HoldsC class + print(acceptC(c.value)) # this originally tried to pass a __Pyx_Optional as a C instance + print(c.value.getX()) + c.value = C(20, False) # make sure that we can change it too + +def dont_test_on_pypy(f): + import sys + if not hasattr(sys, "pypy_version_info"): + return f + +@dont_test_on_pypy # non-deterministic destruction +def testHoldsCDestruction(initialize): + """ + >>> testHoldsCDestruction(True) + ~C() + >>> testHoldsCDestruction(False) # no destructor + """ + x = HoldsC(initialize, True) + del x + +cdef C global_var + +def initialize_global_var(): + global global_var + global_var = C(-1, False) + +def read_global_var(): + """ + >>> read_global_var() + Traceback (most recent call last): + ... + NameError: C++ global 'global_var' is not initialized + >>> initialize_global_var() + >>> read_global_var() + -1 + """ + print(global_var.getX()) diff --git a/tests/run/cpp_locals_directive_unused.pyx b/tests/run/cpp_locals_directive_unused.pyx new file mode 100644 index 000000000..429e64beb --- /dev/null +++ b/tests/run/cpp_locals_directive_unused.pyx @@ -0,0 +1,14 @@ +# mode: run +# tag: cpp, cpp17, no-cpp-locals +# no cpp_locals because this test is already run with cpp_locals explicitly set + +# cython: cpp_locals=True + +cdef cppclass C: + C() + +cdef class PyC: + """ + >>> PyC() and None # doesn't really do anything, but should run + """ + cdef C # this limited usage wasn't triggering the creation of utility code diff --git a/tests/run/cpp_move.pyx b/tests/run/cpp_move.pyx index d93afe28d..ca7cb7794 100644 --- a/tests/run/cpp_move.pyx +++ b/tests/run/cpp_move.pyx @@ -1,5 +1,5 @@ # mode: run -# tag: cpp, werror, cpp11 +# tag: cpp, werror, cpp11, no-cpp-locals from libcpp cimport nullptr from libcpp.memory cimport shared_ptr, make_shared diff --git a/tests/run/cpp_nested_classes.pyx b/tests/run/cpp_nested_classes.pyx index 98ea514f1..b50f79936 100644 --- a/tests/run/cpp_nested_classes.pyx +++ b/tests/run/cpp_nested_classes.pyx @@ -1,4 +1,4 @@ -# tag: cpp +# tag: cpp, no-cpp-locals cdef extern from "cpp_nested_classes_support.h": cdef cppclass A: diff --git a/tests/run/cpp_nonstdint.pyx b/tests/run/cpp_nonstdint.pyx index 62153d190..238107b79 100644 --- a/tests/run/cpp_nonstdint.pyx +++ b/tests/run/cpp_nonstdint.pyx @@ -1,4 +1,4 @@ -# tag: cpp +# tag: cpp, no-cpp-locals cdef extern from "cpp_nonstdint.h": ctypedef int Int24 diff --git a/tests/run/cpp_operator_exc_handling.pyx b/tests/run/cpp_operator_exc_handling.pyx index 381870c22..67b00e39e 100644 --- a/tests/run/cpp_operator_exc_handling.pyx +++ b/tests/run/cpp_operator_exc_handling.pyx @@ -1,5 +1,5 @@ # mode: run -# tag: cpp, werror +# tag: cpp, werror, no-cpp-locals from cython.operator import (preincrement, predecrement, postincrement, postdecrement) diff --git a/tests/run/cpp_operators.pyx b/tests/run/cpp_operators.pyx index 7900aa064..f3ae6452a 100644 --- a/tests/run/cpp_operators.pyx +++ b/tests/run/cpp_operators.pyx @@ -1,5 +1,7 @@ # mode: run # tag: cpp, werror +# tag: no-cpp-locals +# FIXME - cpp_locals should work but doesn't from __future__ import division diff --git a/tests/run/cpp_optional_temps.pyx b/tests/run/cpp_optional_temps.pyx deleted file mode 100644 index ca0580db1..000000000 --- a/tests/run/cpp_optional_temps.pyx +++ /dev/null @@ -1,215 +0,0 @@ -# mode: run -# tag: cpp, cpp17 - -# cython: cpp_locals=True - -cimport cython - -from libcpp cimport bool as cppbool - -cdef extern from *: - r""" - static void print_C_destructor(); - - class C { - public: - C() = delete; // look! No default constructor - C(int x, bool print_destructor=true) : x(x), print_destructor(print_destructor) {} - C(C&& rhs) : x(rhs.x), print_destructor(rhs.print_destructor) { - rhs.print_destructor = false; // moved-from instances are deleted silently - } - C& operator=(C&& rhs) { - x=rhs.x; - print_destructor=rhs.print_destructor; - rhs.print_destructor = false; // moved-from instances are deleted silently - return *this; - } - C(const C& rhs) = default; - C& operator=(const C& rhs) = default; - ~C() { - if (print_destructor) print_C_destructor(); - } - - int getX() const { return x; } - - private: - int x; - bool print_destructor; - }; - - C make_C(int x) { - return C(x); - } - """ - cdef cppclass C: - C(int) - C(int, cppbool) - int getX() const - C make_C(int) except + # needs a temp to receive - -# this function just makes sure the output from the destructor can be captured by doctest -cdef void print_C_destructor "print_C_destructor" () nogil: - print("~C()") - -def maybe_assign_infer(assign, value, do_print): - """ - >>> maybe_assign_infer(True, 5, True) - 5 - ~C() - >>> maybe_assign_infer(False, 0, True) - Traceback (most recent call last): - ... - UnboundLocalError: local variable 'x' referenced before assignment - >>> maybe_assign_infer(False, 0, False) # no destructor call here - """ - if assign: - x = C(value) - if do_print: - print(x.getX()) - -def maybe_assign_cdef(assign, value): - """ - >>> maybe_assign_cdef(True, 5) - 5 - ~C() - >>> maybe_assign_cdef(False, 0) - Traceback (most recent call last): - ... - UnboundLocalError: local variable 'x' referenced before assignment - """ - cdef C x - if assign: - x = C(value) - print(x.getX()) - -def maybe_assign_annotation(assign, value): - """ - >>> maybe_assign_annotation(True, 5) - 5 - ~C() - >>> maybe_assign_annotation(False, 0) - Traceback (most recent call last): - ... - UnboundLocalError: local variable 'x' referenced before assignment - """ - x: C - if assign: - x = C(value) - print(x.getX()) - -def maybe_assign_directive1(assign, value): - """ - >>> maybe_assign_directive1(True, 5) - 5 - ~C() - >>> maybe_assign_directive1(False, 0) - Traceback (most recent call last): - ... - UnboundLocalError: local variable 'x' referenced before assignment - """ - x = cython.declare(C) - if assign: - x = C(value) - print(x.getX()) - -@cython.locals(x=C) -def maybe_assign_directive2(assign, value): - """ - >>> maybe_assign_directive2(True, 5) - 5 - ~C() - >>> maybe_assign_directive2(False, 0) - Traceback (most recent call last): - ... - UnboundLocalError: local variable 'x' referenced before assignment - """ - if assign: - x = C(value) - print(x.getX()) - -def maybe_assign_nocheck(assign, value): - """ - >>> maybe_assign_nocheck(True, 5) - 5 - ~C() - - # unfortunately it's quite difficult to test not assigning because there's a decent chance it'll crash - """ - if assign: - x = C(value) - with cython.initializedcheck(False): - print(x.getX()) - -def uses_temp(value): - """ - needs a temp to handle the result of make_C - still doesn't use the default constructor - >>> uses_temp(10) - 10 - ~C() - """ - - x = make_C(value) - print(x.getX()) - -# c should not be optional - it isn't easy to check this, but we can at least check it compiles -cdef void has_argument(C c): - print(c.getX()) - -def call_has_argument(): - """ - >>> call_has_argument() - 50 - """ - has_argument(C(50, False)) - -cdef class HoldsC: - """ - >>> inst = HoldsC(True, False) - >>> inst.getCX() - 10 - >>> inst = HoldsC(False, False) - >>> inst.getCX() - Traceback (most recent call last): - ... - AttributeError: C++ attribute 'value' is not initialized - """ - cdef C value - def __cinit__(self, initialize, print_destructor): - if initialize: - self.value = C(10, print_destructor) - - def getCX(self): - return self.value.getX() - -def dont_test_on_pypy(f): - import sys - if not hasattr(sys, "pypy_version_info"): - return f - -@dont_test_on_pypy # non-deterministic destruction -def testHoldsCDestruction(initialize): - """ - >>> testHoldsCDestruction(True) - ~C() - >>> testHoldsCDestruction(False) # no destructor - """ - x = HoldsC(initialize, True) - del x - -cdef C global_var - -def initialize_global_var(): - global global_var - global_var = C(-1, False) - -def read_global_var(): - """ - >>> read_global_var() - Traceback (most recent call last): - ... - NameError: C++ global 'global_var' is not initialized - >>> initialize_global_var() - >>> read_global_var() - -1 - """ - print(global_var.getX()) diff --git a/tests/run/cpp_optional_temps_unused.pyx b/tests/run/cpp_optional_temps_unused.pyx deleted file mode 100644 index e420a766d..000000000 --- a/tests/run/cpp_optional_temps_unused.pyx +++ /dev/null @@ -1,13 +0,0 @@ -# mode: run -# tag: cpp, cpp17 - -# cython: cpp_locals=True - -cdef cppclass C: - C() - -cdef class PyC: - """ - >>> PyC() and None # doesn't really do anything, but should run - """ - cdef C # this limited usage wasn't triggering the creation of utility code diff --git a/tests/run/cpp_smart_ptr.pyx b/tests/run/cpp_smart_ptr.pyx index ec2276036..d71151c5e 100644 --- a/tests/run/cpp_smart_ptr.pyx +++ b/tests/run/cpp_smart_ptr.pyx @@ -1,5 +1,5 @@ # mode: run -# tag: cpp, werror, cpp11 +# tag: cpp, werror, cpp11, no-cpp-locals from libcpp.memory cimport unique_ptr, shared_ptr, default_delete, dynamic_pointer_cast from libcpp cimport nullptr diff --git a/tests/run/cpp_static_method_overload.pyx b/tests/run/cpp_static_method_overload.pyx index 25ce5bbb4..59eec2f44 100644 --- a/tests/run/cpp_static_method_overload.pyx +++ b/tests/run/cpp_static_method_overload.pyx @@ -1,5 +1,5 @@ # mode: run -# tag: cpp +# tag: cpp, no-cpp-locals cdef extern from *: """ diff --git a/tests/run/cpp_stl_algo_execpolicies.pyx b/tests/run/cpp_stl_algo_execpolicies.pyx index 058232b5d..989e42f6c 100644 --- a/tests/run/cpp_stl_algo_execpolicies.pyx +++ b/tests/run/cpp_stl_algo_execpolicies.pyx @@ -1,5 +1,5 @@ # mode: run -# tag: cpp, werror, cpp17, cppexecpolicies +# tag: cpp, werror, cpp17, cppexecpolicies, no-cpp-locals from libcpp.algorithm cimport is_sorted, sort, stable_sort, nth_element, all_of, count, copy from libcpp.execution cimport seq diff --git a/tests/run/cpp_stl_algo_modifying_sequence_ops.pyx b/tests/run/cpp_stl_algo_modifying_sequence_ops.pyx index f842d430b..0d823e288 100644 --- a/tests/run/cpp_stl_algo_modifying_sequence_ops.pyx +++ b/tests/run/cpp_stl_algo_modifying_sequence_ops.pyx @@ -1,5 +1,5 @@ # mode: run -# tag: cpp, werror, cpp11 +# tag: cpp, werror, cpp11, no-cpp-locals from __future__ import print_function diff --git a/tests/run/cpp_stl_algo_partitioning_ops.pyx b/tests/run/cpp_stl_algo_partitioning_ops.pyx index 3bdc3d2af..1de80d84b 100644 --- a/tests/run/cpp_stl_algo_partitioning_ops.pyx +++ b/tests/run/cpp_stl_algo_partitioning_ops.pyx @@ -1,5 +1,5 @@ # mode: run -# tag: cpp, werror, cpp11 +# tag: cpp, werror, cpp11, no-cpp-locals from __future__ import print_function diff --git a/tests/run/cpp_stl_algo_sorting_ops.pyx b/tests/run/cpp_stl_algo_sorting_ops.pyx index 299089c4c..ac8d1b586 100644 --- a/tests/run/cpp_stl_algo_sorting_ops.pyx +++ b/tests/run/cpp_stl_algo_sorting_ops.pyx @@ -1,5 +1,5 @@ # mode: run -# tag: cpp, werror, cpp11 +# tag: cpp, werror, cpp11, no-cpp-locals from __future__ import print_function diff --git a/tests/run/cpp_stl_atomic.pyx b/tests/run/cpp_stl_atomic.pyx index e504c70eb..ba187a455 100644 --- a/tests/run/cpp_stl_atomic.pyx +++ b/tests/run/cpp_stl_atomic.pyx @@ -1,5 +1,5 @@ # mode: run -# tag: cpp, cpp11, werror +# tag: cpp, cpp11, werror, no-cpp-locals from cython.operator cimport preincrement as incr, dereference as deref from libc.stdint cimport * diff --git a/tests/run/cpp_stl_cpp11.pyx b/tests/run/cpp_stl_cpp11.pyx index f4fa4d360..be0b72e33 100644 --- a/tests/run/cpp_stl_cpp11.pyx +++ b/tests/run/cpp_stl_cpp11.pyx @@ -1,5 +1,5 @@ # mode: run -# tag: cpp, werror, cpp11 +# tag: cpp, werror, cpp11, no-cpp-locals import sys from libcpp.unordered_map cimport unordered_map diff --git a/tests/run/cpp_stl_forward_list.pyx b/tests/run/cpp_stl_forward_list.pyx index 8ca081c4e..817e4d710 100644 --- a/tests/run/cpp_stl_forward_list.pyx +++ b/tests/run/cpp_stl_forward_list.pyx @@ -1,5 +1,5 @@ # mode: run -# tag: cpp, werror, cpp11 +# tag: cpp, werror, cpp11, no-cpp-locals from cython.operator cimport dereference as deref from cython.operator cimport preincrement as incr diff --git a/tests/run/cpp_stl_list.pyx b/tests/run/cpp_stl_list.pyx index 59367e2f5..6a3c60ee5 100644 --- a/tests/run/cpp_stl_list.pyx +++ b/tests/run/cpp_stl_list.pyx @@ -1,5 +1,5 @@ # mode: run -# tag: cpp, werror +# tag: cpp, werror, no-cpp-locals from cython.operator cimport dereference as deref from cython.operator cimport preincrement as incr diff --git a/tests/run/cpp_stl_vector.pyx b/tests/run/cpp_stl_vector.pyx index 5c943e423..c42cb96b8 100644 --- a/tests/run/cpp_stl_vector.pyx +++ b/tests/run/cpp_stl_vector.pyx @@ -1,5 +1,5 @@ # mode: run -# tag: cpp, werror +# tag: cpp, werror, no-cpp-locals from cython.operator cimport dereference as d from cython.operator cimport preincrement as incr diff --git a/tests/run/cpp_template_functions.pyx b/tests/run/cpp_template_functions.pyx index dce882879..19fa78413 100644 --- a/tests/run/cpp_template_functions.pyx +++ b/tests/run/cpp_template_functions.pyx @@ -1,5 +1,5 @@ # mode: run -# tag: cpp, warnings +# tag: cpp, warnings, no-cpp-locals cimport cython from libcpp.pair cimport pair diff --git a/tests/run/cpp_template_ref_args.pyx b/tests/run/cpp_template_ref_args.pyx index c98c077ef..754122454 100644 --- a/tests/run/cpp_template_ref_args.pyx +++ b/tests/run/cpp_template_ref_args.pyx @@ -1,4 +1,4 @@ -# tag: cpp +# tag: cpp, no-cpp-locals from libcpp.vector cimport vector diff --git a/tests/run/cpp_template_subclasses.pyx b/tests/run/cpp_template_subclasses.pyx index 0ebcd4c6e..3e7f3b506 100644 --- a/tests/run/cpp_template_subclasses.pyx +++ b/tests/run/cpp_template_subclasses.pyx @@ -1,5 +1,5 @@ # mode: run -# tag: cpp, werror +# tag: cpp, werror, no-cpp-locals from cython.operator import dereference as deref from libcpp.pair cimport pair diff --git a/tests/run/fused_cpp.pyx b/tests/run/fused_cpp.pyx index 9f3bb5104..71ad9173a 100644 --- a/tests/run/fused_cpp.pyx +++ b/tests/run/fused_cpp.pyx @@ -1,4 +1,5 @@ -# tag: cpp +# tag: cpp, no-cpp-locals +# FIXME (or at least investigate) - cpp_locals should probably work cimport cython from libcpp.vector cimport vector diff --git a/tests/run/libcpp_all.pyx b/tests/run/libcpp_all.pyx index a45de63f6..6633adb7a 100644 --- a/tests/run/libcpp_all.pyx +++ b/tests/run/libcpp_all.pyx @@ -1,4 +1,4 @@ -# tag: cpp +# tag: cpp, no-cpp-locals import cython diff --git a/tests/run/lvalue_refs.pyx b/tests/run/lvalue_refs.pyx index 6bd0d88bc..c70744533 100644 --- a/tests/run/lvalue_refs.pyx +++ b/tests/run/lvalue_refs.pyx @@ -1,4 +1,4 @@ -# tag: cpp +# tag: cpp, no-cpp-locals from libcpp.vector cimport vector -- cgit v1.2.1